@mingfuyun/star-allinone 0.2.0 → 0.3.1

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.
Files changed (31) hide show
  1. package/README.md +17 -10
  2. package/examples/AGENTS.md +26 -0
  3. package/examples/projects/starlink-frontend.star-allinone.json +7 -0
  4. package/examples/projects/starscope-frontend.star-allinone.json +7 -0
  5. package/examples/skills/custom-domain-invariants/SKILL.md +16 -0
  6. package/package.json +5 -3
  7. package/scripts/cli.js +52 -13
  8. package/scripts/gen/fixtures/run.js +104 -0
  9. package/scripts/gen/fixtures/widgets.openapi.json +194 -0
  10. package/scripts/gen/names.js +117 -0
  11. package/scripts/gen/page.js +483 -0
  12. package/scripts/gen/project.js +154 -0
  13. package/scripts/gen/service.js +264 -0
  14. package/scripts/gen/spec.js +220 -0
  15. package/scripts/gen/ts.js +121 -0
  16. package/skills/README.md +45 -188
  17. package/skills/shared/api-to-service/SKILL.md +18 -10
  18. package/skills/shared/{api-mock/SKILL.md → api-to-service/references/mock.md} +3 -10
  19. package/skills/shared/design-to-page/SKILL.md +45 -6
  20. package/skills/shared/design-to-page/references/list-page-template.md +44 -0
  21. package/skills/shared/design-to-page/references/page-conventions.md +1 -1
  22. package/skills/shared/i-have-adhd/{SKILL.md → i-have-adhd.md} +0 -10
  23. package/skills/shared/project-conventions/references/architecture.md +3 -0
  24. package/skills/shared/publish-test/SKILL.md +4 -2
  25. package/skills/shared/review-and-submit/SKILL.md +8 -16
  26. package/skills/shared/{ai-review/SKILL.md → review-and-submit/references/ai-review.md} +4 -11
  27. package/skills/shared/{commit-current-changes/SKILL.md → review-and-submit/references/commit.md} +4 -12
  28. package/skills/shared/{impact-analysis/SKILL.md → review-and-submit/references/impact-analysis.md} +1 -10
  29. package/skills/shared/{verify/SKILL.md → review-and-submit/references/verify.md} +4 -11
  30. package/skills/shared/project-conventions/SKILL.md +0 -34
  31. /package/skills/shared/{api-mock → api-to-service}/references/mock-conventions.md +0 -0
package/README.md CHANGED
@@ -17,9 +17,10 @@ pnpm add @mingfuyun/star-allinone
17
17
  公共 skill 由本仓库维护,随 `@mingfuyun/star-allinone` 发布。业务项目用 CLI 同步到自己的 `.agents/skills/shared-*` 并提交。Codex / Cursor 直接从业务仓库发现 skill,不写入 `~/.codex/skills`,团队成员也不需要本仓库源码。
18
18
 
19
19
  ```text
20
+ AGENTS.md ← 常驻约定,不要加 .cursor/rules
20
21
  .agents/skills/
21
- ├── shared-* ← npm 包同步,不能在业务项目直接改
22
- └── custom-* ← 业务项目自己维护
22
+ ├── shared-* ← npm 包同步,不能在业务项目直接改
23
+ └── custom-* ← 业务项目自己维护
23
24
  ```
24
25
 
25
26
  完整约定见 [skills/README.md](skills/README.md)。
@@ -32,9 +33,13 @@ pnpm add @mingfuyun/star-allinone
32
33
  pnpm exec star-allinone skills list
33
34
  pnpm exec star-allinone skills sync --dry-run
34
35
  pnpm exec star-allinone skills sync
36
+ pnpm exec star-allinone gen service --operation "GET /api/v1/devices/"
37
+ pnpm exec star-allinone gen page --feature Widgets --route /widgets --title 组件管理 --list-operation "GET /api/v1/widgets/"
35
38
  ```
36
39
 
37
- `list` 查看包内公共 skill。`sync` 把它们写入当前项目的 `.agents/skills/shared-*`,会整体替换过期的 `shared-*`,但不会改 `custom-*`。
40
+ `gen service` 按 OpenAPI/Swagger 写一条 `MFRequest` 方法和对应类型。`gen page` 生成标准列表页骨架,并可先生成相关 Service。
41
+
42
+ `list` 查看包内公共 skill。`sync` 把它们写入当前项目的 `.agents/skills/shared-*`,会整体替换过期的 `shared-*`,但不会改 `custom-*`。仓库根没有 `AGENTS.md` 时,会从 `examples/AGENTS.md` 补一份,已有文件不覆盖。
38
43
 
39
44
  ### 维护者:改公共 skill
40
45
 
@@ -72,26 +77,28 @@ StarLink 示例见 `examples/projects/`。
72
77
  pnpm exec star-allinone skills sync
73
78
  ```
74
79
 
75
- 4. 把真正的业务特例放到 `.agents/skills/custom-*`。审查、提交、发布、Mock 和 Service 生成不要做成 `custom-*`。
80
+ 4. 把真正的业务特例放到 `.agents/skills/custom-*`。审查、提交、发布、单接口、页面生成不要做成 `custom-*`。全量契约重建(若需要)必须 `disable-model-invocation`,并在 `AGENTS.md` 写清命令。
81
+
82
+ 5. 仓库根放 `AGENTS.md`(可从包内 `examples/AGENTS.md` 复制)。`skills sync` 只在该文件缺失时补一份,不会覆盖已有内容。常驻约定只写这一份,不要加 `.cursor/rules`。
76
83
 
77
- 5. 提交配置和 skill:
84
+ 6. 提交配置和 skill:
78
85
 
79
86
  ```bash
80
- git add .star-allinone.json .agents/skills
87
+ git add .star-allinone.json AGENTS.md .agents/skills
81
88
  git commit -m "chore(skill): 接入公共 skill"
82
89
  ```
83
90
 
84
91
  ### 业务项目:日常调用
85
92
 
86
- 在 Codex / Cursor 对话里用 `$skill-name` 显式调用,例如:
93
+ 只使用下面这些**用户入口**。
87
94
 
88
95
  ```text
89
96
  使用 $shared-api-to-service,为 GET /api/v1/devices/ 生成类型、Service 和 JSON Mock。
90
97
  ```
91
98
 
92
99
  ```text
93
- 使用 $shared-design-to-page,根据我上传的设计稿生成页面。
94
- 缺少的 Service、类型和 Mock 一并生成。
100
+ 使用 $shared-design-to-page,生成 /widgets 页面。
101
+ 标准列表接口 GET /api/v1/widgets/。
95
102
  ```
96
103
 
97
104
  ```text
@@ -103,7 +110,7 @@ git commit -m "chore(skill): 接入公共 skill"
103
110
  使用 $shared-publish-test,把当前分支发布到测试环境。
104
111
  ```
105
112
 
106
- 公共流程用 `shared-*`,项目专属规则用 `custom-*`。业务定制 skill 需要复用公共流程时,在 `custom-*` 的 `SKILL.md` 里写明先读取对应的 `.agents/skills/shared-*/SKILL.md`。
113
+ 公共流程用 `shared-*`,项目专属规则用 `custom-*`。`custom-*` 不能再实现一遍 Service/页面生成/审查。全量契约重建若需要,写成不可调用的脚本,并在 `AGENTS.md` 写命令。业务定制需要复用公共流程时,读取对应的 `.agents/skills/shared-*/SKILL.md`,不要再抛一个入口。
107
114
 
108
115
  ### 业务项目:更新公共 skill
109
116
 
@@ -0,0 +1,26 @@
1
+ # Agent instructions
2
+
3
+ This is a Umi Max + Ant Design 5 PC admin app. Shared skills live in `.agents/skills/shared-*` and are synced from `@mingfuyun/star-allinone`. Do not edit `shared-*`. Put project-only rules in `.agents/skills/custom-*` or `.agents/project/`.
4
+
5
+ Always-on instructions are this file. Do not add `.cursor/rules`.
6
+
7
+ ## Default workflow
8
+
9
+ User-facing shared entries only:
10
+
11
+ - One endpoint: `pnpm exec star-allinone gen service --operation "METHOD /path/"` or `$shared-api-to-service`.
12
+ - Pages: `$shared-design-to-page`. Standard lists run `pnpm exec star-allinone gen page` inside that skill.
13
+ - Review and commit: `$shared-review-and-submit`. Wait for confirmation.
14
+ - Test publish: `$shared-publish-test` after explicit pipeline confirmation.
15
+
16
+ ## Hard rules
17
+
18
+ - For non-trivial changes, read `.agents/skills/shared-project-conventions/references/architecture.md`.
19
+ - Pages use `import Service from '@/services'`. Do not call axios or `src/services/request.tsx` from pages.
20
+ - Lists: `MFProTable`. Forms: `ProForm`. Overlays: `CustomDrawer`, `BaseModal`, `HocModal` from `@mingfuyun/star-allinone`.
21
+ - Keep columns and actions in `const.tsx`. Empty cells render as `-`.
22
+ - Keep DTOs in the domain `typings.d.ts` namespace. Preserve backend snake_case.
23
+ - Do not edit `src/services/request.tsx` for a single endpoint.
24
+ - Every `await` uses try/catch. Do not use `Modal.confirm`.
25
+ - After TypeScript changes run `pnpm typecheck`.
26
+ - If `.agents/project/conventions.md` or `.agents/project/review-rules.md` exists, apply them as additions only.
@@ -0,0 +1,7 @@
1
+ {
2
+ "project": "starlink-frontend",
3
+ "publicSkillPrefix": "shared-",
4
+ "serviceDir": "src/services",
5
+ "openapi": "swagger.json",
6
+ "publishPipelineId": "5245265"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "project": "starscope-frontend",
3
+ "publicSkillPrefix": "shared-",
4
+ "serviceDir": "src/services",
5
+ "openapi": ".agents/project/openapi.json",
6
+ "publishPipelineId": "5108336"
7
+ }
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: custom-domain-invariants
3
+ description: Internal. Project domain extras for review. Do not invoke. Not a generate, page, or review entry.
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # Domain invariants
8
+
9
+ Not a user-facing entry. Pages, services, and review still go through `$shared-design-to-page`, `$shared-api-to-service`, and `$shared-review-and-submit`.
10
+
11
+ Read this file only as extra checks after those shared skills. Do not generate pages, services, or mocks here.
12
+
13
+ Example extras (replace with the project's real invariants):
14
+
15
+ - IPv4 and IPv6 branches must both be checked.
16
+ - Do not invent query param aliases that are missing from swagger.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mingfuyun/star-allinone",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "星系运维前端能力包",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -30,7 +30,9 @@
30
30
  "dist",
31
31
  "README.md",
32
32
  "scripts/cli.js",
33
- "skills"
33
+ "scripts/gen",
34
+ "skills",
35
+ "examples"
34
36
  ],
35
37
  "scripts": {
36
38
  "build": "webpack --env format=umd --mode=none && webpack --env format=esm --mode=none",
@@ -39,7 +41,7 @@
39
41
  "skills:sync": "node scripts/cli.js skills sync",
40
42
  "skills:list": "node scripts/cli.js skills list",
41
43
  "prepublishOnly": "npm run build",
42
- "test": "echo \"Error: no test specified\" && exit 1",
44
+ "test": "node scripts/gen/fixtures/run.js",
43
45
  "dev": "webpack --mode=none --watch",
44
46
  "clean": "rm -rf dist"
45
47
  },
package/scripts/cli.js CHANGED
@@ -17,11 +17,17 @@ function usage() {
17
17
  console.log(`Usage:
18
18
  star-allinone skills list
19
19
  star-allinone skills sync [--target <dir>] [--dry-run]
20
+ star-allinone gen service --operation "GET /api/v1/devices/" [--spec <file>] [--domain Devices] [--mock-file] [--dry-run]
21
+ star-allinone gen page --feature Widgets --route /widgets --title 组件管理 --list-operation "GET /api/v1/widgets/" [--create-operation ...] [--update-operation ...] [--delete-operation ...] [--dry-run]
20
22
 
21
- Run the command from a business project containing .star-allinone.json.
23
+ Run from a business project containing .star-allinone.json.
22
24
 
23
- Sync writes package skills/shared/* to <project>/.agents/skills/shared-*.
24
- It never writes ~/.codex/skills and never modifies custom-* skills.`);
25
+ skills sync writes package skills/shared/* to <project>/.agents/skills/shared-*.
26
+ It never writes ~/.codex/skills and never modifies custom-* skills.
27
+ If <project>/AGENTS.md is missing, it copies examples/AGENTS.md once.
28
+
29
+ gen service writes one MFRequest method and types from OpenAPI/Swagger.
30
+ gen page scaffolds a list + drawer + confirm page and can generate services first.`);
25
31
  process.exitCode = 1;
26
32
  }
27
33
 
@@ -33,8 +39,12 @@ function listSharedSkillNames() {
33
39
  .sort();
34
40
  }
35
41
 
42
+ function hasSkillMarkdown(name) {
43
+ return fs.existsSync(path.join(skillsRoot, name, 'SKILL.md'));
44
+ }
45
+
36
46
  function listSkills() {
37
- const skills = listSharedSkillNames();
47
+ const skills = listSharedSkillNames().filter(hasSkillMarkdown);
38
48
  if (!skills.length) {
39
49
  console.log('No shared skills found.');
40
50
  return;
@@ -50,8 +60,12 @@ function parseArgs(args) {
50
60
  for (let index = 0; index < args.length; index += 1) {
51
61
  const arg = args[index];
52
62
  if (arg === '--dry-run') options.dryRun = true;
53
- else if (arg === '--target') options.target = args[++index];
54
- else positional.push(arg);
63
+ else if (arg === '--mock-file') options.mockFile = true;
64
+ else if (arg === '--force') options.force = true;
65
+ else if (arg.startsWith('--')) {
66
+ const key = arg.slice(2).replace(/-([a-z])/g, (_, ch) => ch.toUpperCase());
67
+ options[key] = args[++index];
68
+ } else positional.push(arg);
55
69
  }
56
70
  return { options, positional };
57
71
  }
@@ -132,12 +146,14 @@ function pruneStaleShared(destination, prefix, syncedNames, options) {
132
146
 
133
147
  function selectedSharedSkills(config) {
134
148
  const available = listSharedSkillNames();
149
+ const support = available.filter((name) => !hasSkillMarkdown(name));
135
150
  if (!config.sharedSkills) return available;
136
151
  const missing = config.sharedSkills.filter((name) => !available.includes(name));
137
152
  if (missing.length) {
138
153
  throw new Error(`Unknown sharedSkills: ${missing.join(', ')}`);
139
154
  }
140
- return available.filter((name) => config.sharedSkills.includes(name));
155
+ const selected = available.filter((name) => config.sharedSkills.includes(name));
156
+ return [...new Set([...support, ...selected])];
141
157
  }
142
158
 
143
159
  function syncProject(options) {
@@ -158,17 +174,40 @@ function syncProject(options) {
158
174
  copySkill(path.join(skillsRoot, skillName), destination, skillName, prefix, variables, options);
159
175
  }
160
176
  pruneStaleShared(destination, prefix, syncedNames, options);
177
+ seedAgentsMarkdown(target, options);
161
178
  console.log(`${options.dryRun ? 'Would sync' : 'Synced'} shared skills -> ${destination}`);
162
179
  }
163
180
 
181
+ function seedAgentsMarkdown(target, options) {
182
+ const dest = path.join(target, 'AGENTS.md');
183
+ const source = path.join(root, 'examples', 'AGENTS.md');
184
+ if (fs.existsSync(dest) || !fs.existsSync(source)) return;
185
+ console.log(`${options.dryRun ? '[dry-run] ' : ''}AGENTS.md`);
186
+ if (!options.dryRun) fs.copyFileSync(source, dest);
187
+ }
188
+
164
189
  function main() {
165
190
  const [group, command, ...rest] = process.argv.slice(2);
166
- if (group !== 'skills') return usage();
167
- if (command === 'list') return listSkills();
168
- if (command !== 'sync') return usage();
169
- const { options, positional } = parseArgs(rest);
170
- if (positional.length) return usage();
171
- syncProject(options);
191
+ if (group === 'skills') {
192
+ if (command === 'list') return listSkills();
193
+ if (command !== 'sync') return usage();
194
+ const { options, positional } = parseArgs(rest);
195
+ if (positional.length) return usage();
196
+ return syncProject(options);
197
+ }
198
+ if (group === 'gen') {
199
+ const { options, positional } = parseArgs(rest);
200
+ if (positional.length) return usage();
201
+ if (command === 'service') {
202
+ if (!options.operation) return usage();
203
+ return require('./gen/service').generateService(process.cwd(), options);
204
+ }
205
+ if (command === 'page') {
206
+ if (!options.feature) return usage();
207
+ return require('./gen/page').generatePage(process.cwd(), options);
208
+ }
209
+ }
210
+ return usage();
172
211
  }
173
212
 
174
213
  try {
@@ -0,0 +1,104 @@
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const { generateService } = require('../service');
5
+ const { generatePage } = require('../page');
6
+
7
+ function assert(condition, message) {
8
+ if (!condition) throw new Error(message);
9
+ }
10
+
11
+ function makeProject() {
12
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'star-allinone-gen-'));
13
+ fs.writeFileSync(
14
+ path.join(root, '.star-allinone.json'),
15
+ JSON.stringify(
16
+ {
17
+ project: 'fixture',
18
+ serviceDir: 'src/services',
19
+ openapi: 'openapi.json',
20
+ },
21
+ null,
22
+ 2,
23
+ ),
24
+ );
25
+ fs.copyFileSync(
26
+ path.join(__dirname, 'widgets.openapi.json'),
27
+ path.join(root, 'openapi.json'),
28
+ );
29
+ fs.mkdirSync(path.join(root, 'src/services'), { recursive: true });
30
+ fs.writeFileSync(
31
+ path.join(root, 'src/services/request.tsx'),
32
+ 'const MFRequest = { get() {}, post() {}, patch() {}, delete() {} };\nexport default MFRequest;\n',
33
+ );
34
+ fs.writeFileSync(
35
+ path.join(root, 'src/services/index.ts'),
36
+ 'export default {};\n',
37
+ );
38
+ fs.mkdirSync(path.join(root, 'config'), { recursive: true });
39
+ fs.writeFileSync(
40
+ path.join(root, 'config/route.ts'),
41
+ `export default [
42
+ { path: '/', redirect: '/home' },
43
+ { path: '/*', component: '@/pages/404', name: '404' },
44
+ ];
45
+ `,
46
+ );
47
+ fs.mkdirSync(path.join(root, 'src/components/CommonTable'), { recursive: true });
48
+ fs.mkdirSync(path.join(root, 'src/pages'), { recursive: true });
49
+ return root;
50
+ }
51
+
52
+ function main() {
53
+ const root = makeProject();
54
+ const first = generateService(root, {
55
+ operation: 'GET /api/v1/widgets/',
56
+ mockFile: true,
57
+ });
58
+ assert(first.fn === 'widgetsList', `fn ${first.fn}`);
59
+ assert(first.domain === 'Widgets', `domain ${first.domain}`);
60
+ const service = fs.readFileSync(first.servicePath, 'utf8');
61
+ assert(service.includes("MFRequest.get<WIDGETS_API.WidgetList>('/api/v1/widgets/', params)"), service);
62
+ const typings = fs.readFileSync(first.typingsPath, 'utf8');
63
+ assert(typings.includes('type Widget ='), typings);
64
+ assert(typings.includes('type WidgetList ='), typings);
65
+ assert(typings.includes('results?: Widget[]'), typings);
66
+ assert(typings.includes('status?: string | null'), typings);
67
+ assert(typings.includes('type WidgetsListQuery'), typings);
68
+ const mockFile = path.join(root, 'mock/api/v1/widgets/get.json');
69
+ assert(fs.existsSync(mockFile), 'missing mock file');
70
+ const index = fs.readFileSync(path.join(root, 'src/services/index.ts'), 'utf8');
71
+ assert(index.includes("from './Widgets/widgets'"), index);
72
+
73
+ const reused = generateService(root, { operation: 'GET /api/v1/widgets/' });
74
+ assert(reused.status === 'reuse', 'expected reuse on second run');
75
+
76
+ generateService(root, { operation: 'PATCH /api/v1/widgets/{id}/' });
77
+ const patched = fs.readFileSync(first.servicePath, 'utf8');
78
+ assert(patched.includes('widgetsPartialUpdate'), patched);
79
+ assert(patched.includes('MFRequest.patch<WIDGETS_API.Widget>'), patched);
80
+
81
+ generatePage(root, {
82
+ feature: 'Widgets',
83
+ route: '/widgets',
84
+ title: '组件管理',
85
+ listOperation: 'GET /api/v1/widgets/',
86
+ createOperation: 'POST /api/v1/widgets/',
87
+ updateOperation: 'PATCH /api/v1/widgets/{id}/',
88
+ deleteOperation: 'DELETE /api/v1/widgets/{id}/',
89
+ });
90
+ const page = fs.readFileSync(path.join(root, 'src/pages/Widgets/index.tsx'), 'utf8');
91
+ assert(page.includes('Service.widgetsList'), page);
92
+ assert(page.includes("rowKey=\"id\""), page);
93
+ const route = fs.readFileSync(path.join(root, 'config/route.ts'), 'utf8');
94
+ assert(route.includes("path: '/widgets'"), route);
95
+ fs.rmSync(root, { recursive: true, force: true });
96
+ console.log('codegen fixtures passed');
97
+ }
98
+
99
+ try {
100
+ main();
101
+ } catch (error) {
102
+ console.error(error.stack || error.message);
103
+ process.exit(1);
104
+ }
@@ -0,0 +1,194 @@
1
+ {
2
+ "openapi": "3.0.3",
3
+ "info": {
4
+ "title": "codegen fixture",
5
+ "version": "1.0.0"
6
+ },
7
+ "paths": {
8
+ "/api/v1/widgets/": {
9
+ "get": {
10
+ "operationId": "widgets_list",
11
+ "tags": ["Widgets"],
12
+ "summary": "获取组件列表",
13
+ "parameters": [
14
+ {
15
+ "in": "query",
16
+ "name": "page",
17
+ "required": true,
18
+ "schema": { "type": "integer" }
19
+ },
20
+ {
21
+ "in": "query",
22
+ "name": "page_size",
23
+ "required": true,
24
+ "schema": { "type": "integer" }
25
+ },
26
+ {
27
+ "in": "query",
28
+ "name": "keyword",
29
+ "schema": { "type": "string" }
30
+ }
31
+ ],
32
+ "responses": {
33
+ "200": {
34
+ "description": "ok",
35
+ "content": {
36
+ "application/json": {
37
+ "schema": {
38
+ "type": "object",
39
+ "properties": {
40
+ "code": { "type": "integer" },
41
+ "message": { "type": "string" },
42
+ "data": {
43
+ "$ref": "#/components/schemas/WidgetList"
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ },
52
+ "post": {
53
+ "operationId": "widgets_create",
54
+ "tags": ["Widgets"],
55
+ "summary": "创建组件",
56
+ "requestBody": {
57
+ "content": {
58
+ "application/json": {
59
+ "schema": {
60
+ "$ref": "#/components/schemas/WidgetBody"
61
+ }
62
+ }
63
+ }
64
+ },
65
+ "responses": {
66
+ "201": {
67
+ "description": "created",
68
+ "content": {
69
+ "application/json": {
70
+ "schema": {
71
+ "type": "object",
72
+ "properties": {
73
+ "code": { "type": "integer" },
74
+ "message": { "type": "string" },
75
+ "data": {
76
+ "$ref": "#/components/schemas/Widget"
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+ },
86
+ "/api/v1/widgets/{id}/": {
87
+ "patch": {
88
+ "operationId": "widgets_partial_update",
89
+ "tags": ["Widgets"],
90
+ "summary": "更新组件",
91
+ "parameters": [
92
+ {
93
+ "in": "path",
94
+ "name": "id",
95
+ "required": true,
96
+ "schema": { "type": "string" }
97
+ }
98
+ ],
99
+ "requestBody": {
100
+ "content": {
101
+ "application/json": {
102
+ "schema": {
103
+ "$ref": "#/components/schemas/WidgetBody"
104
+ }
105
+ }
106
+ }
107
+ },
108
+ "responses": {
109
+ "200": {
110
+ "description": "ok",
111
+ "content": {
112
+ "application/json": {
113
+ "schema": {
114
+ "type": "object",
115
+ "properties": {
116
+ "code": { "type": "integer" },
117
+ "message": { "type": "string" },
118
+ "data": {
119
+ "$ref": "#/components/schemas/Widget"
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }
127
+ },
128
+ "delete": {
129
+ "operationId": "widgets_destroy",
130
+ "tags": ["Widgets"],
131
+ "summary": "删除组件",
132
+ "parameters": [
133
+ {
134
+ "in": "path",
135
+ "name": "id",
136
+ "required": true,
137
+ "schema": { "type": "string" }
138
+ }
139
+ ],
140
+ "responses": {
141
+ "200": {
142
+ "description": "ok",
143
+ "content": {
144
+ "application/json": {
145
+ "schema": {
146
+ "type": "object",
147
+ "properties": {
148
+ "code": { "type": "integer" },
149
+ "message": { "type": "string" },
150
+ "data": { "type": "object" }
151
+ }
152
+ }
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ },
160
+ "components": {
161
+ "schemas": {
162
+ "Widget": {
163
+ "type": "object",
164
+ "required": ["id", "name"],
165
+ "properties": {
166
+ "id": { "type": "string" },
167
+ "name": { "type": "string" },
168
+ "status": { "type": "string", "nullable": true },
169
+ "count": { "type": "integer" }
170
+ }
171
+ },
172
+ "WidgetBody": {
173
+ "type": "object",
174
+ "required": ["name"],
175
+ "properties": {
176
+ "name": { "type": "string" },
177
+ "status": { "type": "string" }
178
+ }
179
+ },
180
+ "WidgetList": {
181
+ "type": "object",
182
+ "properties": {
183
+ "results": {
184
+ "type": "array",
185
+ "items": {
186
+ "$ref": "#/components/schemas/Widget"
187
+ }
188
+ },
189
+ "count": { "type": "integer" }
190
+ }
191
+ }
192
+ }
193
+ }
194
+ }