@jezweb/jezpress-cli 1.7.0 → 1.8.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/dist/cli.js +15 -1153
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +29 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +15 -2
- package/dist/client.js.map +1 -1
- package/dist/commands/auth.d.ts +7 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +56 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/docs.d.ts +7 -0
- package/dist/commands/docs.d.ts.map +1 -0
- package/dist/commands/docs.js +41 -0
- package/dist/commands/docs.js.map +1 -0
- package/dist/commands/plugins.d.ts +7 -0
- package/dist/commands/plugins.d.ts.map +1 -0
- package/dist/commands/plugins.js +468 -0
- package/dist/commands/plugins.js.map +1 -0
- package/dist/commands/sites/bulk.d.ts +7 -0
- package/dist/commands/sites/bulk.d.ts.map +1 -0
- package/dist/commands/sites/bulk.js +80 -0
- package/dist/commands/sites/bulk.js.map +1 -0
- package/dist/commands/sites/export.d.ts +7 -0
- package/dist/commands/sites/export.d.ts.map +1 -0
- package/dist/commands/sites/export.js +85 -0
- package/dist/commands/sites/export.js.map +1 -0
- package/dist/commands/sites/index.d.ts +7 -0
- package/dist/commands/sites/index.d.ts.map +1 -0
- package/dist/commands/sites/index.js +406 -0
- package/dist/commands/sites/index.js.map +1 -0
- package/dist/flare/detect.d.ts +18 -0
- package/dist/flare/detect.d.ts.map +1 -0
- package/dist/flare/detect.js +130 -0
- package/dist/flare/detect.js.map +1 -0
- package/dist/flare/html-to-content.d.ts +21 -0
- package/dist/flare/html-to-content.d.ts.map +1 -0
- package/dist/flare/html-to-content.js +233 -0
- package/dist/flare/html-to-content.js.map +1 -0
- package/dist/flare/index.d.ts +62 -0
- package/dist/flare/index.d.ts.map +1 -0
- package/dist/flare/index.js +566 -0
- package/dist/flare/index.js.map +1 -0
- package/dist/flare/types.d.ts +171 -0
- package/dist/flare/types.d.ts.map +1 -0
- package/dist/flare/types.js +6 -0
- package/dist/flare/types.js.map +1 -0
- package/dist/utils/plugin-headers.d.ts +14 -0
- package/dist/utils/plugin-headers.d.ts.map +1 -0
- package/dist/utils/plugin-headers.js +52 -0
- package/dist/utils/plugin-headers.js.map +1 -0
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -1,1170 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* JezPress CLI
|
|
4
|
+
* CLI for managing JezPress plugins on the update server
|
|
5
|
+
*/
|
|
2
6
|
import { Command } from 'commander';
|
|
3
|
-
import {
|
|
4
|
-
import { deleteToken } from './config.js';
|
|
5
|
-
import * as api from './client.js';
|
|
6
|
-
import { existsSync, statSync, writeFileSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
7
8
|
import { fileURLToPath } from 'node:url';
|
|
8
9
|
import { dirname, join } from 'node:path';
|
|
9
|
-
|
|
10
|
-
import {
|
|
10
|
+
// Command modules
|
|
11
|
+
import { registerAuthCommands } from './commands/auth.js';
|
|
12
|
+
import { registerDocsCommand } from './commands/docs.js';
|
|
13
|
+
import { registerPluginCommands } from './commands/plugins.js';
|
|
14
|
+
import { registerSitesCommands } from './commands/sites/index.js';
|
|
11
15
|
// Read version from package.json
|
|
12
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
17
|
const __dirname = dirname(__filename);
|
|
14
18
|
const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
15
19
|
const VERSION = packageJson.version;
|
|
16
20
|
const program = new Command();
|
|
17
|
-
/**
|
|
18
|
-
* Extract WordPress plugin headers from a ZIP file
|
|
19
|
-
*/
|
|
20
|
-
function extractPluginHeaders(zipPath, slug) {
|
|
21
|
-
let zip;
|
|
22
|
-
try {
|
|
23
|
-
zip = new AdmZip(zipPath);
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
const entries = zip.getEntries();
|
|
29
|
-
// Find main plugin file (slug/slug.php or slug.php)
|
|
30
|
-
const mainFilePath1 = `${slug}/${slug}.php`;
|
|
31
|
-
const mainFilePath2 = `${slug}.php`;
|
|
32
|
-
const mainFileEntry = entries.find((e) => e.entryName.toLowerCase() === mainFilePath1.toLowerCase() ||
|
|
33
|
-
e.entryName.toLowerCase() === mainFilePath2.toLowerCase());
|
|
34
|
-
if (!mainFileEntry) {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
const content = mainFileEntry.getData().toString('utf-8');
|
|
38
|
-
const headers = {};
|
|
39
|
-
// Extract headers using regex
|
|
40
|
-
const nameMatch = content.match(/Plugin Name:\s*(.+)/i);
|
|
41
|
-
if (nameMatch)
|
|
42
|
-
headers.name = nameMatch[1].trim();
|
|
43
|
-
const versionMatch = content.match(/Version:\s*(\d+\.\d+(\.\d+)?)/i);
|
|
44
|
-
if (versionMatch)
|
|
45
|
-
headers.version = versionMatch[1];
|
|
46
|
-
const authorMatch = content.match(/Author:\s*(.+)/i);
|
|
47
|
-
if (authorMatch)
|
|
48
|
-
headers.author = authorMatch[1].trim();
|
|
49
|
-
const requiresWPMatch = content.match(/Requires at least:\s*(\d+\.\d+)/i);
|
|
50
|
-
if (requiresWPMatch)
|
|
51
|
-
headers.requires_wp = requiresWPMatch[1];
|
|
52
|
-
const requiresPHPMatch = content.match(/Requires PHP:\s*(\d+\.\d+)/i);
|
|
53
|
-
if (requiresPHPMatch)
|
|
54
|
-
headers.requires_php = requiresPHPMatch[1];
|
|
55
|
-
const testedWPMatch = content.match(/Tested up to:\s*(\d+\.\d+)/i);
|
|
56
|
-
if (testedWPMatch)
|
|
57
|
-
headers.tested_wp = testedWPMatch[1];
|
|
58
|
-
const descMatch = content.match(/Description:\s*(.+)/i);
|
|
59
|
-
if (descMatch)
|
|
60
|
-
headers.description = descMatch[1].trim();
|
|
61
|
-
return headers;
|
|
62
|
-
}
|
|
63
21
|
program
|
|
64
22
|
.name('jezpress')
|
|
65
23
|
.description('CLI for managing JezPress plugins on the update server')
|
|
66
24
|
.version(VERSION);
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
program
|
|
71
|
-
|
|
72
|
-
.description('Log in with your Jezweb Google account')
|
|
73
|
-
.action(async () => {
|
|
74
|
-
try {
|
|
75
|
-
console.log('Starting login...');
|
|
76
|
-
const user = await login();
|
|
77
|
-
console.log(`\nLogged in as ${user.name} (${user.email})`);
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
console.error('Login failed:', error.message);
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
program
|
|
85
|
-
.command('logout')
|
|
86
|
-
.description('Log out and remove stored token')
|
|
87
|
-
.action(() => {
|
|
88
|
-
deleteToken();
|
|
89
|
-
console.log('Logged out successfully');
|
|
90
|
-
});
|
|
91
|
-
// ============================================================
|
|
92
|
-
// Docs Command
|
|
93
|
-
// ============================================================
|
|
94
|
-
program
|
|
95
|
-
.command('docs')
|
|
96
|
-
.description('Display JezPress platform guide for developers and Claude Code')
|
|
97
|
-
.option('--save [path]', 'Save guide to file (default: JEZPRESS.md)')
|
|
98
|
-
.option('--claude', 'Generate project-level CLAUDE.md template')
|
|
99
|
-
.action((options) => {
|
|
100
|
-
if (options.claude) {
|
|
101
|
-
const path = 'CLAUDE.md';
|
|
102
|
-
const jezpressSection = `\n\n---\n\n${PLATFORM_GUIDE}`;
|
|
103
|
-
if (existsSync(path)) {
|
|
104
|
-
// Append to existing CLAUDE.md
|
|
105
|
-
const existing = readFileSync(path, 'utf-8');
|
|
106
|
-
if (existing.includes('# JezPress Platform Guide')) {
|
|
107
|
-
console.log('CLAUDE.md already contains JezPress guide.');
|
|
108
|
-
console.log('Remove the existing JezPress section first if you want to update it.');
|
|
109
|
-
process.exit(1);
|
|
110
|
-
}
|
|
111
|
-
writeFileSync(path, existing + jezpressSection);
|
|
112
|
-
console.log(`JezPress guide appended to existing CLAUDE.md`);
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
// Create new CLAUDE.md with full guide
|
|
116
|
-
writeFileSync(path, PLATFORM_GUIDE);
|
|
117
|
-
console.log(`CLAUDE.md created with full JezPress platform guide.`);
|
|
118
|
-
}
|
|
119
|
-
console.log('Your Claude Code now has full context about JezPress.');
|
|
120
|
-
}
|
|
121
|
-
else if (options.save !== undefined) {
|
|
122
|
-
const path = typeof options.save === 'string' ? options.save : 'JEZPRESS.md';
|
|
123
|
-
writeFileSync(path, PLATFORM_GUIDE);
|
|
124
|
-
console.log(`Platform guide saved to: ${path}`);
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
console.log(PLATFORM_GUIDE);
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
program
|
|
131
|
-
.command('whoami')
|
|
132
|
-
.description('Show current user info and owned plugins')
|
|
133
|
-
.action(async () => {
|
|
134
|
-
// First check local auth
|
|
135
|
-
const auth = checkAuth();
|
|
136
|
-
if (!auth.loggedIn) {
|
|
137
|
-
console.error('Not logged in. Run `jezpress login` first.');
|
|
138
|
-
process.exit(1);
|
|
139
|
-
}
|
|
140
|
-
try {
|
|
141
|
-
const data = await api.whoami();
|
|
142
|
-
console.log(`\nLogged in as: ${data.user.name}`);
|
|
143
|
-
console.log(`Email: ${data.user.email}`);
|
|
144
|
-
console.log(`Token expires in: ${auth.expiresIn}`);
|
|
145
|
-
console.log(`\nPlugins owned (${data.plugins_owned.length}):`);
|
|
146
|
-
if (data.plugins_owned.length === 0) {
|
|
147
|
-
console.log(' (none)');
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
data.plugins_owned.forEach((slug) => {
|
|
151
|
-
console.log(` - ${slug}`);
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
console.error('Error:', error.message);
|
|
157
|
-
process.exit(1);
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
// ============================================================
|
|
161
|
-
// Plugin Commands
|
|
162
|
-
// ============================================================
|
|
163
|
-
const plugins = program
|
|
164
|
-
.command('plugins')
|
|
165
|
-
.description('Manage plugins');
|
|
166
|
-
plugins
|
|
167
|
-
.command('list')
|
|
168
|
-
.description('List all plugins')
|
|
169
|
-
.option('--mine', 'Only show plugins you own')
|
|
170
|
-
.action(async (options) => {
|
|
171
|
-
try {
|
|
172
|
-
const pluginsList = await api.listPlugins();
|
|
173
|
-
let filtered = pluginsList;
|
|
174
|
-
if (options.mine) {
|
|
175
|
-
filtered = pluginsList.filter((p) => p.is_owner);
|
|
176
|
-
}
|
|
177
|
-
console.log(`\nPlugins (${filtered.length}):\n`);
|
|
178
|
-
if (filtered.length === 0) {
|
|
179
|
-
console.log(' (none)');
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
filtered.forEach((p) => {
|
|
183
|
-
const ownerMark = p.is_owner ? ' (yours)' : '';
|
|
184
|
-
console.log(` ${p.slug} v${p.current_version}${ownerMark}`);
|
|
185
|
-
console.log(` Name: ${p.name}`);
|
|
186
|
-
console.log(` Updated: ${p.last_updated}`);
|
|
187
|
-
console.log('');
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
catch (error) {
|
|
191
|
-
console.error('Error:', error.message);
|
|
192
|
-
process.exit(1);
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
plugins
|
|
196
|
-
.command('get <slug>')
|
|
197
|
-
.description('Get plugin details')
|
|
198
|
-
.action(async (slug) => {
|
|
199
|
-
try {
|
|
200
|
-
const data = await api.getPlugin(slug);
|
|
201
|
-
const p = data.plugin;
|
|
202
|
-
console.log(`\n${p.name} (${p.slug})\n`);
|
|
203
|
-
console.log(`Version: ${p.current_version}`);
|
|
204
|
-
console.log(`Type: ${p.type}`);
|
|
205
|
-
console.log(`Author: ${p.author}`);
|
|
206
|
-
console.log(`Requires PHP: ${p.requires_php}`);
|
|
207
|
-
console.log(`Requires WP: ${p.requires_wp}`);
|
|
208
|
-
console.log(`Tested WP: ${p.tested_wp}`);
|
|
209
|
-
console.log(`License required: ${p.requires_license ? 'Yes' : 'No'}`);
|
|
210
|
-
console.log(`Last updated: ${p.last_updated}`);
|
|
211
|
-
console.log(`Owner: ${p.owner_email || 'unassigned'}`);
|
|
212
|
-
console.log(`You own this: ${p.is_owner ? 'Yes' : 'No'}`);
|
|
213
|
-
console.log(`\nAvailable versions: ${data.available_versions.join(', ') || 'none'}`);
|
|
214
|
-
console.log(`Total downloads: ${data.download_stats?.total || 0}`);
|
|
215
|
-
}
|
|
216
|
-
catch (error) {
|
|
217
|
-
console.error('Error:', error.message);
|
|
218
|
-
process.exit(1);
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
plugins
|
|
222
|
-
.command('create <slug>')
|
|
223
|
-
.description('Create a new plugin (you become the owner)')
|
|
224
|
-
.requiredOption('--name <name>', 'Plugin display name')
|
|
225
|
-
.option('-v, --ver <version>', 'Initial version', '1.0.0')
|
|
226
|
-
.option('--type <type>', 'Type: plugin or theme', 'plugin')
|
|
227
|
-
.option('--no-license', 'Plugin does not require license')
|
|
228
|
-
.option('--author <author>', 'Author name', 'Jezweb')
|
|
229
|
-
.action(async (slug, options) => {
|
|
230
|
-
try {
|
|
231
|
-
const plugin = await api.createPlugin({
|
|
232
|
-
slug,
|
|
233
|
-
name: options.name,
|
|
234
|
-
version: options.ver,
|
|
235
|
-
type: options.type,
|
|
236
|
-
requiresLicense: options.license !== false,
|
|
237
|
-
author: options.author,
|
|
238
|
-
});
|
|
239
|
-
console.log(`\nPlugin created: ${plugin.slug}`);
|
|
240
|
-
console.log(`Name: ${plugin.name}`);
|
|
241
|
-
console.log(`Version: ${plugin.current_version}`);
|
|
242
|
-
console.log(`Owner: ${plugin.owner_email}`);
|
|
243
|
-
console.log('\nNext: Upload a version with `jezpress plugins upload`');
|
|
244
|
-
}
|
|
245
|
-
catch (error) {
|
|
246
|
-
console.error('Error:', error.message);
|
|
247
|
-
process.exit(1);
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
plugins
|
|
251
|
-
.command('update <slug>')
|
|
252
|
-
.description('Update plugin metadata (owner only)')
|
|
253
|
-
.option('--name <name>', 'Update display name')
|
|
254
|
-
.option('--tested-wp <version>', 'Update tested WP version')
|
|
255
|
-
.option('--requires-wp <version>', 'Update minimum WP version')
|
|
256
|
-
.option('--requires-php <version>', 'Update minimum PHP version')
|
|
257
|
-
.action(async (slug, options) => {
|
|
258
|
-
try {
|
|
259
|
-
const updates = {};
|
|
260
|
-
if (options.name)
|
|
261
|
-
updates.name = options.name;
|
|
262
|
-
if (options.testedWp)
|
|
263
|
-
updates.tested_wp = options.testedWp;
|
|
264
|
-
if (options.requiresWp)
|
|
265
|
-
updates.requires_wp = options.requiresWp;
|
|
266
|
-
if (options.requiresPhp)
|
|
267
|
-
updates.requires_php = options.requiresPhp;
|
|
268
|
-
if (Object.keys(updates).length === 0) {
|
|
269
|
-
console.error('No updates provided. Use --name, --tested-wp, etc.');
|
|
270
|
-
process.exit(1);
|
|
271
|
-
}
|
|
272
|
-
const plugin = await api.updatePlugin(slug, updates);
|
|
273
|
-
console.log(`\nPlugin updated: ${plugin.slug}`);
|
|
274
|
-
console.log(`Name: ${plugin.name}`);
|
|
275
|
-
console.log(`Tested WP: ${plugin.tested_wp}`);
|
|
276
|
-
}
|
|
277
|
-
catch (error) {
|
|
278
|
-
console.error('Error:', error.message);
|
|
279
|
-
process.exit(1);
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
plugins
|
|
283
|
-
.command('upload <slug> <zipFile>')
|
|
284
|
-
.description('Upload a new plugin version (owner only)')
|
|
285
|
-
.option('-v, --ver <version>', 'Version number (auto-detected from ZIP if omitted)')
|
|
286
|
-
.option('--sync-metadata', 'Update plugin metadata (requires_php, requires_wp, tested_wp) from ZIP headers')
|
|
287
|
-
.action(async (slug, zipFile, options) => {
|
|
288
|
-
// Check file exists
|
|
289
|
-
if (!existsSync(zipFile)) {
|
|
290
|
-
console.error(`File not found: ${zipFile}`);
|
|
291
|
-
process.exit(1);
|
|
292
|
-
}
|
|
293
|
-
try {
|
|
294
|
-
// Extract headers from ZIP
|
|
295
|
-
const headers = extractPluginHeaders(zipFile, slug);
|
|
296
|
-
if (!headers) {
|
|
297
|
-
console.error(`Could not find main plugin file in ZIP. Expected ${slug}/${slug}.php`);
|
|
298
|
-
process.exit(1);
|
|
299
|
-
}
|
|
300
|
-
// Determine version: use --ver if provided, otherwise extract from ZIP
|
|
301
|
-
let version = options.ver;
|
|
302
|
-
if (!version) {
|
|
303
|
-
if (!headers.version) {
|
|
304
|
-
console.error('Could not extract version from plugin header. Use -v to specify manually.');
|
|
305
|
-
process.exit(1);
|
|
306
|
-
}
|
|
307
|
-
version = headers.version;
|
|
308
|
-
console.log(`Auto-detected version: ${version}`);
|
|
309
|
-
}
|
|
310
|
-
console.log(`Uploading ${zipFile} as version ${version}...`);
|
|
311
|
-
const result = await api.uploadVersion(slug, version, zipFile);
|
|
312
|
-
console.log(`\n${result.message}`);
|
|
313
|
-
console.log(`Size: ${(result.size_bytes / 1024).toFixed(1)} KB`);
|
|
314
|
-
// Sync metadata if requested
|
|
315
|
-
if (options.syncMetadata) {
|
|
316
|
-
const updates = {};
|
|
317
|
-
let hasUpdates = false;
|
|
318
|
-
if (headers.requires_php) {
|
|
319
|
-
updates.requires_php = headers.requires_php;
|
|
320
|
-
hasUpdates = true;
|
|
321
|
-
}
|
|
322
|
-
if (headers.requires_wp) {
|
|
323
|
-
updates.requires_wp = headers.requires_wp;
|
|
324
|
-
hasUpdates = true;
|
|
325
|
-
}
|
|
326
|
-
if (headers.tested_wp) {
|
|
327
|
-
updates.tested_wp = headers.tested_wp;
|
|
328
|
-
hasUpdates = true;
|
|
329
|
-
}
|
|
330
|
-
if (hasUpdates) {
|
|
331
|
-
console.log('\nSyncing metadata from plugin headers...');
|
|
332
|
-
const plugin = await api.updatePlugin(slug, updates);
|
|
333
|
-
console.log(` Requires PHP: ${plugin.requires_php}`);
|
|
334
|
-
console.log(` Requires WP: ${plugin.requires_wp}`);
|
|
335
|
-
console.log(` Tested WP: ${plugin.tested_wp}`);
|
|
336
|
-
}
|
|
337
|
-
else {
|
|
338
|
-
console.log('\nNo metadata headers found in plugin file to sync.');
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
catch (error) {
|
|
343
|
-
console.error('Error:', error.message);
|
|
344
|
-
process.exit(1);
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
plugins
|
|
348
|
-
.command('preflight <slug> <zipFile>')
|
|
349
|
-
.description('Validate plugin ZIP before uploading')
|
|
350
|
-
.action(async (slug, zipFile) => {
|
|
351
|
-
// Check file exists
|
|
352
|
-
if (!existsSync(zipFile)) {
|
|
353
|
-
console.error(`File not found: ${zipFile}`);
|
|
354
|
-
process.exit(1);
|
|
355
|
-
}
|
|
356
|
-
console.log(`\nValidating ${zipFile} for plugin "${slug}"...\n`);
|
|
357
|
-
const issues = [];
|
|
358
|
-
const warnings = [];
|
|
359
|
-
const info = [];
|
|
360
|
-
// Open ZIP file
|
|
361
|
-
let zip;
|
|
362
|
-
try {
|
|
363
|
-
zip = new AdmZip(zipFile);
|
|
364
|
-
}
|
|
365
|
-
catch {
|
|
366
|
-
console.error('❌ FAILED: Not a valid ZIP file');
|
|
367
|
-
process.exit(1);
|
|
368
|
-
}
|
|
369
|
-
const entries = zip.getEntries();
|
|
370
|
-
const entryNames = entries.map((e) => e.entryName);
|
|
371
|
-
// Get ZIP file size from filesystem (don't use toBuffer - it corrupts entries)
|
|
372
|
-
const zipStats = statSync(zipFile);
|
|
373
|
-
info.push(`ZIP file size: ${(zipStats.size / 1024).toFixed(1)} KB`);
|
|
374
|
-
info.push(`Files in ZIP: ${entries.length}`);
|
|
375
|
-
// Find main plugin file (slug/slug.php or slug.php)
|
|
376
|
-
const mainFilePath1 = `${slug}/${slug}.php`;
|
|
377
|
-
const mainFilePath2 = `${slug}.php`;
|
|
378
|
-
let mainFileEntry = entries.find((e) => e.entryName.toLowerCase() === mainFilePath1.toLowerCase() ||
|
|
379
|
-
e.entryName.toLowerCase() === mainFilePath2.toLowerCase());
|
|
380
|
-
if (!mainFileEntry) {
|
|
381
|
-
issues.push(`Missing main plugin file: expected ${mainFilePath1} or ${mainFilePath2}`);
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
info.push(`Main plugin file: ${mainFileEntry.entryName}`);
|
|
385
|
-
// Read and parse the main plugin file
|
|
386
|
-
const mainFileContent = mainFileEntry.getData().toString('utf-8');
|
|
387
|
-
// Check for WordPress plugin headers
|
|
388
|
-
const pluginNameMatch = mainFileContent.match(/Plugin Name:\s*(.+)/i);
|
|
389
|
-
const versionMatch = mainFileContent.match(/Version:\s*(\d+\.\d+(\.\d+)?)/i);
|
|
390
|
-
const authorMatch = mainFileContent.match(/Author:\s*(.+)/i);
|
|
391
|
-
const requiresWPMatch = mainFileContent.match(/Requires at least:\s*(\d+\.\d+)/i);
|
|
392
|
-
const requiresPHPMatch = mainFileContent.match(/Requires PHP:\s*(\d+\.\d+)/i);
|
|
393
|
-
const testedWPMatch = mainFileContent.match(/Tested up to:\s*(\d+\.\d+)/i);
|
|
394
|
-
if (!pluginNameMatch) {
|
|
395
|
-
issues.push("Missing 'Plugin Name' header in main plugin file");
|
|
396
|
-
}
|
|
397
|
-
else {
|
|
398
|
-
info.push(`Plugin Name: ${pluginNameMatch[1].trim()}`);
|
|
399
|
-
}
|
|
400
|
-
if (!versionMatch) {
|
|
401
|
-
issues.push("Missing 'Version' header in main plugin file");
|
|
402
|
-
}
|
|
403
|
-
else {
|
|
404
|
-
info.push(`Version: ${versionMatch[1]}`);
|
|
405
|
-
}
|
|
406
|
-
if (!authorMatch) {
|
|
407
|
-
warnings.push("Missing 'Author' header (recommended)");
|
|
408
|
-
}
|
|
409
|
-
if (!requiresWPMatch) {
|
|
410
|
-
warnings.push("Missing 'Requires at least' header (recommended)");
|
|
411
|
-
}
|
|
412
|
-
if (!requiresPHPMatch) {
|
|
413
|
-
warnings.push("Missing 'Requires PHP' header (recommended)");
|
|
414
|
-
}
|
|
415
|
-
if (!testedWPMatch) {
|
|
416
|
-
warnings.push("Missing 'Tested up to' header (recommended)");
|
|
417
|
-
}
|
|
418
|
-
// Security checks on all PHP files
|
|
419
|
-
const phpEntries = entries.filter((e) => e.entryName.toLowerCase().endsWith('.php'));
|
|
420
|
-
let hasEval = false;
|
|
421
|
-
let hasBase64 = false;
|
|
422
|
-
let hasShellExec = false;
|
|
423
|
-
for (const entry of phpEntries) {
|
|
424
|
-
const content = entry.getData().toString('utf-8');
|
|
425
|
-
if (/\beval\s*\(/i.test(content))
|
|
426
|
-
hasEval = true;
|
|
427
|
-
if (/\bbase64_decode\s*\(/i.test(content))
|
|
428
|
-
hasBase64 = true;
|
|
429
|
-
if (/\b(shell_exec|exec)\s*\(/i.test(content))
|
|
430
|
-
hasShellExec = true;
|
|
431
|
-
}
|
|
432
|
-
if (hasEval) {
|
|
433
|
-
warnings.push('Uses eval() - ensure this is intentional and safe');
|
|
434
|
-
}
|
|
435
|
-
if (hasBase64) {
|
|
436
|
-
warnings.push('Uses base64_decode() - ensure this is intentional and safe');
|
|
437
|
-
}
|
|
438
|
-
if (hasShellExec) {
|
|
439
|
-
warnings.push('Uses shell_exec/exec - ensure this is intentional and safe');
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
// Check for readme.txt
|
|
443
|
-
const readmePath = `${slug}/readme.txt`;
|
|
444
|
-
const hasReadme = entryNames.some((name) => name.toLowerCase() === readmePath.toLowerCase());
|
|
445
|
-
if (!hasReadme) {
|
|
446
|
-
warnings.push('Missing readme.txt (recommended for update server)');
|
|
447
|
-
}
|
|
448
|
-
else {
|
|
449
|
-
info.push('readme.txt found');
|
|
450
|
-
}
|
|
451
|
-
// Check folder structure - should be slug/
|
|
452
|
-
const hasCorrectFolder = entryNames.some((name) => name.toLowerCase().startsWith(`${slug.toLowerCase()}/`));
|
|
453
|
-
if (!hasCorrectFolder) {
|
|
454
|
-
issues.push(`ZIP should contain a "${slug}/" folder with all plugin files`);
|
|
455
|
-
}
|
|
456
|
-
// Print results
|
|
457
|
-
console.log('INFO:');
|
|
458
|
-
info.forEach((i) => console.log(` ✓ ${i}`));
|
|
459
|
-
console.log('');
|
|
460
|
-
if (warnings.length > 0) {
|
|
461
|
-
console.log('WARNINGS:');
|
|
462
|
-
warnings.forEach((w) => console.log(` ⚠ ${w}`));
|
|
463
|
-
console.log('');
|
|
464
|
-
}
|
|
465
|
-
if (issues.length > 0) {
|
|
466
|
-
console.log('ISSUES:');
|
|
467
|
-
issues.forEach((i) => console.log(` ✗ ${i}`));
|
|
468
|
-
console.log('');
|
|
469
|
-
console.log(`❌ FAILED: ${issues.length} issue(s) found`);
|
|
470
|
-
process.exit(1);
|
|
471
|
-
}
|
|
472
|
-
console.log(`✅ PASSED: Plugin "${slug}" is ready for upload${warnings.length > 0 ? ` (with ${warnings.length} warning(s))` : ''}`);
|
|
473
|
-
});
|
|
474
|
-
// ============================================================
|
|
475
|
-
// New Version Management Commands
|
|
476
|
-
// ============================================================
|
|
477
|
-
plugins
|
|
478
|
-
.command('versions <slug>')
|
|
479
|
-
.description('List all versions of a plugin')
|
|
480
|
-
.action(async (slug) => {
|
|
481
|
-
try {
|
|
482
|
-
const data = await api.listVersions(slug);
|
|
483
|
-
console.log(`\nVersions for ${data.name} (${data.slug}):`);
|
|
484
|
-
console.log(`Current version: ${data.current_version}`);
|
|
485
|
-
console.log(`Total downloads: ${data.total_downloads}`);
|
|
486
|
-
console.log(`You own this: ${data.is_owner ? 'Yes' : 'No'}\n`);
|
|
487
|
-
if (data.versions.length === 0) {
|
|
488
|
-
console.log(' (no versions)');
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
data.versions.forEach((v) => {
|
|
492
|
-
const isCurrent = v.version === data.current_version ? ' (current)' : '';
|
|
493
|
-
console.log(` v${v.version}${isCurrent}`);
|
|
494
|
-
console.log(` Released: ${v.release_date}`);
|
|
495
|
-
console.log(` Downloads: ${v.downloads}`);
|
|
496
|
-
if (v.changelog) {
|
|
497
|
-
console.log(` Changelog: ${v.changelog.slice(0, 60)}${v.changelog.length > 60 ? '...' : ''}`);
|
|
498
|
-
}
|
|
499
|
-
console.log('');
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
catch (error) {
|
|
503
|
-
console.error('Error:', error.message);
|
|
504
|
-
process.exit(1);
|
|
505
|
-
}
|
|
506
|
-
});
|
|
507
|
-
plugins
|
|
508
|
-
.command('stats <slug>')
|
|
509
|
-
.description('View download statistics for a plugin')
|
|
510
|
-
.action(async (slug) => {
|
|
511
|
-
try {
|
|
512
|
-
const data = await api.getStats(slug);
|
|
513
|
-
console.log(`\nStatistics for ${data.name} (${data.slug}):\n`);
|
|
514
|
-
console.log(` Total downloads: ${data.total_downloads}`);
|
|
515
|
-
console.log(` Version count: ${data.version_count}`);
|
|
516
|
-
console.log(` Current version: ${data.current_version}`);
|
|
517
|
-
console.log(` Last updated: ${data.last_updated}`);
|
|
518
|
-
const byVersion = Object.entries(data.by_version);
|
|
519
|
-
if (byVersion.length > 0) {
|
|
520
|
-
console.log(`\n Downloads by version:`);
|
|
521
|
-
// Sort by version descending
|
|
522
|
-
byVersion.sort((a, b) => {
|
|
523
|
-
const aParts = a[0].split('.').map(Number);
|
|
524
|
-
const bParts = b[0].split('.').map(Number);
|
|
525
|
-
for (let i = 0; i < 3; i++) {
|
|
526
|
-
if ((bParts[i] || 0) !== (aParts[i] || 0)) {
|
|
527
|
-
return (bParts[i] || 0) - (aParts[i] || 0);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
return 0;
|
|
531
|
-
});
|
|
532
|
-
byVersion.forEach(([version, count]) => {
|
|
533
|
-
console.log(` v${version}: ${count}`);
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
catch (error) {
|
|
538
|
-
console.error('Error:', error.message);
|
|
539
|
-
process.exit(1);
|
|
540
|
-
}
|
|
541
|
-
});
|
|
542
|
-
plugins
|
|
543
|
-
.command('download <slug> <version>')
|
|
544
|
-
.description('Download a plugin version ZIP (owner only)')
|
|
545
|
-
.option('-o, --output <path>', 'Output file path')
|
|
546
|
-
.action(async (slug, version, options) => {
|
|
547
|
-
try {
|
|
548
|
-
console.log(`Downloading ${slug} v${version}...`);
|
|
549
|
-
const result = await api.downloadVersion(slug, version);
|
|
550
|
-
const outputPath = options.output || result.filename;
|
|
551
|
-
writeFileSync(outputPath, Buffer.from(result.data));
|
|
552
|
-
console.log(`\n✅ Downloaded to: ${outputPath}`);
|
|
553
|
-
console.log(`Size: ${(result.data.byteLength / 1024).toFixed(1)} KB`);
|
|
554
|
-
}
|
|
555
|
-
catch (error) {
|
|
556
|
-
console.error('Error:', error.message);
|
|
557
|
-
process.exit(1);
|
|
558
|
-
}
|
|
559
|
-
});
|
|
560
|
-
plugins
|
|
561
|
-
.command('delete-version <slug> <version>')
|
|
562
|
-
.description('Delete a plugin version (owner only)')
|
|
563
|
-
.option('-y, --yes', 'Skip confirmation')
|
|
564
|
-
.action(async (slug, version, options) => {
|
|
565
|
-
try {
|
|
566
|
-
if (!options.yes) {
|
|
567
|
-
console.log(`\n⚠️ You are about to delete version ${version} of ${slug}`);
|
|
568
|
-
console.log('This action cannot be undone.');
|
|
569
|
-
console.log('\nRun with --yes to confirm.');
|
|
570
|
-
process.exit(1);
|
|
571
|
-
}
|
|
572
|
-
console.log(`Deleting ${slug} v${version}...`);
|
|
573
|
-
const result = await api.deleteVersion(slug, version);
|
|
574
|
-
console.log(`\n✅ ${result.message}`);
|
|
575
|
-
console.log(`Current version is now: ${result.current_version}`);
|
|
576
|
-
}
|
|
577
|
-
catch (error) {
|
|
578
|
-
console.error('Error:', error.message);
|
|
579
|
-
process.exit(1);
|
|
580
|
-
}
|
|
581
|
-
});
|
|
582
|
-
plugins
|
|
583
|
-
.command('changelog <slug> [version] [text]')
|
|
584
|
-
.description('View or update changelog for a plugin version')
|
|
585
|
-
.option('-s, --set <text>', 'Set changelog text (alternative to positional argument)')
|
|
586
|
-
.action(async (slug, version, text, options) => {
|
|
587
|
-
try {
|
|
588
|
-
// Text can come from positional arg or --set option
|
|
589
|
-
const changelogText = text || options.set;
|
|
590
|
-
if (changelogText) {
|
|
591
|
-
// Update changelog
|
|
592
|
-
if (!version) {
|
|
593
|
-
console.error('Version is required when setting changelog');
|
|
594
|
-
process.exit(1);
|
|
595
|
-
}
|
|
596
|
-
console.log(`Updating changelog for ${slug} v${version}...`);
|
|
597
|
-
const result = await api.updateVersion(slug, version, { changelog: changelogText });
|
|
598
|
-
console.log(`\n✅ ${result.message}`);
|
|
599
|
-
console.log(`Changelog: ${result.version.changelog || '(empty)'}`);
|
|
600
|
-
}
|
|
601
|
-
else {
|
|
602
|
-
// View changelog
|
|
603
|
-
const data = await api.listVersions(slug);
|
|
604
|
-
if (version) {
|
|
605
|
-
// Show specific version changelog
|
|
606
|
-
const v = data.versions.find((ver) => ver.version === version);
|
|
607
|
-
if (!v) {
|
|
608
|
-
console.error(`Version ${version} not found`);
|
|
609
|
-
process.exit(1);
|
|
610
|
-
}
|
|
611
|
-
console.log(`\nChangelog for ${data.name} v${version}:\n`);
|
|
612
|
-
console.log(v.changelog || '(no changelog)');
|
|
613
|
-
}
|
|
614
|
-
else {
|
|
615
|
-
// Show all changelogs
|
|
616
|
-
console.log(`\nChangelogs for ${data.name}:\n`);
|
|
617
|
-
data.versions.forEach((v) => {
|
|
618
|
-
console.log(`v${v.version} (${v.release_date}):`);
|
|
619
|
-
console.log(` ${v.changelog || '(no changelog)'}\n`);
|
|
620
|
-
});
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
catch (error) {
|
|
625
|
-
console.error('Error:', error.message);
|
|
626
|
-
process.exit(1);
|
|
627
|
-
}
|
|
628
|
-
});
|
|
629
|
-
plugins
|
|
630
|
-
.command('transfer <slug> <email>')
|
|
631
|
-
.description('Transfer plugin ownership to another @jezweb user (owner only)')
|
|
632
|
-
.option('-y, --yes', 'Skip confirmation')
|
|
633
|
-
.action(async (slug, email, options) => {
|
|
634
|
-
try {
|
|
635
|
-
if (!options.yes) {
|
|
636
|
-
console.log(`\n⚠️ You are about to transfer ownership of ${slug} to ${email}`);
|
|
637
|
-
console.log('You will lose all access to this plugin.');
|
|
638
|
-
console.log('\nRun with --yes to confirm.');
|
|
639
|
-
process.exit(1);
|
|
640
|
-
}
|
|
641
|
-
console.log(`Transferring ${slug} to ${email}...`);
|
|
642
|
-
const result = await api.transferOwnership(slug, email);
|
|
643
|
-
console.log(`\n✅ ${result.message}`);
|
|
644
|
-
console.log(`Previous owner: ${result.previous_owner}`);
|
|
645
|
-
console.log(`New owner: ${result.new_owner}`);
|
|
646
|
-
}
|
|
647
|
-
catch (error) {
|
|
648
|
-
console.error('Error:', error.message);
|
|
649
|
-
process.exit(1);
|
|
650
|
-
}
|
|
651
|
-
});
|
|
652
|
-
// ============================================================
|
|
653
|
-
// Site Commands
|
|
654
|
-
// ============================================================
|
|
655
|
-
const sites = program
|
|
656
|
-
.command('sites')
|
|
657
|
-
.description('Manage WordPress sites');
|
|
658
|
-
sites
|
|
659
|
-
.command('list')
|
|
660
|
-
.description('List all registered sites')
|
|
661
|
-
.option('--status <status>', 'Filter by status (approved/pending/rejected)')
|
|
662
|
-
.option('--woo', 'Only show sites with WooCommerce')
|
|
663
|
-
.option('--sort <column>', 'Sort by: domain, version, users, admins (default: domain)')
|
|
664
|
-
.option('--desc', 'Sort descending')
|
|
665
|
-
.option('--table', 'Show as compact table')
|
|
666
|
-
.option('--admins-only', 'Only show sites where total users = admin users')
|
|
667
|
-
.action(async (options) => {
|
|
668
|
-
try {
|
|
669
|
-
let sitesList = await api.listSites({
|
|
670
|
-
status: options.status,
|
|
671
|
-
woo: options.woo,
|
|
672
|
-
});
|
|
673
|
-
// Filter: admins only (no extra users)
|
|
674
|
-
if (options.adminsOnly) {
|
|
675
|
-
sitesList = sitesList.filter((s) => s.user_count_total !== undefined &&
|
|
676
|
-
s.user_count_total > 0 &&
|
|
677
|
-
s.user_count_total === s.user_count_admin);
|
|
678
|
-
}
|
|
679
|
-
// Sort
|
|
680
|
-
const sortCol = options.sort || 'domain';
|
|
681
|
-
sitesList.sort((a, b) => {
|
|
682
|
-
let cmp = 0;
|
|
683
|
-
switch (sortCol) {
|
|
684
|
-
case 'version':
|
|
685
|
-
cmp = (a.jezpress_version || '0').localeCompare(b.jezpress_version || '0');
|
|
686
|
-
break;
|
|
687
|
-
case 'users':
|
|
688
|
-
cmp = (a.user_count_total || 0) - (b.user_count_total || 0);
|
|
689
|
-
break;
|
|
690
|
-
case 'admins':
|
|
691
|
-
cmp = (a.user_count_admin || 0) - (b.user_count_admin || 0);
|
|
692
|
-
break;
|
|
693
|
-
default:
|
|
694
|
-
cmp = a.domain.localeCompare(b.domain);
|
|
695
|
-
}
|
|
696
|
-
return options.desc ? -cmp : cmp;
|
|
697
|
-
});
|
|
698
|
-
console.log(`\nSites (${sitesList.length}):\n`);
|
|
699
|
-
if (sitesList.length === 0) {
|
|
700
|
-
console.log(' (none)');
|
|
701
|
-
return;
|
|
702
|
-
}
|
|
703
|
-
if (options.table) {
|
|
704
|
-
// Compact table format
|
|
705
|
-
console.log(' Domain | JezPress | Users | Admins | WP ');
|
|
706
|
-
console.log(' ------------------------------------------|----------|-------|--------|------');
|
|
707
|
-
sitesList.forEach((s) => {
|
|
708
|
-
const domain = s.domain.padEnd(42).slice(0, 42);
|
|
709
|
-
const version = (s.jezpress_version || '-').padEnd(8);
|
|
710
|
-
const users = String(s.user_count_total ?? '-').padStart(5);
|
|
711
|
-
const admins = String(s.user_count_admin ?? '-').padStart(6);
|
|
712
|
-
const wp = (s.wp_version || '-').padEnd(5);
|
|
713
|
-
console.log(` ${domain} | ${version} | ${users} | ${admins} | ${wp}`);
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
else {
|
|
717
|
-
// Detailed format
|
|
718
|
-
sitesList.forEach((s) => {
|
|
719
|
-
const woo = s.woocommerce_active ? ' [WooCommerce]' : '';
|
|
720
|
-
console.log(` ${s.domain} (${s.status})${woo}`);
|
|
721
|
-
if (s.jezpress_version) {
|
|
722
|
-
console.log(` JezPress: v${s.jezpress_version}, WP: ${s.wp_version || 'unknown'}, PHP: ${s.php_version || 'unknown'}`);
|
|
723
|
-
}
|
|
724
|
-
if (s.user_count_total !== undefined) {
|
|
725
|
-
console.log(` Users: ${s.user_count_total} total (${s.user_count_admin || 0} admins)`);
|
|
726
|
-
}
|
|
727
|
-
console.log('');
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
// Summary
|
|
731
|
-
const wooCount = sitesList.filter((s) => s.woocommerce_active === 1).length;
|
|
732
|
-
console.log(`\nSummary: ${sitesList.length} sites, ${wooCount} with WooCommerce`);
|
|
733
|
-
}
|
|
734
|
-
catch (error) {
|
|
735
|
-
console.error('Error:', error.message);
|
|
736
|
-
process.exit(1);
|
|
737
|
-
}
|
|
738
|
-
});
|
|
739
|
-
sites
|
|
740
|
-
.command('check <domain>')
|
|
741
|
-
.description('Check if a domain is registered in JezPress')
|
|
742
|
-
.action(async (domain) => {
|
|
743
|
-
try {
|
|
744
|
-
const sitesList = await api.listSites({});
|
|
745
|
-
const normalizedDomain = domain.toLowerCase()
|
|
746
|
-
.replace(/^https?:\/\//, '')
|
|
747
|
-
.replace(/^www\./, '')
|
|
748
|
-
.replace(/\/$/, '');
|
|
749
|
-
const found = sitesList.find((s) => s.domain === normalizedDomain);
|
|
750
|
-
if (found) {
|
|
751
|
-
console.log(`\n✓ ${normalizedDomain} is registered in JezPress`);
|
|
752
|
-
console.log(` Status: ${found.status}`);
|
|
753
|
-
if (found.jezpress_version) {
|
|
754
|
-
console.log(` JezPress: v${found.jezpress_version}`);
|
|
755
|
-
}
|
|
756
|
-
if (found.user_count_total !== undefined) {
|
|
757
|
-
console.log(` Users: ${found.user_count_total} (${found.user_count_admin} admins)`);
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
else {
|
|
761
|
-
console.log(`\n✗ ${normalizedDomain} is NOT registered in JezPress`);
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
catch (error) {
|
|
765
|
-
console.error('Error:', error.message);
|
|
766
|
-
process.exit(1);
|
|
767
|
-
}
|
|
768
|
-
});
|
|
769
|
-
sites
|
|
770
|
-
.command('scan-all')
|
|
771
|
-
.description('Scan all sites and update cached data')
|
|
772
|
-
.action(async () => {
|
|
773
|
-
try {
|
|
774
|
-
console.log('Queuing scan for all sites...\n');
|
|
775
|
-
const { jobId, total } = await api.bulkScan();
|
|
776
|
-
console.log(`Queued ${total} sites (job: ${jobId})\n`);
|
|
777
|
-
// Poll for progress
|
|
778
|
-
let lastProgress = -1;
|
|
779
|
-
// eslint-disable-next-line no-constant-condition
|
|
780
|
-
while (true) {
|
|
781
|
-
const status = await api.getScanStatus(jobId);
|
|
782
|
-
const progress = status.progress;
|
|
783
|
-
if (progress !== lastProgress) {
|
|
784
|
-
process.stdout.write(`\rProgress: ${status.completed}/${status.total} complete, ${status.failed} failed (${progress}%)`);
|
|
785
|
-
lastProgress = progress;
|
|
786
|
-
}
|
|
787
|
-
if (status.status === 'completed') {
|
|
788
|
-
console.log('\n\n✓ Scan complete!');
|
|
789
|
-
console.log(` Succeeded: ${status.completed}`);
|
|
790
|
-
console.log(` Failed: ${status.failed}`);
|
|
791
|
-
break;
|
|
792
|
-
}
|
|
793
|
-
if (status.status === 'failed') {
|
|
794
|
-
console.log('\n\n✗ Scan failed');
|
|
795
|
-
break;
|
|
796
|
-
}
|
|
797
|
-
// Wait 2 seconds before polling again
|
|
798
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
catch (error) {
|
|
802
|
-
console.error('Error:', error.message);
|
|
803
|
-
process.exit(1);
|
|
804
|
-
}
|
|
805
|
-
});
|
|
806
|
-
sites
|
|
807
|
-
.command('update-all')
|
|
808
|
-
.description('Update JezPress on all sites')
|
|
809
|
-
.option('--force', 'Clear WordPress update cache before checking (use when updates are stuck)')
|
|
810
|
-
.action(async (options) => {
|
|
811
|
-
try {
|
|
812
|
-
const force = options.force === true;
|
|
813
|
-
console.log(`Queuing JezPress updates for all sites${force ? ' (force mode)' : ''}...\n`);
|
|
814
|
-
const { jobId, total } = await api.bulkUpdate({ force });
|
|
815
|
-
console.log(`Queued ${total} sites (job: ${jobId})\n`);
|
|
816
|
-
// Poll for progress
|
|
817
|
-
let lastProgress = -1;
|
|
818
|
-
// eslint-disable-next-line no-constant-condition
|
|
819
|
-
while (true) {
|
|
820
|
-
const status = await api.getScanStatus(jobId);
|
|
821
|
-
const progress = status.progress;
|
|
822
|
-
if (progress !== lastProgress) {
|
|
823
|
-
process.stdout.write(`\rProgress: ${status.completed}/${status.total} complete, ${status.failed} failed (${progress}%)`);
|
|
824
|
-
lastProgress = progress;
|
|
825
|
-
}
|
|
826
|
-
if (status.status === 'completed') {
|
|
827
|
-
console.log('\n\n✓ Update complete!');
|
|
828
|
-
console.log(` Succeeded: ${status.completed}`);
|
|
829
|
-
console.log(` Failed: ${status.failed}`);
|
|
830
|
-
break;
|
|
831
|
-
}
|
|
832
|
-
if (status.status === 'failed') {
|
|
833
|
-
console.log('\n\n✗ Update failed');
|
|
834
|
-
break;
|
|
835
|
-
}
|
|
836
|
-
// Wait 2 seconds before polling again
|
|
837
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
catch (error) {
|
|
841
|
-
console.error('Error:', error.message);
|
|
842
|
-
process.exit(1);
|
|
843
|
-
}
|
|
844
|
-
});
|
|
845
|
-
sites
|
|
846
|
-
.command('update <domain>')
|
|
847
|
-
.description('Update JezPress on a single site')
|
|
848
|
-
.action(async (domain) => {
|
|
849
|
-
try {
|
|
850
|
-
// First get current status
|
|
851
|
-
console.log(`Checking ${domain}...`);
|
|
852
|
-
const status = await api.getSiteStatus(domain);
|
|
853
|
-
console.log(`Current version: ${status.version}`);
|
|
854
|
-
console.log(`WP: ${status.wp_version}, PHP: ${status.php_version}\n`);
|
|
855
|
-
console.log('Triggering update...');
|
|
856
|
-
const result = await api.updateSitePlugin(domain);
|
|
857
|
-
if (result.old_version === result.new_version) {
|
|
858
|
-
console.log(`\n✓ Already up to date (v${result.new_version})`);
|
|
859
|
-
}
|
|
860
|
-
else {
|
|
861
|
-
console.log(`\n✓ Updated: v${result.old_version} → v${result.new_version}`);
|
|
862
|
-
if (result.reactivated) {
|
|
863
|
-
console.log(' Plugin was reactivated after update');
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
catch (error) {
|
|
868
|
-
console.error('Error:', error.message);
|
|
869
|
-
process.exit(1);
|
|
870
|
-
}
|
|
871
|
-
});
|
|
872
|
-
sites
|
|
873
|
-
.command('status <domain>')
|
|
874
|
-
.description('Get live status of a site via REST API')
|
|
875
|
-
.action(async (domain) => {
|
|
876
|
-
try {
|
|
877
|
-
console.log(`Checking ${domain}...\n`);
|
|
878
|
-
const status = await api.getSiteStatus(domain);
|
|
879
|
-
console.log(` Domain: ${status.domain}`);
|
|
880
|
-
console.log(` Status: ${status.status}`);
|
|
881
|
-
console.log(` JezPress: v${status.version}`);
|
|
882
|
-
console.log(` WordPress: ${status.wp_version}`);
|
|
883
|
-
console.log(` PHP: ${status.php_version}`);
|
|
884
|
-
}
|
|
885
|
-
catch (error) {
|
|
886
|
-
console.error('Error:', error.message);
|
|
887
|
-
process.exit(1);
|
|
888
|
-
}
|
|
889
|
-
});
|
|
890
|
-
sites
|
|
891
|
-
.command('info <domain>')
|
|
892
|
-
.description('Get comprehensive site information')
|
|
893
|
-
.action(async (domain) => {
|
|
894
|
-
try {
|
|
895
|
-
console.log(`Fetching site info for ${domain}...\n`);
|
|
896
|
-
const info = await api.getSiteInfo(domain);
|
|
897
|
-
console.log('Versions:');
|
|
898
|
-
console.log(` WordPress: ${info.versions.wp}`);
|
|
899
|
-
console.log(` PHP: ${info.versions.php}`);
|
|
900
|
-
console.log(` JezPress: ${info.versions.jezpress}`);
|
|
901
|
-
console.log('\nUsers:');
|
|
902
|
-
for (const [role, count] of Object.entries(info.users)) {
|
|
903
|
-
if (count > 0) {
|
|
904
|
-
console.log(` ${role}: ${count}`);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
console.log('\nWooCommerce:');
|
|
908
|
-
if (info.woocommerce.active) {
|
|
909
|
-
console.log(` Active: Yes (v${info.woocommerce.version})`);
|
|
910
|
-
}
|
|
911
|
-
else {
|
|
912
|
-
console.log(' Active: No');
|
|
913
|
-
}
|
|
914
|
-
console.log('\nSettings:');
|
|
915
|
-
console.log(` OAuth Only: ${info.settings.disable_password_login ? 'Yes' : 'No'}`);
|
|
916
|
-
}
|
|
917
|
-
catch (error) {
|
|
918
|
-
console.error('Error:', error.message);
|
|
919
|
-
process.exit(1);
|
|
920
|
-
}
|
|
921
|
-
});
|
|
922
|
-
sites
|
|
923
|
-
.command('plugins <domain>')
|
|
924
|
-
.description('List plugins with update status')
|
|
925
|
-
.option('--updates', 'Only show plugins with updates available')
|
|
926
|
-
.action(async (domain, options) => {
|
|
927
|
-
try {
|
|
928
|
-
console.log(`Fetching plugins for ${domain}...\n`);
|
|
929
|
-
const data = await api.getSitePlugins(domain);
|
|
930
|
-
let plugins = data.plugins;
|
|
931
|
-
if (options.updates) {
|
|
932
|
-
plugins = plugins.filter(p => p.update_available);
|
|
933
|
-
}
|
|
934
|
-
if (plugins.length === 0) {
|
|
935
|
-
if (options.updates) {
|
|
936
|
-
console.log('No plugins with updates available.');
|
|
937
|
-
}
|
|
938
|
-
else {
|
|
939
|
-
console.log('No plugins found.');
|
|
940
|
-
}
|
|
941
|
-
return;
|
|
942
|
-
}
|
|
943
|
-
for (const plugin of plugins) {
|
|
944
|
-
const status = plugin.active ? '●' : '○';
|
|
945
|
-
const update = plugin.update_available
|
|
946
|
-
? ` → ${plugin.new_version}`
|
|
947
|
-
: '';
|
|
948
|
-
console.log(`${status} ${plugin.name} (v${plugin.version}${update})`);
|
|
949
|
-
}
|
|
950
|
-
console.log(`\n${data.total} plugins, ${data.updates_available} with updates`);
|
|
951
|
-
}
|
|
952
|
-
catch (error) {
|
|
953
|
-
console.error('Error:', error.message);
|
|
954
|
-
process.exit(1);
|
|
955
|
-
}
|
|
956
|
-
});
|
|
957
|
-
sites
|
|
958
|
-
.command('clear-cache <domain>')
|
|
959
|
-
.description('Clear site caches (WP Rocket, LiteSpeed, etc)')
|
|
960
|
-
.action(async (domain) => {
|
|
961
|
-
try {
|
|
962
|
-
console.log(`Clearing cache on ${domain}...`);
|
|
963
|
-
const result = await api.clearSiteCache(domain);
|
|
964
|
-
if (result.cleared.length > 0) {
|
|
965
|
-
console.log(`\n✓ Cleared: ${result.cleared.join(', ')}`);
|
|
966
|
-
}
|
|
967
|
-
else {
|
|
968
|
-
console.log('\n✓ Cache cleared (no specific plugins detected)');
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
catch (error) {
|
|
972
|
-
console.error('Error:', error.message);
|
|
973
|
-
process.exit(1);
|
|
974
|
-
}
|
|
975
|
-
});
|
|
976
|
-
sites
|
|
977
|
-
.command('clear-update-cache <domain>')
|
|
978
|
-
.description('Force site to check for fresh plugin updates')
|
|
979
|
-
.action(async (domain) => {
|
|
980
|
-
try {
|
|
981
|
-
console.log(`Clearing update cache on ${domain}...`);
|
|
982
|
-
await api.clearSiteUpdateCache(domain);
|
|
983
|
-
console.log('\n✓ Update cache cleared - next update check will fetch fresh data');
|
|
984
|
-
}
|
|
985
|
-
catch (error) {
|
|
986
|
-
console.error('Error:', error.message);
|
|
987
|
-
process.exit(1);
|
|
988
|
-
}
|
|
989
|
-
});
|
|
990
|
-
sites
|
|
991
|
-
.command('approve <domain>')
|
|
992
|
-
.description('Approve a pending site for OAuth access')
|
|
993
|
-
.action(async (domain) => {
|
|
994
|
-
try {
|
|
995
|
-
console.log(`Approving ${domain}...`);
|
|
996
|
-
const result = await api.approveSite(domain);
|
|
997
|
-
console.log(`\n✓ ${result.message}`);
|
|
998
|
-
}
|
|
999
|
-
catch (error) {
|
|
1000
|
-
console.error('Error:', error.message);
|
|
1001
|
-
process.exit(1);
|
|
1002
|
-
}
|
|
1003
|
-
});
|
|
1004
|
-
sites
|
|
1005
|
-
.command('reject <domain>')
|
|
1006
|
-
.description('Reject a site from OAuth access')
|
|
1007
|
-
.action(async (domain) => {
|
|
1008
|
-
try {
|
|
1009
|
-
console.log(`Rejecting ${domain}...`);
|
|
1010
|
-
const result = await api.rejectSite(domain);
|
|
1011
|
-
console.log(`\n✓ ${result.message}`);
|
|
1012
|
-
}
|
|
1013
|
-
catch (error) {
|
|
1014
|
-
console.error('Error:', error.message);
|
|
1015
|
-
process.exit(1);
|
|
1016
|
-
}
|
|
1017
|
-
});
|
|
1018
|
-
// ========== NEW API COMMANDS (v1.4.0) ==========
|
|
1019
|
-
sites
|
|
1020
|
-
.command('export <domain>')
|
|
1021
|
-
.description('Export site content (pages, posts, menus, media)')
|
|
1022
|
-
.option('--include <sections>', 'Sections to include (comma-separated: site,pages,posts,menus,media)')
|
|
1023
|
-
.option('--json', 'Output raw JSON')
|
|
1024
|
-
.action(async (domain, options) => {
|
|
1025
|
-
try {
|
|
1026
|
-
console.log(`Exporting ${domain}...`);
|
|
1027
|
-
const data = await api.getSiteExport(domain, {
|
|
1028
|
-
include: options.include,
|
|
1029
|
-
});
|
|
1030
|
-
if (options.json) {
|
|
1031
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1032
|
-
return;
|
|
1033
|
-
}
|
|
1034
|
-
console.log(`\nExport Summary:`);
|
|
1035
|
-
console.log(` Exported at: ${data.exported_at}`);
|
|
1036
|
-
if (data.site)
|
|
1037
|
-
console.log(` Site config: included`);
|
|
1038
|
-
if (data.pages)
|
|
1039
|
-
console.log(` Pages: ${data.pages.length}`);
|
|
1040
|
-
if (data.posts)
|
|
1041
|
-
console.log(` Posts: ${data.posts.length}`);
|
|
1042
|
-
if (data.menus)
|
|
1043
|
-
console.log(` Menus: ${data.menus.length}`);
|
|
1044
|
-
if (data.media)
|
|
1045
|
-
console.log(` Media: ${data.media.length}`);
|
|
1046
|
-
}
|
|
1047
|
-
catch (error) {
|
|
1048
|
-
console.error('Error:', error.message);
|
|
1049
|
-
process.exit(1);
|
|
1050
|
-
}
|
|
1051
|
-
});
|
|
1052
|
-
sites
|
|
1053
|
-
.command('config <domain>')
|
|
1054
|
-
.description('Get site configuration (theme, Elementor, options)')
|
|
1055
|
-
.option('--json', 'Output raw JSON')
|
|
1056
|
-
.action(async (domain, options) => {
|
|
1057
|
-
try {
|
|
1058
|
-
console.log(`Fetching config for ${domain}...\n`);
|
|
1059
|
-
const data = await api.getSiteConfig(domain);
|
|
1060
|
-
if (options.json) {
|
|
1061
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1062
|
-
return;
|
|
1063
|
-
}
|
|
1064
|
-
console.log('Theme:');
|
|
1065
|
-
console.log(` Name: ${data.theme.name}`);
|
|
1066
|
-
console.log(` Version: ${data.theme.version}`);
|
|
1067
|
-
if (data.theme.parent)
|
|
1068
|
-
console.log(` Parent: ${data.theme.parent}`);
|
|
1069
|
-
console.log('\nElementor:');
|
|
1070
|
-
console.log(` Active: ${data.elementor.active ? 'Yes' : 'No'}`);
|
|
1071
|
-
if (data.elementor.globals?.colors) {
|
|
1072
|
-
console.log(` Global colors: ${data.elementor.globals.colors.length}`);
|
|
1073
|
-
}
|
|
1074
|
-
if (data.elementor.globals?.typography) {
|
|
1075
|
-
console.log(` Global fonts: ${data.elementor.globals.typography.length}`);
|
|
1076
|
-
}
|
|
1077
|
-
console.log('\nSite Options:');
|
|
1078
|
-
const opts = data.options;
|
|
1079
|
-
const keys = ['blogname', 'siteurl', 'admin_email', 'timezone_string'];
|
|
1080
|
-
for (const key of keys) {
|
|
1081
|
-
if (opts[key])
|
|
1082
|
-
console.log(` ${key}: ${opts[key]}`);
|
|
1083
|
-
}
|
|
1084
|
-
console.log(` ... and ${Object.keys(opts).length - keys.length} more options`);
|
|
1085
|
-
}
|
|
1086
|
-
catch (error) {
|
|
1087
|
-
console.error('Error:', error.message);
|
|
1088
|
-
process.exit(1);
|
|
1089
|
-
}
|
|
1090
|
-
});
|
|
1091
|
-
sites
|
|
1092
|
-
.command('diagnostics <domain>')
|
|
1093
|
-
.description('Get site diagnostics (health, errors, cron, database)')
|
|
1094
|
-
.option('--json', 'Output raw JSON')
|
|
1095
|
-
.action(async (domain, options) => {
|
|
1096
|
-
try {
|
|
1097
|
-
console.log(`Fetching diagnostics for ${domain}...\n`);
|
|
1098
|
-
const data = await api.getSiteDiagnostics(domain);
|
|
1099
|
-
if (options.json) {
|
|
1100
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1101
|
-
return;
|
|
1102
|
-
}
|
|
1103
|
-
console.log('Health:');
|
|
1104
|
-
const healthTests = data.health.tests || {};
|
|
1105
|
-
const passed = Object.values(healthTests).filter((t) => t.status === 'good').length;
|
|
1106
|
-
console.log(` Tests: ${passed}/${Object.keys(healthTests).length} passed`);
|
|
1107
|
-
console.log('\nErrors:');
|
|
1108
|
-
const errors = data.errors.log || [];
|
|
1109
|
-
console.log(` Log entries: ${errors.length}`);
|
|
1110
|
-
if (errors.length > 0) {
|
|
1111
|
-
console.log(` Latest: ${errors[0].substring(0, 70)}...`);
|
|
1112
|
-
}
|
|
1113
|
-
console.log('\nDatabase:');
|
|
1114
|
-
console.log(` Version: ${data.database.version || 'N/A'}`);
|
|
1115
|
-
if (data.database.size)
|
|
1116
|
-
console.log(` Size: ${data.database.size}`);
|
|
1117
|
-
console.log('\nCron:');
|
|
1118
|
-
if (Array.isArray(data.cron)) {
|
|
1119
|
-
console.log(` Scheduled jobs: ${data.cron.length}`);
|
|
1120
|
-
}
|
|
1121
|
-
else {
|
|
1122
|
-
console.log(` Cron entries: ${Object.keys(data.cron).length}`);
|
|
1123
|
-
}
|
|
1124
|
-
console.log('\nTransients:');
|
|
1125
|
-
console.log(` Count: ${data.transients.count}`);
|
|
1126
|
-
}
|
|
1127
|
-
catch (error) {
|
|
1128
|
-
console.error('Error:', error.message);
|
|
1129
|
-
process.exit(1);
|
|
1130
|
-
}
|
|
1131
|
-
});
|
|
1132
|
-
sites
|
|
1133
|
-
.command('ai <domain>')
|
|
1134
|
-
.description('Get AI-optimized site knowledge bundle')
|
|
1135
|
-
.option('--json', 'Output raw JSON')
|
|
1136
|
-
.action(async (domain, options) => {
|
|
1137
|
-
try {
|
|
1138
|
-
console.log(`Fetching AI knowledge for ${domain}...\n`);
|
|
1139
|
-
const data = await api.getSiteAI(domain);
|
|
1140
|
-
if (options.json) {
|
|
1141
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1142
|
-
return;
|
|
1143
|
-
}
|
|
1144
|
-
console.log('Site Knowledge Bundle:');
|
|
1145
|
-
console.log(` Pages: ${data.page_count}`);
|
|
1146
|
-
console.log(` Plugins: ${data.plugin_count}`);
|
|
1147
|
-
console.log(` Menus: ${data.menus?.length || 0}`);
|
|
1148
|
-
console.log('\nPages (first 5):');
|
|
1149
|
-
const pages = data.pages || [];
|
|
1150
|
-
pages.slice(0, 5).forEach((p) => {
|
|
1151
|
-
console.log(` - ${p.title} (/${p.slug})`);
|
|
1152
|
-
});
|
|
1153
|
-
if (pages.length > 5)
|
|
1154
|
-
console.log(` ... and ${pages.length - 5} more`);
|
|
1155
|
-
console.log('\nActive Plugins:');
|
|
1156
|
-
const plugins = (data.plugins || []).filter((p) => p.active);
|
|
1157
|
-
plugins.slice(0, 5).forEach((p) => {
|
|
1158
|
-
console.log(` - ${p.name}`);
|
|
1159
|
-
});
|
|
1160
|
-
if (plugins.length > 5)
|
|
1161
|
-
console.log(` ... and ${plugins.length - 5} more`);
|
|
1162
|
-
}
|
|
1163
|
-
catch (error) {
|
|
1164
|
-
console.error('Error:', error.message);
|
|
1165
|
-
process.exit(1);
|
|
1166
|
-
}
|
|
1167
|
-
});
|
|
25
|
+
// Register all command groups
|
|
26
|
+
registerAuthCommands(program);
|
|
27
|
+
registerDocsCommand(program);
|
|
28
|
+
registerPluginCommands(program);
|
|
29
|
+
registerSitesCommands(program);
|
|
1168
30
|
// Parse and run
|
|
1169
31
|
program.parse();
|
|
1170
32
|
//# sourceMappingURL=cli.js.map
|