@falldownthesystem/devtool 0.1.0
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 +15 -0
- package/README.md +59 -0
- package/package.json +40 -0
- package/src/clients/bitbucket.js +85 -0
- package/src/clients/confluence.js +63 -0
- package/src/clients/jira.js +61 -0
- package/src/commands/config.js +68 -0
- package/src/commands/confluence.js +113 -0
- package/src/commands/jira.js +95 -0
- package/src/commands/pr.js +132 -0
- package/src/config/store.js +156 -0
- package/src/formatters/confluence.js +212 -0
- package/src/formatters/jira.js +236 -0
- package/src/formatters/pr.js +306 -0
- package/src/index.js +40 -0
- package/src/utils/errors.js +43 -0
- package/src/utils/mode.js +10 -0
- package/src/utils/output.js +68 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import {
|
|
3
|
+
listPullRequests,
|
|
4
|
+
getPullRequest,
|
|
5
|
+
getPullRequestComments,
|
|
6
|
+
getPullRequestDiffstat,
|
|
7
|
+
getPullRequestActivity,
|
|
8
|
+
} from '../clients/bitbucket.js';
|
|
9
|
+
import {
|
|
10
|
+
formatPrList, formatPrView, formatPrComments, formatPrDiffstat, formatPrActivity,
|
|
11
|
+
plainPrList, plainPrView, plainPrComments, plainPrDiffstat, plainPrActivity,
|
|
12
|
+
curateListJson, curateViewJson, curateCommentsJson, curateDiffstatJson, curateActivityJson,
|
|
13
|
+
} from '../formatters/pr.js';
|
|
14
|
+
import { spinner } from '../utils/output.js';
|
|
15
|
+
import { handleError } from '../utils/errors.js';
|
|
16
|
+
import { getOutputMode, outputJson } from '../utils/mode.js';
|
|
17
|
+
|
|
18
|
+
function withMode(cmd, fetchFn, { json, plain, rich }) {
|
|
19
|
+
return async (...args) => {
|
|
20
|
+
const mode = getOutputMode(cmd);
|
|
21
|
+
const s = mode === 'rich' ? spinner('Loading...').start() : null;
|
|
22
|
+
try {
|
|
23
|
+
const data = await fetchFn(...args);
|
|
24
|
+
if (s) s.success({ text: '' });
|
|
25
|
+
if (mode === 'json') outputJson(json(data));
|
|
26
|
+
else if (mode === 'plain') plain(data);
|
|
27
|
+
else rich(data);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (s) s.error({ text: 'Failed' });
|
|
30
|
+
handleError(err, err.url || 'bitbucket');
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const pr = new Command('pr')
|
|
36
|
+
.description('Bitbucket pull requests');
|
|
37
|
+
|
|
38
|
+
pr.command('list')
|
|
39
|
+
.alias('ls')
|
|
40
|
+
.description('List pull requests')
|
|
41
|
+
.option('-s, --state <state>', 'PR state (OPEN, MERGED, DECLINED)', 'OPEN')
|
|
42
|
+
.option('-a, --author <name>', 'Filter by author')
|
|
43
|
+
.option('-r, --repo <slug>', 'Repository slug (overrides default)')
|
|
44
|
+
.action(async function (opts) {
|
|
45
|
+
const mode = getOutputMode(this);
|
|
46
|
+
const s = mode === 'rich' ? spinner('Fetching pull requests...').start() : null;
|
|
47
|
+
try {
|
|
48
|
+
const prs = await listPullRequests({ state: opts.state, author: opts.author, repo: opts.repo });
|
|
49
|
+
if (s) s.success({ text: `${prs.length} pull request(s) found` });
|
|
50
|
+
if (mode === 'json') outputJson(curateListJson(prs));
|
|
51
|
+
else if (mode === 'plain') plainPrList(prs);
|
|
52
|
+
else formatPrList(prs);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
if (s) s.error({ text: 'Failed to fetch pull requests' });
|
|
55
|
+
handleError(err, err.url || 'bitbucket');
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
pr.command('view <id>')
|
|
60
|
+
.alias('read')
|
|
61
|
+
.description('View pull request details')
|
|
62
|
+
.option('-r, --repo <slug>', 'Repository slug (overrides default)')
|
|
63
|
+
.action(async function (id, opts) {
|
|
64
|
+
const mode = getOutputMode(this);
|
|
65
|
+
const s = mode === 'rich' ? spinner(`Fetching PR #${id}...`).start() : null;
|
|
66
|
+
try {
|
|
67
|
+
const data = await getPullRequest(id, opts.repo);
|
|
68
|
+
if (s) s.success({ text: `PR #${id}` });
|
|
69
|
+
if (mode === 'json') outputJson(curateViewJson(data));
|
|
70
|
+
else if (mode === 'plain') plainPrView(data);
|
|
71
|
+
else formatPrView(data);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
if (s) s.error({ text: `Failed to fetch PR #${id}` });
|
|
74
|
+
handleError(err, err.url || 'bitbucket');
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
pr.command('comments <id>')
|
|
79
|
+
.description('List pull request comments')
|
|
80
|
+
.option('-r, --repo <slug>', 'Repository slug (overrides default)')
|
|
81
|
+
.action(async function (id, opts) {
|
|
82
|
+
const mode = getOutputMode(this);
|
|
83
|
+
const s = mode === 'rich' ? spinner(`Fetching comments for PR #${id}...`).start() : null;
|
|
84
|
+
try {
|
|
85
|
+
const comments = await getPullRequestComments(id, opts.repo);
|
|
86
|
+
if (s) s.success({ text: `${comments.length} comment(s)` });
|
|
87
|
+
if (mode === 'json') outputJson(curateCommentsJson(comments));
|
|
88
|
+
else if (mode === 'plain') plainPrComments(comments);
|
|
89
|
+
else formatPrComments(comments);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
if (s) s.error({ text: `Failed to fetch comments for PR #${id}` });
|
|
92
|
+
handleError(err, err.url || 'bitbucket');
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
pr.command('diff <id>')
|
|
97
|
+
.description('Show pull request diffstat')
|
|
98
|
+
.option('-r, --repo <slug>', 'Repository slug (overrides default)')
|
|
99
|
+
.action(async function (id, opts) {
|
|
100
|
+
const mode = getOutputMode(this);
|
|
101
|
+
const s = mode === 'rich' ? spinner(`Fetching diffstat for PR #${id}...`).start() : null;
|
|
102
|
+
try {
|
|
103
|
+
const files = await getPullRequestDiffstat(id, opts.repo);
|
|
104
|
+
if (s) s.success({ text: `${files.length} file(s) changed` });
|
|
105
|
+
if (mode === 'json') outputJson(curateDiffstatJson(files));
|
|
106
|
+
else if (mode === 'plain') plainPrDiffstat(files);
|
|
107
|
+
else formatPrDiffstat(files);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
if (s) s.error({ text: `Failed to fetch diffstat for PR #${id}` });
|
|
110
|
+
handleError(err, err.url || 'bitbucket');
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
pr.command('activity <id>')
|
|
115
|
+
.description('Show pull request activity')
|
|
116
|
+
.option('-r, --repo <slug>', 'Repository slug (overrides default)')
|
|
117
|
+
.action(async function (id, opts) {
|
|
118
|
+
const mode = getOutputMode(this);
|
|
119
|
+
const s = mode === 'rich' ? spinner(`Fetching activity for PR #${id}...`).start() : null;
|
|
120
|
+
try {
|
|
121
|
+
const activities = await getPullRequestActivity(id, opts.repo);
|
|
122
|
+
if (s) s.success({ text: `${activities.length} activity item(s)` });
|
|
123
|
+
if (mode === 'json') outputJson(curateActivityJson(activities));
|
|
124
|
+
else if (mode === 'plain') plainPrActivity(activities);
|
|
125
|
+
else formatPrActivity(activities);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (s) s.error({ text: `Failed to fetch activity for PR #${id}` });
|
|
128
|
+
handleError(err, err.url || 'bitbucket');
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
export default pr;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
import * as p from '@clack/prompts';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
|
|
5
|
+
const schema = {
|
|
6
|
+
bitbucket: {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
workspace: { type: 'string' },
|
|
10
|
+
defaultRepo: { type: 'string' },
|
|
11
|
+
email: { type: 'string' },
|
|
12
|
+
token: { type: 'string' },
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
jira: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
baseUrl: { type: 'string' },
|
|
19
|
+
token: { type: 'string' },
|
|
20
|
+
defaultProject: { type: 'string' },
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
confluence: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
baseUrl: { type: 'string' },
|
|
27
|
+
email: { type: 'string' },
|
|
28
|
+
token: { type: 'string' },
|
|
29
|
+
defaultSpace: { type: 'string' },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const config = new Conf({
|
|
35
|
+
projectName: 'devtool',
|
|
36
|
+
schema,
|
|
37
|
+
defaults: {
|
|
38
|
+
bitbucket: { workspace: '', defaultRepo: '', email: '', token: '' },
|
|
39
|
+
jira: { baseUrl: '', token: '', defaultProject: '' },
|
|
40
|
+
confluence: { baseUrl: '', email: '', token: '', defaultSpace: '' },
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export function get(key) {
|
|
45
|
+
return config.get(key);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function set(key, value) {
|
|
49
|
+
config.set(key, value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getAll() {
|
|
53
|
+
return config.store;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isConfigured() {
|
|
57
|
+
const bb = config.get('bitbucket');
|
|
58
|
+
const jira = config.get('jira');
|
|
59
|
+
return !!(bb.workspace && bb.token && jira.baseUrl && jira.token);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function configPath() {
|
|
63
|
+
return config.path;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function runSetup() {
|
|
67
|
+
p.intro(pc.cyan('DevTool Setup'));
|
|
68
|
+
|
|
69
|
+
const bb = await p.group({
|
|
70
|
+
workspace: () => p.text({
|
|
71
|
+
message: 'Bitbucket workspace slug',
|
|
72
|
+
placeholder: 'my-workspace',
|
|
73
|
+
initialValue: config.get('bitbucket.workspace') || '',
|
|
74
|
+
validate: (v) => !v ? 'Required' : undefined,
|
|
75
|
+
}),
|
|
76
|
+
defaultRepo: () => p.text({
|
|
77
|
+
message: 'Bitbucket default repo slug',
|
|
78
|
+
placeholder: 'my-repo',
|
|
79
|
+
initialValue: config.get('bitbucket.defaultRepo') || '',
|
|
80
|
+
validate: (v) => !v ? 'Required' : undefined,
|
|
81
|
+
}),
|
|
82
|
+
email: () => p.text({
|
|
83
|
+
message: 'Bitbucket account email',
|
|
84
|
+
placeholder: 'you@company.com',
|
|
85
|
+
initialValue: config.get('bitbucket.email') || '',
|
|
86
|
+
validate: (v) => !v ? 'Required' : undefined,
|
|
87
|
+
}),
|
|
88
|
+
token: () => p.password({
|
|
89
|
+
message: 'Bitbucket API token',
|
|
90
|
+
validate: (v) => !v ? 'Required' : undefined,
|
|
91
|
+
}),
|
|
92
|
+
}, {
|
|
93
|
+
onCancel: () => {
|
|
94
|
+
p.cancel('Setup cancelled.');
|
|
95
|
+
process.exit(0);
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const jira = await p.group({
|
|
100
|
+
baseUrl: () => p.text({
|
|
101
|
+
message: 'Jira Server base URL',
|
|
102
|
+
placeholder: 'https://jira.company.com',
|
|
103
|
+
initialValue: config.get('jira.baseUrl') || '',
|
|
104
|
+
validate: (v) => !v ? 'Required' : undefined,
|
|
105
|
+
}),
|
|
106
|
+
token: () => p.password({
|
|
107
|
+
message: 'Jira Personal Access Token',
|
|
108
|
+
validate: (v) => !v ? 'Required' : undefined,
|
|
109
|
+
}),
|
|
110
|
+
defaultProject: () => p.text({
|
|
111
|
+
message: 'Jira default project key',
|
|
112
|
+
placeholder: 'PROJ',
|
|
113
|
+
initialValue: config.get('jira.defaultProject') || '',
|
|
114
|
+
validate: (v) => !v ? 'Required' : undefined,
|
|
115
|
+
}),
|
|
116
|
+
}, {
|
|
117
|
+
onCancel: () => {
|
|
118
|
+
p.cancel('Setup cancelled.');
|
|
119
|
+
process.exit(0);
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const confluence = await p.group({
|
|
124
|
+
baseUrl: () => p.text({
|
|
125
|
+
message: 'Confluence Cloud base URL',
|
|
126
|
+
placeholder: 'https://company.atlassian.net',
|
|
127
|
+
initialValue: config.get('confluence.baseUrl') || '',
|
|
128
|
+
}),
|
|
129
|
+
email: () => p.text({
|
|
130
|
+
message: 'Confluence account email',
|
|
131
|
+
placeholder: 'you@company.com',
|
|
132
|
+
initialValue: config.get('confluence.email') || '',
|
|
133
|
+
}),
|
|
134
|
+
token: () => p.password({
|
|
135
|
+
message: 'Confluence API token',
|
|
136
|
+
}),
|
|
137
|
+
defaultSpace: () => p.text({
|
|
138
|
+
message: 'Confluence default space key',
|
|
139
|
+
placeholder: 'DEV',
|
|
140
|
+
initialValue: config.get('confluence.defaultSpace') || '',
|
|
141
|
+
}),
|
|
142
|
+
}, {
|
|
143
|
+
onCancel: () => {
|
|
144
|
+
p.cancel('Setup cancelled.');
|
|
145
|
+
process.exit(0);
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
config.set('bitbucket', bb);
|
|
150
|
+
config.set('jira', jira);
|
|
151
|
+
config.set('confluence', confluence);
|
|
152
|
+
|
|
153
|
+
p.outro(pc.green('Configuration saved!'));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export default config;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { table, heading, field, divider, pc } from '../utils/output.js';
|
|
2
|
+
|
|
3
|
+
function stripHtml(html) {
|
|
4
|
+
if (!html) return '';
|
|
5
|
+
return html
|
|
6
|
+
.replace(/<br\s*\/?>/gi, '\n')
|
|
7
|
+
.replace(/<\/p>/gi, '\n')
|
|
8
|
+
.replace(/<\/li>/gi, '\n')
|
|
9
|
+
.replace(/<\/h[1-6]>/gi, '\n')
|
|
10
|
+
.replace(/<[^>]+>/g, '')
|
|
11
|
+
.replace(/&/g, '&')
|
|
12
|
+
.replace(/</g, '<')
|
|
13
|
+
.replace(/>/g, '>')
|
|
14
|
+
.replace(/"/g, '"')
|
|
15
|
+
.replace(/'/g, "'")
|
|
16
|
+
.replace(/ /g, ' ')
|
|
17
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
18
|
+
.trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function relativeTime(dateStr) {
|
|
22
|
+
if (!dateStr) return '';
|
|
23
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
24
|
+
const mins = Math.floor(diff / 60000);
|
|
25
|
+
if (mins < 60) return `${mins}m ago`;
|
|
26
|
+
const hours = Math.floor(mins / 60);
|
|
27
|
+
if (hours < 24) return `${hours}h ago`;
|
|
28
|
+
const days = Math.floor(hours / 24);
|
|
29
|
+
if (days < 30) return `${days}d ago`;
|
|
30
|
+
return new Date(dateStr).toLocaleDateString();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function spaceType(type) {
|
|
34
|
+
if (!type) return '';
|
|
35
|
+
if (type === 'global') return pc.blue('global');
|
|
36
|
+
if (type === 'personal') return pc.dim('personal');
|
|
37
|
+
return type;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// --- JSON curators ---
|
|
41
|
+
|
|
42
|
+
export function curateSpacesJson(spaces) {
|
|
43
|
+
return spaces.map((s) => ({
|
|
44
|
+
id: s.id,
|
|
45
|
+
key: s.key,
|
|
46
|
+
name: s.name,
|
|
47
|
+
type: s.type || null,
|
|
48
|
+
status: s.status || null,
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function curatePageJson(page) {
|
|
53
|
+
return {
|
|
54
|
+
id: page.id,
|
|
55
|
+
title: page.title,
|
|
56
|
+
spaceId: page.spaceId || null,
|
|
57
|
+
status: page.status || null,
|
|
58
|
+
createdAt: page.createdAt || null,
|
|
59
|
+
version: page.version?.number || null,
|
|
60
|
+
body: stripHtml(page.body?.storage?.value),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function curatePagesJson(pages) {
|
|
65
|
+
return pages.map((p) => ({
|
|
66
|
+
id: p.id,
|
|
67
|
+
title: p.title,
|
|
68
|
+
status: p.status || null,
|
|
69
|
+
createdAt: p.createdAt || null,
|
|
70
|
+
version: p.version?.number || null,
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function curateSearchJson(result) {
|
|
75
|
+
return {
|
|
76
|
+
totalSize: result.totalSize || 0,
|
|
77
|
+
results: (result.results || []).map((r) => ({
|
|
78
|
+
title: r.content?.title || r.title || '',
|
|
79
|
+
id: r.content?.id || r.id || null,
|
|
80
|
+
type: r.content?.type || r.type || null,
|
|
81
|
+
space: r.resultGlobalContainer?.title || null,
|
|
82
|
+
excerpt: stripHtml(r.excerpt),
|
|
83
|
+
url: r.url || null,
|
|
84
|
+
lastModified: r.lastModified || r.content?.version?.when || null,
|
|
85
|
+
})),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --- Plain formatters ---
|
|
90
|
+
|
|
91
|
+
export function plainSpaces(spaces) {
|
|
92
|
+
if (spaces.length === 0) { console.log('No spaces found.'); return; }
|
|
93
|
+
for (const s of spaces) {
|
|
94
|
+
console.log(`${s.key} "${s.name}" (${s.type || 'unknown'}, ${s.status || 'current'})`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function plainPageView(page) {
|
|
99
|
+
console.log(`${page.id} ${page.title}`);
|
|
100
|
+
if (page.spaceId) console.log(`Space ID: ${page.spaceId}`);
|
|
101
|
+
console.log(`Status: ${page.status || 'current'}`);
|
|
102
|
+
if (page.version?.number) console.log(`Version: ${page.version.number}`);
|
|
103
|
+
if (page.createdAt) console.log(`Created: ${page.createdAt}`);
|
|
104
|
+
const body = stripHtml(page.body?.storage?.value);
|
|
105
|
+
if (body) console.log(`Content:\n${body}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function plainPages(pages) {
|
|
109
|
+
if (pages.length === 0) { console.log('No pages found.'); return; }
|
|
110
|
+
for (const p of pages) {
|
|
111
|
+
console.log(`${p.id} "${p.title}" (${p.status || 'current'})`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function plainSearch(result) {
|
|
116
|
+
const results = result.results || [];
|
|
117
|
+
if (results.length === 0) { console.log('No results found.'); return; }
|
|
118
|
+
for (const r of results) {
|
|
119
|
+
const title = r.content?.title || r.title || 'Untitled';
|
|
120
|
+
const id = r.content?.id || '';
|
|
121
|
+
const space = r.resultGlobalContainer?.title || '';
|
|
122
|
+
const excerpt = stripHtml(r.excerpt).split('\n')[0].slice(0, 100);
|
|
123
|
+
console.log(`${id} [${space}] "${title}" - ${excerpt}`);
|
|
124
|
+
}
|
|
125
|
+
if (result.totalSize > results.length) {
|
|
126
|
+
console.log(`Showing ${results.length} of ${result.totalSize} results`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --- Rich formatters ---
|
|
131
|
+
|
|
132
|
+
export function formatSpaces(spaces) {
|
|
133
|
+
if (spaces.length === 0) {
|
|
134
|
+
console.log(pc.dim(' No spaces found.'));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const rows = spaces.map((s) => [
|
|
139
|
+
pc.bold(s.key),
|
|
140
|
+
s.name || '',
|
|
141
|
+
spaceType(s.type),
|
|
142
|
+
s.status || 'current',
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
console.log(table(['Key', 'Name', 'Type', 'Status'], rows));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function formatPageView(page) {
|
|
149
|
+
heading(`${page.title}`);
|
|
150
|
+
divider();
|
|
151
|
+
field('Page ID', page.id);
|
|
152
|
+
if (page.spaceId) field('Space ID', page.spaceId);
|
|
153
|
+
field('Status', page.status || 'current');
|
|
154
|
+
if (page.version?.number) field('Version', page.version.number);
|
|
155
|
+
if (page.createdAt) field('Created', new Date(page.createdAt).toLocaleString());
|
|
156
|
+
|
|
157
|
+
const body = stripHtml(page.body?.storage?.value);
|
|
158
|
+
if (body) {
|
|
159
|
+
heading('Content');
|
|
160
|
+
const lines = body.split('\n');
|
|
161
|
+
const truncated = lines.slice(0, 50);
|
|
162
|
+
for (const line of truncated) {
|
|
163
|
+
console.log(` ${line}`);
|
|
164
|
+
}
|
|
165
|
+
if (lines.length > 50) {
|
|
166
|
+
console.log(pc.dim(` ... (${lines.length - 50} more lines)`));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log('');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function formatPages(pages) {
|
|
174
|
+
if (pages.length === 0) {
|
|
175
|
+
console.log(pc.dim(' No pages found.'));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const rows = pages.map((p) => [
|
|
180
|
+
p.id,
|
|
181
|
+
(p.title || '').length > 60 ? p.title.slice(0, 57) + '...' : (p.title || ''),
|
|
182
|
+
p.status || 'current',
|
|
183
|
+
p.version?.number || '',
|
|
184
|
+
relativeTime(p.createdAt),
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
console.log(table(['ID', 'Title', 'Status', 'Version', 'Created'], rows));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function formatSearch(result) {
|
|
191
|
+
const results = result.results || [];
|
|
192
|
+
if (results.length === 0) {
|
|
193
|
+
console.log(pc.dim(' No results found.'));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const rows = results.map((r) => [
|
|
198
|
+
r.content?.id || '',
|
|
199
|
+
(r.content?.title || r.title || '').length > 50
|
|
200
|
+
? (r.content?.title || r.title || '').slice(0, 47) + '...'
|
|
201
|
+
: (r.content?.title || r.title || ''),
|
|
202
|
+
r.resultGlobalContainer?.title || '',
|
|
203
|
+
stripHtml(r.excerpt).split('\n')[0].slice(0, 60),
|
|
204
|
+
]);
|
|
205
|
+
|
|
206
|
+
console.log(table(['ID', 'Title', 'Space', 'Excerpt'], rows));
|
|
207
|
+
|
|
208
|
+
if (result.totalSize > results.length) {
|
|
209
|
+
console.log(pc.dim(` Showing ${results.length} of ${result.totalSize} results`));
|
|
210
|
+
}
|
|
211
|
+
console.log('');
|
|
212
|
+
}
|