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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -124
  3. package/package.json +21 -8
  4. package/resources/skill/SKILL.md +62 -89
  5. package/resources/skill/init/SKILL.md +18 -67
  6. package/resources/skill/manifest.json +27 -0
  7. package/resources/skill/pull/SKILL.md +18 -44
  8. package/resources/skill/push/SKILL.md +30 -247
  9. package/resources/skill/references/aihub.md +86 -0
  10. package/resources/skill/references/api-endpoints.md +178 -0
  11. package/resources/skill/references/api.json +20248 -0
  12. package/resources/skill/{quickref → references}/app-api.md +44 -14
  13. package/resources/skill/{core → references}/architecture.md +6 -26
  14. package/resources/skill/references/chat-sdk.md +201 -0
  15. package/resources/skill/references/custom-services.md +308 -0
  16. package/resources/skill/references/data.md +298 -0
  17. package/resources/skill/references/db-relations.md +227 -0
  18. package/resources/skill/references/frontend.md +788 -0
  19. package/resources/skill/references/modules.md +66 -0
  20. package/resources/skill/references/parallel.md +48 -0
  21. package/resources/skill/{specs → references}/runtime.md +31 -1
  22. package/resources/skill/{specs → references}/security.md +3 -3
  23. package/resources/skill/references/ui-protocol.md +99 -0
  24. package/resources/skill/scripts/draftgo_delete.py +0 -2
  25. package/resources/skill/scripts/draftgo_init.py +15 -3
  26. package/resources/skill/scripts/draftgo_pull.py +154 -87
  27. package/resources/skill/scripts/draftgo_push.py +440 -183
  28. package/resources/skill/story/SKILL.md +13 -23
  29. package/src/cli.js +22 -7
  30. package/src/commandRegistry.js +34 -0
  31. package/src/commands/api.js +204 -0
  32. package/src/commands/autoPush.js +41 -0
  33. package/src/commands/check.js +27 -17
  34. package/src/commands/delete.js +6 -4
  35. package/src/commands/deploy.js +31 -0
  36. package/src/commands/help.js +41 -28
  37. package/src/commands/init.js +34 -20
  38. package/src/commands/local.js +9 -3
  39. package/src/commands/map.js +18 -7
  40. package/src/commands/sync.js +11 -4
  41. package/src/commands/update.js +39 -52
  42. package/src/commands/verifyUi.js +199 -0
  43. package/src/index.js +13 -46
  44. package/src/localdev/compose.js +48 -197
  45. package/src/localdev/index.js +116 -216
  46. package/src/localdev/mysqlClient.js +12 -9
  47. package/src/localdev/services.js +163 -0
  48. package/src/platforms.js +3 -3
  49. package/src/projectConfig.js +12 -2
  50. package/src/projectMap.js +240 -68
  51. package/src/skill.js +113 -29
  52. package/src/updateCheck.js +37 -15
  53. package/resources/skill/core/modules.md +0 -54
  54. package/resources/skill/practices/anti-patterns.md +0 -70
  55. package/resources/skill/practices/best-practices.md +0 -41
  56. package/resources/skill/practices/dev-declaration.md +0 -94
  57. package/resources/skill/quickref/api-endpoints.md +0 -130
  58. package/resources/skill/quickref/api.json +0 -17675
  59. package/resources/skill/quickref/dg-components.md +0 -198
  60. package/resources/skill/rules/dev-workflow.md +0 -652
  61. package/resources/skill/rules/frontend.md +0 -210
  62. package/resources/skill/rules/parallel.md +0 -263
  63. package/resources/skill/specs/data.md +0 -108
  64. package/resources/skill/specs/ui-protocol.md +0 -68
  65. package/src/commands/doctor.js +0 -54
  66. package/src/commands/new.js +0 -183
  67. package/src/commands/projectScript.js +0 -37
  68. /package/resources/skill/{rules → references}/debugging-syntax.md +0 -0
@@ -7,57 +7,63 @@ function help() {
7
7
  const targets = all.map((i) => ` ${i.name.padEnd(12)} ${i.displayName}`).join('\n');
8
8
  console.log(`draftgo v${getPackageVersion()} — DraftGo Next workbench CLI for AI coding agents
9
9
 
10
- DraftGo Next frontend baseline: React + Vite + shadcn/ui + Tailwind.
11
- Database pages use dg-* as the shadcn HTML runtime protocol.
10
+ DraftGo Next frontend baseline: React + Vite.
11
+ Database pages use standard HTML + Tailwind CSS.
12
12
  The CLI is the workbench layer for local runtime, resource sync, checks, and push/pull flows.
13
13
 
14
14
  Usage:
15
15
  draftgo init [<target>...] Install skill. No target = auto-detect.
16
16
  Use "all" to install every target.
17
- draftgo update [<target>...] Update skill + entry files. If a newer CLI
18
- version exists on npm, upgrades the CLI
19
- automatically then reruns itself.
17
+ draftgo update [<target>...] Fully update the global CLI, then atomically
18
+ refresh skill + entry files.
20
19
  draftgo uninstall <target|all> Remove entry files for target(s).
21
20
  Use explicit "all" to remove every target.
22
21
  --purge also removes .draftgo/ (runtime data).
23
22
  draftgo status Show installed targets and skill version.
24
- draftgo doctor Diagnose environment (python, targets,
25
- CLI freshness).
26
23
  draftgo map Print a local DraftGo project resource map
27
24
  for fast AI orientation.
28
- draftgo check Check local resource closure: routes,
29
- entry binding, files, obvious mock risks.
30
- draftgo dev Run this project's npm dev script.
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
25
+ draftgo check Check page entry binding, page/navigation
26
+ HTML parsing, and unregistered local files.
27
+ draftgo verify-ui <url> Run a deterministic browser smoke check.
28
+ Uses one viewport and screenshots on failure.
29
+ draftgo api <keyword> Search DraftGo base APIs and show request/
30
+ response/authentication contracts.
31
+ Custom service routes come from app.Route;
32
+ they are not part of the bundled OpenAPI.
35
33
  draftgo delete <type> <id> Delete resource from server + local index.
36
34
  Types: pages | nav | db_meta | custom_scripts
37
- | docs | doc_categories | external_apis
35
+ | docs | doc_categories | aihub
38
36
  Alias: del, rm. Flags: --yes / -y skip confirm.
39
37
  draftgo pull [type] [id...] Pull DraftGo resources via the bundled
40
38
  sync script. Defaults to --all.
41
39
  draftgo push <type> [id...] Push DraftGo resources via the bundled
42
40
  sync script.
43
- draftgo local up|down|logs|status
41
+ draftgo deploy [type] [id...] Run check, then push explicitly.
42
+ --delivery local|preview|deploy
43
+ draftgo auto-push [type] [id...] Run check, then push only when
44
+ config.auto_push is true.
45
+ Supports --batch <type> <ids> ... too.
46
+ draftgo local setup|start|stop|logs|status
44
47
  Manage .draftgo/docker/docker-compose.yaml
45
- generated by draftgo local-dev.
48
+ generated by local setup. up/down remain
49
+ compatible aliases for start/stop.
46
50
  draftgo list-targets List supported AI tools.
47
51
  draftgo connect Bind this project to an existing DraftGo
48
52
  server. Prompts for BaseURL + access
49
53
  token (or pass --server / --token).
50
- draftgo local-dev One-click local DraftGo stack via Docker
51
- (MySQL + Redis + app). Generates
52
- <project>/.draftgo/docker/docker-compose.yaml
53
- with random secrets, then guides you to
54
- finish setup and paste an access token.
54
+ draftgo local setup One-click local DraftGo stack via Docker
55
+ (shared MySQL, Redis, Milvus + app). It
56
+ generates an app-only project compose,
57
+ <project>/.draftgo/docker/docker-compose.yaml,
58
+ then guides you to finish setup and paste
59
+ an access token.
60
+ draftgo local-dev Compatibility alias for \`draftgo local setup\`.
55
61
  draftgo -v | --version Print CLI version.
56
62
  draftgo -h | --help Show this help.
57
63
 
58
64
  v3 Workbench:
59
- draftgo local up|down|logs Local stack lifecycle commands.
60
- draftgo dev|build|check Project workflow gates.
65
+ draftgo local start|stop|logs Local stack lifecycle commands.
66
+ draftgo check Local resource closure gate.
61
67
  draftgo pull|push First-class resource sync wrappers.
62
68
 
63
69
  Flags:
@@ -67,15 +73,21 @@ Flags:
67
73
  --skip-update-check Do not contact npm to check for a newer CLI.
68
74
  --yes Assume "yes" for interactive prompts.
69
75
  --connect (init) Skip the prompt and run \`draftgo connect\`.
70
- --local-dev (init) Skip the prompt and run \`draftgo local-dev\`.
76
+ --local-dev (init) Skip the prompt and run \`draftgo local setup\`.
71
77
  --no-setup (init) Don't offer either flow after installing.
72
78
  --server <url> (connect) Provide the DraftGo BaseURL non-interactively.
73
79
  --token <sat> (connect) Provide the access token non-interactively.
74
80
  --output <json> (map/check) Print machine-readable JSON.
75
81
  --strict (check) Treat warnings as failures.
82
+ --mobile-check <mode> (verify-ui) auto | always | never. Default: auto.
83
+ --screenshot <mode> (verify-ui) on-failure | always | never.
84
+ --browser <name> (verify-ui) chromium | chrome | msedge.
85
+ --selector <css> (verify-ui) Require a key element to be visible.
86
+ --width/--height <px> (verify-ui) Override the default 390x844 viewport.
87
+ --delivery <mode> (deploy) local | preview | deploy.
76
88
 
77
89
  Environment:
78
- DRAFTGO_NO_UPDATE_CHECK=1 Disable the automatic CLI-freshness check.
90
+ DRAFTGO_NO_UPDATE_CHECK=1 Disable the cached CLI version check.
79
91
 
80
92
  Targets:
81
93
  ${targets}
@@ -85,9 +97,10 @@ Examples:
85
97
  draftgo init claudecode # install for Claude Code only
86
98
  draftgo init claudecode kiro # install for both
87
99
  draftgo init all # install for every supported target
88
- draftgo update # upgrade CLI if needed, then refresh skill
100
+ draftgo update # fully update CLI and installed skills
89
101
  draftgo map # inspect pages/nav/db/scripts before development
90
- draftgo check --strict # fail on closure warnings before push
102
+ draftgo check --strict # fail on any resource-check warning before push
103
+ draftgo verify-ui http://localhost:5173 --mobile-check auto
91
104
  draftgo uninstall all --purge # full removal incl. runtime data
92
105
  `);
93
106
  }
@@ -12,8 +12,8 @@ const { ask } = require('../prompt');
12
12
  function warnIfNoPython() {
13
13
  const py = findPython();
14
14
  if (!py) {
15
- log.warn('Python 未检测到。DraftGo init/sync 脚本需要 Python 3.9+。');
16
- log.dim(' 请安装 Python 3.9+ 后再运行 /draftgo init 子技能。');
15
+ log.warn('Python 未检测到。DraftGo 资源同步需要 Python 3.9+。');
16
+ log.dim(' 安装 Python 3.9+ 后运行 `draftgo pull` 获取项目资源。');
17
17
  } else {
18
18
  log.dim(` detected ${py.bin} (${py.version})`);
19
19
  }
@@ -47,17 +47,32 @@ async function init(projectDir, positional, flags) {
47
47
 
48
48
  // 2) Render skill body into each target's own directory (no shared
49
49
  // .draftgo/skill/ indirection anymore — each AI tool gets a real copy).
50
- log.step('写入各 AI 工具 skill 目录(含 SKILL.md / 子技能 / rules / scripts)');
50
+ log.step('写入各 AI 工具 skill 目录(含 SKILL.md / 子技能 / references / scripts)');
51
51
  ensureRuntime(projectDir);
52
+ const failures = [];
53
+ let installedCount = 0;
54
+ let skippedCount = 0;
52
55
  for (const t of resolved) {
53
56
  try {
54
57
  const r = t.install(projectDir, { force: !!flags.force });
55
- log.ok(`${t.displayName.padEnd(16)} → ${r.path}`);
58
+ if (r.skipped) {
59
+ skippedCount += 1;
60
+ log.warn(`${t.displayName.padEnd(16)} 已存在,未覆盖;使用 draftgo update 或 --force 更新。`);
61
+ } else {
62
+ installedCount += 1;
63
+ log.ok(`${t.displayName.padEnd(16)} → ${r.path}`);
64
+ }
56
65
  } catch (e) {
57
66
  log.err(`${t.displayName} 安装失败:${e.message}`);
67
+ failures.push(t.displayName);
58
68
  }
59
69
  }
60
- writeInstalledVersion(projectDir);
70
+ if (failures.length) {
71
+ log.err(`安装未完成,失败目标:${failures.join(', ')}`);
72
+ return 1;
73
+ }
74
+ if (installedCount > 0 && skippedCount === 0) writeInstalledVersion(projectDir);
75
+ if (skippedCount > 0) log.dim(' 存在未覆盖目标,未更新全局 skill 版本标记;运行 draftgo update 可统一刷新。');
61
76
  log.dim(` CLI 版本:${getPackageVersion()}`);
62
77
 
63
78
  // 3) Environment check (advisory).
@@ -86,29 +101,28 @@ async function init(projectDir, positional, flags) {
86
101
  return await require('./localDev')(projectDir, [], flags);
87
102
  }
88
103
  if (interactive) {
89
- log.title('未检测到 DraftGo 服务器配置');
90
- log.plain('选择一种方式完成绑定:');
91
- log.plain(` ${log.c.cyan('[1]')} 连接已有 DraftGo 服务器 ${log.c.gray('(输入 BaseURL + 系统访问令牌)')}`);
92
- log.plain(` ${log.c.cyan('[2]')} 进入本地开发模式 ${log.c.gray('(没部署基座,用 Docker 一键启动)')}`);
93
- log.plain(` ${log.c.cyan('[3]')} 稍后再说`);
94
- const choice = (await ask('请选择 [1/2/3]', { default: '1' })).trim();
95
- if (choice === '1') {
104
+ log.title('选择 DraftGo 基座处理方式');
105
+ log.plain(` ${log.c.cyan('[A]')} 连接基座服务`);
106
+ log.dim(' 使用已有 DraftGo 服务,接下来输入 BaseURL 和系统访问令牌。');
107
+ log.plain(` ${log.c.cyan('[B]')} 本地启动基座`);
108
+ log.dim(' 使用 Docker 启动 DraftGo;按需准备共享的 MySQL、Redis 和 Milvus。');
109
+ log.plain(` ${log.c.cyan('[C]')} 稍后自行处理`);
110
+ log.dim(' 仅安装 Skill,不写入服务配置,也不创建容器。');
111
+ const choice = (await ask('请选择 [A/B/C]', { default: 'C' })).trim().toUpperCase();
112
+ if (choice === 'A') {
96
113
  return await require('./connect')(projectDir, [], flags);
97
114
  }
98
- if (choice === '2') {
99
- return await require('./localDev')(projectDir, [], flags);
115
+ if (choice === 'B') {
116
+ return await require('./localDev')(projectDir, [], { ...flags, yes: true });
100
117
  }
101
- if (choice !== '3') {
102
- log.warn('Invalid choice; skipped server setup. Run `draftgo connect` or `draftgo local-dev` later.');
118
+ if (choice !== 'C') {
119
+ log.warn('无效选择,已跳过基座处理。');
103
120
  }
104
121
  }
105
122
  }
106
123
 
107
124
  log.title('完成');
108
- log.plain('下一步:在你的 AI 工具里说「初始化 draftgo 项目」,它会引导你配置服务器和 token。');
109
- log.dim('或直接运行:');
110
- log.dim(' draftgo connect # 连接已有 DraftGo 服务器');
111
- log.dim(' draftgo local-dev # 本地一键部署 DraftGo');
125
+ log.plain(hasConfig ? 'Skill 已安装,项目已连接 DraftGo。' : 'Skill 已安装。');
112
126
  return 0;
113
127
  }
114
128
 
@@ -5,6 +5,7 @@ const { spawnSync } = require('child_process');
5
5
  const log = require('../logger');
6
6
  const { exists } = require('../fsx');
7
7
  const { detectDocker } = require('../localdev/detect');
8
+ const { runWizard } = require('../localdev');
8
9
 
9
10
  function composeFile(projectDir) {
10
11
  return path.join(projectDir, '.draftgo', 'docker', 'docker-compose.yaml');
@@ -22,7 +23,7 @@ function runCompose(projectDir, args, stdio = 'inherit') {
22
23
  const file = composeFile(projectDir);
23
24
  if (!exists(file)) {
24
25
  log.err('未找到 .draftgo/docker/docker-compose.yaml。');
25
- log.dim(' 请先运行 `draftgo local-dev` 生成本地 DraftGo stack。');
26
+ log.dim(' 请先运行 `draftgo local setup` 生成本地 DraftGo stack。');
26
27
  return 1;
27
28
  }
28
29
 
@@ -34,13 +35,17 @@ function runCompose(projectDir, args, stdio = 'inherit') {
34
35
  return r.status || 0;
35
36
  }
36
37
 
37
- function local(projectDir, positional) {
38
+ function local(projectDir, positional, flags = {}) {
38
39
  const action = positional[0] || 'status';
39
40
 
40
41
  switch (action) {
42
+ case 'setup':
43
+ return runWizard(projectDir, { yes: !!(flags.yes || flags.y) });
41
44
  case 'up':
45
+ case 'start':
42
46
  return runCompose(projectDir, ['up', '-d']);
43
47
  case 'down':
48
+ case 'stop':
44
49
  return runCompose(projectDir, ['down']);
45
50
  case 'logs':
46
51
  return runCompose(projectDir, ['logs', ...(positional.slice(1).length ? positional.slice(1) : ['-f', 'app'])]);
@@ -49,7 +54,8 @@ function local(projectDir, positional) {
49
54
  return runCompose(projectDir, ['ps']);
50
55
  default:
51
56
  log.err(`未知 local 子命令:${action}`);
52
- log.dim(' 可用:draftgo local up | down | logs | status');
57
+ log.dim(' 可用:draftgo local setup | start | stop | logs | status');
58
+ log.dim(' 兼容别名:up | down');
53
59
  return 1;
54
60
  }
55
61
  }
@@ -40,13 +40,13 @@ function mapCommand(projectDir, flags = {}) {
40
40
  });
41
41
 
42
42
  printList('自定义脚本', map.custom_scripts, (s) => {
43
- return `${s.slug || '(无 slug)'} ${s.mode || 'mode?'} ${s.name || ''}${s.code_file ? ` ${s.code_file}` : ''}`;
44
- });
45
-
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}`;
43
+ const bindings = [
44
+ ...(s.routes || []),
45
+ ...(s.events || []).map((event) => `EVENT ${event}`),
46
+ ...(s.schedules || []).map((cron) => `CRON ${cron}`),
47
+ ];
48
+ const summary = bindings.length ? ` [${bindings.join(' | ')}]` : '';
49
+ return `${s.slug || '(无 slug)'} ${s.mode || 'mode?'} ${s.name || ''}${summary}${s.code_file ? ` ${s.code_file}` : ''}`;
50
50
  });
51
51
 
52
52
  printList('文档', map.docs, (d) => {
@@ -82,6 +82,17 @@ function mapCommand(projectDir, flags = {}) {
82
82
  });
83
83
  printList('角色', map.roles, (r) => `${r.code || r.id || 'new'} ${r.name || ''}`);
84
84
 
85
+ console.log('');
86
+ log.info('页面分区:');
87
+ const areaRows = [
88
+ ['home', map.pageGroups.home.length],
89
+ ['admin', map.pageGroups.admin.length],
90
+ ['business', map.pageGroups.business.length],
91
+ ['system', map.pageGroups.system.length],
92
+ ['unknown', map.pageGroups.unknown.length],
93
+ ];
94
+ log.dim(' ' + areaRows.map(([k, v]) => `${k} ${v}`).join(' '));
95
+
85
96
  console.log('');
86
97
  log.info('入口引用:');
87
98
  const routes = Object.keys(map.routeRefs).sort();
@@ -35,20 +35,27 @@ function sync(projectDir, command, positional, flags = {}) {
35
35
 
36
36
  if (!exists(path.join(projectDir, '.draftgo', 'config.json'))) {
37
37
  log.err('未找到 .draftgo/config.json。');
38
- log.dim(' 请先运行 `draftgo connect` 或 `/draftgo init`。');
38
+ log.dim(' 请先运行 `draftgo connect`。');
39
39
  return 1;
40
40
  }
41
41
 
42
42
  const args = positional.length ? positional : ['--all'];
43
- if (command === 'push' && flags.force) {
44
- args.push('--force');
43
+ if (command === 'push' && flags['dry-run']) {
44
+ args.push('--dry-run');
45
+ }
46
+ if (command === 'push' && flags['probe-routes']) {
47
+ args.push('--probe-routes');
45
48
  }
46
49
  const r = spawnSync(py.bin, [script, ...args], {
47
50
  cwd: projectDir,
48
51
  stdio: 'inherit',
49
52
  shell: false,
50
53
  });
51
- return r.status || 0;
54
+ if (r.error) {
55
+ log.err(`${command} 启动失败:${r.error.message}`);
56
+ return 1;
57
+ }
58
+ return Number.isInteger(r.status) ? r.status : 1;
52
59
  }
53
60
 
54
61
  module.exports = sync;
@@ -7,77 +7,57 @@ const { ensureRuntime, writeInstalledVersion, readInstalledVersion, getPackageVe
7
7
  const { fetchLatestVersion, cmpSemver } = require('../updateCheck');
8
8
  const { detectTargets } = require('../detect');
9
9
 
10
- // Env flag we set before re-exec to avoid infinite upgrade loops.
11
10
  const REENTRY_FLAG = 'DRAFTGO_UPDATE_REENTERED';
12
11
 
13
- function runNpmInstallLatest() {
14
- const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
15
- log.step('执行:npm install -g draftgo-cli@latest');
16
- const r = spawnSync(npm, ['install', '-g', 'draftgo-cli@latest'], { stdio: 'inherit' });
17
- if (r.error) { log.err(`升级失败:${r.error.message}`); return false; }
18
- if (r.status !== 0) {
19
- log.err(`npm install 返回码 ${r.status}`);
20
- log.dim(' 常见原因:权限不足(macOS/Linux 用 sudo,或用 nvm 管理 Node)');
21
- return false;
22
- }
23
- return true;
24
- }
25
-
26
- function reExecSameCommand(projectDir) {
27
- // Re-run `draftgo update` from the freshly installed global CLI, so the user
28
- // gets the newest skill content written into .draftgo/skill in the same call.
29
- const cmd = process.platform === 'win32' ? 'draftgo.cmd' : 'draftgo';
30
- const args = ['update', '--project', projectDir, '--skip-update-check'];
31
- log.step('以新版 CLI 重新执行:draftgo update');
32
- const r = spawnSync(cmd, args, {
33
- stdio: 'inherit',
34
- env: { ...process.env, [REENTRY_FLAG]: '1' },
35
- });
36
- if (r.error) {
37
- log.err(`重新执行失败:${r.error.message}`);
38
- log.dim(' 请手动在项目目录再跑一次:draftgo update');
39
- return 1;
12
+ async function updateCliIfNeeded(projectDir, positional, flags) {
13
+ if (process.env[REENTRY_FLAG] === '1' || flags['skip-update-check'] || process.env.DRAFTGO_NO_UPDATE_CHECK === '1') {
14
+ return null;
40
15
  }
41
- return r.status || 0;
42
- }
43
-
44
- async function maybeAutoUpgrade(projectDir, flags) {
45
- if (flags['skip-update-check']) return { upgraded: false };
46
- if (process.env[REENTRY_FLAG] === '1') return { upgraded: false };
47
- if (process.env.DRAFTGO_NO_UPDATE_CHECK === '1') return { upgraded: false };
48
-
49
16
  const current = getPackageVersion();
50
- const latest = await fetchLatestVersion();
17
+ const latest = await fetchLatestVersion(5000, { force: true });
51
18
  if (!latest) {
52
- log.dim(' (查询最新版失败,按当前 CLI 继续)');
53
- return { upgraded: false };
19
+ log.warn('无法查询 npm 最新版本;将用当前 CLI 刷新 skill。');
20
+ return null;
54
21
  }
55
22
  if (cmpSemver(latest, current) <= 0) {
56
23
  log.dim(` CLI 已是最新(${current})`);
57
- return { upgraded: false };
24
+ return null;
25
+ }
26
+
27
+ const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
28
+ log.step(`更新 draftgo-cli:${current} → ${latest}`);
29
+ const install = spawnSync(npm, ['install', '-g', `draftgo-cli@${latest}`], { stdio: 'inherit', shell: false });
30
+ if (install.error) {
31
+ log.err(`CLI 更新启动失败:${install.error.message}`);
32
+ return 1;
33
+ }
34
+ if (install.status !== 0) {
35
+ log.err(`npm install 返回码 ${install.status}`);
36
+ return Number.isInteger(install.status) ? install.status : 1;
58
37
  }
59
38
 
60
- log.warn(`draftgo-cli 有新版:${current} → ${latest},自动升级中……`);
61
- const ok = runNpmInstallLatest();
62
- if (!ok) {
63
- log.warn('自动升级失败,回退到当前 CLI 继续执行。');
64
- return { upgraded: false };
39
+ const command = process.platform === 'win32' ? 'draftgo.cmd' : 'draftgo';
40
+ const rerun = spawnSync(command, ['update', ...positional, '--project', projectDir, '--skip-update-check'], {
41
+ stdio: 'inherit',
42
+ shell: false,
43
+ env: { ...process.env, [REENTRY_FLAG]: '1' },
44
+ });
45
+ if (rerun.error) {
46
+ log.err(`新版 CLI 刷新 skill 失败:${rerun.error.message}`);
47
+ return 1;
65
48
  }
66
- log.ok(`CLI 已升级到 ${latest}。`);
67
- const code = reExecSameCommand(projectDir);
68
- return { upgraded: true, exitCode: code };
49
+ return Number.isInteger(rerun.status) ? rerun.status : 1;
69
50
  }
70
51
 
71
52
  async function update(projectDir, positional, flags) {
72
53
  log.title('draftgo update');
73
54
 
55
+ const reentryCode = await updateCliIfNeeded(projectDir, positional, flags);
56
+ if (reentryCode !== null) return reentryCode;
57
+
74
58
  const prev = readInstalledVersion(projectDir);
75
59
  log.info(`当前已装 skill:${prev || '未安装'},CLI 版本:${getPackageVersion()}`);
76
60
 
77
- // Auto-upgrade-and-rerun path.
78
- const { upgraded, exitCode } = await maybeAutoUpgrade(projectDir, flags);
79
- if (upgraded) return exitCode;
80
-
81
61
  // Determine which entry files to refresh:
82
62
  // - no positional → refresh installed targets plus project-level tool signals
83
63
  // - "all" → refresh every known target
@@ -111,14 +91,20 @@ async function update(projectDir, positional, flags) {
111
91
 
112
92
  ensureRuntime(projectDir);
113
93
  log.step('用 CLI 内置版本覆盖各 AI 工具 skill 目录');
94
+ const failures = [];
114
95
  for (const t of resolved) {
115
96
  try {
116
97
  const r = t.install(projectDir, { force: true });
117
98
  log.ok(`${t.displayName.padEnd(16)} → ${r.path}`);
118
99
  } catch (e) {
119
100
  log.err(`${t.displayName} 更新失败:${e.message}`);
101
+ failures.push(t.displayName);
120
102
  }
121
103
  }
104
+ if (failures.length) {
105
+ log.err(`更新未完成,失败目标:${failures.join(', ')}`);
106
+ return 1;
107
+ }
122
108
  writeInstalledVersion(projectDir);
123
109
 
124
110
  log.title('完成');
@@ -126,3 +112,4 @@ async function update(projectDir, positional, flags) {
126
112
  }
127
113
 
128
114
  module.exports = update;
115
+ module.exports.updateCliIfNeeded = updateCliIfNeeded;