wpops 0.1.1 → 0.2.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/README.md +92 -6
- package/bin/wpops.js +191 -249
- package/lib/abilities.js +51 -0
- package/lib/app-passwords.js +39 -0
- package/lib/args.js +6 -35
- package/lib/batch.js +54 -0
- package/lib/blocks.js +72 -0
- package/lib/commands.js +323 -83
- package/lib/env.js +2 -1
- package/lib/errors.js +10 -0
- package/lib/global-styles.js +25 -0
- package/lib/health.js +41 -0
- package/lib/help.js +117 -0
- package/lib/introspect.js +59 -0
- package/lib/navigation.js +71 -0
- package/lib/revisions.js +56 -0
- package/lib/run.js +509 -0
- package/lib/search.js +31 -0
- package/lib/settings.js +74 -0
- package/lib/spec.js +391 -0
- package/lib/templates.js +69 -0
- package/lib/util.js +143 -0
- package/lib/widgets.js +93 -0
- package/package.json +3 -2
- package/skills/wpops/SKILL.md +56 -34
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// 全局样式(/wp-json/wp/v2/global-styles):区块主题的 theme.json 样式配置。
|
|
2
|
+
import { stripTags, printJson, dry, parseJson, readJsonFile } from './util.js';
|
|
3
|
+
import { UsageError } from './errors.js';
|
|
4
|
+
|
|
5
|
+
const GLOBAL_STYLES = '/wp-json/wp/v2/global-styles';
|
|
6
|
+
|
|
7
|
+
export async function getGlobalStyles(client, id, flags) {
|
|
8
|
+
const { data } = await client.request('GET', `${GLOBAL_STYLES}/${id}`);
|
|
9
|
+
if (flags.json) return printJson(data);
|
|
10
|
+
const title = stripTags(data.title?.rendered || data.title || '');
|
|
11
|
+
console.log(`${data.id} ${title}`.trimEnd());
|
|
12
|
+
console.log(JSON.stringify(data.styles ?? {}, null, 2));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function updateGlobalStyles(client, id, flags) {
|
|
16
|
+
let styles;
|
|
17
|
+
if (flags['from-file'] !== undefined) styles = readJsonFile(flags['from-file']);
|
|
18
|
+
else if (flags.styles !== undefined) styles = parseJson(flags.styles);
|
|
19
|
+
if (styles === undefined) throw new UsageError('更新全局样式需要 --styles JSON 或 --from-file');
|
|
20
|
+
const body = { styles };
|
|
21
|
+
if (flags['dry-run']) return dry(`更新全局样式 ${id} ${JSON.stringify(body)}`);
|
|
22
|
+
const { data } = await client.request('POST', `${GLOBAL_STYLES}/${id}`, { body });
|
|
23
|
+
if (flags.json) return printJson(data);
|
|
24
|
+
console.log(`✓ 已更新全局样式 ${data.id}`);
|
|
25
|
+
}
|
package/lib/health.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// 站点健康命令(/wp-json/wp-site-health/v1)。
|
|
2
|
+
// 多数测试端点是只读的;directory-sizes 走单独路径。
|
|
3
|
+
import { printJson } from './util.js';
|
|
4
|
+
import { UsageError } from './errors.js';
|
|
5
|
+
|
|
6
|
+
const BASE = '/wp-json/wp-site-health/v1';
|
|
7
|
+
|
|
8
|
+
/** 已知的站点健康测试名。 */
|
|
9
|
+
export const KNOWN_TESTS = [
|
|
10
|
+
'authorization-header',
|
|
11
|
+
'background-updates',
|
|
12
|
+
'dotorg-communication',
|
|
13
|
+
'https-status',
|
|
14
|
+
'loopback-requests',
|
|
15
|
+
'page-cache',
|
|
16
|
+
'directory-sizes',
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
/** 列出已知测试名与用法,不发任何请求。 */
|
|
20
|
+
export async function listHealth(client, flags) {
|
|
21
|
+
if (flags.json) return printJson(KNOWN_TESTS);
|
|
22
|
+
console.log('已知站点健康测试:');
|
|
23
|
+
for (const test of KNOWN_TESTS) console.log(` ${test}`);
|
|
24
|
+
console.log('用法:wpops health <test> [--json]');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 运行一个健康测试:directory-sizes 走专用路径,其余走 tests/{test}。 */
|
|
28
|
+
export async function runHealth(client, test, flags) {
|
|
29
|
+
if (!test) {
|
|
30
|
+
throw new UsageError(`health 需要一个测试名:wpops health <test>;可用:${KNOWN_TESTS.join(', ')}`);
|
|
31
|
+
}
|
|
32
|
+
const path = test === 'directory-sizes'
|
|
33
|
+
? `${BASE}/directory-sizes`
|
|
34
|
+
: `${BASE}/tests/${encodeURIComponent(test)}`;
|
|
35
|
+
const { data } = await client.request('GET', path);
|
|
36
|
+
if (flags.json) return printJson(data);
|
|
37
|
+
const status = data?.status;
|
|
38
|
+
const label = data?.label ?? data?.name ?? test;
|
|
39
|
+
const description = data?.description ?? data?.message ?? '';
|
|
40
|
+
console.log(`[${status === undefined || status === null ? '?' : status}] ${label} — ${description}`);
|
|
41
|
+
}
|
package/lib/help.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// 由 lib/spec.js 生成所有帮助文本与机器清单,保证与实现同源。
|
|
2
|
+
import { GROUPS, GLOBAL_FLAGS, groupByName, manifest } from './spec.js';
|
|
3
|
+
|
|
4
|
+
function groupLine(g, width) {
|
|
5
|
+
const alias = g.aliases?.length ? ` (别名:${g.aliases.join(',')})` : '';
|
|
6
|
+
return ` ${g.name.padEnd(width)} ${g.summary}${alias}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** 总帮助。 */
|
|
10
|
+
export function generalHelp(version = 'unknown') {
|
|
11
|
+
const width = Math.max(...GROUPS.map((g) => g.name.length)) + 1;
|
|
12
|
+
const local = GROUPS.filter((g) => g.local);
|
|
13
|
+
const remote = GROUPS.filter((g) => !g.local);
|
|
14
|
+
|
|
15
|
+
const lines = [];
|
|
16
|
+
lines.push(`wpops ${version} —— WordPress REST 操作工具`);
|
|
17
|
+
lines.push('');
|
|
18
|
+
lines.push('用法: wpops [--site 名字 | --all] <组> <动作> [参数] [--flags]');
|
|
19
|
+
lines.push('');
|
|
20
|
+
lines.push('先看这个(现查现用,不要背细节):');
|
|
21
|
+
lines.push(' wpops <组> --help · wpops help <组> [动作] 某组的动作与参数');
|
|
22
|
+
lines.push(' wpops commands --json 机器可读命令清单(组/动作/写操作)');
|
|
23
|
+
lines.push(' wpops raw GET /wp-json/ 站点真实路由与命名空间');
|
|
24
|
+
lines.push(' wpops raw OPTIONS <path> 端点支持的字段与枚举');
|
|
25
|
+
lines.push(' wpops doctor 连接与权限体检(先跑这个)');
|
|
26
|
+
lines.push('');
|
|
27
|
+
lines.push('内置命令:');
|
|
28
|
+
for (const g of local) lines.push(groupLine(g, width));
|
|
29
|
+
lines.push('');
|
|
30
|
+
lines.push('站点命令:');
|
|
31
|
+
for (const g of remote) lines.push(groupLine(g, width));
|
|
32
|
+
lines.push('');
|
|
33
|
+
lines.push('多站点:');
|
|
34
|
+
lines.push(' --site <名字> / -s <名字> 对 sites/<名字>.env 执行,可逗号分隔多个');
|
|
35
|
+
lines.push(' --all / -a 对所有已配置站点批量执行(写操作需 --yes)');
|
|
36
|
+
lines.push('');
|
|
37
|
+
lines.push('安全:');
|
|
38
|
+
lines.push(' --dry-run 只预览不执行(--force 仅用于真正执行)');
|
|
39
|
+
lines.push(' --yes / -y 批量写操作确认');
|
|
40
|
+
lines.push('');
|
|
41
|
+
lines.push('全局开关:');
|
|
42
|
+
for (const [flag, desc] of GLOBAL_FLAGS) lines.push(` ${flag.padEnd(28)} ${desc}`);
|
|
43
|
+
lines.push('');
|
|
44
|
+
lines.push('示例:');
|
|
45
|
+
lines.push(' wpops doctor');
|
|
46
|
+
lines.push(' wpops -s blog-a posts list --per-page 5 --orderby date --order desc');
|
|
47
|
+
lines.push(' wpops posts update 12 --from-file ./post.md --status draft');
|
|
48
|
+
lines.push(' wpops menus list --help');
|
|
49
|
+
lines.push(' wpops menu-items list --menus 190');
|
|
50
|
+
lines.push(' wpops --all plugins list');
|
|
51
|
+
lines.push(' wpops posts list --all-pages --fields id,title --csv');
|
|
52
|
+
return lines.join('\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 某组(可带动作)的帮助。未知组返回 null。
|
|
57
|
+
* 动作级只补充该动作说明;细节参数统一用 `wpops raw OPTIONS <path>` 或上方用法。
|
|
58
|
+
*/
|
|
59
|
+
export function groupHelp(name, action) {
|
|
60
|
+
const g = groupByName(name);
|
|
61
|
+
if (!g) return null;
|
|
62
|
+
|
|
63
|
+
const lines = [];
|
|
64
|
+
lines.push(`${g.name} —— ${g.summary}`);
|
|
65
|
+
if (g.aliases?.length) lines.push(`别名:${g.aliases.join(', ')}`);
|
|
66
|
+
lines.push('');
|
|
67
|
+
|
|
68
|
+
const actions = Object.entries(g.actions || {});
|
|
69
|
+
if (actions.length) {
|
|
70
|
+
lines.push('动作:');
|
|
71
|
+
for (const [a, desc] of actions) lines.push(` ${a.padEnd(10)} ${desc}`);
|
|
72
|
+
lines.push('');
|
|
73
|
+
} else {
|
|
74
|
+
lines.push('(无子动作,直接运行)');
|
|
75
|
+
lines.push('');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (action) {
|
|
79
|
+
const desc = (g.actions || {})[action];
|
|
80
|
+
if (desc) lines.push(`动作 ${action}:${desc}`);
|
|
81
|
+
else if (actions.length) lines.push(`未知动作 "${action}";可用:${actions.map(([a]) => a).join(', ')}`);
|
|
82
|
+
lines.push('');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (g.usage?.length) {
|
|
86
|
+
lines.push('用法:');
|
|
87
|
+
for (const u of g.usage) lines.push(` ${u}`);
|
|
88
|
+
lines.push('');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (g.flags?.length) {
|
|
92
|
+
lines.push('参数:');
|
|
93
|
+
for (const f of g.flags) lines.push(` ${f}`);
|
|
94
|
+
lines.push('');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
lines.push('提示:wpops commands --json 可列出全部组与动作;端点字段用 wpops raw OPTIONS <path>。');
|
|
98
|
+
return lines.join('\n');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** 用法错误时用的精简用法(优先与该动作相关的行,最多几行)。 */
|
|
102
|
+
export function usageHint(name, action) {
|
|
103
|
+
const g = groupByName(name);
|
|
104
|
+
if (!g || !g.usage?.length) return [];
|
|
105
|
+
if (action) {
|
|
106
|
+
const escaped = String(action).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
107
|
+
const re = new RegExp(`(^|\\s)${escaped}(\\s|$)`);
|
|
108
|
+
const matches = g.usage.filter((u) => re.test(u));
|
|
109
|
+
if (matches.length) return matches.slice(0, 3);
|
|
110
|
+
}
|
|
111
|
+
return g.usage.slice(0, 4);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** 机器可读清单。 */
|
|
115
|
+
export function commandsJson(version = 'unknown') {
|
|
116
|
+
return JSON.stringify(manifest(version), null, 2);
|
|
117
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// 站点自省:列出 / 查看已注册的文章类型、分类法与文章状态。
|
|
2
|
+
// 这些端点返回以 slug 为键的对象,而非数组。
|
|
3
|
+
|
|
4
|
+
import { printJson } from './util.js';
|
|
5
|
+
|
|
6
|
+
const TYPES = '/wp-json/wp/v2/types';
|
|
7
|
+
const TAXONOMIES = '/wp-json/wp/v2/taxonomies';
|
|
8
|
+
const STATUSES = '/wp-json/wp/v2/statuses';
|
|
9
|
+
|
|
10
|
+
export async function listTypes(client, flags) {
|
|
11
|
+
const { data } = await client.request('GET', TYPES);
|
|
12
|
+
if (flags.json) return printJson(data);
|
|
13
|
+
|
|
14
|
+
const entries = Object.entries(data || {});
|
|
15
|
+
console.log(`文章类型 ${entries.length} 个:`);
|
|
16
|
+
for (const [name, type] of entries) {
|
|
17
|
+
console.log(` ${name} rest_base=${type.rest_base}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function getType(client, type, flags) {
|
|
22
|
+
const { data } = await client.request('GET', `${TYPES}/${type}`);
|
|
23
|
+
if (flags.json) return printJson(data);
|
|
24
|
+
|
|
25
|
+
console.log(`name:${data.name}`);
|
|
26
|
+
console.log(`slug:${data.slug}`);
|
|
27
|
+
console.log(`rest_base:${data.rest_base}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function listTaxonomies(client, flags) {
|
|
31
|
+
const { data } = await client.request('GET', TAXONOMIES);
|
|
32
|
+
if (flags.json) return printJson(data);
|
|
33
|
+
|
|
34
|
+
const entries = Object.entries(data || {});
|
|
35
|
+
console.log(`分类法 ${entries.length} 个:`);
|
|
36
|
+
for (const [name, tax] of entries) {
|
|
37
|
+
console.log(` ${name} rest_base=${tax.rest_base}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function getTaxonomy(client, tax, flags) {
|
|
42
|
+
const { data } = await client.request('GET', `${TAXONOMIES}/${tax}`);
|
|
43
|
+
if (flags.json) return printJson(data);
|
|
44
|
+
|
|
45
|
+
console.log(`name:${data.name}`);
|
|
46
|
+
console.log(`slug:${data.slug}`);
|
|
47
|
+
console.log(`rest_base:${data.rest_base}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function listStatuses(client, flags) {
|
|
51
|
+
const { data } = await client.request('GET', STATUSES);
|
|
52
|
+
if (flags.json) return printJson(data);
|
|
53
|
+
|
|
54
|
+
const entries = Object.entries(data || {});
|
|
55
|
+
console.log(`状态 ${entries.length} 个:`);
|
|
56
|
+
for (const [slug, status] of entries) {
|
|
57
|
+
console.log(` ${slug} ${status.name}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// 区块主题导航(wp_navigation):/wp-json/wp/v2/navigation。
|
|
2
|
+
// 结构与 blocks.js 一致。
|
|
3
|
+
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { stripTags, printJson, dry, listRequest } from './util.js';
|
|
6
|
+
import { UsageError } from './errors.js';
|
|
7
|
+
|
|
8
|
+
const NAVIGATION = '/wp-json/wp/v2/navigation';
|
|
9
|
+
|
|
10
|
+
export async function listNavigation(client, flags) {
|
|
11
|
+
const query = {
|
|
12
|
+
per_page: flags['per-page'] ?? 20,
|
|
13
|
+
page: flags.page,
|
|
14
|
+
search: flags.search,
|
|
15
|
+
status: flags.status,
|
|
16
|
+
context: 'edit',
|
|
17
|
+
};
|
|
18
|
+
const { data, total } = await listRequest(client, NAVIGATION, query, flags);
|
|
19
|
+
if (flags.json) return printJson(data);
|
|
20
|
+
console.log(`导航 ${total ?? data.length} 个:`);
|
|
21
|
+
for (const n of data) {
|
|
22
|
+
console.log(` #${n.id} [${n.status}] ${stripTags(n.title?.raw ?? n.title?.rendered ?? '(无标题)')}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function getNavigation(client, id, flags) {
|
|
27
|
+
const { data } = await client.request('GET', `${NAVIGATION}/${Number(id)}`, {
|
|
28
|
+
query: { context: flags.context || 'edit' },
|
|
29
|
+
});
|
|
30
|
+
if (flags.json) return printJson(data);
|
|
31
|
+
console.log(`#${data.id} [${data.status}] ${stripTags(data.title?.raw ?? data.title?.rendered ?? '')}`);
|
|
32
|
+
console.log('─'.repeat(44));
|
|
33
|
+
console.log(data.content?.raw ?? stripTags(data.content?.rendered || ''));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function navigationBody(flags) {
|
|
37
|
+
const body = {};
|
|
38
|
+
if (flags.title !== undefined) body.title = String(flags.title);
|
|
39
|
+
if (flags['from-file'] !== undefined) body.content = readFileSync(String(flags['from-file']), 'utf8');
|
|
40
|
+
else if (flags.content !== undefined) body.content = String(flags.content);
|
|
41
|
+
if (flags.status !== undefined) body.status = String(flags.status);
|
|
42
|
+
return body;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function createNavigation(client, flags) {
|
|
46
|
+
const body = navigationBody(flags);
|
|
47
|
+
if (body.title === undefined) throw new UsageError('创建导航需要 --title');
|
|
48
|
+
if (flags['dry-run']) return dry(`创建导航 ${JSON.stringify(body)}`);
|
|
49
|
+
const { data } = await client.request('POST', NAVIGATION, { body });
|
|
50
|
+
if (flags.json) return printJson(data);
|
|
51
|
+
console.log(`✓ 已创建导航 #${data.id} [${data.status}]`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function updateNavigation(client, id, flags) {
|
|
55
|
+
const body = navigationBody(flags);
|
|
56
|
+
if (!Object.keys(body).length) {
|
|
57
|
+
throw new UsageError('没有可更新的字段(--title/--content/--from-file/--status)');
|
|
58
|
+
}
|
|
59
|
+
if (flags['dry-run']) return dry(`更新导航 #${id} ${JSON.stringify(body)}`);
|
|
60
|
+
const { data } = await client.request('POST', `${NAVIGATION}/${Number(id)}`, { body });
|
|
61
|
+
if (flags.json) return printJson(data);
|
|
62
|
+
console.log(`✓ 已更新导航 #${data.id}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function deleteNavigation(client, id, flags) {
|
|
66
|
+
if (flags['dry-run']) return dry(`删除导航 #${id}`);
|
|
67
|
+
if (!flags.force) throw new UsageError('删除导航需要 --force(不可撤销)');
|
|
68
|
+
const { data } = await client.request('DELETE', `${NAVIGATION}/${Number(id)}`, { query: { force: 'true' } });
|
|
69
|
+
if (flags.json) return printJson(data);
|
|
70
|
+
console.log(data.deleted ? `✓ 已删除导航 #${id}` : `? 导航 #${id} 未删除`);
|
|
71
|
+
}
|
package/lib/revisions.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// 内容修订(revisions):列出 / 查看 / 删除 / 恢复到某个修订。
|
|
2
|
+
// type 取 posts 或 pages,默认 posts;端点挂在具体内容下。
|
|
3
|
+
|
|
4
|
+
import { stripTags, printJson, dry, listRequest } from './util.js';
|
|
5
|
+
import { UsageError } from './errors.js';
|
|
6
|
+
|
|
7
|
+
function revisionsPath(type, id) {
|
|
8
|
+
return `/wp-json/wp/v2/${type}/${Number(id)}/revisions`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function listRevisions(client, type, id, flags) {
|
|
12
|
+
const query = {
|
|
13
|
+
per_page: flags['per-page'] ?? 10,
|
|
14
|
+
page: flags.page,
|
|
15
|
+
};
|
|
16
|
+
const { data, total } = await listRequest(client, revisionsPath(type, id), query, flags);
|
|
17
|
+
if (flags.json) return printJson(data);
|
|
18
|
+
console.log(`${type} #${id} 修订 ${total ?? data.length} 条:`);
|
|
19
|
+
for (const rev of data) {
|
|
20
|
+
console.log(` #${rev.id} ${rev.date} ${rev.author} ${stripTags(rev.title?.rendered || '')}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function getRevision(client, type, id, rev, flags) {
|
|
25
|
+
const { data } = await client.request('GET', `${revisionsPath(type, id)}/${Number(rev)}`);
|
|
26
|
+
if (flags.json) return printJson(data);
|
|
27
|
+
console.log(`#${data.id} ${data.date} ${data.author} ${stripTags(data.title?.rendered || '')}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function deleteRevision(client, type, id, rev, flags) {
|
|
31
|
+
if (flags['dry-run']) return dry(`删除 ${type} #${id} 修订 #${rev}`);
|
|
32
|
+
if (!flags.force) throw new UsageError('删除修订需要 --force(不可撤销)');
|
|
33
|
+
const { data } = await client.request('DELETE', `${revisionsPath(type, id)}/${Number(rev)}`, {
|
|
34
|
+
query: { force: 'true' },
|
|
35
|
+
});
|
|
36
|
+
if (flags.json) return printJson(data);
|
|
37
|
+
console.log(data.deleted ? `✓ 已删除 ${type} #${id} 修订 #${rev}` : `? 修订 #${rev} 未删除`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 取修订字段:优先 .raw,退回 .rendered。 */
|
|
41
|
+
function revisionValue(field) {
|
|
42
|
+
return field?.raw ?? field?.rendered;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function restoreRevision(client, type, id, rev, flags) {
|
|
46
|
+
const { data: revision } = await client.request('GET', `${revisionsPath(type, id)}/${Number(rev)}`);
|
|
47
|
+
const body = {
|
|
48
|
+
title: revisionValue(revision.title),
|
|
49
|
+
content: revisionValue(revision.content),
|
|
50
|
+
excerpt: revisionValue(revision.excerpt),
|
|
51
|
+
};
|
|
52
|
+
if (flags['dry-run']) return dry(`恢复 ${type} #${id} 到修订 #${rev}`);
|
|
53
|
+
const { data } = await client.request('POST', `/wp-json/wp/v2/${type}/${Number(id)}`, { body });
|
|
54
|
+
if (flags.json) return printJson(data);
|
|
55
|
+
console.log(`✓ 已恢复 ${type} #${id} 到修订 #${rev}`);
|
|
56
|
+
}
|