plugship 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/commands/ignore.js +1 -1
- package/src/commands/init.js +111 -72
- package/src/lib/constants.js +1 -1
- package/src/lib/deployer.js +32 -7
- package/src/lib/zipper.js +1 -1
package/package.json
CHANGED
package/src/commands/ignore.js
CHANGED
|
@@ -10,7 +10,7 @@ const DEFAULT_TEMPLATE = `# .plugshipignore
|
|
|
10
10
|
#
|
|
11
11
|
# The following are always excluded by default (no need to list them):
|
|
12
12
|
# node_modules, .git, .github, .DS_Store, .env, *.log,
|
|
13
|
-
# .vscode, .idea, tests, phpunit.xml,
|
|
13
|
+
# .vscode, .idea, tests, phpunit.xml, builds
|
|
14
14
|
|
|
15
15
|
# Source files (uncomment as needed)
|
|
16
16
|
# src/**
|
package/src/commands/init.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { input, password } from '@inquirer/prompts';
|
|
1
|
+
import { input, password, confirm } from '@inquirer/prompts';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { addSite } from '../lib/config.js';
|
|
4
4
|
import { WordPressApi } from '../lib/wordpress-api.js';
|
|
@@ -13,87 +13,119 @@ export async function initCommand() {
|
|
|
13
13
|
validate: (v) => (v.trim() ? true : 'Required'),
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
let siteUrl, username, appPassword, api;
|
|
17
|
+
let connected = false;
|
|
18
|
+
|
|
19
|
+
// Loop until connection + auth succeed
|
|
20
|
+
while (!connected) {
|
|
21
|
+
siteUrl = await input({
|
|
22
|
+
message: 'WordPress site URL:',
|
|
23
|
+
validate: (v) => {
|
|
24
|
+
if (!v.trim()) return 'Required';
|
|
25
|
+
try {
|
|
26
|
+
const parsed = new URL(v);
|
|
27
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
|
|
28
|
+
return 'URL must start with https:// or http://';
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
return 'Invalid URL';
|
|
24
33
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return 'Invalid URL';
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
});
|
|
34
|
+
},
|
|
35
|
+
});
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
message: 'WordPress username:',
|
|
34
|
-
validate: (v) => (v.trim() ? true : 'Required'),
|
|
35
|
-
});
|
|
37
|
+
siteUrl = siteUrl.replace(/\/+$/, '');
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
validate: (v) => (v.trim() ? true : 'Required'),
|
|
41
|
-
});
|
|
39
|
+
// Test connection
|
|
40
|
+
const spin = logger.spinner('Testing connection to WordPress REST API...');
|
|
41
|
+
spin.start();
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
try {
|
|
44
|
+
const tempApi = new WordPressApi({ url: siteUrl, username: '', appPassword: '' });
|
|
45
|
+
await tempApi.testConnection();
|
|
46
|
+
spin.succeed('REST API is accessible');
|
|
47
|
+
} catch (err) {
|
|
48
|
+
spin.stop();
|
|
49
|
+
spin.clear();
|
|
50
|
+
logger.error('Cannot reach WordPress REST API');
|
|
51
|
+
logger.error(`Make sure ${siteUrl}/wp-json/ is accessible.\n ${err.message}`);
|
|
52
|
+
console.log('');
|
|
53
|
+
const retry = await confirm({ message: 'Try a different URL?', default: true });
|
|
54
|
+
if (!retry) return;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
45
57
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
// Loop until auth succeeds or user quits
|
|
59
|
+
let authenticated = false;
|
|
60
|
+
while (!authenticated) {
|
|
61
|
+
username = await input({
|
|
62
|
+
message: 'WordPress username:',
|
|
63
|
+
validate: (v) => (v.trim() ? true : 'Required'),
|
|
64
|
+
});
|
|
49
65
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
logger.error(`Make sure ${siteUrl}/wp-json/ is accessible.\n ${err.message}`);
|
|
56
|
-
process.exitCode = 1;
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
66
|
+
appPassword = await password({
|
|
67
|
+
message: 'Application password:',
|
|
68
|
+
mask: '*',
|
|
69
|
+
validate: (v) => (v.trim() ? true : 'Required'),
|
|
70
|
+
});
|
|
59
71
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
api = new WordPressApi({ url: siteUrl, username, appPassword });
|
|
73
|
+
|
|
74
|
+
const spin2 = logger.spinner('Verifying credentials...');
|
|
75
|
+
spin2.start();
|
|
76
|
+
try {
|
|
77
|
+
const user = await api.testAuth();
|
|
78
|
+
const caps = user.capabilities || {};
|
|
79
|
+
if (!caps.install_plugins) {
|
|
80
|
+
spin2.stop();
|
|
81
|
+
spin2.clear();
|
|
82
|
+
logger.error('User does not have the "install_plugins" capability');
|
|
83
|
+
logger.error('The user must be an Administrator to deploy plugins.');
|
|
84
|
+
console.log('');
|
|
85
|
+
const retry = await confirm({ message: 'Try different credentials?', default: true });
|
|
86
|
+
if (!retry) return;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
spin2.succeed(`Authenticated as "${user.name}"`);
|
|
90
|
+
authenticated = true;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
spin2.stop();
|
|
93
|
+
spin2.clear();
|
|
94
|
+
logger.error(`Authentication failed`);
|
|
95
|
+
logger.error(`Check your username and application password.\n ${err.message}`);
|
|
96
|
+
console.log('');
|
|
97
|
+
const retry = await confirm({ message: 'Try again?', default: true });
|
|
98
|
+
if (!retry) return;
|
|
99
|
+
}
|
|
70
100
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
spin.fail('Authentication failed');
|
|
74
|
-
logger.error(`Check your username and application password.\n ${err.message}`);
|
|
75
|
-
process.exitCode = 1;
|
|
76
|
-
return;
|
|
101
|
+
|
|
102
|
+
connected = true;
|
|
77
103
|
}
|
|
78
104
|
|
|
79
105
|
// Check receiver plugin
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
spin.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
106
|
+
let receiverActive = false;
|
|
107
|
+
while (!receiverActive) {
|
|
108
|
+
const spin = logger.spinner('Checking for plugship-receiver plugin...');
|
|
109
|
+
spin.start();
|
|
110
|
+
try {
|
|
111
|
+
const status = await api.checkReceiver();
|
|
112
|
+
spin.succeed(`Receiver plugin active (v${status.version})`);
|
|
113
|
+
receiverActive = true;
|
|
114
|
+
} catch {
|
|
115
|
+
spin.warn('Receiver plugin not detected');
|
|
116
|
+
console.log('');
|
|
117
|
+
logger.warn(
|
|
118
|
+
'The plugship-receiver plugin must be installed and activated on your WordPress site.'
|
|
119
|
+
);
|
|
120
|
+
console.log(
|
|
121
|
+
chalk.dim(
|
|
122
|
+
` 1. Download: ${RECEIVER_DOWNLOAD_URL}\n` +
|
|
123
|
+
' 2. Upload and activate in WordPress admin (Plugins > Add New > Upload Plugin)\n'
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
const retry = await confirm({ message: 'Check again?', default: true });
|
|
127
|
+
if (!retry) break;
|
|
128
|
+
}
|
|
97
129
|
}
|
|
98
130
|
|
|
99
131
|
// Save config
|
|
@@ -103,5 +135,12 @@ export async function initCommand() {
|
|
|
103
135
|
appPassword,
|
|
104
136
|
});
|
|
105
137
|
|
|
106
|
-
logger.success(`Site "${name.trim()}" saved and set as default
|
|
138
|
+
logger.success(`Site "${name.trim()}" saved and set as default.`);
|
|
139
|
+
console.log(chalk.bold('\nNext steps:\n'));
|
|
140
|
+
console.log(` Navigate to your plugin directory and run:\n`);
|
|
141
|
+
console.log(chalk.cyan(` plugship deploy\n`));
|
|
142
|
+
console.log(chalk.dim(` Other useful commands:`));
|
|
143
|
+
console.log(chalk.dim(` plugship deploy --dry-run Preview without uploading`));
|
|
144
|
+
console.log(chalk.dim(` plugship deploy --site ${name.trim()} Deploy to this site`));
|
|
145
|
+
console.log(chalk.dim(` plugship ignore Set up file exclusions\n`));
|
|
107
146
|
}
|
package/src/lib/constants.js
CHANGED
package/src/lib/deployer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { access } from 'node:fs/promises';
|
|
2
|
+
import { access, stat } from 'node:fs/promises';
|
|
3
3
|
import { select, confirm } from '@inquirer/prompts';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { getSite, listSites } from './config.js';
|
|
@@ -83,12 +83,37 @@ export async function deploy({ siteName, activate = true, dryRun = false, all =
|
|
|
83
83
|
// Detect plugin
|
|
84
84
|
const plugin = await detectPlugin(cwd);
|
|
85
85
|
|
|
86
|
-
//
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
// Check for existing ZIP
|
|
87
|
+
const existingZipPath = join(cwd, 'builds', `${plugin.slug}.zip`);
|
|
88
|
+
let zipPath, size;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const zipStat = await stat(existingZipPath);
|
|
92
|
+
const sizeMB = (zipStat.size / 1024 / 1024).toFixed(2);
|
|
93
|
+
logger.info(`Existing ZIP found: builds/${plugin.slug}.zip (${sizeMB} MB)`);
|
|
94
|
+
const action = await select({
|
|
95
|
+
message: 'What do you want to do?',
|
|
96
|
+
choices: [
|
|
97
|
+
{ name: 'Use existing ZIP', value: 'existing' },
|
|
98
|
+
{ name: 'Build a new ZIP', value: 'rebuild' },
|
|
99
|
+
],
|
|
100
|
+
});
|
|
101
|
+
if (action === 'rebuild') {
|
|
102
|
+
const spin = logger.spinner('Creating ZIP archive...');
|
|
103
|
+
spin.start();
|
|
104
|
+
({ zipPath, size } = await createPluginZip(cwd, plugin.slug));
|
|
105
|
+
spin.succeed(`ZIP created (${(size / 1024 / 1024).toFixed(2)} MB)`);
|
|
106
|
+
} else {
|
|
107
|
+
zipPath = existingZipPath;
|
|
108
|
+
size = zipStat.size;
|
|
109
|
+
logger.success(`Using existing ZIP (${sizeMB} MB)`);
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
const spin = logger.spinner('Creating ZIP archive...');
|
|
113
|
+
spin.start();
|
|
114
|
+
({ zipPath, size } = await createPluginZip(cwd, plugin.slug));
|
|
115
|
+
spin.succeed(`ZIP created (${(size / 1024 / 1024).toFixed(2)} MB)`);
|
|
116
|
+
}
|
|
92
117
|
|
|
93
118
|
// Dry run — show summary and exit
|
|
94
119
|
if (dryRun) {
|
package/src/lib/zipper.js
CHANGED
|
@@ -21,7 +21,7 @@ async function loadIgnorePatterns(sourceDir) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export async function createPluginZip(sourceDir, slug) {
|
|
24
|
-
const buildDir = join(sourceDir, '
|
|
24
|
+
const buildDir = join(sourceDir, 'builds');
|
|
25
25
|
await mkdir(buildDir, { recursive: true });
|
|
26
26
|
const zipName = `${slug}.zip`;
|
|
27
27
|
const zipPath = join(buildDir, zipName);
|