create-cordisx-plugin 0.1.0-beta.0 → 0.1.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-cordisx-plugin",
3
- "version": "0.1.0-beta.0",
4
- "description": "Create a minimal CordisX plugin project",
3
+ "version": "0.1.0-beta.3",
4
+ "description": "Create standalone, workspace, or embedded CordisX plugin projects",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",
@@ -15,10 +15,11 @@
15
15
  "create-cordisx-plugin": "dist/cli.js"
16
16
  },
17
17
  "scripts": {
18
- "build": "tsc -p tsconfig.json",
19
- "check": "npm run typecheck",
18
+ "build": "tsc -p tsconfig.json && node scripts/normalize-bin-mode.mjs",
19
+ "check": "npm run typecheck && npm run build && npm test",
20
20
  "check:package": "node scripts/check-package-contents.mjs",
21
21
  "prepack": "npm run build",
22
+ "test": "node --test test/*.node-test.mjs",
22
23
  "typecheck": "tsc -p tsconfig.json --noEmit"
23
24
  },
24
25
  "engines": {
@@ -42,11 +43,13 @@
42
43
  ],
43
44
  "publishConfig": {
44
45
  "access": "public",
45
- "tag": "beta",
46
46
  "provenance": true
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^22.18.1",
50
50
  "typescript": "^5.9.2"
51
+ },
52
+ "dependencies": {
53
+ "yaml": "2.8.1"
51
54
  }
52
55
  }
@@ -1,21 +1,48 @@
1
1
  # {{packageName}}
2
2
 
3
- A minimal trusted-local CordisX plugin. Its manifest is exported from
4
- `src/{{pluginId}}.ts`, which is also the runtime entry used by CordisX.
3
+ A minimal trusted-local CordisX React plugin. Its manifest is exported from
4
+ `src/{{pluginId}}.tsx`, which is also CordisX's local-development entry.
5
+ The page component lives in `src/overview-page.tsx` as a named component-only
6
+ module so local development can preserve its state with React Fast Refresh.
5
7
 
6
8
  ```bash
7
- npm install
8
- npm run check
9
- npm run dev:dry-run
10
- npm run dev
9
+ {{packageManager}} install
10
+ {{packageManager}} run check
11
+ {{packageManager}} run dev:dry-run
12
+ {{packageManager}} run dev
11
13
  ```
12
14
 
13
- `npm run dev:dry-run` bundles the plugin without launching Codex Desktop.
14
- `npm run dev` launches the separate CordisX development host.
15
+ `{{packageManager}} run dev:dry-run` validates the plugin module graph without
16
+ launching Codex Desktop. `{{packageManager}} run dev` launches the separate
17
+ CordisX development host. Component-only modules use React Fast Refresh;
18
+ manifest, entry, and `apply()` changes replace the plugin generation.
15
19
 
16
- The template intentionally requests no platform capabilities. It contributes
17
- structured data to a host-owned toolbar surface and does not depend on a
18
- marketplace, signing, a permission sandbox, or hot module replacement.
20
+ `{{packageManager}} run build` creates a production Vite ESM graph in `dist/runtime/`.
21
+ Its stable `module.js` entry may load content-addressed JavaScript chunks, CSS,
22
+ and static assets only when an `import()` reaches them. The generated formal
23
+ `artifact.json` records that graph for packaging; declarations are kept outside
24
+ the closed graph in `dist/types/`, and `package.json#files` includes the complete
25
+ `dist/` tree. Production generations remain immutable package
26
+ artifacts and do not use the development HMR connection.
27
+
28
+ The generated config calls the public `cordisx/vite`
29
+ `cordisXPluginViteConfig()` helper. That shared author/Host pipeline owns the
30
+ output rules, virtualizes only the closed Host singleton imports, and emits the
31
+ formal `dist/runtime/artifact.json`. A portable CordisX package manifest points its
32
+ browser entry at the adjacent prebuilt `dist/runtime/module.js`; the Host validates and
33
+ retains the complete indexed graph.
34
+
35
+ The template intentionally requests no platform capabilities. It contributes a
36
+ structured toolbar route and a React page body. Import React from
37
+ `cordisx/react` and components from `cordisx/ui`; do not install `react`,
38
+ `react-dom`, React type packages, or a component library. CordisX supplies one
39
+ Host React singleton and rejects a plugin artifact that bundles another copy.
40
+
41
+ The route and page use closed route-v2/page-v3 documents with separate retained
42
+ localized `title` and `description` messages. Path, outlet, page id, route id,
43
+ params, and chrome remain untranslated. CordisX owns the route, page header,
44
+ theme, error boundary, and React root cleanup; the plugin owns only the page
45
+ body component. Plugins remain trusted local code, not sandboxed code.
19
46
 
20
47
  This generated project is Marked Template Material under the
21
48
  [CordisX Independent Plugin Exception](https://github.com/cordisx/cordisx/blob/main/CORDISX-INDEPENDENT-PLUGIN-EXCEPTION.md).
@@ -24,3 +51,25 @@ CordisX plugin interfaces, it may be commercial and use a license you choose.
24
51
  Replace `UNLICENSED` in `package.json` with that license before distribution.
25
52
  The Exception does not cover copying or modifying CordisX host/runtime/CLI code
26
53
  or using private interfaces.
54
+
55
+ Plugin-owned styles use `import './details.css'` in the owning component (or
56
+ lazy page module). Keep `src/vite-env.d.ts`: Vite types distinguish side-effect
57
+ CSS, CSS Module class maps, and explicit `?inline` text. Ordinary CSS imports
58
+ do not return strings. Component unmount is not CSS-module disposal; verify
59
+ plugin replacement separately from CSS HMR. See the maintained
60
+ [CSS guidance](https://github.com/cordisx/cordisx/blob/main/skills/cordisx-plugin-development/references/css-and-lifecycle.md)
61
+ for lifecycle boundaries and installed-Host CSS Modules limitations.
62
+
63
+ For operation feedback, use `ctx.notifications.show()` and declare
64
+ `notifications` in plugin injection. Host supplies plugin identity, cards and
65
+ user suppression rules. Do not create a Toast provider, manually positioned
66
+ alert or page-wide operation error; keep field validation and durable business
67
+ state next to the relevant control. See the
68
+ [notification guide](https://github.com/cordisx/cordisx/blob/efbff656d84b482d51598bc5ba303d24134e0c62/.agents/docs/notifications.md).
69
+ Verify SDK/Host availability first: an older version without this service is a
70
+ capability gap, not a reason to build a private DOM fallback.
71
+
72
+ For modals, use Host `Dialog`/`DialogProvider` from `cordisx/ui` or `ctx.dialogs`.
73
+ Keep full JSX in the body and use structured header/footer actions. The Host
74
+ owns the rightmost close button, focus, theme and lifecycle. See the
75
+ [dialog guide](https://github.com/cordisx/cordisx/blob/main/.agents/docs/dialogs.md).
@@ -0,0 +1,39 @@
1
+ # {{packageName}}
2
+
3
+ 一个最小的 CordisX 本地可信插件。插件清单由
4
+ `src/{{pluginId}}.tsx` 导出;该文件同时也是 CordisX 本地开发使用的入口。
5
+
6
+ ```bash
7
+ {{packageManager}} install
8
+ {{packageManager}} run check
9
+ {{packageManager}} run dev:dry-run
10
+ {{packageManager}} run dev
11
+ ```
12
+
13
+ `{{packageManager}} run dev:dry-run` 会在不启动 Codex Desktop 的情况下打包插件;
14
+ `{{packageManager}} run dev` 会启动独立的 CordisX 开发宿主。
15
+
16
+ `{{packageManager}} run build` 会在 `dist/runtime/` 中生成生产用 Vite ESM graph。固定入口
17
+ `module.js` 可以在 `import()` 实际触发后再加载带内容摘要的 JavaScript chunk、
18
+ CSS 和静态资源;正式的 `artifact.json` 会记录完整产物图,类型声明则隔离在
19
+ `dist/types/`,
20
+ `package.json#files` 会将整个 `dist/` 纳入打包。生产 generation 仍是不可变包
21
+ 产物,不使用开发 HMR 连接。
22
+
23
+ 生成的配置会调用公开的 `cordisx/vite` `cordisXPluginViteConfig()` helper。
24
+ 这个作者侧与 Host 共用的流水线负责产物规则,只虚拟化封闭的 Host 单例导入,并
25
+ 生成正式的 `dist/runtime/artifact.json`。可移植 CordisX package manifest 应把浏览器入口
26
+ 指向相邻的预构建 `dist/runtime/module.js`;Host 会验证并保留该完整索引 graph。
27
+
28
+ 这个生成项目属于 [CordisX 独立插件例外](https://github.com/cordisx/cordisx/blob/main/CORDISX-INDEPENDENT-PLUGIN-EXCEPTION.md)
29
+ 定义的“已标记模板材料”。只要它保持为独立插件,并且只使用有文档、带版本的
30
+ CordisX 公共插件接口,就可以用于商业用途并采用你选择的许可证。发布前请将
31
+ `package.json` 中的 `UNLICENSED` 替换为所选许可证。该例外不包括复制或修改
32
+ CordisX Host、Runtime、CLI 代码,也不包括使用私有接口。
33
+
34
+ 插件自有样式在所属组件或 lazy 页面模块中使用 `import './details.css'`。
35
+ 保留 `src/vite-env.d.ts`:Vite 类型区分普通 CSS 副作用导入、CSS Modules
36
+ 类名映射和显式 `?inline` 文本,普通 CSS 不返回字符串。组件卸载不等于
37
+ CSS 模块回收;CSS HMR 与插件替换需要分别验证。具体生命周期和已安装
38
+ Host 的 CSS Modules 限制见
39
+ [CSS 指导](https://github.com/cordisx/cordisx/blob/main/skills/cordisx-plugin-development/references/css-and-lifecycle.md)。
@@ -5,20 +5,33 @@
5
5
  "license": "UNLICENSED",
6
6
  "description": "A CordisX plugin",
7
7
  "type": "module",
8
- "main": "./dist/{{pluginId}}.js",
9
- "types": "./dist/{{pluginId}}.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "README.md",
11
+ "README.zh-Hans.md"
12
+ ],
13
+ "main": "./dist/runtime/module.js",
14
+ "types": "./dist/types/{{pluginId}}.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/types/{{pluginId}}.d.ts",
18
+ "import": "./dist/runtime/module.js",
19
+ "default": "./dist/runtime/module.js"
20
+ }
21
+ },
10
22
  "scripts": {
11
- "build": "tsc -p tsconfig.json",
12
- "check": "npm run typecheck && npm run build && npm test",
13
- "dev": "cordisx dev ./src/{{pluginId}}.ts",
14
- "dev:dry-run": "cordisx dev ./src/{{pluginId}}.ts --dry-run",
23
+ "build": "vite build && tsc -p tsconfig.json --emitDeclarationOnly",
24
+ "check": "tsc -p tsconfig.json --noEmit && vite build && tsc -p tsconfig.json --emitDeclarationOnly && node --test test/manifest.mjs",
25
+ "dev": "cordisx dev ./src/{{pluginId}}.tsx",
26
+ "dev:dry-run": "cordisx dev ./src/{{pluginId}}.tsx --dry-run",
15
27
  "test": "node --test test/manifest.mjs",
16
28
  "typecheck": "tsc -p tsconfig.json --noEmit"
17
29
  },
18
30
  "devDependencies": {
19
31
  "@deepseek-ai/cordis": "4.0.1",
20
32
  "cordisx": "{{cordisxVersion}}",
21
- "typescript": "^5.9.2"
33
+ "typescript": "^5.9.2",
34
+ "vite": "8.2.2"
22
35
  },
23
36
  "engines": {
24
37
  "node": ">=22.19"
@@ -0,0 +1,30 @@
1
+ import { useState } from 'cordisx/react'
2
+ import { Button, Card, Heading, Stack, Text } from 'cordisx/ui'
3
+ import type { CordisXReactPageProps } from 'cordisx/contracts'
4
+
5
+ export type Messages = {
6
+ 'command.open': undefined
7
+ 'page.title': undefined
8
+ 'page.description': undefined
9
+ 'route.title': undefined
10
+ 'route.description': undefined
11
+ 'counter.label': { count: number }
12
+ }
13
+
14
+ /** A named component-only module forms a stable React Fast Refresh boundary. */
15
+ export function OverviewPage({ t }: CordisXReactPageProps<Messages>) {
16
+ const [count, setCount] = useState(0)
17
+ return (
18
+ <Stack gap="large">
19
+ <Heading>{t('page.title')}</Heading>
20
+ <Card>
21
+ <Stack gap="medium" align="flex-start">
22
+ <Text tone="muted">{t('page.description')}</Text>
23
+ <Button variant="primary" onClick={() => setCount(value => value + 1)}>
24
+ {t('counter.label', { count })}
25
+ </Button>
26
+ </Stack>
27
+ </Card>
28
+ </Stack>
29
+ )
30
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,96 @@
1
+ import type { Context } from '@deepseek-ai/cordis'
2
+ import { defineReactPage, lazy, Suspense } from 'cordisx/react'
3
+ import {
4
+ CORDISX_PAGE_SCHEMA_V3,
5
+ CORDISX_PLUGIN_MANIFEST_SCHEMA_V1,
6
+ CORDISX_ROUTE_SCHEMA_V2,
7
+ type CordisXPluginManifestV1,
8
+ type CordisXReactPageProps,
9
+ } from 'cordisx/contracts'
10
+ import type { Messages } from './overview-page.js'
11
+
12
+ export const manifest = {
13
+ $schema: CORDISX_PLUGIN_MANIFEST_SCHEMA_V1,
14
+ schemaVersion: 1,
15
+ id: '{{pluginId}}',
16
+ name: '{{packageName}}',
17
+ capabilities: [],
18
+ } as const satisfies CordisXPluginManifestV1
19
+
20
+ export const inject = ['i18n', 'pages', 'routes', 'slots']
21
+
22
+ const page = {
23
+ $schema: CORDISX_PAGE_SCHEMA_V3,
24
+ schemaVersion: 3,
25
+ id: 'overview',
26
+ title: { key: 'page.title', fallback: '{{packageName}}' },
27
+ description: { key: 'page.description', fallback: 'A React page rendered by the shared CordisX Host runtime.' },
28
+ icon: 'host:info',
29
+ } as const
30
+
31
+ const route = {
32
+ $schema: CORDISX_ROUTE_SCHEMA_V2,
33
+ schemaVersion: 2,
34
+ id: 'overview',
35
+ path: '/app/{{pluginId}}',
36
+ outlet: 'app',
37
+ page: 'overview',
38
+ title: { key: 'route.title', fallback: '{{packageName}}' },
39
+ description: { key: 'route.description', fallback: 'Open the plugin React example.' },
40
+ } as const
41
+
42
+ const OverviewPage = lazy(async () => {
43
+ const module = await import('./overview-page.js')
44
+ return { default: module.OverviewPage }
45
+ })
46
+
47
+ function OverviewPageBoundary(props: CordisXReactPageProps<Messages>) {
48
+ return (
49
+ <Suspense fallback={null}>
50
+ <OverviewPage {...props} />
51
+ </Suspense>
52
+ )
53
+ }
54
+
55
+ const mountOverview = defineReactPage<Messages>(OverviewPageBoundary)
56
+
57
+ export function apply(ctx: Context): void {
58
+ ctx.i18n.define<Messages>({
59
+ namespace: '{{pluginId}}',
60
+ locale: 'en',
61
+ default: true,
62
+ messages: {
63
+ 'command.open': 'Open {{packageName}}',
64
+ 'page.title': '{{packageName}}',
65
+ 'page.description': 'This page uses the React singleton and components provided by CordisX.',
66
+ 'route.title': '{{packageName}}',
67
+ 'route.description': 'Open the plugin React example.',
68
+ 'counter.label': 'Clicked {count, number} times',
69
+ },
70
+ })
71
+ ctx.i18n.define<Messages>({
72
+ namespace: '{{pluginId}}',
73
+ locale: 'zh-CN',
74
+ messages: {
75
+ 'command.open': '打开 {{packageName}}',
76
+ 'page.title': '{{packageName}}',
77
+ 'page.description': '此页面复用 CordisX 提供的 React 单例与组件。',
78
+ 'route.title': '{{packageName}}',
79
+ 'route.description': '打开插件的 React 示例。',
80
+ 'counter.label': '已点击 {count, number} 次',
81
+ },
82
+ })
83
+ ctx.pages.register<Messages>(page, mountOverview)
84
+ ctx.routes.register(route)
85
+ ctx.slots.register({
86
+ name: 'workspace.toolbar.items',
87
+ id: 'open',
88
+ order: 10,
89
+ }, {
90
+ anchor: 'workspace.primary',
91
+ placement: 'after',
92
+ label: { key: 'command.open', fallback: 'Open {{packageName}}' },
93
+ icon: 'host:open',
94
+ route: { id: 'overview' },
95
+ })
96
+ }
@@ -1,11 +1,27 @@
1
1
  import assert from 'node:assert/strict'
2
2
  import test from 'node:test'
3
- import { apply, inject, manifest } from '../dist/{{pluginId}}.js'
3
+
4
+ const component = () => null
5
+ const noop = () => undefined
6
+ const React = new Proxy({ Fragment: Symbol('Fragment'), Suspense: component, lazy: () => component }, {
7
+ get(target, property) {
8
+ return Reflect.get(target, property) ?? noop
9
+ },
10
+ })
11
+ globalThis.__cordisxSharedReactRuntime = {
12
+ React,
13
+ defineReactPage: page => page,
14
+ jsxRuntime: { Fragment: React.Fragment, jsx: component, jsxs: component },
15
+ jsxDevRuntime: { Fragment: React.Fragment, jsxDEV: component },
16
+ ui: new Proxy({}, { get: () => component }),
17
+ }
18
+
19
+ const { apply, inject, manifest } = await import('../dist/runtime/module.js')
4
20
 
5
21
  test('exports a minimal CordisX plugin module', () => {
6
22
  assert.equal(manifest.schemaVersion, 1)
7
23
  assert.equal(manifest.id, '{{pluginId}}')
8
24
  assert.deepEqual(manifest.capabilities, [])
9
- assert.deepEqual(inject, ['i18n', 'commands', 'slots'])
25
+ assert.deepEqual(inject, ['i18n', 'pages', 'routes', 'slots'])
10
26
  assert.equal(typeof apply, 'function')
11
27
  })
@@ -3,14 +3,16 @@
3
3
  "target": "ES2022",
4
4
  "module": "NodeNext",
5
5
  "moduleResolution": "NodeNext",
6
+ "jsx": "react-jsx",
7
+ "jsxImportSource": "cordisx/react",
6
8
  "lib": ["ES2023", "DOM"],
7
9
  "rootDir": "src",
8
- "outDir": "dist",
10
+ "outDir": "dist/types",
9
11
  "strict": true,
10
12
  "declaration": true,
11
13
  "forceConsistentCasingInFileNames": true,
12
14
  "skipLibCheck": true
13
15
  },
14
- "include": ["src/**/*.ts"],
16
+ "include": ["src/**/*.ts", "src/**/*.tsx"],
15
17
  "exclude": ["dist", "node_modules"]
16
18
  }
@@ -0,0 +1,13 @@
1
+ import { fileURLToPath } from 'node:url'
2
+ import { cordisXPluginViteConfig } from 'cordisx/vite'
3
+
4
+ const projectRoot = fileURLToPath(new URL('.', import.meta.url))
5
+
6
+ const config: ReturnType<typeof cordisXPluginViteConfig> = cordisXPluginViteConfig({
7
+ root: projectRoot,
8
+ entry: fileURLToPath(new URL('./{{sourceEntry}}', import.meta.url)),
9
+ outDir: fileURLToPath(new URL('./{{outDir}}', import.meta.url)),
10
+ entryFileName: 'module.js',
11
+ })
12
+
13
+ export default config
@@ -1,48 +0,0 @@
1
- import type { Context } from '@deepseek-ai/cordis'
2
- import {
3
- CORDISX_PLUGIN_MANIFEST_SCHEMA_V1,
4
- type CordisXPluginManifestV1,
5
- } from 'cordisx/contracts'
6
- import type {} from 'cordisx/contracts'
7
-
8
- export const manifest = {
9
- $schema: CORDISX_PLUGIN_MANIFEST_SCHEMA_V1,
10
- schemaVersion: 1,
11
- id: '{{pluginId}}',
12
- name: '{{packageName}}',
13
- capabilities: [],
14
- } as const satisfies CordisXPluginManifestV1
15
-
16
- export const inject = ['i18n', 'commands', 'slots']
17
-
18
- export function apply(ctx: Context): void {
19
- ctx.i18n.define({
20
- namespace: '{{pluginId}}',
21
- locale: 'en',
22
- default: true,
23
- messages: { 'command.hello': 'Say hello from {{packageName}}' },
24
- })
25
- ctx.commands.register({
26
- id: 'hello',
27
- title: {
28
- namespace: '{{pluginId}}',
29
- key: 'command.hello',
30
- fallback: 'Say hello from {{packageName}}',
31
- },
32
- }, () => console.info('[{{pluginId}}] hello'))
33
- ctx.slots.register({
34
- name: 'workspace.toolbar.items',
35
- id: 'hello',
36
- order: 10,
37
- }, {
38
- anchor: 'workspace.primary',
39
- placement: 'after',
40
- label: {
41
- namespace: '{{pluginId}}',
42
- key: 'command.hello',
43
- fallback: 'Say hello from {{packageName}}',
44
- },
45
- icon: 'host:open',
46
- command: { id: 'hello' },
47
- })
48
- }