draftgo-cli 2.0.5 → 3.0.0
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/README.md +70 -15
- package/package.json +1 -1
- package/resources/skill/SKILL.md +74 -1333
- package/resources/skill/core/architecture.md +91 -0
- package/resources/skill/core/modules.md +54 -0
- package/resources/skill/practices/anti-patterns.md +70 -0
- package/resources/skill/practices/best-practices.md +41 -0
- package/resources/skill/practices/dev-declaration.md +94 -0
- package/resources/skill/push/SKILL.md +39 -5
- package/resources/skill/quickref/api-endpoints.md +130 -0
- package/resources/skill/quickref/api.json +17675 -0
- package/resources/skill/quickref/app-api.md +110 -0
- package/resources/skill/quickref/dg-components.md +198 -0
- package/resources/skill/rules/dev-workflow.md +70 -70
- package/resources/skill/rules/frontend.md +129 -594
- package/resources/skill/scripts/draftgo_delete.py +151 -0
- package/resources/skill/scripts/draftgo_pull.py +1 -1
- package/resources/skill/scripts/draftgo_push.py +165 -22
- package/resources/skill/specs/data.md +108 -0
- package/resources/skill/specs/runtime.md +98 -0
- package/resources/skill/specs/security.md +74 -0
- package/resources/skill/specs/ui-protocol.md +68 -0
- package/src/commands/check.js +1 -1
- package/src/commands/delete.js +86 -0
- package/src/commands/help.js +7 -0
- package/src/commands/map.js +37 -1
- package/src/commands/new.js +183 -0
- package/src/commands/sync.js +4 -1
- package/src/index.js +6 -0
- package/src/localdev/compose.js +19 -0
- package/src/projectMap.js +142 -2
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
read_when: 开发前检查合规性时 · Code Review 时
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# 开发禁区(违反必报错)
|
|
6
|
+
|
|
7
|
+
## 网络资源
|
|
8
|
+
|
|
9
|
+
| ❌ 禁止 | ✅ 替代 |
|
|
10
|
+
|---|---|
|
|
11
|
+
| 任何境外 CDN(googleapis / jsdelivr / cdnjs / unpkg) | 国内镜像(npmmirror.com / staticfile.net)或本地资源 |
|
|
12
|
+
| 外部 CDN 引入图标 | `/assets/icons/{name}.svg` 内置图标库 |
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## App 对象使用
|
|
17
|
+
|
|
18
|
+
| ❌ 禁止 | ✅ 替代 |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `App()` 写法(App 是对象不是函数) | `const App = window.parent?.App` |
|
|
21
|
+
| `const App = () => window.parent?.App` | 去掉箭头函数 |
|
|
22
|
+
| `App?.user?.role` 判断权限 | `App.isAdmin` 或 `App.currentUser?.role_code` |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 路由 & 导航
|
|
27
|
+
|
|
28
|
+
| ❌ 禁止 | ✅ 替代 |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `window.location.search` 读参数 | `window.__DG_ROUTE_CONTEXT__.query` |
|
|
31
|
+
| `navigate('/login')` 退出 | `window.parent.location.href = '/login'` |
|
|
32
|
+
| 页面内 `window.location.href = ...` 跳转 | `window.parent.location.href = ...`(页面在 iframe 中) |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 弹窗 & 交互
|
|
37
|
+
|
|
38
|
+
| ❌ 禁止 | ✅ 替代 |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `window.alert(msg)` | `App.showModal(msg, title?)` |
|
|
41
|
+
| `window.confirm(msg)` | `await App.confirm(msg, title?)` |
|
|
42
|
+
| `window.prompt(msg)` | 自建 modal + input |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## UI 组件
|
|
47
|
+
|
|
48
|
+
| ❌ 禁止 | ✅ 替代 |
|
|
49
|
+
|---|---|
|
|
50
|
+
| 把 React/TSX 版 shadcn 组件写进数据库 HTML | 用对应 `dg-*` 标签 |
|
|
51
|
+
| 把 `dg-*` 当 daisyUI / Bootstrap / Ant Design | `dg-*` 只能是 shadcn 的 HTML 协议形态 |
|
|
52
|
+
| 硬编码 hex / rgb / rgba 颜色 | 用 `var(--dg-*)` 语义 token |
|
|
53
|
+
| 在页面内读 `localStorage.dg_theme` | `App.theme` |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 全局层
|
|
58
|
+
|
|
59
|
+
| ❌ 禁止 | ✅ 替代 |
|
|
60
|
+
|---|---|
|
|
61
|
+
| 在业务页面内实现全局浮窗 / 客服 / 统计脚本 | 使用 `frontend_global_*` 固定槽位 |
|
|
62
|
+
| 在页面内渲染系统 Header / Logo / 用户头像下拉 | 这些属于壳层,不属于业务页面 |
|
|
63
|
+
| 新增全局层槽位 | 只能编辑系统内置固定槽位 |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 颜色 Token 参考
|
|
68
|
+
|
|
69
|
+
| Token | 用途 |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `--dg-bg-base` / `--dg-bg-page` / `--dg-bg-surface` | 背景层级 |
|
|
72
|
+
| `--dg-text-primary` / `--dg-text-secondary` / `--dg-text-muted` | 文字层级 |
|
|
73
|
+
| `--dg-accent` / `--dg-accent-hover` / `--dg-accent-subtle` | 主题色 |
|
|
74
|
+
| `--dg-border` / `--dg-success` / `--dg-error` / `--dg-warning` | 功能色 |
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
read_when: 开发数据库页面时需要 dg-* 组件 · 了解 shadcn 映射关系时
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# dg-* 完整映射表
|
|
6
|
+
|
|
7
|
+
## 核心认知
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
dg-* ≠ 自研组件库
|
|
11
|
+
dg-* = shadcn/ui 在数据库 HTML 页面里的协议化表达
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
数据库页面不进入 Vite/React 编译链,不能写 TSX;改用 `dg-*` 标签,runtime 解析并渲染对应 shadcn 语义。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## shadcn → dg-* 完整映射
|
|
19
|
+
|
|
20
|
+
| shadcn 组件 | dg-* 标签 | 说明 |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| Button | `dg-button` | variant/size/disabled 属性对齐 |
|
|
23
|
+
| Card | `dg-card` + `dg-card-header/title/description/content/footer` | |
|
|
24
|
+
| Badge | `dg-badge` | variant: default/secondary/destructive/outline |
|
|
25
|
+
| Skeleton | `dg-skeleton` | 骨架屏 |
|
|
26
|
+
| Input | `dg-input` | type/placeholder/value/disabled |
|
|
27
|
+
| Textarea | `dg-textarea` | |
|
|
28
|
+
| Select | `dg-select` + `dg-select-item` | |
|
|
29
|
+
| Checkbox | `dg-checkbox` | |
|
|
30
|
+
| Switch | `dg-switch` | |
|
|
31
|
+
| RadioGroup | `dg-radio-group` + `dg-radio-item` | |
|
|
32
|
+
| Form | `dg-form` + `dg-form-item` + `dg-form-message` | |
|
|
33
|
+
| Table | `dg-table` | source 属性拉取数据,columns via data-dg-props |
|
|
34
|
+
| Tabs | `dg-tabs` + `dg-tabs-list/trigger/content` | default-value 属性 |
|
|
35
|
+
| Dialog | `dg-dialog` + `dg-dialog-trigger/content/header/title/footer` | |
|
|
36
|
+
| Sheet | `dg-sheet` + `dg-sheet-trigger/content/header/title` | side: right/left/top/bottom |
|
|
37
|
+
| DropdownMenu | `dg-dropdown-menu` + `dg-dropdown-menu-trigger/content/item/separator` | |
|
|
38
|
+
| Tooltip | `dg-tooltip` | content 属性 |
|
|
39
|
+
| Popover | `dg-popover` + `dg-popover-trigger/content` | |
|
|
40
|
+
| Alert | `dg-alert` + `dg-alert-title/description` | variant: default/destructive |
|
|
41
|
+
| Progress | `dg-progress` | value/max 属性 |
|
|
42
|
+
| Separator | `dg-separator` | orientation: horizontal/vertical |
|
|
43
|
+
| ScrollArea | `dg-scroll-area` | |
|
|
44
|
+
| Avatar | `dg-avatar` + `dg-avatar-image/fallback` | |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 属性约定
|
|
49
|
+
|
|
50
|
+
`dg-*` 属性命名跟随 shadcn 语义:
|
|
51
|
+
- `variant`、`size`、`disabled`、`default-value`、`data-state`、`aria-*` 保持原意
|
|
52
|
+
- 复杂组件使用 `data-dg-props='{"key":"value"}'` 传递结构化配置
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 禁止混淆
|
|
57
|
+
|
|
58
|
+
- ❌ `dg-*` 不是 daisyUI
|
|
59
|
+
- ❌ `dg-*` 不是 Bootstrap
|
|
60
|
+
- ❌ `dg-*` 不是 Ant Design / Element Plus
|
|
61
|
+
- ❌ `dg-*` 不是任意相似样式的泛称
|
|
62
|
+
- ✅ `dg-*` 只能是 shadcn 的 DraftGo HTML 协议形态
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 使用示例速查
|
|
67
|
+
|
|
68
|
+
完整带代码的示例见 `{{SKILL_DIR}}/quickref/dg-components.md`
|
package/src/commands/check.js
CHANGED
|
@@ -15,7 +15,7 @@ function check(projectDir, flags = {}) {
|
|
|
15
15
|
log.info(`项目目录:${projectDir}`);
|
|
16
16
|
|
|
17
17
|
console.log('');
|
|
18
|
-
log.info(`资源概览:pages=${result.map.pages.length}, nav=${result.map.navigations.length}, db=${result.map.db_meta.length}, scripts=${result.map.custom_scripts.length}, aihub=${result.map.aihub.length}`);
|
|
18
|
+
log.info(`资源概览:pages=${result.map.pages.length}, nav=${result.map.navigations.length}, db=${result.map.db_meta.length}, scripts=${result.map.custom_scripts.length}, aihub=${result.map.aihub.length}, external_apis=${result.map.external_apis.length}, docs=${result.map.docs.length}, doc_categories=${result.map.doc_categories.length}, system_config=${result.map.system_config.length}`);
|
|
19
19
|
|
|
20
20
|
if (result.errors.length) {
|
|
21
21
|
console.log('');
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { exists } = require('../fsx');
|
|
5
|
+
const log = require('../logger');
|
|
6
|
+
const { confirm } = require('../prompt');
|
|
7
|
+
const { findPython } = require('../python');
|
|
8
|
+
const { spawnSync } = require('child_process');
|
|
9
|
+
const { platforms } = require('../platforms');
|
|
10
|
+
|
|
11
|
+
function findDeleteScript(projectDir) {
|
|
12
|
+
for (const platform of platforms) {
|
|
13
|
+
const candidate = path.join(projectDir, platform.assetDir, 'scripts', 'draftgo_delete.py');
|
|
14
|
+
if (exists(candidate)) return candidate;
|
|
15
|
+
}
|
|
16
|
+
const bundled = path.resolve(__dirname, '..', '..', 'resources', 'skill', 'scripts', 'draftgo_delete.py');
|
|
17
|
+
return exists(bundled) ? bundled : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const TYPE_ALIASES = {
|
|
21
|
+
page: 'pages',
|
|
22
|
+
pages: 'pages',
|
|
23
|
+
nav: 'nav',
|
|
24
|
+
navigations: 'nav',
|
|
25
|
+
db_meta: 'db_meta',
|
|
26
|
+
script: 'custom_scripts',
|
|
27
|
+
scripts: 'custom_scripts',
|
|
28
|
+
custom_scripts: 'custom_scripts',
|
|
29
|
+
doc: 'docs',
|
|
30
|
+
docs: 'docs',
|
|
31
|
+
doc_category: 'doc_categories',
|
|
32
|
+
doc_categories: 'doc_categories',
|
|
33
|
+
external_api: 'external_apis',
|
|
34
|
+
external_apis: 'external_apis',
|
|
35
|
+
aihub: 'aihub',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
async function deleteResource(projectDir, positional, flags) {
|
|
39
|
+
const [typeRaw, id] = positional;
|
|
40
|
+
|
|
41
|
+
if (!typeRaw || !id) {
|
|
42
|
+
log.err('用法:draftgo delete <type> <id>');
|
|
43
|
+
log.dim(' 支持类型:pages | nav | db_meta | custom_scripts | docs | doc_categories | external_apis | aihub');
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const type = TYPE_ALIASES[typeRaw];
|
|
48
|
+
if (!type) {
|
|
49
|
+
log.err(`未知类型:${typeRaw}`);
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!exists(path.join(projectDir, '.draftgo', 'config.json'))) {
|
|
54
|
+
log.err('未找到 .draftgo/config.json,请先运行 /draftgo init');
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const py = findPython();
|
|
59
|
+
if (!py) {
|
|
60
|
+
log.err('未检测到 Python,无法运行删除脚本');
|
|
61
|
+
return 1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const script = findDeleteScript(projectDir);
|
|
65
|
+
if (!script) {
|
|
66
|
+
log.err('未找到 draftgo_delete.py,请运行 draftgo update 更新');
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!flags.yes && !flags.y) {
|
|
71
|
+
const ok = await confirm(`确认删除 ${type} id=${id}?此操作不可恢复`);
|
|
72
|
+
if (!ok) {
|
|
73
|
+
log.dim('已取消');
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const r = spawnSync(py.bin, [script, type, id], {
|
|
79
|
+
cwd: projectDir,
|
|
80
|
+
stdio: 'inherit',
|
|
81
|
+
shell: false,
|
|
82
|
+
});
|
|
83
|
+
return r.status || 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = deleteResource;
|
package/src/commands/help.js
CHANGED
|
@@ -29,6 +29,13 @@ Usage:
|
|
|
29
29
|
entry binding, files, obvious mock risks.
|
|
30
30
|
draftgo dev Run this project's npm dev script.
|
|
31
31
|
draftgo build Run this project's npm build script.
|
|
32
|
+
draftgo new <type> <slug> Scaffold a new resource locally (no network).
|
|
33
|
+
Creates template file + index.json entry.
|
|
34
|
+
Types: page | nav | db_meta | script | doc
|
|
35
|
+
draftgo delete <type> <id> Delete resource from server + local index.
|
|
36
|
+
Types: pages | nav | db_meta | custom_scripts
|
|
37
|
+
| docs | doc_categories | external_apis
|
|
38
|
+
Alias: del, rm. Flags: --yes / -y skip confirm.
|
|
32
39
|
draftgo pull [type] [id...] Pull DraftGo resources via the bundled
|
|
33
40
|
sync script. Defaults to --all.
|
|
34
41
|
draftgo push <type> [id...] Push DraftGo resources via the bundled
|
package/src/commands/map.js
CHANGED
|
@@ -43,7 +43,43 @@ function mapCommand(projectDir, flags = {}) {
|
|
|
43
43
|
return `${s.slug || '(无 slug)'} ${s.mode || 'mode?'} ${s.name || ''}${s.code_file ? ` ${s.code_file}` : ''}`;
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
printList('
|
|
46
|
+
printList('外部 API', map.external_apis, (a) => {
|
|
47
|
+
const route = `${a.method || 'GET'} ${a.path || '/'}`;
|
|
48
|
+
const tags = a.tags && a.tags.length ? ` #${a.tags.join(',#')}` : '';
|
|
49
|
+
return `${a.id || 'new'} ${a.code || '(无 code)'} ${route} ${a.name || ''}${a.base_url ? ` ${a.base_url}` : ''}${tags}`;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
printList('文档', map.docs, (d) => {
|
|
53
|
+
return `${d.id || 'new'} ${d.slug || '(无 slug)'} ${d.title || '未命名'}${d.content_file ? ` ${d.content_file}` : ''}`;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
printList('文档分类', map.doc_categories, (c) => {
|
|
57
|
+
const parent = c.parent_id == null ? 'root' : `parent:${c.parent_id}`;
|
|
58
|
+
return `${c.id || 'new'} ${c.slug || '(无 slug)'} ${c.name || '未命名'} ${parent}`;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
printList('系统配置', map.system_config, (c) => {
|
|
62
|
+
const sensitive = c.is_sensitive ? ' sensitive' : '';
|
|
63
|
+
return `${c.config_key || '(无 key)'} ${c.category || 'default'} ${c.value_type || 'value'}${sensitive}`;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
printList('AIHub', map.aihub, (a) => {
|
|
67
|
+
const extra = [];
|
|
68
|
+
if (a.type === 'agent') {
|
|
69
|
+
if (a.mode) extra.push(a.mode);
|
|
70
|
+
if (a.output_format && a.output_format.mode === 'json') extra.push(`json:${a.output_format.json_strategy || 'auto'}`);
|
|
71
|
+
if (a.model_selection && a.model_selection.user_selectable) extra.push('user-model');
|
|
72
|
+
if (a.tools && a.tools.sources.length) extra.push(`tools:${a.tools.sources.length}`);
|
|
73
|
+
} else if (a.type === 'model') {
|
|
74
|
+
extra.push(`models:${a.models_count || 0}`);
|
|
75
|
+
if (a.supports_response_format === true) extra.push('response_format');
|
|
76
|
+
if (a.supports_json_schema === true) extra.push('json_schema');
|
|
77
|
+
} else if (a.type === 'mcp') {
|
|
78
|
+
extra.push(a.transport || 'transport?');
|
|
79
|
+
extra.push(`tools:${a.tools_count || 0}`);
|
|
80
|
+
}
|
|
81
|
+
return `${a.id || 'new'} ${a.type || ''} ${a.name || ''}${extra.length ? ` [${extra.join(', ')}]` : ''}`;
|
|
82
|
+
});
|
|
47
83
|
printList('角色', map.roles, (r) => `${r.code || r.id || 'new'} ${r.name || ''}`);
|
|
48
84
|
|
|
49
85
|
console.log('');
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const log = require('../logger');
|
|
6
|
+
const { exists, ensureDir } = require('../fsx');
|
|
7
|
+
|
|
8
|
+
const TEMPLATES = {
|
|
9
|
+
page: (slug) => ({
|
|
10
|
+
indexEntry: {
|
|
11
|
+
title: slug,
|
|
12
|
+
route: `/${slug}`,
|
|
13
|
+
tag: null,
|
|
14
|
+
menu: null,
|
|
15
|
+
status: 'active',
|
|
16
|
+
permission: { default: 'public' },
|
|
17
|
+
html_file: `.draftgo/pages/page_new_${slug}.html`,
|
|
18
|
+
},
|
|
19
|
+
file: `.draftgo/pages/page_new_${slug}.html`,
|
|
20
|
+
content: `<!DOCTYPE html>
|
|
21
|
+
<html>
|
|
22
|
+
<head><meta charset="UTF-8"></head>
|
|
23
|
+
<body>
|
|
24
|
+
<script>
|
|
25
|
+
const App = window.parent?.App;
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<div class="p-6">
|
|
29
|
+
<h1 class="text-2xl font-bold">${slug}</h1>
|
|
30
|
+
<p class="text-muted-foreground mt-2">页面内容</p>
|
|
31
|
+
</div>
|
|
32
|
+
</body>
|
|
33
|
+
</html>`,
|
|
34
|
+
}),
|
|
35
|
+
|
|
36
|
+
nav: (code) => ({
|
|
37
|
+
indexEntry: {
|
|
38
|
+
name: code,
|
|
39
|
+
code,
|
|
40
|
+
status: 'active',
|
|
41
|
+
order: 0,
|
|
42
|
+
html_file: `.draftgo/navigations/nav_new_${code}.html`,
|
|
43
|
+
},
|
|
44
|
+
file: `.draftgo/navigations/nav_new_${code}.html`,
|
|
45
|
+
content: `<nav class="flex items-center gap-4 px-4 h-14">
|
|
46
|
+
<a href="/" class="font-semibold">首页</a>
|
|
47
|
+
</nav>`,
|
|
48
|
+
}),
|
|
49
|
+
|
|
50
|
+
db_meta: (type) => ({
|
|
51
|
+
indexEntry: {
|
|
52
|
+
type,
|
|
53
|
+
label: type,
|
|
54
|
+
describe: '',
|
|
55
|
+
schema: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
title: { type: 'string', title: '标题', required: true, searchable: 'fuzzy' },
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
permission: {
|
|
62
|
+
public: { read: 'none', create: 'none', update: 'none', delete: 'none' },
|
|
63
|
+
login: { read: 'all', create: 'all', update: 'owner', delete: 'owner' },
|
|
64
|
+
admin: { read: 'all', create: 'all', update: 'all', delete: 'all' },
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
file: null,
|
|
68
|
+
content: null,
|
|
69
|
+
}),
|
|
70
|
+
|
|
71
|
+
script: (slug) => ({
|
|
72
|
+
indexEntry: {
|
|
73
|
+
name: slug,
|
|
74
|
+
slug,
|
|
75
|
+
mode: 'route',
|
|
76
|
+
status: 'active',
|
|
77
|
+
triggers: { path: `/${slug}`, methods: ['GET', 'POST'] },
|
|
78
|
+
config: {},
|
|
79
|
+
permission: { default: 'admin' },
|
|
80
|
+
code_file: `.draftgo/custom_scripts/script_new_${slug}.py`,
|
|
81
|
+
},
|
|
82
|
+
file: `.draftgo/custom_scripts/script_new_${slug}.py`,
|
|
83
|
+
content: `# ${slug}
|
|
84
|
+
# 路由脚本:/api/x/${slug}
|
|
85
|
+
|
|
86
|
+
def handle(request, context):
|
|
87
|
+
"""
|
|
88
|
+
request: { method, path, query, body, headers, user }
|
|
89
|
+
context: { db, config, logger }
|
|
90
|
+
"""
|
|
91
|
+
return {"message": "ok"}
|
|
92
|
+
`,
|
|
93
|
+
}),
|
|
94
|
+
|
|
95
|
+
doc: (slug) => ({
|
|
96
|
+
indexEntry: {
|
|
97
|
+
title: slug,
|
|
98
|
+
key: slug,
|
|
99
|
+
status: 'published',
|
|
100
|
+
category_id: null,
|
|
101
|
+
content_file: `.draftgo/docs/articles/doc_new_${slug}.md`,
|
|
102
|
+
},
|
|
103
|
+
file: `.draftgo/docs/articles/doc_new_${slug}.md`,
|
|
104
|
+
content: `# ${slug}\n\n文档内容\n`,
|
|
105
|
+
}),
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const INDEX_MAP = {
|
|
109
|
+
page: 'pages/index.json',
|
|
110
|
+
nav: 'navigations/index.json',
|
|
111
|
+
db_meta: 'db_meta/index.json',
|
|
112
|
+
script: 'custom_scripts/index.json',
|
|
113
|
+
doc: 'docs/articles/index.json',
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const PUSH_TYPE = {
|
|
117
|
+
page: 'pages',
|
|
118
|
+
nav: 'nav',
|
|
119
|
+
db_meta: 'db_meta',
|
|
120
|
+
script: 'custom_scripts',
|
|
121
|
+
doc: 'docs',
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
function newResource(projectDir, positional) {
|
|
125
|
+
const [type, slug] = positional;
|
|
126
|
+
|
|
127
|
+
if (!type || !TEMPLATES[type]) {
|
|
128
|
+
log.err(`用法:draftgo new <type> <slug>`);
|
|
129
|
+
log.dim(` 支持类型:${Object.keys(TEMPLATES).join(' | ')}`);
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
if (!slug) {
|
|
133
|
+
log.err(`缺少 slug/name 参数,例如:draftgo new ${type} my-slug`);
|
|
134
|
+
return 1;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const draftgoDir = path.join(projectDir, '.draftgo');
|
|
138
|
+
if (!exists(draftgoDir)) {
|
|
139
|
+
log.err('未找到 .draftgo/,请先运行 draftgo connect 或 /draftgo init');
|
|
140
|
+
return 1;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const tpl = TEMPLATES[type](slug);
|
|
144
|
+
const indexRel = INDEX_MAP[type];
|
|
145
|
+
const indexPath = path.join(draftgoDir, indexRel);
|
|
146
|
+
|
|
147
|
+
if (!exists(indexPath)) {
|
|
148
|
+
log.err(`未找到 ${indexPath},请先运行 /draftgo pull`);
|
|
149
|
+
return 1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 追加 index 条目
|
|
153
|
+
const items = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
|
|
154
|
+
|
|
155
|
+
// 检查重复
|
|
156
|
+
const dupKey = type === 'page' ? 'route' : type === 'nav' ? 'code' : type === 'db_meta' ? 'type' : type === 'script' ? 'slug' : 'key';
|
|
157
|
+
const dupVal = tpl.indexEntry[dupKey];
|
|
158
|
+
if (dupVal && items.some((it) => it[dupKey] === dupVal)) {
|
|
159
|
+
log.err(`已存在 ${dupKey}="${dupVal}",请换一个名称`);
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
items.push(tpl.indexEntry);
|
|
164
|
+
fs.writeFileSync(indexPath, JSON.stringify(items, null, 2), 'utf8');
|
|
165
|
+
|
|
166
|
+
// 创建模板文件
|
|
167
|
+
if (tpl.file && tpl.content !== null) {
|
|
168
|
+
const filePath = path.join(projectDir, tpl.file);
|
|
169
|
+
ensureDir(path.dirname(filePath));
|
|
170
|
+
if (exists(filePath)) {
|
|
171
|
+
log.warn(`文件已存在,跳过:${tpl.file}`);
|
|
172
|
+
} else {
|
|
173
|
+
fs.writeFileSync(filePath, tpl.content, 'utf8');
|
|
174
|
+
log.ok(`已创建:${tpl.file}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
log.ok(`已追加到 .draftgo/${indexRel}`);
|
|
179
|
+
log.dim(` 编辑完成后运行:draftgo push ${PUSH_TYPE[type]}`);
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports = newResource;
|
package/src/commands/sync.js
CHANGED
|
@@ -19,7 +19,7 @@ function findSyncScript(projectDir, command) {
|
|
|
19
19
|
return exists(bundled) ? bundled : null;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
function sync(projectDir, command, positional) {
|
|
22
|
+
function sync(projectDir, command, positional, flags = {}) {
|
|
23
23
|
const py = findPython();
|
|
24
24
|
if (!py) {
|
|
25
25
|
log.err('未检测到 Python,无法运行 DraftGo 同步脚本。');
|
|
@@ -40,6 +40,9 @@ function sync(projectDir, command, positional) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
const args = positional.length ? positional : ['--all'];
|
|
43
|
+
if (command === 'push' && flags.force) {
|
|
44
|
+
args.push('--force');
|
|
45
|
+
}
|
|
43
46
|
const r = spawnSync(py.bin, [script, ...args], {
|
|
44
47
|
cwd: projectDir,
|
|
45
48
|
stdio: 'inherit',
|
package/src/index.js
CHANGED
|
@@ -42,6 +42,12 @@ async function run(argv) {
|
|
|
42
42
|
case 'dev':
|
|
43
43
|
case 'build':
|
|
44
44
|
return require('./commands/projectScript')(projectDir, command, flags);
|
|
45
|
+
case 'new':
|
|
46
|
+
return require('./commands/new')(projectDir, positional, flags);
|
|
47
|
+
case 'delete':
|
|
48
|
+
case 'del':
|
|
49
|
+
case 'rm':
|
|
50
|
+
return await require('./commands/delete')(projectDir, positional, flags);
|
|
45
51
|
case 'pull':
|
|
46
52
|
case 'push':
|
|
47
53
|
return require('./commands/sync')(projectDir, command, positional, flags);
|
package/src/localdev/compose.js
CHANGED
|
@@ -101,6 +101,11 @@ function generate(projectDir, opts) {
|
|
|
101
101
|
' image: mysql:8.0',
|
|
102
102
|
` container_name: ${projectName}-mysql`,
|
|
103
103
|
' restart: unless-stopped',
|
|
104
|
+
' logging:',
|
|
105
|
+
' driver: json-file',
|
|
106
|
+
' options:',
|
|
107
|
+
' max-size: "10m"',
|
|
108
|
+
' max-file: "3"',
|
|
104
109
|
' environment:',
|
|
105
110
|
' MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}',
|
|
106
111
|
' MYSQL_DATABASE: ${MYSQL_DATABASE}',
|
|
@@ -138,6 +143,11 @@ function generate(projectDir, opts) {
|
|
|
138
143
|
' image: redis:7-alpine',
|
|
139
144
|
` container_name: ${projectName}-redis`,
|
|
140
145
|
' restart: unless-stopped',
|
|
146
|
+
' logging:',
|
|
147
|
+
' driver: json-file',
|
|
148
|
+
' options:',
|
|
149
|
+
' max-size: "10m"',
|
|
150
|
+
' max-file: "3"',
|
|
141
151
|
' command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}", "--appendonly", "yes"]',
|
|
142
152
|
' volumes:',
|
|
143
153
|
' - ./data/redis:/data',
|
|
@@ -166,6 +176,11 @@ function generate(projectDir, opts) {
|
|
|
166
176
|
' image: cabinai/draftgo:latest',
|
|
167
177
|
` container_name: ${projectName}`,
|
|
168
178
|
' restart: unless-stopped',
|
|
179
|
+
' logging:',
|
|
180
|
+
' driver: json-file',
|
|
181
|
+
' options:',
|
|
182
|
+
' max-size: "10m"',
|
|
183
|
+
' max-file: "3"',
|
|
169
184
|
` ports:`,
|
|
170
185
|
` - "${appPort}:3000"`,
|
|
171
186
|
' environment:',
|
|
@@ -184,6 +199,10 @@ function generate(projectDir, opts) {
|
|
|
184
199
|
' LOG_LEVEL: INFO',
|
|
185
200
|
' LOG_MAX_BYTES: "5242880"',
|
|
186
201
|
' LOG_BACKUP_COUNT: "5"',
|
|
202
|
+
' LOG_TO_CONSOLE: "true"',
|
|
203
|
+
' LOG_CONSOLE_JSON: "true"',
|
|
204
|
+
' LOG_UVICORN_ACCESS: "true"',
|
|
205
|
+
' LOG_RUNTIME_HEARTBEAT_INTERVAL_SECONDS: "300"',
|
|
187
206
|
' UPLOAD_PROVIDER: local',
|
|
188
207
|
' UPLOAD_MAX_SIZE: "10485760"',
|
|
189
208
|
' LOCAL_UPLOAD_DIR: /app/backend/storage/uploads',
|