wpfleet 1.0.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/CHANGELOG.md +30 -0
- package/README.md +143 -0
- package/bin/wpfleet.js +2 -0
- package/package.json +45 -0
- package/src/ai/client.js +149 -0
- package/src/ai/seo-prompts.js +117 -0
- package/src/commands/ai-optimize.js +242 -0
- package/src/commands/ai-write.js +119 -0
- package/src/commands/health.js +100 -0
- package/src/commands/interlink.js +208 -0
- package/src/commands/publish.js +136 -0
- package/src/commands/report.js +108 -0
- package/src/commands/security.js +158 -0
- package/src/commands/sites.js +98 -0
- package/src/commands/stats.js +48 -0
- package/src/commands/traffic.js +87 -0
- package/src/commands/update.js +112 -0
- package/src/config.js +51 -0
- package/src/index.js +231 -0
- package/src/utils/display.js +41 -0
- package/src/utils/http.js +40 -0
- package/src/utils/wp-api.js +75 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import { getSites, getSite } from '../config.js';
|
|
4
|
+
import { makeTable } from '../utils/display.js';
|
|
5
|
+
|
|
6
|
+
function makeClient(site) {
|
|
7
|
+
const baseURL = site.url.replace(/\/$/, '') + '/wp-json';
|
|
8
|
+
const config = { baseURL, timeout: 15000, validateStatus: () => true };
|
|
9
|
+
if (site.wp_user && site.wp_app_password) {
|
|
10
|
+
const token = Buffer.from(`${site.wp_user}:${site.wp_app_password}`).toString('base64');
|
|
11
|
+
config.headers = { Authorization: `Basic ${token}` };
|
|
12
|
+
}
|
|
13
|
+
return axios.create(config);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function fetchPlugins(site) {
|
|
17
|
+
const client = makeClient(site);
|
|
18
|
+
const res = await client.get('/wp/v2/plugins', { params: { per_page: 100 } });
|
|
19
|
+
if (res.status === 401 || res.status === 403) {
|
|
20
|
+
return { error: 'Authentication required (wp_user + wp_app_password with admin role)' };
|
|
21
|
+
}
|
|
22
|
+
if (res.status !== 200 || !Array.isArray(res.data)) {
|
|
23
|
+
return { error: `HTTP ${res.status} — plugins endpoint unavailable` };
|
|
24
|
+
}
|
|
25
|
+
return { plugins: res.data };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function applyUpdate(site, plugin) {
|
|
29
|
+
const client = makeClient(site);
|
|
30
|
+
// WP REST API supports updating plugin status but not version upgrades directly.
|
|
31
|
+
// This attempts the standard REST endpoint; actual upgrades require WP-CLI or admin.
|
|
32
|
+
const slug = plugin.plugin;
|
|
33
|
+
const res = await client.put(`/wp/v2/plugins/${encodeURIComponent(slug)}`, { status: plugin.status });
|
|
34
|
+
return res.status === 200;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function updateCommand(options) {
|
|
38
|
+
const siteName = options.site;
|
|
39
|
+
const applyUpdates = options.apply;
|
|
40
|
+
|
|
41
|
+
let sites;
|
|
42
|
+
if (options.all) {
|
|
43
|
+
sites = getSites();
|
|
44
|
+
} else if (siteName) {
|
|
45
|
+
const site = getSite(siteName);
|
|
46
|
+
if (!site) { console.error(chalk.red(`Site not found: ${siteName}`)); process.exit(1); }
|
|
47
|
+
sites = [site];
|
|
48
|
+
} else {
|
|
49
|
+
sites = getSites();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (sites.length === 0) {
|
|
53
|
+
console.log(chalk.yellow('No sites configured. Run: wpfleet sites add'));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const site of sites) {
|
|
58
|
+
console.log(chalk.bold(`\n=== ${site.name} ===`));
|
|
59
|
+
|
|
60
|
+
const result = await fetchPlugins(site);
|
|
61
|
+
if (result.error) {
|
|
62
|
+
console.log(chalk.yellow(` Cannot fetch plugins: ${result.error}`));
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const { plugins } = result;
|
|
67
|
+
const withUpdates = plugins.filter(p => p.update && p.update !== 'none' && p.update !== null);
|
|
68
|
+
|
|
69
|
+
if (plugins.length === 0) {
|
|
70
|
+
console.log(chalk.dim(' No plugins found'));
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const table = makeTable(['Plugin', 'Version', 'Status', 'Update'], [32, 12, 12, 18]);
|
|
75
|
+
for (const p of plugins) {
|
|
76
|
+
const hasUpdate = p.update && p.update !== 'none';
|
|
77
|
+
const updateStr = hasUpdate
|
|
78
|
+
? chalk.yellow(p.update?.version || 'available')
|
|
79
|
+
: chalk.green('up to date');
|
|
80
|
+
const statusStr = p.status === 'active' ? chalk.green('active') : chalk.gray(p.status || 'unknown');
|
|
81
|
+
table.push([
|
|
82
|
+
(p.name || p.plugin || '').slice(0, 30),
|
|
83
|
+
(p.version || 'n/a').slice(0, 10),
|
|
84
|
+
statusStr,
|
|
85
|
+
updateStr,
|
|
86
|
+
]);
|
|
87
|
+
}
|
|
88
|
+
console.log(table.toString());
|
|
89
|
+
|
|
90
|
+
if (withUpdates.length === 0) {
|
|
91
|
+
console.log(chalk.green(` All ${plugins.length} plugin(s) are up to date`));
|
|
92
|
+
} else {
|
|
93
|
+
console.log(chalk.yellow(` ${withUpdates.length} plugin(s) have updates available`));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (applyUpdates && withUpdates.length > 0) {
|
|
97
|
+
console.log(chalk.dim('\n Attempting updates via REST API...'));
|
|
98
|
+
console.log(chalk.dim(' (Note: full plugin upgrades typically require WP-CLI or WP Admin)'));
|
|
99
|
+
for (const p of withUpdates) {
|
|
100
|
+
process.stdout.write(` Updating ${p.name}... `);
|
|
101
|
+
try {
|
|
102
|
+
const ok = await applyUpdate(site, p);
|
|
103
|
+
console.log(ok ? chalk.green('done') : chalk.yellow('may need WP-CLI'));
|
|
104
|
+
} catch (err) {
|
|
105
|
+
console.log(chalk.red(`failed: ${err.message}`));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} else if (applyUpdates && withUpdates.length === 0) {
|
|
109
|
+
console.log(chalk.green(' Nothing to update'));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import yaml from 'js-yaml';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_CONFIG_PATH = path.join(os.homedir(), '.wpfleet', 'config.yml');
|
|
7
|
+
const LOCAL_CONFIG_PATH = path.join(process.cwd(), 'wpfleet.yml');
|
|
8
|
+
|
|
9
|
+
export function getConfigPath() {
|
|
10
|
+
if (fs.existsSync(LOCAL_CONFIG_PATH)) return LOCAL_CONFIG_PATH;
|
|
11
|
+
return DEFAULT_CONFIG_PATH;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function loadConfig() {
|
|
15
|
+
const configPath = getConfigPath();
|
|
16
|
+
if (!fs.existsSync(configPath)) {
|
|
17
|
+
return { sites: [] };
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
21
|
+
const config = yaml.load(raw) || {};
|
|
22
|
+
if (!config.sites) config.sites = [];
|
|
23
|
+
return config;
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error(`Error reading config at ${configPath}: ${err.message}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function saveConfig(config) {
|
|
31
|
+
const configPath = getConfigPath();
|
|
32
|
+
const dir = path.dirname(configPath);
|
|
33
|
+
if (!fs.existsSync(dir)) {
|
|
34
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
fs.writeFileSync(configPath, yaml.dump(config), 'utf8');
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error(`Error writing config at ${configPath}: ${err.message}`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getSite(name) {
|
|
45
|
+
const config = loadConfig();
|
|
46
|
+
return config.sites.find(s => s.name === name);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function getSites() {
|
|
50
|
+
return loadConfig().sites;
|
|
51
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { sitesList, sitesAdd, sitesRemove } from './commands/sites.js';
|
|
3
|
+
import { statsCommand } from './commands/stats.js';
|
|
4
|
+
import { healthCommand } from './commands/health.js';
|
|
5
|
+
import { publishCommand, bulkPublishCommand } from './commands/publish.js';
|
|
6
|
+
import { interlinkCommand, crosslinkCommand } from './commands/interlink.js';
|
|
7
|
+
import { trafficCommand } from './commands/traffic.js';
|
|
8
|
+
import { reportCommand } from './commands/report.js';
|
|
9
|
+
import { updateCommand } from './commands/update.js';
|
|
10
|
+
import { securityCommand } from './commands/security.js';
|
|
11
|
+
import { aiWriteCommand } from './commands/ai-write.js';
|
|
12
|
+
import { aiOptimizeCommand } from './commands/ai-optimize.js';
|
|
13
|
+
|
|
14
|
+
const program = new Command();
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.name('wpfleet')
|
|
18
|
+
.description('Manage a network of WordPress blogs from the terminal')
|
|
19
|
+
.version('1.0.0');
|
|
20
|
+
|
|
21
|
+
const sites = program.command('sites').description('Manage sites');
|
|
22
|
+
|
|
23
|
+
sites
|
|
24
|
+
.command('list')
|
|
25
|
+
.description('List all configured sites with status')
|
|
26
|
+
.action(async () => {
|
|
27
|
+
try {
|
|
28
|
+
await sitesList();
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.error(`Error: ${err.message}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
sites
|
|
36
|
+
.command('add')
|
|
37
|
+
.description('Add a new site interactively')
|
|
38
|
+
.action(async () => {
|
|
39
|
+
try {
|
|
40
|
+
await sitesAdd();
|
|
41
|
+
} catch (err) {
|
|
42
|
+
console.error(`Error: ${err.message}`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
sites
|
|
48
|
+
.command('remove <name>')
|
|
49
|
+
.description('Remove a site by name')
|
|
50
|
+
.action((name) => {
|
|
51
|
+
try {
|
|
52
|
+
sitesRemove(name);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error(`Error: ${err.message}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
program
|
|
60
|
+
.command('stats [site-name]')
|
|
61
|
+
.description('Show WordPress post/page stats for all or one site')
|
|
62
|
+
.action(async (siteName) => {
|
|
63
|
+
try {
|
|
64
|
+
await statsCommand(siteName);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.error(`Error: ${err.message}`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
program
|
|
72
|
+
.command('health [site-name]')
|
|
73
|
+
.description('Show health metrics for all or one site')
|
|
74
|
+
.action(async (siteName) => {
|
|
75
|
+
try {
|
|
76
|
+
await healthCommand(siteName);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.error(`Error: ${err.message}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
program
|
|
84
|
+
.command('publish <file>')
|
|
85
|
+
.description('Publish a markdown file as a WordPress post')
|
|
86
|
+
.requiredOption('--site <name>', 'Target site name')
|
|
87
|
+
.option('--draft', 'Publish as draft instead of live')
|
|
88
|
+
.option('--schedule <datetime>', 'Schedule post (e.g. "2026-04-01 09:00")')
|
|
89
|
+
.action(async (file, options) => {
|
|
90
|
+
try {
|
|
91
|
+
await publishCommand(file, options);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
console.error(`Error: ${err.message}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
program
|
|
99
|
+
.command('bulk-publish <directory>')
|
|
100
|
+
.description('Publish all .md files in a directory')
|
|
101
|
+
.option('--site <name>', 'Publish all files to this site')
|
|
102
|
+
.option('--distribute', 'Round-robin distribution across all configured sites')
|
|
103
|
+
.option('--dry-run', 'Show what would be published without doing it')
|
|
104
|
+
.option('--draft', 'Publish all as drafts')
|
|
105
|
+
.action(async (directory, options) => {
|
|
106
|
+
try {
|
|
107
|
+
await bulkPublishCommand(directory, options);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
console.error(`Error: ${err.message}`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
program
|
|
115
|
+
.command('interlink')
|
|
116
|
+
.description('Suggest internal links between posts on a site')
|
|
117
|
+
.option('--site <name>', 'Site to analyze (defaults to first site)')
|
|
118
|
+
.option('--auto', 'Automatically apply suggestions via WP REST API')
|
|
119
|
+
.option('--min-relevance <score>', 'Minimum relevance score 0-100 (default: 50)', '50')
|
|
120
|
+
.action(async (options) => {
|
|
121
|
+
try {
|
|
122
|
+
await interlinkCommand(options);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.error(`Error: ${err.message}`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
program
|
|
130
|
+
.command('crosslink <siteA> <siteB>')
|
|
131
|
+
.description('Find cross-linking opportunities between two sites')
|
|
132
|
+
.action(async (siteA, siteB) => {
|
|
133
|
+
try {
|
|
134
|
+
await crosslinkCommand(siteA, siteB);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.error(`Error: ${err.message}`);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
program
|
|
142
|
+
.command('traffic')
|
|
143
|
+
.description('Show traffic data for sites (Jetpack/GA4)')
|
|
144
|
+
.option('--site <name>', 'Show traffic for one site')
|
|
145
|
+
.action(async (options) => {
|
|
146
|
+
try {
|
|
147
|
+
await trafficCommand(options);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
console.error(`Error: ${err.message}`);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
program
|
|
155
|
+
.command('report')
|
|
156
|
+
.description('Generate a site report combining stats and health data')
|
|
157
|
+
.option('--site <name>', 'Generate report for one site')
|
|
158
|
+
.option('--month <YYYY-MM>', 'Report month (default: current)')
|
|
159
|
+
.option('--format <format>', 'Output format: md or html (default: md)', 'md')
|
|
160
|
+
.option('--output <file>', 'Save report to file (default: stdout)')
|
|
161
|
+
.action(async (options) => {
|
|
162
|
+
try {
|
|
163
|
+
await reportCommand(options);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.error(`Error: ${err.message}`);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
program
|
|
171
|
+
.command('update')
|
|
172
|
+
.description('List and apply plugin updates via WP REST API')
|
|
173
|
+
.option('--site <name>', 'Check one site')
|
|
174
|
+
.option('--all', 'Check all sites')
|
|
175
|
+
.option('--apply', 'Apply available updates')
|
|
176
|
+
.action(async (options) => {
|
|
177
|
+
try {
|
|
178
|
+
await updateCommand(options);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
console.error(`Error: ${err.message}`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
program
|
|
186
|
+
.command('security')
|
|
187
|
+
.description('Run a security scan on sites (headers, SSL, WP hardening)')
|
|
188
|
+
.option('--site <name>', 'Scan one site')
|
|
189
|
+
.action(async (options) => {
|
|
190
|
+
try {
|
|
191
|
+
await securityCommand(options);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
console.error(`Error: ${err.message}`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
program
|
|
199
|
+
.command('ai-write')
|
|
200
|
+
.description('Generate an SEO-optimized article and publish it to a site')
|
|
201
|
+
.requiredOption('--topic <topic>', 'Article topic')
|
|
202
|
+
.requiredOption('--site <name>', 'Target site name')
|
|
203
|
+
.option('--lang <lang>', 'Language code (default: en)', 'en')
|
|
204
|
+
.option('--words <n>', 'Target word count (default: 1500)', '1500')
|
|
205
|
+
.option('--keywords <list>', 'Comma-separated keywords to target')
|
|
206
|
+
.option('--draft', 'Save as local .md file instead of publishing')
|
|
207
|
+
.action(async (options) => {
|
|
208
|
+
try {
|
|
209
|
+
await aiWriteCommand(options);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
console.error(`Error: ${err.message}`);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
program
|
|
217
|
+
.command('ai-optimize')
|
|
218
|
+
.description('Analyze and optimize post SEO on a site')
|
|
219
|
+
.requiredOption('--site <name>', 'Site to analyze')
|
|
220
|
+
.option('--post <id>', 'Analyze a specific post by ID')
|
|
221
|
+
.option('--apply', 'Apply optimizations via WP API (requires --post)')
|
|
222
|
+
.action(async (options) => {
|
|
223
|
+
try {
|
|
224
|
+
await aiOptimizeCommand(options);
|
|
225
|
+
} catch (err) {
|
|
226
|
+
console.error(`Error: ${err.message}`);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import Table from 'cli-table3';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
|
|
4
|
+
export function makeTable(head, colWidths) {
|
|
5
|
+
return new Table({
|
|
6
|
+
head: head.map(h => chalk.bold(h)),
|
|
7
|
+
colWidths,
|
|
8
|
+
style: { head: [], border: [] },
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function statusBadge(online) {
|
|
13
|
+
return online ? chalk.green('online') : chalk.red('offline');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function colorByAge(days) {
|
|
17
|
+
if (days === null) return chalk.gray('n/a');
|
|
18
|
+
if (days <= 14) return chalk.red(`${days}d`);
|
|
19
|
+
if (days <= 30) return chalk.yellow(`${days}d`);
|
|
20
|
+
return chalk.green(`${days}d`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function colorByResponseTime(ms) {
|
|
24
|
+
if (ms === null) return chalk.gray('n/a');
|
|
25
|
+
if (ms > 2000) return chalk.red(`${ms}ms`);
|
|
26
|
+
if (ms > 800) return chalk.yellow(`${ms}ms`);
|
|
27
|
+
return chalk.green(`${ms}ms`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function formatDate(isoString) {
|
|
31
|
+
if (!isoString) return chalk.gray('n/a');
|
|
32
|
+
try {
|
|
33
|
+
return new Date(isoString).toISOString().slice(0, 10);
|
|
34
|
+
} catch {
|
|
35
|
+
return chalk.gray('n/a');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function na() {
|
|
40
|
+
return chalk.gray('n/a');
|
|
41
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import https from 'https';
|
|
2
|
+
import http from 'http';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
|
|
5
|
+
export async function headCheck(url) {
|
|
6
|
+
const start = Date.now();
|
|
7
|
+
try {
|
|
8
|
+
await axios.head(url, { timeout: 8000, validateStatus: () => true });
|
|
9
|
+
return { online: true, responseTime: Date.now() - start };
|
|
10
|
+
} catch {
|
|
11
|
+
return { online: false, responseTime: null };
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function getResponseTime(url) {
|
|
16
|
+
const start = Date.now();
|
|
17
|
+
try {
|
|
18
|
+
const res = await axios.get(url, { timeout: 10000, validateStatus: () => true });
|
|
19
|
+
return { ms: Date.now() - start, status: res.status };
|
|
20
|
+
} catch {
|
|
21
|
+
return { ms: null, status: null };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getSslExpiry(hostname) {
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
const options = { host: hostname, port: 443, method: 'HEAD', rejectUnauthorized: false };
|
|
28
|
+
const req = https.request(options, (res) => {
|
|
29
|
+
const cert = res.socket.getPeerCertificate();
|
|
30
|
+
if (cert && cert.valid_to) {
|
|
31
|
+
resolve(new Date(cert.valid_to));
|
|
32
|
+
} else {
|
|
33
|
+
resolve(null);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
req.on('error', () => resolve(null));
|
|
37
|
+
req.setTimeout(8000, () => { req.destroy(); resolve(null); });
|
|
38
|
+
req.end();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
function makeClient(site) {
|
|
4
|
+
const baseURL = site.url.replace(/\/$/, '') + '/wp-json';
|
|
5
|
+
const config = { baseURL, timeout: 12000, validateStatus: () => true };
|
|
6
|
+
if (site.wp_user && site.wp_app_password) {
|
|
7
|
+
const token = Buffer.from(`${site.wp_user}:${site.wp_app_password}`).toString('base64');
|
|
8
|
+
config.headers = { Authorization: `Basic ${token}` };
|
|
9
|
+
}
|
|
10
|
+
return axios.create(config);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function getWpInfo(site) {
|
|
14
|
+
const client = makeClient(site);
|
|
15
|
+
try {
|
|
16
|
+
const res = await client.get('/');
|
|
17
|
+
if (res.status !== 200) return null;
|
|
18
|
+
return res.data;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function getPostCount(site) {
|
|
25
|
+
const client = makeClient(site);
|
|
26
|
+
try {
|
|
27
|
+
const res = await client.get('/wp/v2/posts', { params: { per_page: 1 } });
|
|
28
|
+
if (res.status !== 200) return null;
|
|
29
|
+
return parseInt(res.headers['x-wp-total'] || '0', 10);
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function getPageCount(site) {
|
|
36
|
+
const client = makeClient(site);
|
|
37
|
+
try {
|
|
38
|
+
const res = await client.get('/wp/v2/pages', { params: { per_page: 1 } });
|
|
39
|
+
if (res.status !== 200) return null;
|
|
40
|
+
return parseInt(res.headers['x-wp-total'] || '0', 10);
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function getLastPublished(site) {
|
|
47
|
+
const client = makeClient(site);
|
|
48
|
+
try {
|
|
49
|
+
const res = await client.get('/wp/v2/posts', { params: { per_page: 1, orderby: 'date', order: 'desc' } });
|
|
50
|
+
if (res.status !== 200 || !res.data || !res.data[0]) return null;
|
|
51
|
+
return res.data[0].date;
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function getPlugins(site) {
|
|
58
|
+
const client = makeClient(site);
|
|
59
|
+
try {
|
|
60
|
+
const res = await client.get('/wp/v2/plugins', { params: { per_page: 100 } });
|
|
61
|
+
if (res.status !== 200 || !Array.isArray(res.data)) return null;
|
|
62
|
+
return res.data;
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function validateWpApi(url) {
|
|
69
|
+
try {
|
|
70
|
+
const res = await axios.get(url.replace(/\/$/, '') + '/wp-json/', { timeout: 8000, validateStatus: () => true });
|
|
71
|
+
return res.status === 200 && res.data && res.data.name;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|