draftgo-cli 4.0.22 → 4.0.24

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 (38) hide show
  1. package/README.md +2 -2
  2. package/bin/draftgo.js +8 -8
  3. package/package.json +72 -72
  4. package/resources/custom-service-sdk/auth_test.go +56 -0
  5. package/resources/custom-service-sdk/manifest.json +14 -9
  6. package/resources/custom-service-sdk/platform.go +19 -27
  7. package/resources/custom-service-sdk/resources.go +1 -0
  8. package/resources/custom-service-sdk/resources_scope_test.go +10 -5
  9. package/resources/custom-service-sdk/sdk.go +6 -5
  10. package/resources/skill/SKILL.md +1 -1
  11. package/resources/skill/manifest.json +1 -1
  12. package/resources/skill/references/aihub.md +74 -74
  13. package/resources/skill/references/app-api.md +78 -78
  14. package/resources/skill/references/architecture.md +40 -40
  15. package/resources/skill/references/checkout.md +105 -105
  16. package/resources/skill/references/custom-services.md +6 -6
  17. package/resources/skill/references/data.md +168 -168
  18. package/resources/skill/references/methods.md +3 -0
  19. package/resources/skill/references/modules.md +48 -48
  20. package/resources/skill/references/runtime.md +95 -96
  21. package/resources/skill/story/SKILL.md +264 -264
  22. package/src/commands/help.js +72 -72
  23. package/src/commands/listTargets.js +12 -12
  24. package/src/commands/status.js +2 -2
  25. package/src/commands/uninstall.js +45 -45
  26. package/src/commands/update.js +20 -20
  27. package/src/customServices.js +5 -4
  28. package/src/detect.js +14 -14
  29. package/src/fsx.js +67 -67
  30. package/src/index.js +25 -25
  31. package/src/localRuntime/detect.js +76 -76
  32. package/src/localRuntime/mysqlClient.js +138 -138
  33. package/src/logger.js +37 -37
  34. package/src/mcp/client.js +586 -595
  35. package/src/mcp/hosts.js +520 -520
  36. package/src/mcp/protocol.js +184 -164
  37. package/src/prompt.js +94 -94
  38. package/src/updateCheck.js +16 -16
@@ -1,38 +1,38 @@
1
- 'use strict';
2
-
1
+ 'use strict';
2
+
3
3
  const { all } = require('../targets');
4
- const { HOSTS } = require('../mcp/hosts');
5
- const { getPackageVersion } = require('../skill');
6
-
7
- function help() {
8
- const targets = all.map((i) => ` ${i.name.padEnd(12)} ${i.displayName}`).join('\n');
9
- const mcpTargets = HOSTS.map((host) => {
10
- const status = host.supported === false ? 'unsupported' : host.path;
11
- return ` ${host.name.padEnd(12)} ${status}`;
12
- }).join('\n');
13
-
14
- console.log(`draftgo v${getPackageVersion()} - DraftGo workbench CLI for AI coding agents
15
-
4
+ const { HOSTS } = require('../mcp/hosts');
5
+ const { getPackageVersion } = require('../skill');
6
+
7
+ function help() {
8
+ const targets = all.map((i) => ` ${i.name.padEnd(12)} ${i.displayName}`).join('\n');
9
+ const mcpTargets = HOSTS.map((host) => {
10
+ const status = host.supported === false ? 'unsupported' : host.path;
11
+ return ` ${host.name.padEnd(12)} ${status}`;
12
+ }).join('\n');
13
+
14
+ console.log(`draftgo v${getPackageVersion()} - DraftGo workbench CLI for AI coding agents
15
+
16
16
  DraftGo shell baseline: React + Vite. Database Pages default to native HTML,
17
17
  Tailwind CSS 4, and Basecoat UI; Oat UI remains an optional Web Components layer.
18
18
  MCP handles live discovery and structured resources; complete page, navigation,
19
19
  document, and custom-service bodies use checkout/commit outside MCP context.
20
20
  The installed Skill routes agents to task-specific References and MCP tools.
21
-
22
- Usage:
23
- draftgo init [<target>...] Install the DraftGo Skill. No target = detect.
24
- Use "all" to install every Skill target.
25
- draftgo update [<target>...] Fully update the global CLI, then atomically
26
- refresh installed or detected Skills.
27
- draftgo uninstall <target|all> Remove Skill files. --purge also removes
28
- the entire .draftgo/ runtime directory.
21
+
22
+ Usage:
23
+ draftgo init [<target>...] Install the DraftGo Skill. No target = detect.
24
+ Use "all" to install every Skill target.
25
+ draftgo update [<target>...] Fully update the global CLI, then atomically
26
+ refresh installed or detected Skills.
27
+ draftgo uninstall <target|all> Remove Skill files. --purge also removes
28
+ the entire .draftgo/ runtime directory.
29
29
  draftgo status Show connection health, current scope, targets, and Skill version.
30
-
30
+
31
31
  draftgo connect [<target>...] Verify and save server/API Key, then configure
32
- a detected or explicit MCP host. This never
33
- downloads DraftGo business resources.
34
- draftgo mcp setup [<target>...] Merge project-level stdio MCP configuration.
35
- draftgo mcp status [<target>...] Check host configuration and secret safety.
32
+ a detected or explicit MCP host. This never
33
+ downloads DraftGo business resources.
34
+ draftgo mcp setup [<target>...] Merge project-level stdio MCP configuration.
35
+ draftgo mcp status [<target>...] Check host configuration and secret safety.
36
36
  draftgo mcp test Test project, resource, and API MCP calls.
37
37
  draftgo mcp serve Bridge local stdio to the remote /mcp endpoint.
38
38
 
@@ -41,7 +41,7 @@ Usage:
41
41
  Use --route/--title for exact selection.
42
42
  draftgo checkout <type> <id...> Download a long-form body + verified base,
43
43
  including custom-service worktrees.
44
- --force explicitly replaces local changes.
44
+ --force explicitly replaces local changes.
45
45
  draftgo check [custom-services <id...>]
46
46
  Validate all checkouts or selected custom services.
47
47
  --remote also compares remote hash/version.
@@ -73,20 +73,20 @@ Usage:
73
73
  Publish current validated drafts; batch errors
74
74
  report services already completed.
75
75
  draftgo reconcile <type> <id...> Repair stale metadata only when local equals remote.
76
- draftgo conflicts List unresolved conflicts; --all includes resolved.
77
- draftgo conflict show <type> <id>
78
- Show preserved base/local/remote paths.
79
- draftgo conflict resolve <type> <id>
80
- Mark a merged worktree file ready against the
81
- preserved remote base; then check/diff/commit.
76
+ draftgo conflicts List unresolved conflicts; --all includes resolved.
77
+ draftgo conflict show <type> <id>
78
+ Show preserved base/local/remote paths.
79
+ draftgo conflict resolve <type> <id>
80
+ Mark a merged worktree file ready against the
81
+ preserved remote base; then check/diff/commit.
82
82
  draftgo clean [--dry-run|--yes] Plan or remove all tmp and registered artifacts.
83
83
  draftgo work start <item> Add an active item to .draftgo/worklog.md.
84
84
  draftgo work add <item> Add a pending item.
85
85
  draftgo work start-item <ref> Mark an item active; ref is number or date#number.
86
86
  draftgo work complete <ref> Mark an item complete; --note appends evidence.
87
87
  draftgo work show|list Show the worklog or its structured item list.
88
-
89
- draftgo api <query> Search the live API contract through MCP.
88
+
89
+ draftgo api <query> Search the live API contract through MCP.
90
90
  draftgo api describe <operation_id>
91
91
  Describe one live API operation through MCP.
92
92
  draftgo api call <operation_id> --input <json-file>
@@ -105,24 +105,24 @@ Usage:
105
105
  Manage db_meta record policies (none|owner|all).
106
106
  draftgo delete <operation_id> [id]
107
107
  Confirm and call a live delete operation.
108
- draftgo deploy [<type> <id...>] Check, then diff or commit checked-out content.
109
- --delivery local needs no IDs; preview/deploy
110
- require an explicit type and IDs.
108
+ draftgo deploy [<type> <id...>] Check, then diff or commit checked-out content.
109
+ --delivery local needs no IDs; preview/deploy
110
+ require an explicit type and IDs.
111
111
  draftgo auto-push [<type> <id...>]
112
112
  With config.auto_push=true, check and commit
113
113
  changed checkouts. Any conflict stops the run.
114
114
  draftgo local setup|start|stop|logs|status
115
115
  Manage the project-local Docker stack.
116
- draftgo list-targets List Skill installation targets.
117
- draftgo -v | --version Print CLI version.
118
- draftgo -h | --help Show this help.
119
-
120
- Resource types:
116
+ draftgo list-targets List Skill installation targets.
117
+ draftgo -v | --version Print CLI version.
118
+ draftgo -h | --help Show this help.
119
+
120
+ Resource types:
121
121
  pages | nav/navigations | docs/articles | custom-services
122
-
123
- Important flags:
124
- --project <dir> Operate on <dir> instead of the current directory.
125
- --target <name,...> Select one or more MCP setup/status targets.
122
+
123
+ Important flags:
124
+ --project <dir> Operate on <dir> instead of the current directory.
125
+ --target <name,...> Select one or more MCP setup/status targets.
126
126
  --server <url> (connect) DraftGo base URL.
127
127
  --mcp-url <url> (connect) Explicit endpoint; standard /mcp derives the
128
128
  base URL; custom paths also need --server.
@@ -130,11 +130,11 @@ Important flags:
130
130
  --scope-type space --space-id <id>
131
131
  (connect) Save a target space for workspace resources.
132
132
  --timeout <ms> (connect/mcp test) Network timeout.
133
- --allow-offline (connect) Save only after explicitly accepting a
134
- failed MCP validation.
135
- --no-mcp-setup (connect) Do not write host MCP configuration.
136
- --force (init/checkout) Overwrite Skill or local changes.
137
- --purge (uninstall) Also remove the .draftgo/ directory.
133
+ --allow-offline (connect) Save only after explicitly accepting a
134
+ failed MCP validation.
135
+ --no-mcp-setup (connect) Do not write host MCP configuration.
136
+ --force (init/checkout) Overwrite Skill or local changes.
137
+ --purge (uninstall) Also remove the .draftgo/ directory.
138
138
  --skip-update-check (update) Refresh Skills without contacting npm.
139
139
  --connect (init) Continue into DraftGo server connection.
140
140
  --no-setup (init) Install the Skill without either setup flow.
@@ -150,8 +150,8 @@ Important flags:
150
150
  --stat (diff) Print per-file and total change statistics.
151
151
  --strict Treat check warnings as failures.
152
152
  --remote (check) Compare checkout hashes/versions with remote.
153
- --yes Skip supported confirmation prompts.
154
- --operation-id <id> (delete) Select an operation explicitly.
153
+ --yes Skip supported confirmation prompts.
154
+ --operation-id <id> (delete) Select an operation explicitly.
155
155
  --params <json> (api call/delete) Pass an API parameter object.
156
156
  --input <file> (api call/delete/custom-service test) Read a UTF-8
157
157
  JSON object.
@@ -176,23 +176,23 @@ Important flags:
176
176
  fallback: DRAFTGO_BROWSER_PATH.
177
177
  --selector <css> (verify) Require a visible element in top or iframe DOM.
178
178
  --width/--height <px> (verify) Override the default 1440x900 desktop viewport.
179
-
180
- Security:
179
+
180
+ Security:
181
181
  .draftgo/config.json stores the server and API Key and is gitignored. Host MCP
182
182
  files contain only "draftgo mcp serve"; they never contain the API Key.
183
-
184
- MCP targets (project config or status):
185
- ${mcpTargets}
186
-
187
- Skill targets:
188
- ${targets}
189
-
190
- Examples:
191
- draftgo init codex
183
+
184
+ MCP targets (project config or status):
185
+ ${mcpTargets}
186
+
187
+ Skill targets:
188
+ ${targets}
189
+
190
+ Examples:
191
+ draftgo init codex
192
192
  draftgo connect codex --server https://draftgo.example --api-key <key>
193
193
  draftgo mcp test
194
194
  draftgo map --type pages --route /admin/channel-ops --summary --output json
195
- draftgo checkout pages 42
195
+ draftgo checkout pages 42
196
196
  draftgo verify pages 42
197
197
  draftgo diff pages 42 --stat
198
198
  draftgo commit pages 42
@@ -200,8 +200,8 @@ Examples:
200
200
  draftgo test custom-services 12 --handler route:POST:/health --input request.json
201
201
  draftgo work start "Build document management and role permissions"
202
202
  draftgo work complete 1 --note "Verification and delivery passed"
203
- draftgo deploy docs 7 --delivery preview
204
- `);
205
- }
206
-
207
- module.exports = help;
203
+ draftgo deploy docs 7 --delivery preview
204
+ `);
205
+ }
206
+
207
+ module.exports = help;
@@ -1,13 +1,13 @@
1
- 'use strict';
2
-
1
+ 'use strict';
2
+
3
3
  const { all } = require('../targets');
4
-
5
- function listTargets() {
6
- console.log('draftgo 支持的 AI 工具(target):');
7
- for (const t of all) {
8
- console.log(` ${t.name.padEnd(12)} ${t.displayName}`);
9
- }
10
- return 0;
11
- }
12
-
13
- module.exports = listTargets;
4
+
5
+ function listTargets() {
6
+ console.log('draftgo 支持的 AI 工具(target):');
7
+ for (const t of all) {
8
+ console.log(` ${t.name.padEnd(12)} ${t.displayName}`);
9
+ }
10
+ return 0;
11
+ }
12
+
13
+ module.exports = listTargets;
@@ -1,5 +1,5 @@
1
- 'use strict';
2
-
1
+ 'use strict';
2
+
3
3
  const log = require('../logger');
4
4
  const { all } = require('../targets');
5
5
  const { readInstalledVersion, getPackageVersion } = require('../skill');
@@ -1,52 +1,52 @@
1
- 'use strict';
2
-
3
- const log = require('../logger');
1
+ 'use strict';
2
+
3
+ const log = require('../logger');
4
4
  const { all, resolveTargets } = require('../targets');
5
- const { removePath } = require('../fsx');
6
- const { dgDir } = require('../paths');
7
-
8
- async function uninstall(projectDir, positional, flags) {
9
- log.title('draftgo uninstall');
10
-
11
- let resolved = [];
12
- let unknown = [];
13
- if (positional.length === 0) {
14
- log.err('Please specify a target, or use `draftgo uninstall all` to remove every target.');
15
- log.dim(` Supported targets: ${all.map((i) => i.name).join(', ')}`);
16
- return 1;
17
- }
18
- if (positional.includes('all')) {
19
- resolved = all.slice();
20
- } else {
21
- ({ resolved, unknown } = resolveTargets(positional));
22
- if (unknown.length) {
23
- log.err(`未知 target:${unknown.join(', ')}`);
24
- return 1;
25
- }
26
- }
27
-
28
- log.step('移除入口文件');
29
- for (const t of resolved) {
30
- try {
31
- const removed = t.uninstall(projectDir);
32
- if (removed) log.ok(`${t.displayName} 入口已移除`);
33
- else log.dim(`${t.displayName} 入口不存在,跳过`);
34
- } catch (e) {
35
- log.err(`${t.displayName} 卸载失败:${e.message}`);
36
- }
37
- }
38
-
39
- // Each AI-tool skill directory is self-contained, so per-target uninstall
40
- // already wiped its content above. Only --purge nukes runtime data under
5
+ const { removePath } = require('../fsx');
6
+ const { dgDir } = require('../paths');
7
+
8
+ async function uninstall(projectDir, positional, flags) {
9
+ log.title('draftgo uninstall');
10
+
11
+ let resolved = [];
12
+ let unknown = [];
13
+ if (positional.length === 0) {
14
+ log.err('Please specify a target, or use `draftgo uninstall all` to remove every target.');
15
+ log.dim(` Supported targets: ${all.map((i) => i.name).join(', ')}`);
16
+ return 1;
17
+ }
18
+ if (positional.includes('all')) {
19
+ resolved = all.slice();
20
+ } else {
21
+ ({ resolved, unknown } = resolveTargets(positional));
22
+ if (unknown.length) {
23
+ log.err(`未知 target:${unknown.join(', ')}`);
24
+ return 1;
25
+ }
26
+ }
27
+
28
+ log.step('移除入口文件');
29
+ for (const t of resolved) {
30
+ try {
31
+ const removed = t.uninstall(projectDir);
32
+ if (removed) log.ok(`${t.displayName} 入口已移除`);
33
+ else log.dim(`${t.displayName} 入口不存在,跳过`);
34
+ } catch (e) {
35
+ log.err(`${t.displayName} 卸载失败:${e.message}`);
36
+ }
37
+ }
38
+
39
+ // Each AI-tool skill directory is self-contained, so per-target uninstall
40
+ // already wiped its content above. Only --purge nukes runtime data under
41
41
  // .draftgo/ (config/worklog/lessons/cache etc.).
42
42
  if (flags.purge) {
43
43
  if (removePath(dgDir(projectDir))) {
44
44
  log.warn('已使用 --purge:.draftgo/(含 config/worklog/lessons 等运行时数据)已删除。');
45
45
  }
46
46
  }
47
-
48
- log.title('完成');
49
- return 0;
50
- }
51
-
52
- module.exports = uninstall;
47
+
48
+ log.title('完成');
49
+ return 0;
50
+ }
51
+
52
+ module.exports = uninstall;
@@ -66,7 +66,7 @@ async function updateCliIfNeeded(projectDir, positional, flags) {
66
66
  }
67
67
  return Number.isInteger(rerun.status) ? rerun.status : 1;
68
68
  }
69
-
69
+
70
70
  async function update(projectDir, positional, flags) {
71
71
  log.title('draftgo update');
72
72
 
@@ -75,14 +75,14 @@ async function update(projectDir, positional, flags) {
75
75
 
76
76
  const prev = readInstalledVersion(projectDir);
77
77
  log.info(`当前已装 skill:${prev || '未安装'},CLI 版本:${getPackageVersion()}`);
78
-
78
+
79
79
  // Determine which entry files to refresh:
80
80
  // - no positional → refresh installed targets plus project-level tool signals
81
81
  // - "all" → refresh every known target
82
82
  // - names → refresh the specified targets
83
- let resolved = [];
84
- let unknown = [];
85
-
83
+ let resolved = [];
84
+ let unknown = [];
85
+
86
86
  if (positional.length === 0) {
87
87
  const byTarget = new Map();
88
88
  for (const t of all) {
@@ -100,20 +100,20 @@ async function update(projectDir, positional, flags) {
100
100
  log.info(`刷新目标:${resolved.map((r) => r.displayName).join(', ')}`);
101
101
  }
102
102
  } else {
103
- ({ resolved, unknown } = resolveTargets(positional));
104
- if (unknown.length) {
105
- log.err(`未知 target:${unknown.join(', ')}`);
106
- return 1;
107
- }
108
- }
109
-
103
+ ({ resolved, unknown } = resolveTargets(positional));
104
+ if (unknown.length) {
105
+ log.err(`未知 target:${unknown.join(', ')}`);
106
+ return 1;
107
+ }
108
+ }
109
+
110
110
  ensureRuntime(projectDir);
111
111
  log.step('用 CLI 内置版本覆盖各 AI 工具 skill 目录');
112
112
  const failures = [];
113
113
  for (const t of resolved) {
114
- try {
115
- const r = t.install(projectDir, { force: true });
116
- log.ok(`${t.displayName.padEnd(16)} → ${r.path}`);
114
+ try {
115
+ const r = t.install(projectDir, { force: true });
116
+ log.ok(`${t.displayName.padEnd(16)} → ${r.path}`);
117
117
  } catch (e) {
118
118
  log.err(`${t.displayName} 更新失败:${e.message}`);
119
119
  failures.push(t.displayName);
@@ -132,11 +132,11 @@ async function update(projectDir, positional, flags) {
132
132
  } else {
133
133
  writeInstalledVersion(projectDir);
134
134
  }
135
-
136
- log.title('完成');
137
- return 0;
138
- }
139
-
135
+
136
+ log.title('完成');
137
+ return 0;
138
+ }
139
+
140
140
  module.exports = update;
141
141
  module.exports.updateCliIfNeeded = updateCliIfNeeded;
142
142
  module.exports.installedTargetsNotRefreshed = installedTargetsNotRefreshed;
@@ -316,7 +316,8 @@ async function commit(projectDir, ids, options = {}) {
316
316
  if (entry.server !== config.server) throw new WorktreeError('CHECKOUT_SERVER_MISMATCH', 'Checkout belongs to a different DraftGo server.');
317
317
  syncManagedDevelopmentFiles(entry.local_dir);
318
318
  const state = localState(entry); if (!state.base_valid) throw new WorktreeError('BASE_HASH_MISMATCH', `Custom service ${id} base does not match its manifest.`);
319
- if (!state.modified) return { id, entry, state, unchanged: true };
319
+ const createDraft = Boolean(options.ensureDraft) && entry.checkout_source === 'live';
320
+ if (!state.modified && !createDraft) return { id, entry, state, unchanged: true };
320
321
  const current = await metadata(config, id, options);
321
322
  if (current.content_hash !== entry.base_hash || String(current.base_revision) !== String(entry.base_revision)) {
322
323
  const remoteArchive = await download(config, current, options); const conflict = await writeConflict(projectDir, entry, current, remoteArchive);
@@ -406,7 +407,7 @@ function validationStale(error) {
406
407
  && error.details && error.details.reason === 'validation_stale';
407
408
  }
408
409
  async function validate(projectDir, id, options = {}) {
409
- const config = options.config || loadProjectConfig(projectDir); await commit(projectDir, [id], { ...options, config });
410
+ const config = options.config || loadProjectConfig(projectDir); await commit(projectDir, [id], { ...options, config, ensureDraft: true });
410
411
  const manifest = loadManifest(projectDir); const result = await validateCommitted(projectDir, id, config, manifest, options);
411
412
  if (!result.cached) saveManifest(projectDir, manifest);
412
413
  return result;
@@ -427,7 +428,7 @@ async function test(projectDir, id, input = {}, options = {}) {
427
428
  let entry;
428
429
  let revision;
429
430
  if (source === 'draft') {
430
- await commit(projectDir, [id], { ...options, config });
431
+ await commit(projectDir, [id], { ...options, config, ensureDraft: true });
431
432
  manifest = loadManifest(projectDir);
432
433
  const validation = await validateCommitted(projectDir, id, config, manifest, options);
433
434
  entry = manifest.entries[entryKey(id)];
@@ -447,7 +448,7 @@ async function test(projectDir, id, input = {}, options = {}) {
447
448
  }
448
449
  async function publish(projectDir, ids, options = {}) {
449
450
  const config = options.config || loadProjectConfig(projectDir); const targets = uniqueIDs(ids);
450
- await commit(projectDir, targets, { ...options, config });
451
+ await commit(projectDir, targets, { ...options, config, ensureDraft: true });
451
452
  const manifest = loadManifest(projectDir);
452
453
  const validations = await Promise.all(targets.map((id) => validateCommitted(projectDir, id, config, manifest, options)));
453
454
  if (validations.some((result) => !result.cached)) saveManifest(projectDir, manifest);
package/src/detect.js CHANGED
@@ -1,5 +1,5 @@
1
- 'use strict';
2
-
1
+ 'use strict';
2
+
3
3
  const path = require('path');
4
4
  const { exists } = require('./fsx');
5
5
  const { platforms } = require('./platforms');
@@ -9,15 +9,15 @@ const { platforms } = require('./platforms');
9
9
  const SIGNALS = Object.fromEntries(
10
10
  platforms.map((p) => [p.name, p.signals || []])
11
11
  );
12
-
13
- function detectTargets(projectDir) {
14
- const hits = [];
15
- for (const [name, signals] of Object.entries(SIGNALS)) {
16
- if (signals.some((s) => exists(path.join(projectDir, s)))) {
17
- hits.push(name);
18
- }
19
- }
20
- return hits;
21
- }
22
-
23
- module.exports = { SIGNALS, detectTargets };
12
+
13
+ function detectTargets(projectDir) {
14
+ const hits = [];
15
+ for (const [name, signals] of Object.entries(SIGNALS)) {
16
+ if (signals.some((s) => exists(path.join(projectDir, s)))) {
17
+ hits.push(name);
18
+ }
19
+ }
20
+ return hits;
21
+ }
22
+
23
+ module.exports = { SIGNALS, detectTargets };
package/src/fsx.js CHANGED
@@ -1,67 +1,67 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
-
6
- function exists(p) {
7
- try { fs.accessSync(p); return true; } catch { return false; }
8
- }
9
-
10
- function ensureDir(dir) {
11
- fs.mkdirSync(dir, { recursive: true });
12
- }
13
-
14
- function readText(p) {
15
- return fs.readFileSync(p, 'utf8');
16
- }
17
-
18
- function writeText(p, content) {
19
- ensureDir(path.dirname(p));
20
- fs.writeFileSync(p, content, 'utf8');
21
- }
22
-
23
- function copyFile(src, dest) {
24
- ensureDir(path.dirname(dest));
25
- fs.copyFileSync(src, dest);
26
- }
27
-
28
- function copyDir(src, dest) {
29
- if (!exists(src)) return;
30
- ensureDir(dest);
31
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
32
- const s = path.join(src, entry.name);
33
- const d = path.join(dest, entry.name);
34
- if (entry.isSymbolicLink()) {
35
- ensureDir(path.dirname(d));
36
- const target = fs.readlinkSync(s);
37
- try { fs.symlinkSync(target, d); }
38
- catch (e) { if (e.code !== 'EEXIST') throw e; }
39
- } else if (entry.isDirectory()) copyDir(s, d);
40
- else if (entry.isFile()) copyFile(s, d);
41
- }
42
- }
43
-
44
- function removePath(p) {
45
- if (!exists(p)) return false;
46
- fs.rmSync(p, { recursive: true, force: true });
47
- return true;
48
- }
49
-
50
- function appendGitignoreLine(projectDir, line) {
51
- const gi = path.join(projectDir, '.gitignore');
52
- const content = exists(gi) ? readText(gi) : '';
53
- if (content.split(/\r?\n/).some((l) => l.trim() === line)) return;
54
- const sep = content && !content.endsWith('\n') ? '\n' : '';
55
- fs.appendFileSync(gi, `${sep}${line}\n`);
56
- }
57
-
58
- module.exports = {
59
- exists,
60
- ensureDir,
61
- readText,
62
- writeText,
63
- copyFile,
64
- copyDir,
65
- removePath,
66
- appendGitignoreLine,
67
- };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ function exists(p) {
7
+ try { fs.accessSync(p); return true; } catch { return false; }
8
+ }
9
+
10
+ function ensureDir(dir) {
11
+ fs.mkdirSync(dir, { recursive: true });
12
+ }
13
+
14
+ function readText(p) {
15
+ return fs.readFileSync(p, 'utf8');
16
+ }
17
+
18
+ function writeText(p, content) {
19
+ ensureDir(path.dirname(p));
20
+ fs.writeFileSync(p, content, 'utf8');
21
+ }
22
+
23
+ function copyFile(src, dest) {
24
+ ensureDir(path.dirname(dest));
25
+ fs.copyFileSync(src, dest);
26
+ }
27
+
28
+ function copyDir(src, dest) {
29
+ if (!exists(src)) return;
30
+ ensureDir(dest);
31
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
32
+ const s = path.join(src, entry.name);
33
+ const d = path.join(dest, entry.name);
34
+ if (entry.isSymbolicLink()) {
35
+ ensureDir(path.dirname(d));
36
+ const target = fs.readlinkSync(s);
37
+ try { fs.symlinkSync(target, d); }
38
+ catch (e) { if (e.code !== 'EEXIST') throw e; }
39
+ } else if (entry.isDirectory()) copyDir(s, d);
40
+ else if (entry.isFile()) copyFile(s, d);
41
+ }
42
+ }
43
+
44
+ function removePath(p) {
45
+ if (!exists(p)) return false;
46
+ fs.rmSync(p, { recursive: true, force: true });
47
+ return true;
48
+ }
49
+
50
+ function appendGitignoreLine(projectDir, line) {
51
+ const gi = path.join(projectDir, '.gitignore');
52
+ const content = exists(gi) ? readText(gi) : '';
53
+ if (content.split(/\r?\n/).some((l) => l.trim() === line)) return;
54
+ const sep = content && !content.endsWith('\n') ? '\n' : '';
55
+ fs.appendFileSync(gi, `${sep}${line}\n`);
56
+ }
57
+
58
+ module.exports = {
59
+ exists,
60
+ ensureDir,
61
+ readText,
62
+ writeText,
63
+ copyFile,
64
+ copyDir,
65
+ removePath,
66
+ appendGitignoreLine,
67
+ };