draftgo-cli 3.0.55 → 4.0.1

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 (50) hide show
  1. package/README.md +112 -316
  2. package/package.json +5 -5
  3. package/resources/skill/SKILL.md +25 -24
  4. package/resources/skill/init/SKILL.md +5 -10
  5. package/resources/skill/manifest.json +2 -2
  6. package/resources/skill/references/aihub.md +10 -5
  7. package/resources/skill/references/chat-sdk.md +10 -0
  8. package/resources/skill/references/checkout.md +4 -4
  9. package/resources/skill/references/custom-services.md +65 -226
  10. package/resources/skill/references/data.md +3 -2
  11. package/resources/skill/references/frontend.md +96 -490
  12. package/resources/skill/references/mcp.md +39 -103
  13. package/resources/skill/references/runtime.md +3 -2
  14. package/resources/skill/story/SKILL.md +1 -2
  15. package/src/apiContractCache.js +112 -0
  16. package/src/cli.js +1 -21
  17. package/src/commandRegistry.js +6 -11
  18. package/src/commands/api.js +28 -8
  19. package/src/commands/check.js +1 -10
  20. package/src/commands/customService.js +2 -4
  21. package/src/commands/delete.js +23 -46
  22. package/src/commands/deploy.js +1 -1
  23. package/src/commands/help.js +16 -31
  24. package/src/commands/init.js +4 -10
  25. package/src/commands/listTargets.js +1 -1
  26. package/src/commands/local.js +2 -6
  27. package/src/commands/map.js +0 -11
  28. package/src/commands/status.js +1 -1
  29. package/src/commands/uninstall.js +3 -3
  30. package/src/commands/update.js +1 -1
  31. package/src/commands/verify.js +43 -21
  32. package/src/commands/{verifyUi.js → visualVerify.js} +28 -116
  33. package/src/commands/worklog.js +86 -0
  34. package/src/customServices.js +150 -33
  35. package/src/{localdev → localRuntime}/detect.js +1 -1
  36. package/src/{localdev → localRuntime}/mysqlClient.js +1 -1
  37. package/src/{localdev → localRuntime}/services.js +1 -1
  38. package/src/projectConfig.js +2 -0
  39. package/src/{installers/index.js → targets.js} +3 -5
  40. package/src/worklog.js +274 -0
  41. package/src/workspaceHealth.js +1 -1
  42. package/src/worktree/index.js +81 -51
  43. package/src/changelog.js +0 -276
  44. package/src/commands/changelog.js +0 -24
  45. package/src/commands/localDev.js +0 -9
  46. package/src/commands/sync.js +0 -46
  47. package/src/commands/task.js +0 -408
  48. package/src/commands/verifyUiCompat.js +0 -16
  49. /package/src/{localdev → localRuntime}/compose.js +0 -0
  50. /package/src/{localdev → localRuntime}/index.js +0 -0
package/src/changelog.js DELETED
@@ -1,276 +0,0 @@
1
- 'use strict';
2
-
3
- const crypto = require('crypto');
4
- const fs = require('fs');
5
- const path = require('path');
6
- const { dgDir } = require('./paths');
7
-
8
- const DEFAULT_LOCK_TIMEOUT_MS = 10_000;
9
- const DEFAULT_LOCK_STALE_MS = 60_000;
10
- const DEFAULT_LOCK_RETRY_MS = 10;
11
- const LOCK_WAIT_ARRAY = new Int32Array(new SharedArrayBuffer(4));
12
-
13
- class ChangelogFormatError extends Error {
14
- constructor(message) {
15
- super(message);
16
- this.name = 'ChangelogFormatError';
17
- this.code = 'INVALID_CHANGELOG';
18
- }
19
- }
20
-
21
- function changelogPath(projectDir) {
22
- return path.join(dgDir(projectDir), 'changelog.md');
23
- }
24
-
25
- function formatLocalDate(value = new Date()) {
26
- if (!(value instanceof Date) || Number.isNaN(value.getTime())) {
27
- throw new TypeError('A valid Date is required.');
28
- }
29
- const year = String(value.getFullYear()).padStart(4, '0');
30
- const month = String(value.getMonth() + 1).padStart(2, '0');
31
- const day = String(value.getDate()).padStart(2, '0');
32
- return `${year}-${month}-${day}`;
33
- }
34
-
35
- function isLeapYear(year) {
36
- return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
37
- }
38
-
39
- function isCalendarDate(value) {
40
- const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
41
- if (!match) return false;
42
- const year = Number(match[1]);
43
- const month = Number(match[2]);
44
- const day = Number(match[3]);
45
- if (year < 1 || month < 1 || month > 12 || day < 1) return false;
46
- const days = [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
47
- return day <= days[month - 1];
48
- }
49
-
50
- function normalizeMessage(value) {
51
- const message = value == null ? '' : String(value).trim();
52
- if (!message) throw new TypeError('Changelog result must not be empty.');
53
- if (/[\r\n\u2028\u2029]/.test(message)) {
54
- throw new TypeError('Changelog result must be a single line.');
55
- }
56
- return message;
57
- }
58
-
59
- function invalidFormat(detail) {
60
- return new ChangelogFormatError(`Invalid .draftgo/changelog.md: ${detail}`);
61
- }
62
-
63
- function parseChangelog(source) {
64
- if (typeof source !== 'string') throw new TypeError('Changelog source must be a string.');
65
- if (source === '') return [];
66
- if (/\r(?!\n)/.test(source)) throw invalidFormat('unsupported line ending.');
67
-
68
- const normalized = source.replace(/\r\n/g, '\n');
69
- const body = normalized.endsWith('\n') ? normalized.slice(0, -1) : normalized;
70
- const lines = body.split('\n');
71
- const blocks = [];
72
- let cursor = 0;
73
-
74
- while (cursor < lines.length) {
75
- const date = lines[cursor];
76
- if (!isCalendarDate(date)) throw invalidFormat(`expected a YYYY-MM-DD header at line ${cursor + 1}.`);
77
- if (blocks.length && date <= blocks[blocks.length - 1].date) {
78
- throw invalidFormat(`date blocks must be strictly increasing (line ${cursor + 1}).`);
79
- }
80
- cursor += 1;
81
- if (lines[cursor] !== '===') throw invalidFormat(`expected === at line ${cursor + 1}.`);
82
- cursor += 1;
83
-
84
- const entries = [];
85
- while (cursor < lines.length && lines[cursor] !== '') {
86
- const match = /^([1-9]\d*)\. (.+)$/.exec(lines[cursor]);
87
- if (!match) throw invalidFormat(`invalid entry at line ${cursor + 1}.`);
88
- const number = Number(match[1]);
89
- if (!Number.isSafeInteger(number) || number !== entries.length + 1) {
90
- throw invalidFormat(`entries must be consecutively numbered from 1 (line ${cursor + 1}).`);
91
- }
92
- let message;
93
- try {
94
- message = normalizeMessage(match[2]);
95
- } catch (error) {
96
- throw invalidFormat(`${error.message} (line ${cursor + 1}).`);
97
- }
98
- if (message !== match[2]) throw invalidFormat(`entry has surrounding whitespace at line ${cursor + 1}.`);
99
- entries.push({ number, message });
100
- cursor += 1;
101
- }
102
- if (!entries.length) throw invalidFormat(`date block ${date} has no entries.`);
103
- blocks.push({ date, entries });
104
-
105
- if (cursor >= lines.length) break;
106
- cursor += 1;
107
- if (cursor >= lines.length || lines[cursor] === '') {
108
- throw invalidFormat(`date blocks must be separated by exactly one blank line (line ${cursor + 1}).`);
109
- }
110
- }
111
-
112
- return blocks;
113
- }
114
-
115
- function renderChangelog(blocks) {
116
- if (!Array.isArray(blocks)) throw new TypeError('Changelog blocks must be an array.');
117
- if (!blocks.length) return '';
118
- return `${blocks.map((block) => [
119
- block.date,
120
- '===',
121
- ...block.entries.map((entry) => `${entry.number}. ${entry.message}`),
122
- ].join('\n')).join('\n\n')}\n`;
123
- }
124
-
125
- function appendChangelogEntry(source, value, now = new Date()) {
126
- const message = normalizeMessage(value);
127
- const date = formatLocalDate(now);
128
- const blocks = parseChangelog(source);
129
- const last = blocks[blocks.length - 1];
130
-
131
- if (last && date < last.date) {
132
- throw new ChangelogFormatError(
133
- `Cannot append ${date}: the latest changelog block is ${last.date}. Check the local clock.`
134
- );
135
- }
136
-
137
- let number;
138
- if (last && last.date === date) {
139
- number = last.entries.length + 1;
140
- last.entries.push({ number, message });
141
- } else {
142
- number = 1;
143
- blocks.push({ date, entries: [{ number, message }] });
144
- }
145
-
146
- return { content: renderChangelog(blocks), date, number, message };
147
- }
148
-
149
- function temporaryPath(destination) {
150
- const suffix = crypto.randomBytes(8).toString('hex');
151
- return path.join(path.dirname(destination), `.${path.basename(destination)}.${process.pid}.${suffix}.tmp`);
152
- }
153
-
154
- function lockOption(value, fallback, name, allowZero = false) {
155
- const number = value == null ? fallback : Number(value);
156
- const minimum = allowZero ? 0 : 1;
157
- if (!Number.isSafeInteger(number) || number < minimum) {
158
- throw new TypeError(`${name} must be an integer greater than or equal to ${minimum}.`);
159
- }
160
- return number;
161
- }
162
-
163
- function removeStaleLock(lockPath, staleMs) {
164
- try {
165
- const stat = fs.lstatSync(lockPath);
166
- if (Date.now() - stat.mtimeMs < staleMs) return false;
167
- fs.rmSync(lockPath, { force: true });
168
- return true;
169
- } catch (error) {
170
- if (error && error.code === 'ENOENT') return true;
171
- throw error;
172
- }
173
- }
174
-
175
- function acquireChangelogLock(destination, options = {}) {
176
- fs.mkdirSync(path.dirname(destination), { recursive: true });
177
- const lockPath = `${destination}.lock`;
178
- const timeoutMs = lockOption(
179
- options.lockTimeoutMs,
180
- DEFAULT_LOCK_TIMEOUT_MS,
181
- 'lockTimeoutMs',
182
- true,
183
- );
184
- const staleMs = lockOption(options.lockStaleMs, DEFAULT_LOCK_STALE_MS, 'lockStaleMs');
185
- const retryMs = lockOption(options.lockRetryMs, DEFAULT_LOCK_RETRY_MS, 'lockRetryMs');
186
- const token = `${process.pid}:${crypto.randomBytes(16).toString('hex')}`;
187
- const started = Date.now();
188
-
189
- while (true) {
190
- let descriptor;
191
- let created = false;
192
- try {
193
- descriptor = fs.openSync(lockPath, 'wx', 0o600);
194
- created = true;
195
- fs.writeFileSync(descriptor, `${token}\n`, 'utf8');
196
- fs.fsyncSync(descriptor);
197
- fs.closeSync(descriptor);
198
- descriptor = undefined;
199
- return { path: lockPath, token };
200
- } catch (error) {
201
- if (descriptor !== undefined) {
202
- try { fs.closeSync(descriptor); } catch { /* Preserve the original failure. */ }
203
- }
204
- if (created) {
205
- try { fs.rmSync(lockPath, { force: true }); } catch { /* Best-effort cleanup. */ }
206
- }
207
- if (!error || error.code !== 'EEXIST') throw error;
208
- }
209
-
210
- if (removeStaleLock(lockPath, staleMs)) continue;
211
- const elapsed = Date.now() - started;
212
- if (elapsed >= timeoutMs) {
213
- const error = new Error('Timed out waiting for .draftgo/changelog.md.lock.');
214
- error.code = 'CHANGELOG_LOCK_TIMEOUT';
215
- throw error;
216
- }
217
- Atomics.wait(LOCK_WAIT_ARRAY, 0, 0, Math.min(retryMs, timeoutMs - elapsed));
218
- }
219
- }
220
-
221
- function releaseChangelogLock(lock) {
222
- try {
223
- const token = fs.readFileSync(lock.path, 'utf8').trim();
224
- if (token === lock.token) fs.rmSync(lock.path, { force: true });
225
- } catch (error) {
226
- if (!error || error.code !== 'ENOENT') throw error;
227
- }
228
- }
229
-
230
- function writeTextAtomic(destination, content) {
231
- fs.mkdirSync(path.dirname(destination), { recursive: true });
232
- const temporary = temporaryPath(destination);
233
- let descriptor;
234
- try {
235
- descriptor = fs.openSync(temporary, 'wx', 0o666);
236
- fs.writeFileSync(descriptor, content, 'utf8');
237
- fs.fsyncSync(descriptor);
238
- fs.closeSync(descriptor);
239
- descriptor = undefined;
240
- fs.renameSync(temporary, destination);
241
- } finally {
242
- if (descriptor !== undefined) {
243
- try { fs.closeSync(descriptor); } catch { /* The original error is more useful. */ }
244
- }
245
- try { fs.rmSync(temporary, { force: true }); } catch { /* Best-effort temporary cleanup. */ }
246
- }
247
- }
248
-
249
- function addChangelogEntry(projectDir, value, options = {}) {
250
- const file = changelogPath(projectDir);
251
- const message = normalizeMessage(value);
252
- const lock = acquireChangelogLock(file, options);
253
- try {
254
- const source = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
255
- const result = appendChangelogEntry(source, message, options.now || new Date());
256
- writeTextAtomic(file, result.content);
257
- return { ...result, path: file };
258
- } finally {
259
- releaseChangelogLock(lock);
260
- }
261
- }
262
-
263
- module.exports = {
264
- ChangelogFormatError,
265
- changelogPath,
266
- formatLocalDate,
267
- isCalendarDate,
268
- normalizeMessage,
269
- parseChangelog,
270
- renderChangelog,
271
- appendChangelogEntry,
272
- acquireChangelogLock,
273
- releaseChangelogLock,
274
- writeTextAtomic,
275
- addChangelogEntry,
276
- };
@@ -1,24 +0,0 @@
1
- 'use strict';
2
-
3
- const log = require('../logger');
4
- const { addChangelogEntry } = require('../changelog');
5
-
6
- function printUsage() {
7
- log.err('Usage: draftgo changelog add <completed-result>');
8
- }
9
-
10
- function changelog(projectDir, positional = []) {
11
- const [action, ...messageParts] = positional;
12
- if (action !== 'add' || !messageParts.length) {
13
- printUsage();
14
- return 1;
15
- }
16
-
17
- const result = addChangelogEntry(projectDir, messageParts.join(' '));
18
- log.ok(`Changelog updated: .draftgo/changelog.md (${result.date} #${result.number})`);
19
- return 0;
20
- }
21
-
22
- changelog.printUsage = printUsage;
23
-
24
- module.exports = changelog;
@@ -1,9 +0,0 @@
1
- 'use strict';
2
-
3
- const { runWizard } = require('../localdev');
4
-
5
- async function localDev(projectDir, positional, flags) {
6
- return runWizard(projectDir, { yes: !!(flags.yes || flags.y) });
7
- }
8
-
9
- module.exports = localDev;
@@ -1,46 +0,0 @@
1
- 'use strict';
2
-
3
- const log = require('../logger');
4
- const { canonicalResourceType } = require('../worktree/types');
5
-
6
- async function sync(projectDir, command, positional, flags = {}) {
7
- if (command === 'pull') {
8
- log.err('`draftgo pull` no longer downloads DraftGo resources.');
9
- log.dim(' Use MCP project/resource discovery. For complete pages, navigation, or docs, use `draftgo checkout <type> <id...>`.');
10
- log.dim(' Existing legacy .draftgo indexes are left untouched and ignored.');
11
- return 1;
12
- }
13
-
14
- const [rawType, ...ids] = positional;
15
- if (!rawType) {
16
- log.err('The legacy push-all workflow has been removed.');
17
- log.dim(' Use `draftgo commit <pages|nav|docs> <id...>` for checked-out long content.');
18
- log.dim(' Use DraftGo MCP for structured resources.');
19
- return 1;
20
- }
21
-
22
- let resourceType;
23
- try {
24
- resourceType = canonicalResourceType(rawType);
25
- } catch {
26
- log.err(`Legacy push for ${rawType} is no longer supported.`);
27
- log.dim(' Discover the live API with `draftgo api <query>` and use MCP api_call for structured resources.');
28
- return 1;
29
- }
30
- if (!ids.length) {
31
- log.err('Refusing legacy push-all behavior; specify checked-out resource ids explicitly.');
32
- return 1;
33
- }
34
-
35
- log.warn('`draftgo push` is deprecated; forwarding checked-out long content to `draftgo commit`.');
36
- if (flags['dry-run']) {
37
- for (const id of ids) {
38
- const code = require('./diff')(projectDir, [resourceType, id], flags);
39
- if (code !== 0) return code;
40
- }
41
- return 0;
42
- }
43
- return require('./commit')(projectDir, [resourceType, ...ids], flags);
44
- }
45
-
46
- module.exports = sync;