draftgo-cli 1.0.4
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/LICENSE +21 -0
- package/README.md +249 -0
- package/bin/draftgo.js +9 -0
- package/package.json +70 -0
- package/resources/project-design/README.md +42 -0
- package/resources/skill/SKILL.md +62 -0
- package/resources/skill/init/SKILL.md +41 -0
- package/resources/skill/manifest.json +35 -0
- package/resources/skill/references/ai.md +41 -0
- package/resources/skill/references/app-api.md +97 -0
- package/resources/skill/references/architecture.md +13 -0
- package/resources/skill/references/chat-sdk.md +205 -0
- package/resources/skill/references/checkout.md +140 -0
- package/resources/skill/references/data.md +49 -0
- package/resources/skill/references/db-relations.md +29 -0
- package/resources/skill/references/delivery.md +33 -0
- package/resources/skill/references/development.md +41 -0
- package/resources/skill/references/diagnostics.md +50 -0
- package/resources/skill/references/frontend.md +158 -0
- package/resources/skill/references/mcp.md +110 -0
- package/resources/skill/references/methods.md +143 -0
- package/resources/skill/references/modules.md +75 -0
- package/resources/skill/references/runtime.md +109 -0
- package/resources/skill/references/services.md +32 -0
- package/src/apiContractCache.js +120 -0
- package/src/cli.js +100 -0
- package/src/commandRegistry.js +46 -0
- package/src/commands/api.js +244 -0
- package/src/commands/apiKey.js +30 -0
- package/src/commands/autoPush.js +36 -0
- package/src/commands/capabilities.js +100 -0
- package/src/commands/check.js +82 -0
- package/src/commands/checkout.js +18 -0
- package/src/commands/clean.js +72 -0
- package/src/commands/commit.js +47 -0
- package/src/commands/components.js +554 -0
- package/src/commands/conflict.js +30 -0
- package/src/commands/conflicts.js +16 -0
- package/src/commands/connect.js +91 -0
- package/src/commands/delete.js +95 -0
- package/src/commands/deploy.js +77 -0
- package/src/commands/diff.js +39 -0
- package/src/commands/group.js +37 -0
- package/src/commands/help.js +190 -0
- package/src/commands/init.js +126 -0
- package/src/commands/listTargets.js +13 -0
- package/src/commands/local.js +79 -0
- package/src/commands/map.js +395 -0
- package/src/commands/mcp.js +150 -0
- package/src/commands/reconcile.js +20 -0
- package/src/commands/role.js +31 -0
- package/src/commands/status.js +98 -0
- package/src/commands/uninstall.js +52 -0
- package/src/commands/update.js +79 -0
- package/src/commands/verify.js +188 -0
- package/src/commands/visualVerify.js +281 -0
- package/src/commands/worklog.js +117 -0
- package/src/consoleEncoding.js +34 -0
- package/src/contractCompatibility.js +65 -0
- package/src/detect.js +25 -0
- package/src/diffReport.js +106 -0
- package/src/fsx.js +67 -0
- package/src/index.js +46 -0
- package/src/localRuntime/compose.js +119 -0
- package/src/localRuntime/detect.js +77 -0
- package/src/localRuntime/index.js +211 -0
- package/src/localRuntime/mysqlClient.js +155 -0
- package/src/localRuntime/services.js +117 -0
- package/src/logger.js +37 -0
- package/src/mcp/client.js +558 -0
- package/src/mcp/hosts.js +520 -0
- package/src/mcp/parallel.js +54 -0
- package/src/mcp/protocol.js +223 -0
- package/src/mcp/stdio.js +300 -0
- package/src/mcp/tools.js +51 -0
- package/src/paths.js +32 -0
- package/src/platforms.js +110 -0
- package/src/projectConfig.js +139 -0
- package/src/projectDesign.js +19 -0
- package/src/projectHealth.js +33 -0
- package/src/projectMap.js +220 -0
- package/src/prompt.js +94 -0
- package/src/releaseInstall.js +105 -0
- package/src/runtimeFiles.js +45 -0
- package/src/skill.js +295 -0
- package/src/targets.js +43 -0
- package/src/timeout.js +18 -0
- package/src/updateCheck.js +100 -0
- package/src/worklog.js +276 -0
- package/src/worktree/backend.js +438 -0
- package/src/worktree/errors.js +28 -0
- package/src/worktree/index.js +751 -0
- package/src/worktree/inlineScripts.js +99 -0
- package/src/worktree/locks.js +52 -0
- package/src/worktree/manifest.js +89 -0
- package/src/worktree/status.js +124 -0
- package/src/worktree/streams.js +200 -0
- package/src/worktree/types.js +103 -0
- package/src/worktree/validate.js +37 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const log = require('../logger');
|
|
4
|
+
const { loadProjectConfig } = require('../projectConfig');
|
|
5
|
+
const { TOOL_NAMES, openToolSession, callStructured } = require('../mcp/tools');
|
|
6
|
+
|
|
7
|
+
function itemsOf(value) {
|
|
8
|
+
if (Array.isArray(value)) return value;
|
|
9
|
+
return value && Array.isArray(value.items) ? value.items : [];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function nextCursorOf(value) {
|
|
13
|
+
return value && (value.next_cursor ?? value.nextCursor) || null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function print(value, flags, title) {
|
|
17
|
+
if (flags.output === 'json') console.log(JSON.stringify(value, null, 2));
|
|
18
|
+
else {
|
|
19
|
+
log.title(title);
|
|
20
|
+
if (Array.isArray(value)) value.forEach(item => log.plain(` ${item.operation_id || item.name || JSON.stringify(item)}`));
|
|
21
|
+
else console.log(JSON.stringify(value, null, 2));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function searchArguments(query, flags, cursor) {
|
|
26
|
+
const limit = flags.limit == null ? 20 : Number(flags.limit);
|
|
27
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error('--limit must be an integer between 1 and 100.');
|
|
28
|
+
const input = { limit };
|
|
29
|
+
if (query) input.query = query;
|
|
30
|
+
if (flags.module) input.module = String(flags.module);
|
|
31
|
+
if (flags.type) input.module = String(flags.type);
|
|
32
|
+
if (flags.method) input.method = String(flags.method).toUpperCase();
|
|
33
|
+
if (flags.risk) input.risk = String(flags.risk);
|
|
34
|
+
if (flags.permission) input.permission = String(flags.permission);
|
|
35
|
+
if (cursor) input.cursor = cursor;
|
|
36
|
+
return input;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function collect(session, query, flags, allPages) {
|
|
40
|
+
const result = [];
|
|
41
|
+
let registryRevision = null;
|
|
42
|
+
let total = null;
|
|
43
|
+
let cursor = flags.cursor || null;
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
do {
|
|
46
|
+
const page = await callStructured(session, TOOL_NAMES.apiSearch, searchArguments(query, flags, cursor));
|
|
47
|
+
result.push(...itemsOf(page));
|
|
48
|
+
registryRevision ||= page && page.registry_revision || null;
|
|
49
|
+
if (Number.isFinite(Number(page && page.total))) total = Number(page.total);
|
|
50
|
+
cursor = nextCursorOf(page);
|
|
51
|
+
if (!allPages || !cursor) break;
|
|
52
|
+
if (seen.has(cursor)) throw new Error('DraftGo capability search repeated a cursor.');
|
|
53
|
+
seen.add(cursor);
|
|
54
|
+
} while (true);
|
|
55
|
+
return { items: result, next_cursor: cursor, registry_revision: registryRevision, total };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function capabilities(projectDir, positional = [], flags = {}) {
|
|
59
|
+
if (flags['resource-type'] != null) {
|
|
60
|
+
throw new Error('Capabilities --resource-type is not supported; use --type or --module for a Registry module.');
|
|
61
|
+
}
|
|
62
|
+
const action = String(positional[0] || 'list').toLowerCase();
|
|
63
|
+
const config = loadProjectConfig(projectDir);
|
|
64
|
+
if (action === 'show') {
|
|
65
|
+
const operationID = String(positional[1] || '').trim();
|
|
66
|
+
if (!operationID) throw new Error('Usage: draftgo capabilities show <operation_id>');
|
|
67
|
+
const { registryRevision, descriptionForRevision } = require('./api');
|
|
68
|
+
const session = await openToolSession(config, [TOOL_NAMES.apiSearch, TOOL_NAMES.apiDescribe]);
|
|
69
|
+
const revision = await registryRevision(session, operationID);
|
|
70
|
+
const result = await descriptionForRevision(projectDir, config, session, operationID, revision);
|
|
71
|
+
print(result, flags, `DraftGo capability ${operationID}`);
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
if (action === 'audit') {
|
|
75
|
+
const session = await openToolSession(config, [TOOL_NAMES.apiSearch]);
|
|
76
|
+
const catalog = await collect(session, '', flags, true);
|
|
77
|
+
const result = {
|
|
78
|
+
server: config.server,
|
|
79
|
+
registry_revision: catalog.registry_revision,
|
|
80
|
+
total_operations: catalog.total ?? catalog.items.length,
|
|
81
|
+
discovered_operations: catalog.items.length,
|
|
82
|
+
complete: catalog.next_cursor == null && (catalog.total == null || catalog.total === catalog.items.length),
|
|
83
|
+
};
|
|
84
|
+
print(result, flags, 'DraftGo capability audit');
|
|
85
|
+
return result.complete ? 0 : 1;
|
|
86
|
+
}
|
|
87
|
+
if (action !== 'list' && action !== 'search') throw new Error('Usage: draftgo capabilities list|search|show|audit');
|
|
88
|
+
const query = action === 'search' ? positional.slice(1).join(' ').trim() : '';
|
|
89
|
+
if (action === 'search' && !query && !flags.module && !flags.type && !flags.method && !flags.risk && !flags.permission) {
|
|
90
|
+
throw new Error('Capability search needs a query or filter.');
|
|
91
|
+
}
|
|
92
|
+
const session = await openToolSession(config, [TOOL_NAMES.apiSearch]);
|
|
93
|
+
const result = await collect(session, query, flags, false);
|
|
94
|
+
print(result, flags, action === 'search' ? `DraftGo capabilities: ${query || 'filtered'}` : 'DraftGo capabilities');
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = capabilities;
|
|
99
|
+
module.exports.searchArguments = searchArguments;
|
|
100
|
+
module.exports.itemsOf = itemsOf;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const log = require('../logger');
|
|
4
|
+
const { analyzeProject } = require('../projectMap');
|
|
5
|
+
const { inspectRemoteCheckouts } = require('../worktree/status');
|
|
6
|
+
const { projectHealth } = require('../projectHealth');
|
|
7
|
+
|
|
8
|
+
async function check(projectDir, positional = [], flags = {}) {
|
|
9
|
+
if (!Array.isArray(positional)) {
|
|
10
|
+
flags = positional || {};
|
|
11
|
+
positional = [];
|
|
12
|
+
}
|
|
13
|
+
if (positional.length) {
|
|
14
|
+
if (!flags.quiet) log.err('Usage: draftgo check [--remote]');
|
|
15
|
+
return 1;
|
|
16
|
+
}
|
|
17
|
+
const resourceKeys = flags.resourceKeys || null;
|
|
18
|
+
const quiet = Boolean(flags.quiet);
|
|
19
|
+
const result = analyzeProject(projectDir, { resourceKeys });
|
|
20
|
+
if (resourceKeys) {
|
|
21
|
+
const manifestKeys = new Set(Object.keys(result.map.checkouts.reduce((entries, entry) => {
|
|
22
|
+
entries[`${entry.resource_type}:${entry.resource_id}`] = true;
|
|
23
|
+
return entries;
|
|
24
|
+
}, {})));
|
|
25
|
+
for (const key of resourceKeys) if (!manifestKeys.has(key)) result.errors.push(`${key}: resource is not checked out`);
|
|
26
|
+
}
|
|
27
|
+
const hygiene = projectHealth(projectDir);
|
|
28
|
+
const hygieneWarnings = resourceKeys ? [] : hygiene.warnings;
|
|
29
|
+
hygieneWarnings.forEach((warning) => result.warningDetails.push({ code: warning.code, confidence: 'high', message: `${warning.path}${warning.size ? ` (${warning.size} bytes)` : ''}` }));
|
|
30
|
+
hygieneWarnings.forEach((warning) => result.warnings.push(`${warning.code}: ${warning.path}`));
|
|
31
|
+
let remote = null;
|
|
32
|
+
let remoteError = null;
|
|
33
|
+
if (flags.remote) {
|
|
34
|
+
try {
|
|
35
|
+
remote = await inspectRemoteCheckouts(projectDir, resourceKeys ? {
|
|
36
|
+
entries: result.map.checkouts.filter((entry) => resourceKeys.includes(`${entry.resource_type}:${entry.resource_id}`)),
|
|
37
|
+
} : {});
|
|
38
|
+
for (const entry of remote) {
|
|
39
|
+
if (!['clean', 'clean_local', 'local_modified'].includes(entry.state)) {
|
|
40
|
+
const detail = `${entry.resource_type} ${entry.resource_id}: ${entry.state}`
|
|
41
|
+
+ ` (local=${entry.local_hash || '-'}, base=${entry.manifest_hash || '-'}, `
|
|
42
|
+
+ `remote=${entry.remote_hash || '-'}, remote_version=${entry.remote_version || '-'})`
|
|
43
|
+
+ (entry.recommendation ? `; run ${entry.recommendation}` : '');
|
|
44
|
+
result.errors.push(detail);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
remoteError = { code: error.code || 'REMOTE_CHECK_FAILED', message: error.message };
|
|
49
|
+
result.errors.push(`Remote checkout check failed: ${error.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const output = {
|
|
53
|
+
content_validation: { map: result.map, errors: result.errors, warnings: result.warnings, warning_details: result.warningDetails },
|
|
54
|
+
remote_validation: { resources: remote, error: remoteError },
|
|
55
|
+
project_hygiene: hygiene,
|
|
56
|
+
};
|
|
57
|
+
const code = result.errors.length || (flags.strict && result.warnings.length) ? 1 : 0;
|
|
58
|
+
if (flags.returnResult) return { code, ...output };
|
|
59
|
+
if (quiet) return code;
|
|
60
|
+
if (flags.output === 'json') {
|
|
61
|
+
if (!quiet) console.log(JSON.stringify(output, null, 2));
|
|
62
|
+
return result.errors.length || (flags.strict && result.warnings.length) ? 1 : 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!quiet) log.title('draftgo check');
|
|
66
|
+
const selectedCount = (type, entries) => resourceKeys
|
|
67
|
+
? entries.filter((entry) => resourceKeys.includes(`${type}:${entry.resource_id}`)).length
|
|
68
|
+
: entries.length;
|
|
69
|
+
log.info(`Checked-out pages: ${selectedCount('pages', result.map.pages)}`);
|
|
70
|
+
log.info(`Checked-out navigations: ${selectedCount('navigations', result.map.navigations)}`);
|
|
71
|
+
log.info(`Checked-out docs: ${selectedCount('docs', result.map.docs)}`);
|
|
72
|
+
log.info(`Project: ${hygiene.managed_files} managed, ${hygiene.unknown_files} unknown, ${hygiene.temporary_files} temporary, ${hygiene.artifact_files} artifact file(s)`);
|
|
73
|
+
log.info(`Project size: ${hygiene.total_bytes} bytes; reclaimable: ${hygiene.reclaimable_bytes} bytes`);
|
|
74
|
+
if (remote) log.info(`Remote checkout comparison: ${remote.length} resource(s)`);
|
|
75
|
+
for (const message of result.errors) log.err(message);
|
|
76
|
+
for (const detail of result.warningDetails) log.warn(`[${detail.code}/${detail.confidence}] ${detail.message}`);
|
|
77
|
+
if (result.errors.length || (flags.strict && result.warnings.length)) return 1;
|
|
78
|
+
log.ok(result.warnings.length ? 'Local checkout validation completed with warnings.' : 'Local checkout validation passed.');
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = check;
|
|
@@ -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,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const log = require('../logger');
|
|
6
|
+
const runtime = require('../runtimeFiles');
|
|
7
|
+
|
|
8
|
+
const MANAGED_PREFIXES = ['tmp/', 'artifacts/ui/', 'artifacts/api/'];
|
|
9
|
+
function fileSize(file) { try { const stat = fs.statSync(file); return stat.isFile() ? stat.size : 0; } catch { return 0; } }
|
|
10
|
+
function olderThan(value) {
|
|
11
|
+
if (value == null || value === '') return 0;
|
|
12
|
+
const match = String(value).match(/^(\d+)([dhm])$/i); if (!match) throw new Error('--older-than must use <number>d, <number>h, or <number>m.');
|
|
13
|
+
const unit = { d: 86400000, h: 3600000, m: 60000 }[match[2].toLowerCase()]; return Number(match[1]) * unit;
|
|
14
|
+
}
|
|
15
|
+
function plan(projectDir, flags = {}) {
|
|
16
|
+
const root = runtime.draftgoRoot(projectDir); const manifest = runtime.load(projectDir); const age = olderThan(flags['older-than']); const now = Date.now();
|
|
17
|
+
const types = new Set(String(flags.type || '').split(',').map((item) => item.trim()).filter(Boolean)); const candidates = [];
|
|
18
|
+
for (const entry of manifest.entries) {
|
|
19
|
+
if (!entry.cleanable || (types.size && !types.has(entry.type))) continue;
|
|
20
|
+
const relative = String(entry.path).replace(/\\/g, '/');
|
|
21
|
+
if (!MANAGED_PREFIXES.some((prefix) => relative.startsWith(prefix))) continue;
|
|
22
|
+
const absolute = path.resolve(root, relative); if (!runtime.inside(root, absolute) || !fs.existsSync(absolute)) continue;
|
|
23
|
+
const stat = fs.statSync(absolute); if (age && now - stat.mtimeMs < age) continue;
|
|
24
|
+
candidates.push({ ...entry, absolute, size: stat.isFile() ? stat.size : 0, reason: 'runtime_manifest' });
|
|
25
|
+
}
|
|
26
|
+
const tmp = path.resolve(root, 'tmp');
|
|
27
|
+
if (fs.existsSync(tmp) && runtime.inside(root, tmp) && (!types.size || types.has('tmp') || types.has('ai'))) {
|
|
28
|
+
const visitTmp = (current) => {
|
|
29
|
+
for (const child of fs.readdirSync(current, { withFileTypes: true })) {
|
|
30
|
+
const absolute = path.join(current, child.name);
|
|
31
|
+
if (child.isDirectory()) visitTmp(absolute);
|
|
32
|
+
else if (child.isFile() && !candidates.some((entry) => entry.absolute === absolute)) {
|
|
33
|
+
const stat = fs.statSync(absolute);
|
|
34
|
+
if (!age || now - stat.mtimeMs >= age) candidates.push({
|
|
35
|
+
path: path.relative(root, absolute).replace(/\\/g, '/'), absolute,
|
|
36
|
+
type: path.relative(tmp, absolute).replace(/\\/g, '/').startsWith('ai/') ? 'ai' : 'tmp',
|
|
37
|
+
size: stat.size, reason: 'temporary_area',
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
visitTmp(tmp);
|
|
43
|
+
}
|
|
44
|
+
if (flags['all-artifacts']) {
|
|
45
|
+
for (const prefix of MANAGED_PREFIXES.filter((item) => item.startsWith('artifacts/'))) {
|
|
46
|
+
const directory = path.resolve(root, prefix); if (!runtime.inside(root, directory) || !fs.existsSync(directory)) continue;
|
|
47
|
+
const visit = (current) => { for (const child of fs.readdirSync(current, { withFileTypes: true })) { const absolute = path.join(current, child.name); if (child.isDirectory()) visit(absolute); else if (child.isFile() && !candidates.some((entry) => entry.absolute === absolute)) candidates.push({ path: path.relative(root, absolute).replace(/\\/g, '/'), absolute, type: 'artifact', size: fileSize(absolute), reason: 'all_artifacts' }); } };
|
|
48
|
+
visit(directory);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { entries: candidates, count: candidates.length, bytes: candidates.reduce((sum, entry) => sum + entry.size, 0) };
|
|
52
|
+
}
|
|
53
|
+
async function clean(projectDir, _positional, flags = {}) {
|
|
54
|
+
const result = plan(projectDir, flags); const output = { count: result.count, bytes: result.bytes, entries: result.entries.map(({ absolute, ...entry }) => entry) };
|
|
55
|
+
if (flags.output === 'json') console.log(JSON.stringify({ ...output, deleted: Boolean(flags.yes && !flags['dry-run']) }, null, 2));
|
|
56
|
+
else { log.title('draftgo clean'); result.entries.forEach((entry) => log.info(`${entry.path} (${entry.size} bytes)`)); log.info(`Reclaimable: ${result.bytes} bytes in ${result.count} file(s).`); }
|
|
57
|
+
if (!flags.yes || flags['dry-run']) { if (!flags.output) log.dim('Run with --yes to delete this plan.'); return 0; }
|
|
58
|
+
const manifest = runtime.load(projectDir); const removed = new Set();
|
|
59
|
+
for (const entry of result.entries) { if (!runtime.inside(runtime.draftgoRoot(projectDir), entry.absolute)) throw new Error('Clean target escaped .draftgo.'); fs.rmSync(entry.absolute, { force: true }); removed.add(entry.path); }
|
|
60
|
+
const tmp = path.join(runtime.draftgoRoot(projectDir), 'tmp');
|
|
61
|
+
if (fs.existsSync(tmp)) {
|
|
62
|
+
const removeEmpty = (directory) => {
|
|
63
|
+
for (const child of fs.readdirSync(directory, { withFileTypes: true })) if (child.isDirectory()) removeEmpty(path.join(directory, child.name));
|
|
64
|
+
if (directory !== tmp && fs.readdirSync(directory).length === 0) fs.rmdirSync(directory);
|
|
65
|
+
};
|
|
66
|
+
removeEmpty(tmp);
|
|
67
|
+
}
|
|
68
|
+
manifest.entries = manifest.entries.filter((entry) => !removed.has(entry.path)); runtime.save(projectDir, manifest);
|
|
69
|
+
if (!flags.output) log.ok(`Deleted ${result.count} managed file(s).`); return 0;
|
|
70
|
+
}
|
|
71
|
+
clean.plan = plan;
|
|
72
|
+
module.exports = clean;
|
|
@@ -0,0 +1,47 @@
|
|
|
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 streamed = [];
|
|
13
|
+
const printStatus = (result) => {
|
|
14
|
+
streamed.push(result);
|
|
15
|
+
if (flags.output === 'json') return;
|
|
16
|
+
if (result.status === 'committed') log.ok(`${result.resource_type} ${result.resource_id}: committed`);
|
|
17
|
+
else if (result.status === 'unchanged') log.dim(`${result.resource_type} ${result.resource_id}: unchanged`);
|
|
18
|
+
else if (result.status === 'failed') log.err(`${result.resource_type} ${result.resource_id}: failed (${result.code}) - ${result.message}`
|
|
19
|
+
+ (result.remote_change_possible ? ' Remote change is possible; run draftgo check --remote.' : ''));
|
|
20
|
+
else log.warn(`${result.resource_type} ${result.resource_id}: not started`);
|
|
21
|
+
};
|
|
22
|
+
try {
|
|
23
|
+
const results = await commitResources(projectDir, resourceType, ids, { onStatus: printStatus });
|
|
24
|
+
if (flags.output === 'json') console.log(JSON.stringify({
|
|
25
|
+
completed: results.filter((item) => ['committed', 'unchanged'].includes(item.status)),
|
|
26
|
+
failed: [],
|
|
27
|
+
not_started: [],
|
|
28
|
+
}, null, 2));
|
|
29
|
+
} catch (error) {
|
|
30
|
+
const batch = error.details && (error.details.batch || error.details);
|
|
31
|
+
const summary = batch && Array.isArray(batch.completed) ? batch : {
|
|
32
|
+
completed: streamed.filter((item) => ['committed', 'unchanged'].includes(item.status)),
|
|
33
|
+
failed: streamed.filter((item) => item.status === 'failed'),
|
|
34
|
+
not_started: streamed.filter((item) => item.status === 'not_started'),
|
|
35
|
+
};
|
|
36
|
+
if (flags.output === 'json') console.log(JSON.stringify({ error: {
|
|
37
|
+
code: error.code || 'COMMIT_FAILED', message: error.message,
|
|
38
|
+
}, ...summary }, null, 2));
|
|
39
|
+
else log.err(`Commit batch stopped: ${summary.completed.length} completed, `
|
|
40
|
+
+ `${summary.failed.length} failed, ${summary.not_started.length} not started. `
|
|
41
|
+
+ `${summary.completed.length ? 'Remote changes already occurred for completed resources.' : 'No remote content was changed.'}`);
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = commit;
|