draftgo-cli 3.0.35 → 3.0.39

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 (64) hide show
  1. package/README.md +220 -272
  2. package/package.json +6 -2
  3. package/resources/skill/SKILL.md +114 -55
  4. package/resources/skill/init/SKILL.md +29 -15
  5. package/resources/skill/manifest.json +5 -4
  6. package/resources/skill/push/SKILL.md +41 -29
  7. package/resources/skill/references/aihub.md +8 -5
  8. package/resources/skill/references/api-endpoints.md +5 -3
  9. package/resources/skill/references/architecture.md +1 -1
  10. package/resources/skill/references/checkout.md +116 -0
  11. package/resources/skill/references/custom-services.md +9 -10
  12. package/resources/skill/references/data.md +4 -2
  13. package/resources/skill/references/frontend.md +1 -1
  14. package/resources/skill/references/mcp.md +101 -0
  15. package/resources/skill/references/modules.md +8 -8
  16. package/resources/skill/references/parallel.md +6 -3
  17. package/resources/skill/references/runtime.md +7 -10
  18. package/resources/skill/scripts/README.md +8 -0
  19. package/resources/skill/story/SKILL.md +8 -8
  20. package/src/cli.js +5 -0
  21. package/src/commandRegistry.js +7 -1
  22. package/src/commands/api.js +24 -187
  23. package/src/commands/autoPush.js +48 -17
  24. package/src/commands/check.js +17 -47
  25. package/src/commands/checkout.js +18 -0
  26. package/src/commands/commit.js +21 -0
  27. package/src/commands/conflict.js +30 -0
  28. package/src/commands/conflicts.js +16 -0
  29. package/src/commands/connect.js +60 -48
  30. package/src/commands/delete.js +79 -64
  31. package/src/commands/deploy.js +18 -10
  32. package/src/commands/diff.js +23 -0
  33. package/src/commands/help.js +99 -75
  34. package/src/commands/init.js +4 -10
  35. package/src/commands/local.js +23 -6
  36. package/src/commands/map.js +89 -89
  37. package/src/commands/mcp.js +126 -0
  38. package/src/commands/sync.js +28 -43
  39. package/src/commands/verifyUi.js +3 -2
  40. package/src/localdev/index.js +37 -7
  41. package/src/localdev/mysqlClient.js +1 -1
  42. package/src/mcp/client.js +275 -0
  43. package/src/mcp/hosts.js +520 -0
  44. package/src/mcp/protocol.js +173 -0
  45. package/src/mcp/stdio.js +300 -0
  46. package/src/mcp/tools.js +47 -0
  47. package/src/platforms.js +3 -4
  48. package/src/projectConfig.js +91 -49
  49. package/src/projectMap.js +123 -460
  50. package/src/skill.js +6 -28
  51. package/src/worktree/backend.js +326 -0
  52. package/src/worktree/errors.js +28 -0
  53. package/src/worktree/index.js +461 -0
  54. package/src/worktree/manifest.js +75 -0
  55. package/src/worktree/streams.js +200 -0
  56. package/src/worktree/types.js +103 -0
  57. package/src/worktree/validate.js +37 -0
  58. package/resources/skill/pull/SKILL.md +0 -33
  59. package/resources/skill/references/api.json +0 -20248
  60. package/resources/skill/scripts/draftgo_delete.py +0 -149
  61. package/resources/skill/scripts/draftgo_init.py +0 -80
  62. package/resources/skill/scripts/draftgo_pull.py +0 -427
  63. package/resources/skill/scripts/draftgo_push.py +0 -1022
  64. package/src/python.js +0 -27
@@ -1,62 +1,32 @@
1
1
  'use strict';
2
2
 
3
+ const fs = require('fs');
4
+ const path = require('path');
3
5
  const log = require('../logger');
4
6
  const { analyzeProject } = require('../projectMap');
5
7
 
8
+ function legacyCacheDetected(projectDir) {
9
+ return ['pages', 'navigations', 'docs', 'db_meta', 'custom_scripts', 'aihub', 'roles', 'users', 'system_config']
10
+ .some((name) => fs.existsSync(path.join(projectDir, '.draftgo', name, 'index.json')));
11
+ }
12
+
6
13
  function check(projectDir, flags = {}) {
7
14
  const result = analyzeProject(projectDir);
8
-
9
15
  if (flags.output === 'json') {
10
- console.log(JSON.stringify(result, null, 2));
16
+ console.log(JSON.stringify({ ...result, legacy_cache_ignored: legacyCacheDetected(projectDir) }, null, 2));
11
17
  return result.errors.length || (flags.strict && result.warnings.length) ? 1 : 0;
12
18
  }
13
19
 
14
20
  log.title('draftgo check');
15
- log.info(`项目目录:${projectDir}`);
16
-
17
- const m = result.map;
18
- console.log('');
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(' '));
35
-
36
- if (result.errors.length) {
37
- console.log('');
38
- result.errors.forEach((msg) => console.log(` ${log.c.red('x')} ${msg}`));
39
- }
40
-
41
- if (result.warnings.length) {
42
- console.log('');
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
- });
48
- }
49
-
50
- console.log('');
51
- if (result.errors.length) {
52
- log.err('检查未通过:请先修复错误。');
53
- return 1;
54
- }
55
- if (flags.strict && result.warnings.length) {
56
- log.err('strict 模式:存在提醒项,检查未通过。');
57
- return 1;
58
- }
59
- log.ok(result.warnings.length ? '检查完成(有提醒项)。' : '检查通过。');
21
+ log.info(`Checked-out pages: ${result.map.pages.length}`);
22
+ log.info(`Checked-out navigations: ${result.map.navigations.length}`);
23
+ log.info(`Checked-out docs: ${result.map.docs.length}`);
24
+ for (const message of result.errors) log.err(message);
25
+ for (const detail of result.warningDetails) log.warn(`[${detail.code}/${detail.confidence}] ${detail.message}`);
26
+ if (legacyCacheDetected(projectDir)) log.dim(' Legacy .draftgo index caches were detected and ignored.');
27
+
28
+ if (result.errors.length || (flags.strict && result.warnings.length)) return 1;
29
+ log.ok(result.warnings.length ? 'Local checkout validation completed with warnings.' : 'Local checkout validation passed.');
60
30
  return 0;
61
31
  }
62
32
 
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ const log = require('../logger');
4
+ const { checkoutResources } = require('../worktree');
5
+
6
+ async function checkout(projectDir, positional, flags = {}) {
7
+ const [resourceType, ...ids] = positional;
8
+ if (!resourceType || !ids.length) {
9
+ log.err('Usage: draftgo checkout <pages|nav|docs> <id...>');
10
+ return 1;
11
+ }
12
+ const results = await checkoutResources(projectDir, resourceType, ids, { force: Boolean(flags.force) });
13
+ if (flags.output === 'json') console.log(JSON.stringify(results, null, 2));
14
+ else for (const entry of results) log.ok(`Checked out ${entry.resource_type} ${entry.resource_id} -> ${entry.local_path}`);
15
+ return 0;
16
+ }
17
+
18
+ module.exports = checkout;
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const log = require('../logger');
4
+ const { commitResources } = require('../worktree');
5
+
6
+ async function commit(projectDir, positional, flags = {}) {
7
+ const [resourceType, ...ids] = positional;
8
+ if (!resourceType || !ids.length) {
9
+ log.err('Usage: draftgo commit <pages|nav|docs> <id...>');
10
+ return 1;
11
+ }
12
+ const results = await commitResources(projectDir, resourceType, ids);
13
+ if (flags.output === 'json') console.log(JSON.stringify(results, null, 2));
14
+ else for (const result of results) {
15
+ if (result.status === 'unchanged') log.dim(`${result.resource_type} ${result.resource_id}: unchanged`);
16
+ else log.ok(`Committed ${result.resource_type} ${result.resource_id}`);
17
+ }
18
+ return 0;
19
+ }
20
+
21
+ module.exports = commit;
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ const log = require('../logger');
4
+ const { showConflict, resolveConflict } = require('../worktree');
5
+
6
+ function printRecord(record) {
7
+ log.info(`${record.resource_type} ${record.resource_id}: ${record.status}`);
8
+ log.plain(` base: ${record.base_path}`);
9
+ log.plain(` local: ${record.local_path}`);
10
+ log.plain(` remote: ${record.remote_path}`);
11
+ log.plain(` worktree local: ${record.worktree_local_path}`);
12
+ log.plain(` manifest: ${record.manifest_path}`);
13
+ }
14
+
15
+ async function conflict(projectDir, positional, flags = {}) {
16
+ const [action, resourceType, resourceId] = positional;
17
+ if (!['show', 'resolve'].includes(action) || !resourceType || !resourceId) {
18
+ log.err('Usage: draftgo conflict <show|resolve> <pages|nav|docs> <id>');
19
+ return 1;
20
+ }
21
+ const record = action === 'show'
22
+ ? showConflict(projectDir, resourceType, resourceId)
23
+ : await resolveConflict(projectDir, resourceType, resourceId);
24
+ if (flags.output === 'json') console.log(JSON.stringify(record, null, 2));
25
+ else printRecord(record);
26
+ if (action === 'resolve') log.info('The remote revision is now the base; run check, diff, and commit the merged worktree file.');
27
+ return 0;
28
+ }
29
+
30
+ module.exports = conflict;
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ const log = require('../logger');
4
+ const { listConflicts } = require('../worktree');
5
+
6
+ function conflicts(projectDir, _positional, flags = {}) {
7
+ const records = listConflicts(projectDir, { all: Boolean(flags.all) });
8
+ if (flags.output === 'json') console.log(JSON.stringify(records, null, 2));
9
+ else if (!records.length) log.ok('No unresolved DraftGo conflicts.');
10
+ else for (const record of records) {
11
+ log.warn(`${record.resource_type} ${record.resource_id}: ${record.status} (${record.manifest_path})`);
12
+ }
13
+ return records.some((record) => record.status === 'unresolved') ? 1 : 0;
14
+ }
15
+
16
+ module.exports = conflicts;
@@ -1,61 +1,73 @@
1
1
  'use strict';
2
2
 
3
- // `draftgo connect` — wire this project to an already-deployed DraftGo base.
4
- // Prompts for server URL + access token, writes .draftgo/config.json, and
5
- // (if Python is available) runs draftgo_init.py to pull initial context.
6
-
7
3
  const log = require('../logger');
8
- const { ask, askRequired, askPassword } = require('../prompt');
9
- const { writeProjectConfig, maybeRunInit } = require('../projectConfig');
10
-
11
- function normalizeServer(raw) {
12
- let v = String(raw || '').trim();
13
- if (!v) return '';
14
- if (!/^https?:\/\//i.test(v)) v = 'http://' + v;
15
- return v.replace(/\/+$/, '');
16
- }
4
+ const { ask, askPassword } = require('../prompt');
5
+ const { normalizeServer, writeProjectConfig } = require('../projectConfig');
6
+ const { testConnection } = require('../mcp/client');
7
+ const { redactText } = require('../mcp/protocol');
17
8
 
18
- async function promptServer(defaultVal) {
9
+ async function promptServer(defaultValue) {
19
10
  while (true) {
20
- const v = await ask('DraftGo 服务器地址 (BaseURL)', { default: defaultVal });
21
- const norm = normalizeServer(v);
22
- if (!norm) { log.dim(' 不能为空'); continue; }
11
+ const raw = await ask('DraftGo server (Base URL)', { default: defaultValue });
23
12
  try {
24
- // Sanity-parse the URL so we fail fast on obvious typos.
25
- // eslint-disable-next-line no-new
26
- new URL(norm);
27
- } catch {
28
- log.dim(` 不是合法 URL:${norm}`);
29
- continue;
13
+ const value = normalizeServer(raw);
14
+ if (value) return value;
15
+ } catch (error) {
16
+ log.dim(` ${error.message}`);
30
17
  }
31
- return norm;
32
18
  }
33
19
  }
34
20
 
35
- async function connect(projectDir, positional, flags) {
36
- log.title('draftgo connect — 连接已有的 DraftGo 服务器');
37
- log.dim(' 把本项目绑定到一台已部署的 DraftGo 基座。');
38
-
39
- const server = flags.server
40
- ? normalizeServer(flags.server)
41
- : await promptServer('https://');
42
- const token = flags.token
43
- ? String(flags.token)
44
- : await askRequired('系统访问令牌 (SAT)', {
45
- validate: (v) => v.length < 10 ? 'token 看起来太短了,请检查后重试' : '',
46
- });
47
-
48
- const cfgPath = writeProjectConfig(projectDir, server, token);
49
- log.ok(`已写入 ${cfgPath}`);
50
- maybeRunInit(projectDir, server, token);
51
-
52
- log.title('完成');
53
- log.plain(` - 服务器: ${server}`);
54
- log.plain(` - 配置文件: ${cfgPath}`);
55
- log.dim(' 之后在 AI 工具里说「同步页面 xxx」即可。');
56
- // Silence unused-arg lint if any future tooling complains.
57
- void positional;
58
- void askPassword;
21
+ async function promptToken() {
22
+ while (true) {
23
+ const value = String(await askPassword('DraftGo system access token (SAT)') || '').trim();
24
+ if (value) return value;
25
+ log.dim(' A SAT is required.');
26
+ }
27
+ }
28
+
29
+ async function connect(projectDir, positional, flags = {}) {
30
+ log.title('draftgo connect');
31
+ const server = flags.server ? normalizeServer(flags.server) : await promptServer('https://');
32
+ const token = flags.token ? String(flags.token).trim() : await promptToken();
33
+ if (!token) {
34
+ log.err('A non-empty SAT is required.');
35
+ return 1;
36
+ }
37
+
38
+ log.step('Validating SAT and DraftGo MCP capabilities...');
39
+ try {
40
+ const diagnostic = await testConnection({ server, token }, {
41
+ timeoutMs: flags.timeout ? Number(flags.timeout) : undefined,
42
+ });
43
+ log.ok(`MCP ready (${diagnostic.tools.length} tools; tested ${diagnostic.testedTool}).`);
44
+ } catch (error) {
45
+ const message = redactText(error && error.message ? error.message : error, [token]);
46
+ if (!flags['allow-offline']) {
47
+ log.err(message);
48
+ log.dim(' The project config was not changed. Run `draftgo mcp test` after checking the server and SAT.');
49
+ return 1;
50
+ }
51
+ log.warn(`MCP validation unavailable: ${message}`);
52
+ log.warn('Continuing only because --allow-offline was explicitly supplied.');
53
+ }
54
+
55
+ const configFile = writeProjectConfig(projectDir, server, token);
56
+ log.ok(`Project configuration written: ${configFile}`);
57
+
58
+ if (!flags['no-mcp-setup']) {
59
+ try {
60
+ const setupCode = require('./mcp').setup(projectDir, positional, flags);
61
+ if (setupCode !== 0) log.dim(' Run `draftgo mcp setup <target>` when the AI host is known.');
62
+ } catch (error) {
63
+ log.warn(`MCP host setup was not completed: ${redactText(error.message || error, [token])}`);
64
+ }
65
+ }
66
+
67
+ log.title('Connected');
68
+ log.plain(` server: ${server}`);
69
+ log.plain(` config: ${configFile}`);
70
+ log.dim(' No DraftGo business resources were downloaded. Use MCP discovery or checkout long content explicitly.');
59
71
  return 0;
60
72
  }
61
73
 
@@ -1,88 +1,103 @@
1
1
  'use strict';
2
2
 
3
+ const fs = require('fs');
3
4
  const path = require('path');
4
- const { exists } = require('../fsx');
5
5
  const log = require('../logger');
6
6
  const { confirm } = require('../prompt');
7
- const { findPython } = require('../python');
8
- const { spawnSync } = require('child_process');
9
- const { platforms } = require('../platforms');
7
+ const { loadProjectConfig } = require('../projectConfig');
8
+ const { TOOL_NAMES, openToolSession, callStructured } = require('../mcp/tools');
10
9
 
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
- }
10
+ const LEGACY_TYPES = new Set([
11
+ 'page', 'pages', 'nav', 'navigation', 'navigations', 'doc', 'docs', 'article',
12
+ 'db_meta', 'custom_scripts', 'doc_categories', 'aihub', 'roles', 'users', 'system_config',
13
+ ]);
19
14
 
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
- aihub: 'aihub',
34
- };
15
+ function resultItems(value) {
16
+ if (Array.isArray(value)) return value;
17
+ if (!value || typeof value !== 'object') return [];
18
+ if (Array.isArray(value.items)) return value.items;
19
+ if (Array.isArray(value.operations)) return value.operations;
20
+ return value.data ? resultItems(value.data) : [];
21
+ }
35
22
 
36
- async function deleteResource(projectDir, positional, flags) {
37
- const [typeRaw, id] = positional;
23
+ function operationId(value) {
24
+ return value && (value.operation_id || value.operationId || value.id);
25
+ }
38
26
 
39
- if (!typeRaw || !id) {
40
- log.err('用法:draftgo delete <type> <id>');
41
- log.dim(' 支持类型:pages | nav | db_meta | custom_scripts | docs | doc_categories | aihub');
42
- return 1;
27
+ function readParams(projectDir, flags) {
28
+ const raw = flags.params ? String(flags.params)
29
+ : flags.input ? fs.readFileSync(path.resolve(projectDir, String(flags.input)), 'utf8') : '';
30
+ if (!raw) return {};
31
+ const parsed = JSON.parse(raw);
32
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
33
+ throw new Error('Delete MCP parameters must be a JSON object.');
43
34
  }
35
+ return parsed;
36
+ }
44
37
 
45
- const type = TYPE_ALIASES[typeRaw];
46
- if (!type) {
47
- log.err(`未知类型:${typeRaw}`);
48
- return 1;
49
- }
38
+ function pathParameterName(description) {
39
+ const source = description && (description.parameters || description.params
40
+ || description.operation && description.operation.parameters) || [];
41
+ const pathParameters = source.filter((parameter) => parameter && parameter.in === 'path');
42
+ return pathParameters.length === 1 ? pathParameters[0].name : null;
43
+ }
50
44
 
51
- if (!exists(path.join(projectDir, '.draftgo', 'config.json'))) {
52
- log.err('未找到 .draftgo/config.json,请先运行 /draftgo init');
45
+ async function deleteResource(projectDir, positional, flags = {}) {
46
+ if (!positional.length && !flags['operation-id']) {
47
+ log.err('Usage: draftgo delete <operation_id> [id] [--params JSON]');
48
+ log.dim(' Legacy form is supported when discovery is unambiguous: draftgo delete <type> <id>.');
53
49
  return 1;
54
50
  }
51
+ const config = loadProjectConfig(projectDir);
52
+ const session = await openToolSession(config, [
53
+ TOOL_NAMES.apiSearch, TOOL_NAMES.apiDescribe, TOOL_NAMES.apiCall,
54
+ ]);
55
55
 
56
- const py = findPython();
57
- if (!py) {
58
- log.err('未检测到 Python,无法运行删除脚本');
59
- return 1;
56
+ const first = String(positional[0] || '');
57
+ const legacy = LEGACY_TYPES.has(first.toLowerCase()) && !flags['operation-id'];
58
+ let selectedOperation = flags['operation-id'] ? String(flags['operation-id']) : (legacy ? null : first);
59
+ const resourceId = legacy ? positional[1] : positional[1];
60
+ if (legacy) {
61
+ if (!resourceId) {
62
+ log.err('Legacy delete form requires a resource id.');
63
+ return 1;
64
+ }
65
+ const search = await callStructured(session, TOOL_NAMES.apiSearch, { query: `delete ${first}` });
66
+ const candidates = resultItems(search).filter((item) => operationId(item)
67
+ && (!item.method || String(item.method).toUpperCase() === 'DELETE'));
68
+ if (candidates.length !== 1) {
69
+ log.err(`Delete operation discovery was ambiguous (${candidates.length} matches).`);
70
+ log.dim(` Run \`draftgo api delete ${first}\`, then pass --operation-id explicitly.`);
71
+ return 1;
72
+ }
73
+ selectedOperation = operationId(candidates[0]);
60
74
  }
61
75
 
62
- const script = findDeleteScript(projectDir);
63
- if (!script) {
64
- log.err('未找到 draftgo_delete.py,请运行 draftgo update 更新');
65
- return 1;
76
+ const description = await callStructured(session, TOOL_NAMES.apiDescribe, {
77
+ operation_id: selectedOperation,
78
+ });
79
+ const params = readParams(projectDir, flags);
80
+ if (resourceId && Object.keys(params).length === 0) {
81
+ const parameterName = pathParameterName(description);
82
+ if (!parameterName) {
83
+ log.err('The operation does not expose exactly one path parameter; provide --params JSON explicitly.');
84
+ return 1;
85
+ }
86
+ params.path_params = { [parameterName]: resourceId };
66
87
  }
67
-
68
88
  if (!flags.yes && !flags.y) {
69
- const ok = await confirm(`确认删除 ${type} id=${id}?此操作不可恢复`);
70
- if (!ok) {
71
- log.dim('已取消');
72
- return 0;
73
- }
89
+ const approved = await confirm(`Call destructive DraftGo operation ${selectedOperation}?`, { default: false });
90
+ if (!approved) return 0;
74
91
  }
75
-
76
- const r = spawnSync(py.bin, [script, type, id], {
77
- cwd: projectDir,
78
- stdio: 'inherit',
79
- shell: false,
92
+ const result = await callStructured(session, TOOL_NAMES.apiCall, {
93
+ ...params,
94
+ operation_id: selectedOperation,
95
+ confirm: true,
80
96
  });
81
- if (r.error) {
82
- log.err(`删除脚本启动失败:${r.error.message}`);
83
- return 1;
84
- }
85
- return Number.isInteger(r.status) ? r.status : 1;
97
+ if (flags.output === 'json') console.log(JSON.stringify(result, null, 2));
98
+ else log.ok(`DraftGo operation completed: ${selectedOperation}`);
99
+ return 0;
86
100
  }
87
101
 
88
102
  module.exports = deleteResource;
103
+ module.exports.pathParameterName = pathParameterName;
@@ -2,30 +2,38 @@
2
2
 
3
3
  const log = require('../logger');
4
4
  const check = require('./check');
5
- const sync = require('./sync');
6
5
 
7
- function deploy(projectDir, positional, flags = {}) {
6
+ async function deploy(projectDir, positional, flags = {}) {
8
7
  const delivery = String(flags.delivery || 'deploy').toLowerCase();
9
8
  if (!['local', 'preview', 'deploy'].includes(delivery)) {
10
- log.err('--delivery 只支持 localpreviewdeploy');
9
+ log.err('--delivery accepts local, preview, or deploy.');
11
10
  return 1;
12
11
  }
13
- log.step('deploy: 先执行 check …');
12
+
13
+ log.step('deploy: validating checked-out content...');
14
14
  const checkCode = check(projectDir, { strict: flags.strict });
15
15
  if (checkCode !== 0) {
16
- log.err('deploy 终止:check 未通过,请修复后重试。');
16
+ log.err('Deploy stopped because local validation failed.');
17
17
  return checkCode;
18
18
  }
19
19
  if (delivery === 'local') {
20
- log.ok('delivery=local:本地检查通过,未修改云端。');
20
+ log.ok('Local validation passed; no remote content was changed.');
21
21
  return 0;
22
22
  }
23
+
24
+ const [resourceType, ...ids] = positional;
25
+ if (!resourceType || !ids.length) {
26
+ log.err('Deploy no longer commits every legacy index. Specify <pages|nav|docs> <id...>.');
27
+ return 1;
28
+ }
23
29
  if (delivery === 'preview') {
24
- log.step('delivery=preview:执行 push dry-run …');
25
- return sync(projectDir, 'push', positional, { ...flags, 'dry-run': true });
30
+ for (const id of ids) {
31
+ const code = require('./diff')(projectDir, [resourceType, id], flags);
32
+ if (code !== 0) return code;
33
+ }
34
+ return 0;
26
35
  }
27
- log.step('delivery=deploy:开始 push …');
28
- return sync(projectDir, 'push', positional, flags);
36
+ return require('./commit')(projectDir, [resourceType, ...ids], flags);
29
37
  }
30
38
 
31
39
  module.exports = deploy;
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ const log = require('../logger');
4
+ const { diffResource } = require('../worktree');
5
+
6
+ function diff(projectDir, positional, flags = {}) {
7
+ const [resourceType, resourceId] = positional;
8
+ if (!resourceType || !resourceId) {
9
+ log.err('Usage: draftgo diff <pages|nav|docs> <id>');
10
+ return 1;
11
+ }
12
+ const result = diffResource(projectDir, resourceType, resourceId);
13
+ if (flags.output === 'json') {
14
+ console.log(JSON.stringify({ changed: result.changed, entry: result.entry, diff: result.output }, null, 2));
15
+ } else if (result.changed) {
16
+ process.stdout.write(result.output);
17
+ } else {
18
+ log.dim(`${result.entry.resource_type} ${result.entry.resource_id}: no local changes`);
19
+ }
20
+ return 0;
21
+ }
22
+
23
+ module.exports = diff;