localwp-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +229 -0
- package/dist/backup.d.ts +72 -0
- package/dist/backup.js +351 -0
- package/dist/backup.js.map +1 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.js +58 -0
- package/dist/config.js.map +1 -0
- package/dist/environment-check.d.ts +187 -0
- package/dist/environment-check.js +174 -0
- package/dist/environment-check.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/local-doctor.d.ts +42 -0
- package/dist/local-doctor.js +224 -0
- package/dist/local-doctor.js.map +1 -0
- package/dist/local-sites.d.ts +103 -0
- package/dist/local-sites.js +234 -0
- package/dist/local-sites.js.map +1 -0
- package/dist/local-tooling.d.ts +2 -0
- package/dist/local-tooling.js +23 -0
- package/dist/local-tooling.js.map +1 -0
- package/dist/logs.d.ts +144 -0
- package/dist/logs.js +208 -0
- package/dist/logs.js.map +1 -0
- package/dist/mysql.d.ts +17 -0
- package/dist/mysql.js +229 -0
- package/dist/mysql.js.map +1 -0
- package/dist/permissions.d.ts +4 -0
- package/dist/permissions.js +129 -0
- package/dist/permissions.js.map +1 -0
- package/dist/platform-paths.d.ts +10 -0
- package/dist/platform-paths.js +95 -0
- package/dist/platform-paths.js.map +1 -0
- package/dist/process-utils.d.ts +13 -0
- package/dist/process-utils.js +128 -0
- package/dist/process-utils.js.map +1 -0
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.js +64 -0
- package/dist/prompts.js.map +1 -0
- package/dist/resources.d.ts +2 -0
- package/dist/resources.js +110 -0
- package/dist/resources.js.map +1 -0
- package/dist/results.d.ts +15 -0
- package/dist/results.js +26 -0
- package/dist/results.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +23 -0
- package/dist/server.js.map +1 -0
- package/dist/tool-schemas.d.ts +14 -0
- package/dist/tool-schemas.js +20 -0
- package/dist/tool-schemas.js.map +1 -0
- package/dist/tools/backup-site.d.ts +2 -0
- package/dist/tools/backup-site.js +42 -0
- package/dist/tools/backup-site.js.map +1 -0
- package/dist/tools/db-export.d.ts +2 -0
- package/dist/tools/db-export.js +40 -0
- package/dist/tools/db-export.js.map +1 -0
- package/dist/tools/db-import.d.ts +2 -0
- package/dist/tools/db-import.js +42 -0
- package/dist/tools/db-import.js.map +1 -0
- package/dist/tools/execute-wp-cli.d.ts +2 -0
- package/dist/tools/execute-wp-cli.js +34 -0
- package/dist/tools/execute-wp-cli.js.map +1 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +29 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/list-local-sites.d.ts +2 -0
- package/dist/tools/list-local-sites.js +32 -0
- package/dist/tools/list-local-sites.js.map +1 -0
- package/dist/tools/local-doctor.d.ts +2 -0
- package/dist/tools/local-doctor.js +23 -0
- package/dist/tools/local-doctor.js.map +1 -0
- package/dist/tools/local-environment-check.d.ts +2 -0
- package/dist/tools/local-environment-check.js +32 -0
- package/dist/tools/local-environment-check.js.map +1 -0
- package/dist/tools/local-logs.d.ts +2 -0
- package/dist/tools/local-logs.js +31 -0
- package/dist/tools/local-logs.js.map +1 -0
- package/dist/tools/local-site-info.d.ts +2 -0
- package/dist/tools/local-site-info.js +46 -0
- package/dist/tools/local-site-info.js.map +1 -0
- package/dist/tools/mysql-execute.d.ts +2 -0
- package/dist/tools/mysql-execute.js +46 -0
- package/dist/tools/mysql-execute.js.map +1 -0
- package/dist/tools/mysql-query.d.ts +2 -0
- package/dist/tools/mysql-query.js +43 -0
- package/dist/tools/mysql-query.js.map +1 -0
- package/dist/tools/mysql-schema.d.ts +2 -0
- package/dist/tools/mysql-schema.js +50 -0
- package/dist/tools/mysql-schema.js.map +1 -0
- package/dist/tools/restore-backup.d.ts +2 -0
- package/dist/tools/restore-backup.js +52 -0
- package/dist/tools/restore-backup.js.map +1 -0
- package/dist/types.d.ts +84 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/wp-cli.d.ts +14 -0
- package/dist/wp-cli.js +140 -0
- package/dist/wp-cli.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# localwp-mcp
|
|
2
|
+
|
|
3
|
+
`localwp-mcp` is an MCP server for LocalWP projects.
|
|
4
|
+
|
|
5
|
+
It discovers Local sites from Local's own metadata, resolves the correct Local PHP and MySQL runtimes for each site, and gives AI agents simple access to:
|
|
6
|
+
|
|
7
|
+
- Local site discovery
|
|
8
|
+
- Local logs and doctor-style diagnostics
|
|
9
|
+
- site-aware WP-CLI
|
|
10
|
+
- safe SQL reads
|
|
11
|
+
- full SQL access when you opt into it
|
|
12
|
+
- database export/import
|
|
13
|
+
- Local-friendly backups
|
|
14
|
+
- restore workflows
|
|
15
|
+
- machine-readable Local diagnostics
|
|
16
|
+
- MCP resources and prompts
|
|
17
|
+
|
|
18
|
+
## Project Docs
|
|
19
|
+
|
|
20
|
+
- [Contributing](./CONTRIBUTING.md)
|
|
21
|
+
- [Security Policy](./SECURITY.md)
|
|
22
|
+
- [Release Checklist](./docs/RELEASE_CHECKLIST.md)
|
|
23
|
+
- [Windows Test Handoff](./docs/WINDOWS_TEST_HANDOFF.md)
|
|
24
|
+
|
|
25
|
+
## Profiles
|
|
26
|
+
|
|
27
|
+
The package now has only 2 profiles:
|
|
28
|
+
|
|
29
|
+
- `safe`
|
|
30
|
+
Good default. Read-focused SQL plus safe WP-CLI inspection commands.
|
|
31
|
+
- `full-access`
|
|
32
|
+
Best for local development when you want the agent to fully work on the site and database.
|
|
33
|
+
|
|
34
|
+
`safe` is the default.
|
|
35
|
+
|
|
36
|
+
## Tools
|
|
37
|
+
|
|
38
|
+
- `list_local_sites`
|
|
39
|
+
- `local_environment_check`
|
|
40
|
+
- `local_doctor`
|
|
41
|
+
- `local_logs`
|
|
42
|
+
- `local_site_info`
|
|
43
|
+
- `backup_site`
|
|
44
|
+
- `db_export`
|
|
45
|
+
- `db_import`
|
|
46
|
+
- `restore_backup`
|
|
47
|
+
- `mysql_query`
|
|
48
|
+
Safe profile SQL reads only.
|
|
49
|
+
- `mysql_execute`
|
|
50
|
+
Full-access profile single-statement SQL execution.
|
|
51
|
+
- `mysql_schema`
|
|
52
|
+
Accepts `table` and also `tableName` as a compatibility alias.
|
|
53
|
+
- `execute_wp_cli`
|
|
54
|
+
|
|
55
|
+
## Quick Setup
|
|
56
|
+
|
|
57
|
+
### npm
|
|
58
|
+
|
|
59
|
+
Once published, the simplest MCP config is:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"mcpServers": {
|
|
64
|
+
"localwp": {
|
|
65
|
+
"command": "npx",
|
|
66
|
+
"args": ["localwp-mcp"],
|
|
67
|
+
"env": {
|
|
68
|
+
"LOCAL_SITE_NAME": "plovercrm",
|
|
69
|
+
"LOCALWP_MCP_PROFILE": "full-access"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If you want the cautious default instead, set:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"LOCALWP_MCP_PROFILE": "safe"
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
After a global install, you can also use:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"mcpServers": {
|
|
89
|
+
"localwp": {
|
|
90
|
+
"command": "localwp-mcp",
|
|
91
|
+
"env": {
|
|
92
|
+
"LOCALWP_MCP_PROFILE": "safe"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### From Source
|
|
100
|
+
|
|
101
|
+
If you are running from a local clone:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pnpm install
|
|
105
|
+
pnpm build
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Then point your MCP client at the built entrypoint:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"mcpServers": {
|
|
113
|
+
"localwp": {
|
|
114
|
+
"command": "node",
|
|
115
|
+
"args": [
|
|
116
|
+
"/Users/your-user/Projects/MCP-Servers/localwp-mcp/dist/index.js"
|
|
117
|
+
],
|
|
118
|
+
"env": {
|
|
119
|
+
"LOCAL_SITE_NAME": "plovercrm",
|
|
120
|
+
"LOCALWP_MCP_PROFILE": "full-access"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Recommended First Command
|
|
128
|
+
|
|
129
|
+
Run `local_environment_check` or `local_doctor` first on any machine. They show:
|
|
130
|
+
|
|
131
|
+
- resolved Local metadata paths
|
|
132
|
+
- resolved Local app-resource paths
|
|
133
|
+
- WP-CLI/tooling resolution
|
|
134
|
+
- optional WP-CLI and MySQL probes for a selected site
|
|
135
|
+
- log availability and practical next steps
|
|
136
|
+
|
|
137
|
+
That makes support and cross-platform debugging much easier.
|
|
138
|
+
|
|
139
|
+
## Backups and Database Transfers
|
|
140
|
+
|
|
141
|
+
- `backup_site`
|
|
142
|
+
Creates a backup folder for the selected site.
|
|
143
|
+
- `backup_site` with `scope=database`
|
|
144
|
+
Creates a timestamped backup folder with a fresh SQL dump.
|
|
145
|
+
- `backup_site` with `scope=full`
|
|
146
|
+
Copies the site's `app`, `conf`, and `logs` directories and writes a fresh SQL dump into `app/sql`.
|
|
147
|
+
- `db_export`
|
|
148
|
+
Writes a SQL file directly. Good when you only want the database.
|
|
149
|
+
- `db_import`
|
|
150
|
+
Imports either a `.sql` file or a `backup_site` directory. Requires `full-access`.
|
|
151
|
+
- `restore_backup`
|
|
152
|
+
Restores from a `.sql` file or a `backup_site` directory. In `full-access`, it can also restore `app`, `conf`, and `logs` from a full backup.
|
|
153
|
+
|
|
154
|
+
The full backup is intentionally folder-based instead of shelling out to platform-specific `zip` or `tar` commands. That keeps the MCP predictable across macOS, Windows, and Linux, and it stays close to Local's own site-folder restore shape.
|
|
155
|
+
|
|
156
|
+
## Resources and Prompts
|
|
157
|
+
|
|
158
|
+
This MCP now also exposes lightweight resources and prompts:
|
|
159
|
+
|
|
160
|
+
- Resource: `localwp://sites`
|
|
161
|
+
JSON catalog of discovered Local sites.
|
|
162
|
+
- Resource template: `localwp://sites/{siteName}/summary`
|
|
163
|
+
Per-site Local resolution summary.
|
|
164
|
+
- Resource template: `localwp://sites/{siteName}/doctor`
|
|
165
|
+
Per-site doctor output.
|
|
166
|
+
- Resource template: `localwp://sites/{siteName}/logs`
|
|
167
|
+
Per-site recent logs.
|
|
168
|
+
- Prompt: `diagnose_local_site`
|
|
169
|
+
Helps an agent diagnose a LocalWP site with the MCP tools.
|
|
170
|
+
- Prompt: `restore_local_site`
|
|
171
|
+
Helps an agent restore a site from a SQL dump or backup directory.
|
|
172
|
+
|
|
173
|
+
## Platform Compatibility
|
|
174
|
+
|
|
175
|
+
- `macOS`
|
|
176
|
+
Uses Local metadata under `~/Library/Application Support/Local` and the standard Local app bundle resources.
|
|
177
|
+
- `Windows`
|
|
178
|
+
Uses Local metadata under `%APPDATA%\\Local` and searches both per-user and `Program Files` installs for Local resources.
|
|
179
|
+
- `Linux`
|
|
180
|
+
Uses Local metadata under `~/.config/Local` and the common `/opt/Local/resources/extraResources` install path.
|
|
181
|
+
|
|
182
|
+
The resolver supports both current `lightning-services` layouts and older `site-binaries` layouts.
|
|
183
|
+
|
|
184
|
+
## Configuration
|
|
185
|
+
|
|
186
|
+
Optional environment variables:
|
|
187
|
+
|
|
188
|
+
- `LOCALWP_MCP_PROFILE`
|
|
189
|
+
`safe` or `full-access`
|
|
190
|
+
- `LOCALWP_MCP_BACKUPS_DIR`
|
|
191
|
+
Optional shared backup directory. If omitted, backups are written under each Local site's `localwp-mcp-backups` folder.
|
|
192
|
+
- `LOCAL_SITE_NAME`
|
|
193
|
+
- `LOCAL_SITE_ID`
|
|
194
|
+
- `LOCAL_APP_SUPPORT_DIR`
|
|
195
|
+
- `LOCAL_EXTRA_RESOURCES_DIRS`
|
|
196
|
+
- `LOCAL_RUN_DIR`
|
|
197
|
+
- `LOCAL_LIGHTNING_SERVICES_DIR`
|
|
198
|
+
- `LOCAL_LIGHTNING_SERVICES_DIRS`
|
|
199
|
+
- `LOCAL_SITES_JSON`
|
|
200
|
+
- `LOCAL_SITE_STATUSES_JSON`
|
|
201
|
+
- `LOCAL_WP_CLI_PHAR`
|
|
202
|
+
- `LOCAL_WP_CLI_CONFIG`
|
|
203
|
+
- `LOCAL_HELPER_BIN_DIRS`
|
|
204
|
+
- `LOCAL_MYSQL_HOST`
|
|
205
|
+
|
|
206
|
+
## Why It Stays Simple
|
|
207
|
+
|
|
208
|
+
- plain TypeScript
|
|
209
|
+
- `pnpm`
|
|
210
|
+
- `tsc`
|
|
211
|
+
- stdio transport
|
|
212
|
+
|
|
213
|
+
There is no bundler here because this is a Node MCP server and the simpler build is easier to debug and maintain.
|
|
214
|
+
|
|
215
|
+
## Development
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
pnpm install
|
|
219
|
+
pnpm check
|
|
220
|
+
pnpm test
|
|
221
|
+
pnpm build
|
|
222
|
+
node dist/index.js
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
For contribution and release workflows, see:
|
|
226
|
+
|
|
227
|
+
- [CONTRIBUTING.md](./CONTRIBUTING.md)
|
|
228
|
+
- [SECURITY.md](./SECURITY.md)
|
|
229
|
+
- [docs/RELEASE_CHECKLIST.md](./docs/RELEASE_CHECKLIST.md)
|
package/dist/backup.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { BackupScope, SiteContext } from "./types.js";
|
|
2
|
+
interface BackupManifest {
|
|
3
|
+
format: string;
|
|
4
|
+
createdAt?: string;
|
|
5
|
+
scope?: BackupScope;
|
|
6
|
+
accessProfile?: string;
|
|
7
|
+
site?: Record<string, unknown>;
|
|
8
|
+
database?: {
|
|
9
|
+
file?: string;
|
|
10
|
+
absolutePath?: string;
|
|
11
|
+
source?: string;
|
|
12
|
+
};
|
|
13
|
+
copiedDirectories?: string[];
|
|
14
|
+
notes?: string[];
|
|
15
|
+
}
|
|
16
|
+
interface ResolvedBackupSource {
|
|
17
|
+
inputPath: string;
|
|
18
|
+
sqlFilePath: string;
|
|
19
|
+
backupDir: string | null;
|
|
20
|
+
manifestPath: string | null;
|
|
21
|
+
manifest: BackupManifest | null;
|
|
22
|
+
copiedDirectories: string[];
|
|
23
|
+
}
|
|
24
|
+
export declare function createSiteBackup(context: SiteContext, options?: {
|
|
25
|
+
scope?: BackupScope;
|
|
26
|
+
outputDir?: string;
|
|
27
|
+
label?: string;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
backupDir: string;
|
|
30
|
+
manifestPath: string;
|
|
31
|
+
scope: BackupScope;
|
|
32
|
+
databaseFilePath: string;
|
|
33
|
+
copiedDirectories: string[];
|
|
34
|
+
}>;
|
|
35
|
+
export declare function restoreSiteBackup(context: SiteContext, sourcePath: string, options?: {
|
|
36
|
+
restoreFiles?: boolean;
|
|
37
|
+
replaceDirectories?: boolean;
|
|
38
|
+
backupBeforeRestore?: boolean;
|
|
39
|
+
}): Promise<{
|
|
40
|
+
sourcePath: string;
|
|
41
|
+
sqlFilePath: string;
|
|
42
|
+
backupDir: string | null;
|
|
43
|
+
manifestPath: string | null;
|
|
44
|
+
restoredFiles: string[];
|
|
45
|
+
replaceDirectories: boolean;
|
|
46
|
+
restartRecommended: boolean;
|
|
47
|
+
preRestoreBackupDir: string | null;
|
|
48
|
+
preRestoreManifestPath: string | null;
|
|
49
|
+
stdout: string;
|
|
50
|
+
stderr: string | null;
|
|
51
|
+
}>;
|
|
52
|
+
export declare function exportDatabase(context: SiteContext, options?: {
|
|
53
|
+
destinationPath?: string;
|
|
54
|
+
label?: string;
|
|
55
|
+
}): Promise<{
|
|
56
|
+
outputPath: string;
|
|
57
|
+
stdout: string;
|
|
58
|
+
stderr: string | null;
|
|
59
|
+
}>;
|
|
60
|
+
export declare function importDatabase(context: SiteContext, sourcePath: string, options?: {
|
|
61
|
+
backupBeforeImport?: boolean;
|
|
62
|
+
}): Promise<{
|
|
63
|
+
sourcePath: string;
|
|
64
|
+
backupPath: string | null;
|
|
65
|
+
stdout: string;
|
|
66
|
+
stderr: string | null;
|
|
67
|
+
}>;
|
|
68
|
+
export declare function buildBackupDirectoryName(siteName: string, scope: BackupScope, label?: string, createdAt?: Date): string;
|
|
69
|
+
export declare function sanitizeBackupSegment(value: string): string;
|
|
70
|
+
export declare function resolveImportSourcePath(inputPath: string): Promise<string>;
|
|
71
|
+
export declare function resolveBackupSource(inputPath: string): Promise<ResolvedBackupSource>;
|
|
72
|
+
export {};
|
package/dist/backup.js
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { cp, mkdir, readdir, readFile, rm, stat, writeFile } from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { config } from "./config.js";
|
|
4
|
+
import { summarizeSite } from "./local-sites.js";
|
|
5
|
+
import { ensureMysqlReady } from "./mysql.js";
|
|
6
|
+
import { assertReadable, isReadablePath, spawnCommand } from "./process-utils.js";
|
|
7
|
+
const defaultBackupDirectories = ["app", "conf", "logs"];
|
|
8
|
+
const backupManifestVersion = "localwp-mcp-backup-v1";
|
|
9
|
+
export async function createSiteBackup(context, options = {}) {
|
|
10
|
+
const scope = options.scope || "full";
|
|
11
|
+
await ensureMysqlReady(context);
|
|
12
|
+
const backupRoot = resolveBackupRoot(context, options.outputDir);
|
|
13
|
+
const backupName = buildBackupDirectoryName(context.site.name, scope, options.label);
|
|
14
|
+
const backupDir = path.join(backupRoot, backupName);
|
|
15
|
+
const copiedDirectories = [];
|
|
16
|
+
await mkdir(backupDir, { recursive: true });
|
|
17
|
+
let databaseFileRelativePath = "database.sql";
|
|
18
|
+
if (scope === "full") {
|
|
19
|
+
for (const directoryName of defaultBackupDirectories) {
|
|
20
|
+
const sourcePath = path.join(context.site.absolutePath, directoryName);
|
|
21
|
+
if (!(await isReadablePath(sourcePath))) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
await cp(sourcePath, path.join(backupDir, directoryName), {
|
|
25
|
+
recursive: true,
|
|
26
|
+
});
|
|
27
|
+
copiedDirectories.push(directoryName);
|
|
28
|
+
}
|
|
29
|
+
databaseFileRelativePath = path.join("app", "sql", `${sanitizeBackupSegment(context.site.name)}-backup.sql`);
|
|
30
|
+
}
|
|
31
|
+
const databaseFilePath = path.join(backupDir, databaseFileRelativePath);
|
|
32
|
+
const exportResult = await exportDatabase(context, {
|
|
33
|
+
destinationPath: databaseFilePath,
|
|
34
|
+
});
|
|
35
|
+
const manifest = {
|
|
36
|
+
format: "localwp-mcp-backup-v1",
|
|
37
|
+
createdAt: new Date().toISOString(),
|
|
38
|
+
scope,
|
|
39
|
+
accessProfile: config.profile,
|
|
40
|
+
site: summarizeSite(context.site),
|
|
41
|
+
database: {
|
|
42
|
+
file: databaseFileRelativePath,
|
|
43
|
+
absolutePath: exportResult.outputPath,
|
|
44
|
+
source: "mysqldump",
|
|
45
|
+
},
|
|
46
|
+
copiedDirectories,
|
|
47
|
+
notes: scope === "full"
|
|
48
|
+
? [
|
|
49
|
+
"This backup folder mirrors a Local site layout closely enough to inspect manually or zip for Local-friendly restore workflows.",
|
|
50
|
+
"Use db_import with the backup directory path to import the SQL dump from this backup.",
|
|
51
|
+
]
|
|
52
|
+
: [
|
|
53
|
+
"This backup contains a fresh SQL export only.",
|
|
54
|
+
"Use db_import with this backup directory path or the SQL file path.",
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
const manifestPath = path.join(backupDir, "manifest.json");
|
|
58
|
+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
59
|
+
return {
|
|
60
|
+
backupDir,
|
|
61
|
+
manifestPath,
|
|
62
|
+
scope,
|
|
63
|
+
databaseFilePath: exportResult.outputPath,
|
|
64
|
+
copiedDirectories,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export async function restoreSiteBackup(context, sourcePath, options = {}) {
|
|
68
|
+
await ensureMysqlReady(context);
|
|
69
|
+
const resolvedSource = await resolveBackupSource(sourcePath);
|
|
70
|
+
const restoreFiles = options.restoreFiles ?? true;
|
|
71
|
+
const replaceDirectories = options.replaceDirectories ?? true;
|
|
72
|
+
const fileRestoreAvailable = restoreFiles &&
|
|
73
|
+
Boolean(resolvedSource.backupDir &&
|
|
74
|
+
resolvedSource.manifest?.scope === "full" &&
|
|
75
|
+
resolvedSource.copiedDirectories.length > 0);
|
|
76
|
+
let preRestoreBackup = null;
|
|
77
|
+
if (options.backupBeforeRestore ?? true) {
|
|
78
|
+
preRestoreBackup = await createSiteBackup(context, {
|
|
79
|
+
scope: fileRestoreAvailable ? "full" : "database",
|
|
80
|
+
label: "pre-restore",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (fileRestoreAvailable && resolvedSource.backupDir) {
|
|
84
|
+
await restoreDirectoriesFromBackup(context, resolvedSource.backupDir, resolvedSource.copiedDirectories, replaceDirectories);
|
|
85
|
+
}
|
|
86
|
+
const importResult = await importDatabase(context, resolvedSource.sqlFilePath, {
|
|
87
|
+
backupBeforeImport: false,
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
sourcePath: resolvedSource.inputPath,
|
|
91
|
+
sqlFilePath: resolvedSource.sqlFilePath,
|
|
92
|
+
backupDir: resolvedSource.backupDir,
|
|
93
|
+
manifestPath: resolvedSource.manifestPath,
|
|
94
|
+
restoredFiles: fileRestoreAvailable ? resolvedSource.copiedDirectories : [],
|
|
95
|
+
replaceDirectories,
|
|
96
|
+
restartRecommended: fileRestoreAvailable,
|
|
97
|
+
preRestoreBackupDir: preRestoreBackup?.backupDir || null,
|
|
98
|
+
preRestoreManifestPath: preRestoreBackup?.manifestPath || null,
|
|
99
|
+
stdout: importResult.stdout,
|
|
100
|
+
stderr: importResult.stderr,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export async function exportDatabase(context, options = {}) {
|
|
104
|
+
await ensureMysqlReady(context);
|
|
105
|
+
const outputPath = await resolveExportPath(context, options.destinationPath, {
|
|
106
|
+
label: options.label,
|
|
107
|
+
});
|
|
108
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
109
|
+
const result = await executeMysqlExport(context, outputPath);
|
|
110
|
+
return {
|
|
111
|
+
outputPath,
|
|
112
|
+
stdout: result.stdout,
|
|
113
|
+
stderr: result.stderr || null,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export async function importDatabase(context, sourcePath, options = {}) {
|
|
117
|
+
await ensureMysqlReady(context);
|
|
118
|
+
const resolvedSourcePath = await resolveImportSourcePath(sourcePath);
|
|
119
|
+
let backupPath = null;
|
|
120
|
+
if (options.backupBeforeImport ?? true) {
|
|
121
|
+
const backupResult = await exportDatabase(context, {
|
|
122
|
+
destinationPath: path.join(resolveBackupRoot(context), "pre-import", `${sanitizeBackupSegment(context.site.name)}-before-import-${createTimestamp()}.sql`),
|
|
123
|
+
});
|
|
124
|
+
backupPath = backupResult.outputPath;
|
|
125
|
+
}
|
|
126
|
+
const result = await executeMysqlImport(context, resolvedSourcePath);
|
|
127
|
+
return {
|
|
128
|
+
sourcePath: resolvedSourcePath,
|
|
129
|
+
backupPath,
|
|
130
|
+
stdout: result.stdout,
|
|
131
|
+
stderr: result.stderr || null,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export function buildBackupDirectoryName(siteName, scope, label, createdAt = new Date()) {
|
|
135
|
+
const parts = [
|
|
136
|
+
sanitizeBackupSegment(siteName),
|
|
137
|
+
scope,
|
|
138
|
+
formatTimestamp(createdAt),
|
|
139
|
+
];
|
|
140
|
+
if (label) {
|
|
141
|
+
parts.push(sanitizeBackupSegment(label));
|
|
142
|
+
}
|
|
143
|
+
return parts.filter(Boolean).join("-");
|
|
144
|
+
}
|
|
145
|
+
export function sanitizeBackupSegment(value) {
|
|
146
|
+
return value
|
|
147
|
+
.trim()
|
|
148
|
+
.toLowerCase()
|
|
149
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
150
|
+
.replace(/^-+|-+$/g, "")
|
|
151
|
+
.replace(/-{2,}/g, "-");
|
|
152
|
+
}
|
|
153
|
+
export async function resolveImportSourcePath(inputPath) {
|
|
154
|
+
const resolvedSource = await resolveBackupSource(inputPath);
|
|
155
|
+
return resolvedSource.sqlFilePath;
|
|
156
|
+
}
|
|
157
|
+
export async function resolveBackupSource(inputPath) {
|
|
158
|
+
const absolutePath = path.resolve(inputPath);
|
|
159
|
+
await assertReadable(absolutePath, "The backup source path is not readable");
|
|
160
|
+
const fileStats = await stat(absolutePath);
|
|
161
|
+
if (fileStats.isFile()) {
|
|
162
|
+
if (path.extname(absolutePath).toLowerCase() !== ".sql") {
|
|
163
|
+
throw new Error("restore_backup and db_import expect a .sql file or a backup directory.");
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
inputPath: absolutePath,
|
|
167
|
+
sqlFilePath: absolutePath,
|
|
168
|
+
backupDir: null,
|
|
169
|
+
manifestPath: null,
|
|
170
|
+
manifest: null,
|
|
171
|
+
copiedDirectories: [],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (!fileStats.isDirectory()) {
|
|
175
|
+
throw new Error("restore_backup and db_import expect a readable .sql file or directory.");
|
|
176
|
+
}
|
|
177
|
+
const manifestPath = path.join(absolutePath, "manifest.json");
|
|
178
|
+
const manifest = (await isReadablePath(manifestPath))
|
|
179
|
+
? (JSON.parse(await readFile(manifestPath, "utf8")) ??
|
|
180
|
+
null)
|
|
181
|
+
: null;
|
|
182
|
+
if (manifest?.format && manifest.format !== backupManifestVersion) {
|
|
183
|
+
throw new Error(`Unsupported backup manifest format '${manifest.format}'.`);
|
|
184
|
+
}
|
|
185
|
+
const relativeDatabaseFile = manifest?.database?.file;
|
|
186
|
+
if (relativeDatabaseFile) {
|
|
187
|
+
const manifestSqlPath = path.join(absolutePath, relativeDatabaseFile);
|
|
188
|
+
await assertReadable(manifestSqlPath, "The SQL file referenced by this backup manifest is not readable");
|
|
189
|
+
return {
|
|
190
|
+
inputPath: absolutePath,
|
|
191
|
+
sqlFilePath: manifestSqlPath,
|
|
192
|
+
backupDir: absolutePath,
|
|
193
|
+
manifestPath,
|
|
194
|
+
manifest,
|
|
195
|
+
copiedDirectories: normalizeCopiedDirectories(manifest?.copiedDirectories),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const sqlFiles = await findSqlFiles(absolutePath);
|
|
199
|
+
if (sqlFiles.length === 1) {
|
|
200
|
+
return {
|
|
201
|
+
inputPath: absolutePath,
|
|
202
|
+
sqlFilePath: sqlFiles[0],
|
|
203
|
+
backupDir: absolutePath,
|
|
204
|
+
manifestPath: manifest ? manifestPath : null,
|
|
205
|
+
manifest,
|
|
206
|
+
copiedDirectories: normalizeCopiedDirectories(manifest?.copiedDirectories),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (sqlFiles.length === 0) {
|
|
210
|
+
throw new Error("No SQL file was found in that directory. Pass a .sql file path or a backup directory created by backup_site.");
|
|
211
|
+
}
|
|
212
|
+
throw new Error("Multiple SQL files were found in that directory. Pass the exact .sql file path you want to import.");
|
|
213
|
+
}
|
|
214
|
+
async function resolveExportPath(context, destinationPath, options = {}) {
|
|
215
|
+
const defaultFileName = `${sanitizeBackupSegment(context.site.name)}-${createTimestamp()}${options.label ? `-${sanitizeBackupSegment(options.label)}` : ""}.sql`;
|
|
216
|
+
if (!destinationPath) {
|
|
217
|
+
return path.join(resolveBackupRoot(context), "database-exports", defaultFileName);
|
|
218
|
+
}
|
|
219
|
+
const absolutePath = path.resolve(destinationPath);
|
|
220
|
+
if (await isReadablePath(absolutePath)) {
|
|
221
|
+
const existingStats = await stat(absolutePath);
|
|
222
|
+
if (existingStats.isDirectory()) {
|
|
223
|
+
return path.join(absolutePath, defaultFileName);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (path.extname(absolutePath).toLowerCase() === ".sql") {
|
|
227
|
+
return absolutePath;
|
|
228
|
+
}
|
|
229
|
+
return path.join(absolutePath, defaultFileName);
|
|
230
|
+
}
|
|
231
|
+
function resolveBackupRoot(context, outputDir) {
|
|
232
|
+
if (outputDir) {
|
|
233
|
+
return path.resolve(outputDir);
|
|
234
|
+
}
|
|
235
|
+
if (config.backupsDirOverride) {
|
|
236
|
+
return path.resolve(config.backupsDirOverride);
|
|
237
|
+
}
|
|
238
|
+
return path.join(context.site.absolutePath, "localwp-mcp-backups");
|
|
239
|
+
}
|
|
240
|
+
async function findSqlFiles(directoryPath) {
|
|
241
|
+
const entries = await readdir(directoryPath, { withFileTypes: true });
|
|
242
|
+
const sqlFiles = [];
|
|
243
|
+
for (const entry of entries) {
|
|
244
|
+
const entryPath = path.join(directoryPath, entry.name);
|
|
245
|
+
if (entry.isDirectory()) {
|
|
246
|
+
sqlFiles.push(...(await findSqlFiles(entryPath)));
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (entry.isFile() && path.extname(entry.name).toLowerCase() === ".sql") {
|
|
250
|
+
sqlFiles.push(entryPath);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return sqlFiles.sort();
|
|
254
|
+
}
|
|
255
|
+
async function restoreDirectoriesFromBackup(context, backupDir, copiedDirectories, replaceDirectories) {
|
|
256
|
+
for (const directoryName of copiedDirectories) {
|
|
257
|
+
const sourcePath = path.join(backupDir, directoryName);
|
|
258
|
+
const targetPath = path.join(context.site.absolutePath, directoryName);
|
|
259
|
+
await assertReadable(sourcePath, `The backup directory is missing the expected '${directoryName}' folder`);
|
|
260
|
+
if (replaceDirectories) {
|
|
261
|
+
await rm(targetPath, { recursive: true, force: true });
|
|
262
|
+
}
|
|
263
|
+
await cp(sourcePath, targetPath, {
|
|
264
|
+
recursive: true,
|
|
265
|
+
force: true,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function formatTimestamp(value) {
|
|
270
|
+
return value
|
|
271
|
+
.toISOString()
|
|
272
|
+
.replace(/[-:]/g, "")
|
|
273
|
+
.replace(/\.\d{3}Z$/, "")
|
|
274
|
+
.replace("T", "-");
|
|
275
|
+
}
|
|
276
|
+
function createTimestamp() {
|
|
277
|
+
return formatTimestamp(new Date());
|
|
278
|
+
}
|
|
279
|
+
function normalizeCopiedDirectories(value) {
|
|
280
|
+
return (value || []).filter((directoryName) => defaultBackupDirectories.includes(directoryName));
|
|
281
|
+
}
|
|
282
|
+
function buildMysqlConnectionArgs(context) {
|
|
283
|
+
if (context.mysqlSocket) {
|
|
284
|
+
return ["--protocol=SOCKET", `--socket=${context.mysqlSocket}`];
|
|
285
|
+
}
|
|
286
|
+
const args = [
|
|
287
|
+
"--protocol=TCP",
|
|
288
|
+
`--host=${context.mysqlHost || config.defaultMysqlHost}`,
|
|
289
|
+
];
|
|
290
|
+
if (context.mysqlPort) {
|
|
291
|
+
args.push(`--port=${context.mysqlPort}`);
|
|
292
|
+
}
|
|
293
|
+
return args;
|
|
294
|
+
}
|
|
295
|
+
function getMysqldumpPath(context) {
|
|
296
|
+
return path.join(path.dirname(context.mysql.binaryPath), process.platform === "win32" ? "mysqldump.exe" : "mysqldump");
|
|
297
|
+
}
|
|
298
|
+
async function executeMysqlExport(context, outputPath) {
|
|
299
|
+
const args = [
|
|
300
|
+
`--defaults-file=${context.mysqlDefaultsFile}`,
|
|
301
|
+
"--default-character-set=utf8mb4",
|
|
302
|
+
"--add-drop-table",
|
|
303
|
+
`--result-file=${outputPath}`,
|
|
304
|
+
...buildMysqlConnectionArgs(context),
|
|
305
|
+
context.database,
|
|
306
|
+
];
|
|
307
|
+
const result = await spawnCommand(getMysqldumpPath(context), args, {
|
|
308
|
+
cwd: context.wpRoot,
|
|
309
|
+
env: process.env,
|
|
310
|
+
});
|
|
311
|
+
if (result.timedOut) {
|
|
312
|
+
throw new Error(`Database export timed out after ${config.defaultTimeoutMs / 1000}s for site '${context.site.name}'.`);
|
|
313
|
+
}
|
|
314
|
+
if (result.exitCode !== 0) {
|
|
315
|
+
throw new Error([
|
|
316
|
+
`mysqldump exited with code ${result.exitCode} for site '${context.site.name}'.`,
|
|
317
|
+
result.stderr,
|
|
318
|
+
result.stdout,
|
|
319
|
+
]
|
|
320
|
+
.filter(Boolean)
|
|
321
|
+
.join("\n\n"));
|
|
322
|
+
}
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
async function executeMysqlImport(context, sourcePath) {
|
|
326
|
+
const args = [
|
|
327
|
+
`--defaults-file=${context.mysqlDefaultsFile}`,
|
|
328
|
+
"--default-character-set=utf8mb4",
|
|
329
|
+
...buildMysqlConnectionArgs(context),
|
|
330
|
+
context.database,
|
|
331
|
+
];
|
|
332
|
+
const result = await spawnCommand(context.mysql.binaryPath, args, {
|
|
333
|
+
cwd: context.wpRoot,
|
|
334
|
+
env: process.env,
|
|
335
|
+
stdinFilePath: sourcePath,
|
|
336
|
+
});
|
|
337
|
+
if (result.timedOut) {
|
|
338
|
+
throw new Error(`Database import timed out after ${config.defaultTimeoutMs / 1000}s for site '${context.site.name}'.`);
|
|
339
|
+
}
|
|
340
|
+
if (result.exitCode !== 0) {
|
|
341
|
+
throw new Error([
|
|
342
|
+
`mysql exited with code ${result.exitCode} for site '${context.site.name}'.`,
|
|
343
|
+
result.stderr,
|
|
344
|
+
result.stdout,
|
|
345
|
+
]
|
|
346
|
+
.filter(Boolean)
|
|
347
|
+
.join("\n\n"));
|
|
348
|
+
}
|
|
349
|
+
return result;
|
|
350
|
+
}
|
|
351
|
+
//# sourceMappingURL=backup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"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"}
|