draftgo-cli 3.0.1 → 3.0.33
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/LICENSE +21 -0
- package/README.md +73 -124
- package/package.json +21 -8
- package/resources/skill/SKILL.md +62 -89
- package/resources/skill/init/SKILL.md +18 -67
- package/resources/skill/manifest.json +27 -0
- package/resources/skill/pull/SKILL.md +18 -44
- package/resources/skill/push/SKILL.md +30 -247
- package/resources/skill/references/aihub.md +86 -0
- package/resources/skill/references/api-endpoints.md +178 -0
- package/resources/skill/references/api.json +20248 -0
- package/resources/skill/{quickref → references}/app-api.md +44 -14
- package/resources/skill/{core → references}/architecture.md +6 -26
- package/resources/skill/references/chat-sdk.md +201 -0
- package/resources/skill/references/custom-services.md +308 -0
- package/resources/skill/references/data.md +298 -0
- package/resources/skill/references/db-relations.md +227 -0
- package/resources/skill/references/frontend.md +788 -0
- package/resources/skill/references/modules.md +66 -0
- package/resources/skill/references/parallel.md +48 -0
- package/resources/skill/{specs → references}/runtime.md +31 -1
- package/resources/skill/{specs → references}/security.md +3 -3
- package/resources/skill/references/ui-protocol.md +99 -0
- package/resources/skill/scripts/draftgo_delete.py +0 -2
- package/resources/skill/scripts/draftgo_init.py +15 -3
- package/resources/skill/scripts/draftgo_pull.py +154 -87
- package/resources/skill/scripts/draftgo_push.py +440 -183
- package/resources/skill/story/SKILL.md +13 -23
- package/src/cli.js +22 -7
- package/src/commandRegistry.js +34 -0
- package/src/commands/api.js +204 -0
- package/src/commands/autoPush.js +41 -0
- package/src/commands/check.js +27 -17
- package/src/commands/delete.js +6 -4
- package/src/commands/deploy.js +31 -0
- package/src/commands/help.js +41 -28
- package/src/commands/init.js +34 -20
- package/src/commands/local.js +9 -3
- package/src/commands/map.js +18 -7
- package/src/commands/sync.js +11 -4
- package/src/commands/update.js +39 -52
- package/src/commands/verifyUi.js +199 -0
- package/src/index.js +13 -46
- package/src/localdev/compose.js +48 -197
- package/src/localdev/index.js +116 -216
- package/src/localdev/mysqlClient.js +12 -9
- package/src/localdev/services.js +163 -0
- package/src/platforms.js +3 -3
- package/src/projectConfig.js +12 -2
- package/src/projectMap.js +240 -68
- package/src/skill.js +113 -29
- package/src/updateCheck.js +37 -15
- package/resources/skill/core/modules.md +0 -54
- package/resources/skill/practices/anti-patterns.md +0 -70
- package/resources/skill/practices/best-practices.md +0 -41
- package/resources/skill/practices/dev-declaration.md +0 -94
- package/resources/skill/quickref/api-endpoints.md +0 -130
- package/resources/skill/quickref/api.json +0 -17675
- package/resources/skill/quickref/dg-components.md +0 -198
- package/resources/skill/rules/dev-workflow.md +0 -652
- package/resources/skill/rules/frontend.md +0 -210
- package/resources/skill/rules/parallel.md +0 -263
- package/resources/skill/specs/data.md +0 -108
- package/resources/skill/specs/ui-protocol.md +0 -68
- package/src/commands/doctor.js +0 -54
- package/src/commands/new.js +0 -183
- package/src/commands/projectScript.js +0 -37
- /package/resources/skill/{rules → references}/debugging-syntax.md +0 -0
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: draftgo-story
|
|
3
|
-
description: "Use this skill to build, load, evolve, or conflict-check the project's Story file (.draftgo/story.yaml).
|
|
4
|
-
version: 1.0.0
|
|
5
|
-
allowed-tools: Read, Write, Glob, Bash(find:*)
|
|
3
|
+
description: "Use this skill to build, load, evolve, or conflict-check the project's Story file (.draftgo/story.yaml). Use it when the user requests product-story work, when an existing Story conflicts with a requested product decision, or when preserving a durable product decision is valuable."
|
|
6
4
|
---
|
|
7
5
|
|
|
8
6
|
# DraftGo Story System
|
|
9
7
|
|
|
10
|
-
>
|
|
11
|
-
>
|
|
12
|
-
> Story 不是文档,不是规范,是创作者对"这个系统是什么、为什么存在、不做什么"的持续叙述。
|
|
13
|
-
> 文件只是理解的副产品。
|
|
8
|
+
> Story 记录产品身份、设计总述、长期决策和当前方向。项目已有 Story 且请求触及定位或既有决策时加载;用户要求或形成需要长期保留的产品决策时创建或更新。
|
|
14
9
|
|
|
15
10
|
---
|
|
16
11
|
|
|
@@ -18,11 +13,11 @@ allowed-tools: Read, Write, Glob, Bash(find:*)
|
|
|
18
13
|
|
|
19
14
|
| 原则 | 含义 |
|
|
20
15
|
|------|------|
|
|
21
|
-
|
|
|
16
|
+
| Decision record | 记录会影响后续取舍的产品定位与决策 |
|
|
22
17
|
| AI 维护,人确认 | 开发者只聊天,AI 负责提炼和写入 |
|
|
23
18
|
| 三层结构 | 身份(稳) → 决策(只增) → 当前(频变) |
|
|
24
19
|
| 决策日志最值钱 | 取舍依据是代码永远表达不了的信息 |
|
|
25
|
-
| 工具无关 |
|
|
20
|
+
| 工具无关 | 使用可移植的 YAML 文件 |
|
|
26
21
|
|
|
27
22
|
---
|
|
28
23
|
|
|
@@ -43,7 +38,7 @@ identity:
|
|
|
43
38
|
what: "一句话讲清这是什么,不超过 30 字"
|
|
44
39
|
why: |
|
|
45
40
|
解决什么具体痛点,给谁用。
|
|
46
|
-
|
|
41
|
+
用两三句具体描述说明使用者和场景。
|
|
47
42
|
not:
|
|
48
43
|
- "不是 XX"
|
|
49
44
|
- "不变成 YY"
|
|
@@ -71,7 +66,7 @@ decisions:
|
|
|
71
66
|
# === 第四层:当前方向(频繁更新)===
|
|
72
67
|
now:
|
|
73
68
|
focus: "当前阶段在做什么"
|
|
74
|
-
next: "
|
|
69
|
+
next: "下一步的大致方向"
|
|
75
70
|
open_questions:
|
|
76
71
|
- "还没想清楚的事"
|
|
77
72
|
```
|
|
@@ -81,18 +76,16 @@ now:
|
|
|
81
76
|
## 触发协议
|
|
82
77
|
|
|
83
78
|
```
|
|
84
|
-
|
|
79
|
+
需要 Story 时:
|
|
85
80
|
|
|
86
81
|
1. 静默检查 .draftgo/story.yaml
|
|
87
82
|
2. 分流:
|
|
88
|
-
├─ 不存在 →
|
|
89
|
-
├─ maturity = seed →
|
|
83
|
+
├─ 不存在 → 用户要求创建或需要沉淀关键决策时,进入「构建流程」
|
|
84
|
+
├─ maturity = seed → 加载并把不确定项作为后续判断背景
|
|
90
85
|
└─ growing / stable → 静默加载,直接开始
|
|
91
86
|
3. 整个对话期间,Story 作为最高优先级上下文锚定
|
|
92
87
|
```
|
|
93
88
|
|
|
94
|
-
**Gate 不可妥协**:开发者说"加个页面",AI 必须先确保 Story 存在,再动手。
|
|
95
|
-
|
|
96
89
|
---
|
|
97
90
|
|
|
98
91
|
## 构建流程
|
|
@@ -102,7 +95,7 @@ now:
|
|
|
102
95
|
```
|
|
103
96
|
扫描 .draftgo/ 下的数据,判断走哪条路径:
|
|
104
97
|
|
|
105
|
-
├─ 存在用户自建内容(非 tag="系统" 的页面、db_meta、custom_scripts
|
|
98
|
+
├─ 存在用户自建内容(非 tag="系统" 的页面、db_meta、custom_scripts)→ 路径 A
|
|
106
99
|
├─ 用户提供了设计文档(PRD、原型图描述、需求文档等)→ 路径 A(以文档为分析素材)
|
|
107
100
|
└─ 只有内置系统页面,无用户自建内容,无设计文档 → 路径 B(对话采集)
|
|
108
101
|
```
|
|
@@ -117,7 +110,6 @@ now:
|
|
|
117
110
|
- .draftgo/navigations/index.json → 导航结构
|
|
118
111
|
- .draftgo/db_meta/index.json → 数据模型
|
|
119
112
|
- .draftgo/custom_scripts/index.json → 自定义脚本
|
|
120
|
-
- .draftgo/external_apis/index.json → 外部 API
|
|
121
113
|
- .draftgo/changelog.md → 更新日志
|
|
122
114
|
- .draftgo/lessons/ → 开发经验记录
|
|
123
115
|
- .draftgo/Task/ → 历史任务文档
|
|
@@ -144,9 +136,9 @@ now:
|
|
|
144
136
|
```
|
|
145
137
|
输出:[ 检测到全新项目,先聊一下你想做什么 ]
|
|
146
138
|
|
|
147
|
-
|
|
139
|
+
通过简短对话采集,信息足够后即进入提炼:
|
|
148
140
|
|
|
149
|
-
Q1:
|
|
141
|
+
Q1: 这个系统的整体定位和存在理由是什么?
|
|
150
142
|
Q2: 谁会用它?什么场景下用?
|
|
151
143
|
Q3: 类似的东西可能有哪些?你想做出什么不同?
|
|
152
144
|
Q4: 大致有哪些模块/功能块?它们之间怎么串起来?
|
|
@@ -242,15 +234,13 @@ AI 在开发过程中检测到"方向性决策"时,主动提议沉淀:
|
|
|
242
234
|
| `identity.*` | 极少 | 仅重大方向转向时(冲突检测中确认) |
|
|
243
235
|
| `design.overview` | 里程碑级 | 核心流转发生结构性变化时更新 |
|
|
244
236
|
| `design.modules` | 新增模块时 | 只增/改,不删已有模块(模块下线标注即可) |
|
|
245
|
-
| `decisions` | 只增不删 |
|
|
237
|
+
| `decisions` | 只增不删 | supersede 时不删旧的只标记 |
|
|
246
238
|
| `now.focus` | 周/里程碑 | 对话结束时 AI 提议更新 |
|
|
247
239
|
| `now.next` | 周 | 同上 |
|
|
248
240
|
| `now.open_questions` | 随时 | 出现新的悬而未决事项 |
|
|
249
241
|
| `meta.last_touched` | 每次更新 | 自动 |
|
|
250
242
|
| `meta.maturity` | 手动 | seed→growing: 首次确认后; growing→stable: 决策≥5条且identity不再变 |
|
|
251
243
|
|
|
252
|
-
**decisions ID 编号规则:** D001 起递增,永不复用,永不删除。
|
|
253
|
-
|
|
254
244
|
---
|
|
255
245
|
|
|
256
246
|
## 操作流程汇总
|
package/src/cli.js
CHANGED
|
@@ -9,13 +9,22 @@ const VALUE_FLAGS = new Set([
|
|
|
9
9
|
'server',
|
|
10
10
|
'token',
|
|
11
11
|
'output',
|
|
12
|
+
'url',
|
|
13
|
+
'browser',
|
|
14
|
+
'mobile-check',
|
|
15
|
+
'screenshot',
|
|
16
|
+
'selector',
|
|
17
|
+
'width',
|
|
18
|
+
'height',
|
|
19
|
+
'wait-ms',
|
|
20
|
+
'delivery',
|
|
12
21
|
'assignee',
|
|
13
22
|
'assignee-id',
|
|
14
23
|
]);
|
|
15
24
|
|
|
16
25
|
function parse(argv) {
|
|
17
26
|
const args = argv.slice();
|
|
18
|
-
const out = { command: null, positional: [], flags: {} };
|
|
27
|
+
const out = { command: null, positional: [], flags: {}, errors: [] };
|
|
19
28
|
while (args.length) {
|
|
20
29
|
const a = args.shift();
|
|
21
30
|
if (a === '--') { out.positional.push(...args); break; }
|
|
@@ -23,16 +32,22 @@ function parse(argv) {
|
|
|
23
32
|
const raw = a.slice(2);
|
|
24
33
|
const eq = raw.indexOf('=');
|
|
25
34
|
if (eq >= 0) {
|
|
26
|
-
|
|
35
|
+
const key = raw.slice(0, eq);
|
|
36
|
+
const value = raw.slice(eq + 1);
|
|
37
|
+
if (VALUE_FLAGS.has(key) && !value) {
|
|
38
|
+
out.errors.push(`选项 --${key} 需要一个值。`);
|
|
39
|
+
} else {
|
|
40
|
+
out.flags[key] = value;
|
|
41
|
+
}
|
|
27
42
|
} else {
|
|
28
43
|
const next = args[0];
|
|
29
|
-
if (
|
|
30
|
-
//
|
|
31
|
-
if (
|
|
44
|
+
if (VALUE_FLAGS.has(raw)) {
|
|
45
|
+
// A declared value flag must never silently become boolean true.
|
|
46
|
+
if (next === undefined || next === '--' || next.startsWith('--')) {
|
|
47
|
+
out.errors.push(`选项 --${raw} 需要一个值。`);
|
|
48
|
+
} else {
|
|
32
49
|
out.flags[raw] = next;
|
|
33
50
|
args.shift();
|
|
34
|
-
} else {
|
|
35
|
-
out.flags[raw] = true;
|
|
36
51
|
}
|
|
37
52
|
} else {
|
|
38
53
|
out.flags[raw] = true;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Keep command names, aliases, and dispatch behavior in one place. This avoids
|
|
4
|
+
// aliases silently drifting away from the implementation as the CLI grows.
|
|
5
|
+
const commands = [
|
|
6
|
+
{ name: 'init', run: (dir, args, flags) => require('./commands/init')(dir, args, flags) },
|
|
7
|
+
{ name: 'update', run: (dir, args, flags) => require('./commands/update')(dir, args, flags) },
|
|
8
|
+
{ name: 'uninstall', aliases: ['remove'], run: (dir, args, flags) => require('./commands/uninstall')(dir, args, flags) },
|
|
9
|
+
{ name: 'status', run: (dir) => require('./commands/status')(dir) },
|
|
10
|
+
{ name: 'map', run: (dir, _args, flags) => require('./commands/map')(dir, flags) },
|
|
11
|
+
{ name: 'check', run: (dir, _args, flags) => require('./commands/check')(dir, flags) },
|
|
12
|
+
{ name: 'verify-ui', aliases: ['verifyui'], run: (dir, args, flags) => require('./commands/verifyUi')(dir, args, flags) },
|
|
13
|
+
{ name: 'api', run: (_dir, args, flags) => require('./commands/api')(args, flags) },
|
|
14
|
+
{ name: 'delete', aliases: ['del', 'rm'], run: (dir, args, flags) => require('./commands/delete')(dir, args, flags) },
|
|
15
|
+
{ name: 'deploy', run: (dir, args, flags) => require('./commands/deploy')(dir, args, flags) },
|
|
16
|
+
{ name: 'auto-push', aliases: ['autopush'], run: (dir, args, flags) => require('./commands/autoPush')(dir, args, flags) },
|
|
17
|
+
{ name: 'pull', run: (dir, args, flags) => require('./commands/sync')(dir, 'pull', args, flags) },
|
|
18
|
+
{ name: 'push', run: (dir, args, flags) => require('./commands/sync')(dir, 'push', args, flags) },
|
|
19
|
+
{ name: 'local', run: (dir, args, flags) => require('./commands/local')(dir, args, flags) },
|
|
20
|
+
{ name: 'list-targets', aliases: ['targets'], run: () => require('./commands/listTargets')() },
|
|
21
|
+
{ name: 'local-dev', aliases: ['localdev'], run: (dir, args, flags) => require('./commands/localDev')(dir, args, flags) },
|
|
22
|
+
{ name: 'connect', aliases: ['login'], run: (dir, args, flags) => require('./commands/connect')(dir, args, flags) },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const aliases = new Map();
|
|
26
|
+
for (const command of commands) {
|
|
27
|
+
for (const alias of [command.name, ...(command.aliases || [])]) aliases.set(alias, command);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function resolveCommand(name) {
|
|
31
|
+
return aliases.get(name);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { commands, resolveCommand };
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const log = require('../logger');
|
|
6
|
+
|
|
7
|
+
const API_FILE = path.resolve(__dirname, '..', '..', 'resources', 'skill', 'references', 'api.json');
|
|
8
|
+
const HTTP_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete', 'head', 'options']);
|
|
9
|
+
const AUTH_HEADER_NAMES = new Set(['authorization', 'proxy-authorization', 'x-api-key']);
|
|
10
|
+
|
|
11
|
+
function resolveRef(spec, ref) {
|
|
12
|
+
if (!ref || !ref.startsWith('#/')) return null;
|
|
13
|
+
return ref.slice(2).split('/').reduce((value, part) => value && value[part], spec);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function schemaType(schema, spec) {
|
|
17
|
+
if (!schema || !Object.keys(schema).length) return 'unspecified';
|
|
18
|
+
if (schema.$ref) {
|
|
19
|
+
const target = resolveRef(spec, schema.$ref);
|
|
20
|
+
return target && (target.title || schema.$ref.split('/').pop()) || schema.$ref;
|
|
21
|
+
}
|
|
22
|
+
if (schema.anyOf) return schema.anyOf.map((item) => schemaType(item, spec)).join(' | ');
|
|
23
|
+
if (schema.oneOf) return schema.oneOf.map((item) => schemaType(item, spec)).join(' | ');
|
|
24
|
+
if (schema.allOf) return schema.allOf.map((item) => schemaType(item, spec)).join(' & ');
|
|
25
|
+
if (schema.type === 'array') return `${schemaType(schema.items, spec)}[]`;
|
|
26
|
+
if (schema.type) return schema.format ? `${schema.type} (${schema.format})` : schema.type;
|
|
27
|
+
if (schema.properties) return 'object';
|
|
28
|
+
return 'unspecified';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function schemaNotes(schema) {
|
|
32
|
+
const notes = [];
|
|
33
|
+
if (schema.description) notes.push(schema.description);
|
|
34
|
+
if (schema.default !== undefined) notes.push(`default: ${JSON.stringify(schema.default)}`);
|
|
35
|
+
if (schema.enum) notes.push(`enum: ${schema.enum.map((item) => JSON.stringify(item)).join(', ')}`);
|
|
36
|
+
return notes.length ? ` (${notes.join('; ')})` : '';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function printSchema(schema, spec, indent = ' ', seen = new Set()) {
|
|
40
|
+
if (!schema || !Object.keys(schema).length) {
|
|
41
|
+
console.log(`${indent}schema: unspecified`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let resolved = schema;
|
|
46
|
+
let refName = '';
|
|
47
|
+
if (schema.$ref) {
|
|
48
|
+
refName = schema.$ref.split('/').pop();
|
|
49
|
+
if (seen.has(schema.$ref)) {
|
|
50
|
+
console.log(`${indent}${refName} (recursive)`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
resolved = resolveRef(spec, schema.$ref) || schema;
|
|
54
|
+
seen = new Set(seen).add(schema.$ref);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log(`${indent}schema: ${refName ? `${refName} (${schemaType(resolved, spec)})` : schemaType(resolved, spec)}${schemaNotes(resolved)}`);
|
|
58
|
+
const variants = resolved.anyOf || resolved.oneOf || resolved.allOf;
|
|
59
|
+
if (variants) {
|
|
60
|
+
variants.forEach((variant, index) => {
|
|
61
|
+
console.log(`${indent} variant ${index + 1}:`);
|
|
62
|
+
printSchema(variant, spec, `${indent} `, seen);
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const required = new Set(resolved.required || []);
|
|
67
|
+
for (const [name, property] of Object.entries(resolved.properties || {})) {
|
|
68
|
+
const fieldSchema = property.$ref ? resolveRef(spec, property.$ref) || property : property;
|
|
69
|
+
const marker = required.has(name) ? ' required' : ' optional';
|
|
70
|
+
console.log(`${indent} ${name}: ${schemaType(property, spec)}${marker}${schemaNotes(fieldSchema)}`);
|
|
71
|
+
}
|
|
72
|
+
if (resolved.additionalProperties === true) console.log(`${indent} additional properties: allowed`);
|
|
73
|
+
if (resolved.items && !resolved.properties) printSchema(resolved.items, spec, `${indent} `, seen);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function authenticationFor(operation, spec) {
|
|
77
|
+
const security = operation.security === undefined ? spec.security : operation.security;
|
|
78
|
+
const schemes = (security || []).flatMap((requirement) => Object.keys(requirement).map((name) => {
|
|
79
|
+
const definition = spec.components && spec.components.securitySchemes && spec.components.securitySchemes[name] || {};
|
|
80
|
+
return { name, type: definition.type || 'unknown', scheme: definition.scheme, bearerFormat: definition.bearerFormat };
|
|
81
|
+
}));
|
|
82
|
+
const headers = (operation.parameters || []).filter((parameter) =>
|
|
83
|
+
parameter.in === 'header' && AUTH_HEADER_NAMES.has(String(parameter.name).toLowerCase())
|
|
84
|
+
);
|
|
85
|
+
return { schemes, headers };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function printAuthentication(operation, spec) {
|
|
89
|
+
const auth = authenticationFor(operation, spec);
|
|
90
|
+
if (!auth.schemes.length && !auth.headers.length) {
|
|
91
|
+
console.log(' Authentication: no requirement declared in the bundled OpenAPI.');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
console.log(' Authentication:');
|
|
95
|
+
for (const scheme of auth.schemes) {
|
|
96
|
+
const detail = [scheme.type, scheme.scheme, scheme.bearerFormat].filter(Boolean).join(', ');
|
|
97
|
+
console.log(` security scheme ${scheme.name}${detail ? ` (${detail})` : ''}`);
|
|
98
|
+
}
|
|
99
|
+
for (const header of auth.headers) {
|
|
100
|
+
console.log(` header ${header.name}: ${schemaType(header.schema, spec)}${header.required ? ' (required)' : ' (optional)'}${schemaNotes(header)}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function printOperation(item, spec) {
|
|
105
|
+
console.log(`\n${item.method} ${item.route}${item.summary ? ` ${item.summary}` : ''}`);
|
|
106
|
+
if (item.operationId) console.log(` Operation: ${item.operationId}`);
|
|
107
|
+
printAuthentication(item.operation, spec);
|
|
108
|
+
|
|
109
|
+
const parameters = (item.operation.parameters || []).filter((parameter) =>
|
|
110
|
+
!(parameter.in === 'header' && AUTH_HEADER_NAMES.has(String(parameter.name).toLowerCase()))
|
|
111
|
+
);
|
|
112
|
+
if (parameters.length) {
|
|
113
|
+
console.log(' Parameters:');
|
|
114
|
+
for (const parameter of parameters) {
|
|
115
|
+
console.log(` ${parameter.in} ${parameter.name}: ${schemaType(parameter.schema, spec)}${parameter.required ? ' (required)' : ' (optional)'}${schemaNotes(parameter)}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (item.operation.requestBody) {
|
|
120
|
+
console.log(` Request body: ${item.operation.requestBody.required ? 'required' : 'optional'}`);
|
|
121
|
+
for (const [contentType, media] of Object.entries(item.operation.requestBody.content || {})) {
|
|
122
|
+
console.log(` ${contentType}:`);
|
|
123
|
+
printSchema(media.schema, spec, ' ');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const responses = Object.entries(item.operation.responses || {});
|
|
128
|
+
if (responses.length) {
|
|
129
|
+
console.log(' Responses:');
|
|
130
|
+
for (const [status, response] of responses) {
|
|
131
|
+
console.log(` ${status}${response.description ? ` ${response.description}` : ''}`);
|
|
132
|
+
for (const [contentType, media] of Object.entries(response.content || {})) {
|
|
133
|
+
console.log(` ${contentType}:`);
|
|
134
|
+
printSchema(media.schema, spec, ' ');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function apiCommand(positional, flags = {}) {
|
|
141
|
+
const keyword = String(positional.join(' ') || '').trim().toLowerCase();
|
|
142
|
+
if (!keyword) {
|
|
143
|
+
log.err('用法:draftgo api <keyword>');
|
|
144
|
+
log.dim(' 示例:draftgo api pages batch');
|
|
145
|
+
return 1;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let spec;
|
|
149
|
+
try {
|
|
150
|
+
spec = JSON.parse(fs.readFileSync(API_FILE, 'utf8'));
|
|
151
|
+
} catch (err) {
|
|
152
|
+
log.err(`无法读取 OpenAPI:${err.message}`);
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const words = keyword.split(/\s+/).filter(Boolean);
|
|
157
|
+
const matches = [];
|
|
158
|
+
for (const [route, methods] of Object.entries(spec.paths || {})) {
|
|
159
|
+
for (const [method, operation] of Object.entries(methods || {})) {
|
|
160
|
+
if (!HTTP_METHODS.has(method.toLowerCase())) continue;
|
|
161
|
+
const haystack = [route, method, operation.summary, operation.operationId, ...(operation.tags || [])]
|
|
162
|
+
.filter(Boolean).join(' ').toLowerCase();
|
|
163
|
+
if (!words.every((word) => haystack.includes(word))) continue;
|
|
164
|
+
matches.push({
|
|
165
|
+
method: method.toUpperCase(),
|
|
166
|
+
route,
|
|
167
|
+
summary: operation.summary || '',
|
|
168
|
+
operationId: operation.operationId || '',
|
|
169
|
+
tags: operation.tags || [],
|
|
170
|
+
operation,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (flags.output === 'json') {
|
|
176
|
+
console.log(JSON.stringify({
|
|
177
|
+
source: {
|
|
178
|
+
kind: 'DraftGo bundled base OpenAPI',
|
|
179
|
+
openapi: spec.openapi || null,
|
|
180
|
+
version: spec.info && spec.info.version || null,
|
|
181
|
+
customServiceRoutes: {
|
|
182
|
+
included: false,
|
|
183
|
+
reason: 'Custom service routes are registered dynamically from local app.Route calls.',
|
|
184
|
+
derive: '<METHOD> /api/x/<slug><route-path>',
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
query: keyword,
|
|
188
|
+
matches,
|
|
189
|
+
}, null, 2));
|
|
190
|
+
return matches.length ? 0 : 1;
|
|
191
|
+
}
|
|
192
|
+
if (!matches.length) {
|
|
193
|
+
log.warn(`未找到包含“${keyword}”的 API。`);
|
|
194
|
+
return 1;
|
|
195
|
+
}
|
|
196
|
+
log.title(`DraftGo base API — ${keyword}`);
|
|
197
|
+
log.dim(`Source: bundled DraftGo base OpenAPI v${spec.info && spec.info.version || 'unknown'}; these are platform APIs, not project-defined APIs.`);
|
|
198
|
+
log.dim('Custom service routes are not indexed here: derive them from local app.Route calls as <METHOD> /api/x/<slug><route-path>.');
|
|
199
|
+
for (const item of matches.slice(0, 50)) printOperation(item, spec);
|
|
200
|
+
if (matches.length > 50) log.dim(`\nOnly the first 50 of ${matches.length} matches are shown; use --output json for all contracts.`);
|
|
201
|
+
return 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = apiCommand;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const log = require('../logger');
|
|
6
|
+
const check = require('./check');
|
|
7
|
+
const sync = require('./sync');
|
|
8
|
+
|
|
9
|
+
function autoPush(projectDir, positional, flags = {}) {
|
|
10
|
+
const configPath = path.join(projectDir, '.draftgo', 'config.json');
|
|
11
|
+
if (!fs.existsSync(configPath)) {
|
|
12
|
+
log.dim('自动推送未执行:未连接 DraftGo 服务器(缺少 .draftgo/config.json)。');
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let config;
|
|
17
|
+
try {
|
|
18
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
19
|
+
} catch (err) {
|
|
20
|
+
log.err(`自动推送未执行:config.json 解析失败:${err.message}`);
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
log.step('auto-push: 先执行 check …');
|
|
25
|
+
const checkCode = check(projectDir, { strict: flags.strict });
|
|
26
|
+
if (checkCode !== 0) {
|
|
27
|
+
log.err('自动推送终止:check 未通过,请修复后重试。');
|
|
28
|
+
return checkCode;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (config.auto_push !== true) {
|
|
32
|
+
log.dim('自动推送已跳过:check 已通过,但 .draftgo/config.json 的 auto_push 不为 true。');
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
log.step('auto_push=true:开始 push …');
|
|
37
|
+
const pushArgs = flags.batch ? ['--batch', ...positional] : positional;
|
|
38
|
+
return sync(projectDir, 'push', pushArgs, flags);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = autoPush;
|
package/src/commands/check.js
CHANGED
|
@@ -14,39 +14,49 @@ function check(projectDir, flags = {}) {
|
|
|
14
14
|
log.title('draftgo check');
|
|
15
15
|
log.info(`项目目录:${projectDir}`);
|
|
16
16
|
|
|
17
|
+
const m = result.map;
|
|
17
18
|
console.log('');
|
|
18
|
-
|
|
19
|
+
const overview = [
|
|
20
|
+
['pages', m.pages.length],
|
|
21
|
+
['nav', m.navigations.length],
|
|
22
|
+
['db', m.db_meta.length],
|
|
23
|
+
['scripts', m.custom_scripts.length],
|
|
24
|
+
['aihub', m.aihub.length],
|
|
25
|
+
['docs', m.docs.length],
|
|
26
|
+
];
|
|
27
|
+
console.log(' ' + overview.map(([k, v]) => `${log.c.cyan(k)} ${v}`).join(' '));
|
|
28
|
+
const pageAreas = [
|
|
29
|
+
['home', m.pageGroups.home.length],
|
|
30
|
+
['admin', m.pageGroups.admin.length],
|
|
31
|
+
['business', m.pageGroups.business.length],
|
|
32
|
+
['system', m.pageGroups.system.length],
|
|
33
|
+
];
|
|
34
|
+
console.log(' ' + pageAreas.map(([k, v]) => `${log.c.cyan(k)} ${v}`).join(' '));
|
|
19
35
|
|
|
20
36
|
if (result.errors.length) {
|
|
21
37
|
console.log('');
|
|
22
|
-
console.log(
|
|
23
|
-
result.errors.forEach((msg) => console.log(` - ${msg}`));
|
|
38
|
+
result.errors.forEach((msg) => console.log(` ${log.c.red('x')} ${msg}`));
|
|
24
39
|
}
|
|
25
40
|
|
|
26
41
|
if (result.warnings.length) {
|
|
27
42
|
console.log('');
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
console.log('');
|
|
34
|
-
log.ok('未发现明显闭环问题。');
|
|
43
|
+
result.warnings.forEach((msg) => {
|
|
44
|
+
const detail = (result.warningDetails || []).find((item) => item.message === msg);
|
|
45
|
+
const prefix = detail ? `[${detail.code}/${detail.confidence}] ` : '';
|
|
46
|
+
console.log(` ${log.c.yellow('!')} ${prefix}${msg}`);
|
|
47
|
+
});
|
|
35
48
|
}
|
|
36
49
|
|
|
50
|
+
console.log('');
|
|
37
51
|
if (result.errors.length) {
|
|
38
|
-
|
|
39
|
-
console.log(`${log.c.red('x')} 检查未通过:请先修复错误。`);
|
|
52
|
+
log.err('检查未通过:请先修复错误。');
|
|
40
53
|
return 1;
|
|
41
54
|
}
|
|
42
55
|
if (flags.strict && result.warnings.length) {
|
|
43
|
-
|
|
44
|
-
console.log(`${log.c.red('x')} strict 模式:存在提醒项,检查未通过。`);
|
|
56
|
+
log.err('strict 模式:存在提醒项,检查未通过。');
|
|
45
57
|
return 1;
|
|
46
58
|
}
|
|
47
|
-
|
|
48
|
-
console.log('');
|
|
49
|
-
log.ok(result.warnings.length ? '检查完成:存在提醒项,建议开发代理处理后再 push。' : '检查通过。');
|
|
59
|
+
log.ok(result.warnings.length ? '检查完成(有提醒项)。' : '检查通过。');
|
|
50
60
|
return 0;
|
|
51
61
|
}
|
|
52
62
|
|
package/src/commands/delete.js
CHANGED
|
@@ -30,8 +30,6 @@ const TYPE_ALIASES = {
|
|
|
30
30
|
docs: 'docs',
|
|
31
31
|
doc_category: 'doc_categories',
|
|
32
32
|
doc_categories: 'doc_categories',
|
|
33
|
-
external_api: 'external_apis',
|
|
34
|
-
external_apis: 'external_apis',
|
|
35
33
|
aihub: 'aihub',
|
|
36
34
|
};
|
|
37
35
|
|
|
@@ -40,7 +38,7 @@ async function deleteResource(projectDir, positional, flags) {
|
|
|
40
38
|
|
|
41
39
|
if (!typeRaw || !id) {
|
|
42
40
|
log.err('用法:draftgo delete <type> <id>');
|
|
43
|
-
log.dim(' 支持类型:pages | nav | db_meta | custom_scripts | docs | doc_categories |
|
|
41
|
+
log.dim(' 支持类型:pages | nav | db_meta | custom_scripts | docs | doc_categories | aihub');
|
|
44
42
|
return 1;
|
|
45
43
|
}
|
|
46
44
|
|
|
@@ -80,7 +78,11 @@ async function deleteResource(projectDir, positional, flags) {
|
|
|
80
78
|
stdio: 'inherit',
|
|
81
79
|
shell: false,
|
|
82
80
|
});
|
|
83
|
-
|
|
81
|
+
if (r.error) {
|
|
82
|
+
log.err(`删除脚本启动失败:${r.error.message}`);
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
return Number.isInteger(r.status) ? r.status : 1;
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
module.exports = deleteResource;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const log = require('../logger');
|
|
4
|
+
const check = require('./check');
|
|
5
|
+
const sync = require('./sync');
|
|
6
|
+
|
|
7
|
+
function deploy(projectDir, positional, flags = {}) {
|
|
8
|
+
const delivery = String(flags.delivery || 'deploy').toLowerCase();
|
|
9
|
+
if (!['local', 'preview', 'deploy'].includes(delivery)) {
|
|
10
|
+
log.err('--delivery 只支持 local、preview、deploy。');
|
|
11
|
+
return 1;
|
|
12
|
+
}
|
|
13
|
+
log.step('deploy: 先执行 check …');
|
|
14
|
+
const checkCode = check(projectDir, { strict: flags.strict });
|
|
15
|
+
if (checkCode !== 0) {
|
|
16
|
+
log.err('deploy 终止:check 未通过,请修复后重试。');
|
|
17
|
+
return checkCode;
|
|
18
|
+
}
|
|
19
|
+
if (delivery === 'local') {
|
|
20
|
+
log.ok('delivery=local:本地检查通过,未修改云端。');
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
if (delivery === 'preview') {
|
|
24
|
+
log.step('delivery=preview:执行 push dry-run …');
|
|
25
|
+
return sync(projectDir, 'push', positional, { ...flags, 'dry-run': true });
|
|
26
|
+
}
|
|
27
|
+
log.step('delivery=deploy:开始 push …');
|
|
28
|
+
return sync(projectDir, 'push', positional, flags);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = deploy;
|