@instawp/cli 0.0.1-beta.3 → 0.0.1-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -0
- package/README.md +58 -10
- package/bin/win32/NOTICE.md +38 -0
- package/bin/win32/busybox.exe +0 -0
- package/bin/win32/msys-2.0.dll +0 -0
- package/bin/win32/msys-crypto-3.dll +0 -0
- package/bin/win32/msys-iconv-2.dll +0 -0
- package/bin/win32/msys-lz4-1.dll +0 -0
- package/bin/win32/msys-xxhash-0.dll +0 -0
- package/bin/win32/msys-zstd-1.dll +0 -0
- package/bin/win32/rsync.exe +0 -0
- package/dist/commands/db.d.ts +2 -0
- package/dist/commands/db.js +331 -0
- package/dist/commands/db.js.map +1 -0
- package/dist/commands/exec.js +36 -3
- package/dist/commands/exec.js.map +1 -1
- package/dist/commands/local.js +138 -83
- package/dist/commands/local.js.map +1 -1
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +239 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/open.d.ts +2 -0
- package/dist/commands/open.js +114 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/sites.js +76 -0
- package/dist/commands/sites.js.map +1 -1
- package/dist/commands/sync.js +3 -0
- package/dist/commands/sync.js.map +1 -1
- package/dist/index.js +38 -8
- package/dist/index.js.map +1 -1
- package/dist/lib/output.js +14 -1
- package/dist/lib/output.js.map +1 -1
- package/dist/lib/paths.d.ts +22 -0
- package/dist/lib/paths.js +41 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/ssh-connection.js +14 -4
- package/dist/lib/ssh-connection.js.map +1 -1
- package/dist/lib/windows-binaries.d.ts +10 -0
- package/dist/lib/windows-binaries.js +31 -0
- package/dist/lib/windows-binaries.js.map +1 -0
- package/package.json +6 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.0.1-beta.5 (2026-05-23)
|
|
4
|
+
|
|
5
|
+
### New Commands
|
|
6
|
+
- `db push <site> <file>` — Push a local SQL dump (`.sql` or `.sql.gz`) to the remote MySQL database. Always backs up the remote DB to `~/db-backup-{ISO}.sql.gz` first (skip with `--no-backup`). Confirmation prompt unless `--force`. Closes the #1 gap blocking full-site deploys from the CLI.
|
|
7
|
+
- `db pull <site>` — Stream the remote MySQL database to a local gzipped dump. `--output <path>` and `--no-compress` flags.
|
|
8
|
+
- `open <site>` — Open the site URL in the default browser. `--admin` opens `/wp-admin`, `--magic` opens the Magic Login URL, `--print` pipes the URL to stdout instead.
|
|
9
|
+
- `logs <site>` — Tail logs via SSH. `--wp` (default, debug.log), `--php` (PHP-FPM error log), `--nginx` (nginx error log), `--follow` / `-f`, `--lines <n>`. Multiple flags multi-tail. Probes HestiaCP path variations automatically.
|
|
10
|
+
- `sites creds <site>` — Re-fetch WP admin credentials + Magic Login URL for an existing site (previously only available in the `create` output).
|
|
11
|
+
|
|
12
|
+
### Improvements
|
|
13
|
+
- `wp <site>` is now positioned as the primary remote-access command; `exec` is documented as the escape hatch for non-WP shell commands.
|
|
14
|
+
- `wp` / `exec` accept POSIX `--` to forward raw args verbatim: `instawp wp my-site -- post list --post_type=page`.
|
|
15
|
+
- Spinners are suppressed in non-TTY contexts, CI environments (`CI` env var), `--json` mode, `NO_COLOR`, and `INSTAWP_QUIET` — fixes "Resolving site..." leaking into piped output.
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
- `instawp wp <site> eval '...'` no longer breaks on parens, quotes, or other shell metacharacters. Each arg is now POSIX shell-quoted before being piped to the remote shell's stdin (previously `args.join(' ')` left metacharacters unescaped, causing remote `bash: syntax error near unexpected token '('`).
|
|
19
|
+
|
|
20
|
+
### Docs
|
|
21
|
+
- New `ROADMAP.md` capturing 15 forward-looking improvement areas (multi-site bulk ops, cost transparency, CI/CD deploy command, shell completion, `doctor`, config file, snapshot/migration CLI, self-update, etc.) ranked by ROI.
|
|
22
|
+
- README + CLAUDE.md updated with `wp`-primary positioning and examples for all new commands.
|
|
23
|
+
|
|
24
|
+
## 0.0.1-beta.4 (2026-05-22)
|
|
25
|
+
|
|
26
|
+
### Windows — Zero-Install Support
|
|
27
|
+
- Bundled `rsync.exe` (with msys2 runtime DLLs) and BusyBox-w64 (`awk` provider) in `bin/win32/`. No more "install Git for Windows / cwRsync" prerequisite — `instawp local clone`, `local push/pull`, and `sync push/pull` work out of the box on Windows.
|
|
28
|
+
- Replaced the external `sqlite3` CLI dependency with the `better-sqlite3` Node module.
|
|
29
|
+
- New `src/lib/windows-binaries.ts` resolves bundled binaries; falls back to PATH then common Git-for-Windows install dirs.
|
|
30
|
+
|
|
31
|
+
### Bug Fixes (Windows)
|
|
32
|
+
- `instawp local clone` now resolves the bundled `mysql2sqlite` script correctly (was broken by `new URL(import.meta.url).pathname` returning `/C:/...`).
|
|
33
|
+
- `mysql2sqlite` is invoked as `awk -f script` explicitly; no longer relies on shebang interpretation.
|
|
34
|
+
- `rsync` no longer treats Windows drive paths (`C:\...`) as remote hostnames — paths are converted to msys style (`/c/...`) inside `rsyncViaSsh`.
|
|
35
|
+
- `-e ssh -i <key>` argument uses forward slashes + quoted paths so msys/cygwin sh inside rsync parses the key path correctly.
|
|
36
|
+
- Eliminated the SQL injection risk in `local clone`'s URL search-replace (now uses bound parameters via better-sqlite3).
|
|
37
|
+
|
|
38
|
+
### Internals
|
|
39
|
+
- New `scripts/fetch-windows-binaries.sh` (maintainer-only) refreshes the Windows bundle from MSYS2 + frippery.org.
|
|
40
|
+
- 32 new tests covering path conversion and bundled-binary resolution.
|
|
41
|
+
|
|
42
|
+
## 0.0.1-beta.3 (2026-04-12)
|
|
43
|
+
|
|
44
|
+
### New Commands
|
|
45
|
+
- `local create` — Create local WordPress sites (powered by WordPress Playground, no Docker needed)
|
|
46
|
+
- `local clone <site>` — Clone an InstaWP cloud site to local (files + database)
|
|
47
|
+
- `local start/stop` — Start in foreground or `--background` mode
|
|
48
|
+
- `local push/pull` — Sync wp-content between local and cloud (incremental rsync)
|
|
49
|
+
- `local list` — Show local sites with running/stopped status
|
|
50
|
+
- `local delete` — Remove local sites
|
|
51
|
+
- `sites php <site>` — View or update PHP version and settings
|
|
52
|
+
- `sites update <site>` — Update site label, description, or expiration
|
|
53
|
+
- `teams switch <team>` — Switch active team context
|
|
54
|
+
|
|
55
|
+
### Improvements
|
|
56
|
+
- `create --wp <version>` — Specify WordPress version when creating sites
|
|
57
|
+
- `sites list` — 50 per page default, `--all` flag, pagination hints
|
|
58
|
+
- Login now shows user name and team after success
|
|
59
|
+
- Site resolver caches name-to-ID lookups for 10 minutes
|
|
60
|
+
- rsync only shows actually changed files (`--itemize-changes`)
|
|
61
|
+
- Magic login URL fixed to use correct `/wordpress-auto-login` endpoint
|
|
62
|
+
|
|
63
|
+
### Bug Fixes
|
|
64
|
+
- Windows: SSH key generation now works (removed Unix-specific shell commands)
|
|
65
|
+
- Windows: command detection uses `where` instead of `which`
|
|
66
|
+
- `exec/wp --api` flag now works at any position in the command
|
|
67
|
+
- Terminal restored after local site Ctrl+C (`stty sane`)
|
|
68
|
+
|
|
69
|
+
## 0.0.1-beta.2 (2026-03-23)
|
|
70
|
+
|
|
71
|
+
### New Commands
|
|
72
|
+
- `local create/clone/start/stop/push/pull/list/delete` — Full local development workflow
|
|
73
|
+
- `teams switch` — Client-side team context
|
|
74
|
+
|
|
75
|
+
### Improvements
|
|
76
|
+
- Site resolver caching
|
|
77
|
+
- Incremental rsync output
|
|
78
|
+
|
|
79
|
+
## 0.0.1-beta.1 (2026-03-02)
|
|
80
|
+
|
|
81
|
+
### Initial Release
|
|
82
|
+
- `login` — OAuth browser flow or `--token`
|
|
83
|
+
- `whoami` — Show current session
|
|
84
|
+
- `create` — Create WordPress sites with provisioning progress
|
|
85
|
+
- `sites list/delete` — Manage sites
|
|
86
|
+
- `exec/wp` — Run commands via SSH or API
|
|
87
|
+
- `ssh` — Interactive SSH sessions
|
|
88
|
+
- `sync push/pull` — rsync wp-content via SSH
|
|
89
|
+
- `teams list/members` — View teams
|
|
90
|
+
- `--json` mode for all commands
|
package/README.md
CHANGED
|
@@ -33,26 +33,42 @@ instawp sites list
|
|
|
33
33
|
instawp create --name my-site
|
|
34
34
|
instawp create --name my-site --php 8.3
|
|
35
35
|
|
|
36
|
+
# Re-fetch admin credentials + Magic Login URL
|
|
37
|
+
instawp sites creds <site>
|
|
38
|
+
|
|
39
|
+
# Open the site (or admin / magic login) in your browser
|
|
40
|
+
instawp open <site>
|
|
41
|
+
instawp open <site> --admin
|
|
42
|
+
instawp open <site> --magic
|
|
43
|
+
|
|
36
44
|
# Delete a site
|
|
37
45
|
instawp sites delete <site>
|
|
38
46
|
instawp sites delete <site> --force
|
|
39
47
|
```
|
|
40
48
|
|
|
41
|
-
### Run Commands
|
|
49
|
+
### Run Commands (WP-CLI + shell)
|
|
50
|
+
|
|
51
|
+
`wp` is the **primary** command for interacting with a remote site. `exec` is the escape hatch for non-WP shell commands.
|
|
42
52
|
|
|
43
53
|
```bash
|
|
44
|
-
#
|
|
54
|
+
# WP-CLI on a remote site
|
|
55
|
+
instawp wp <site> plugin list
|
|
56
|
+
instawp wp <site> option get siteurl
|
|
57
|
+
instawp wp <site> user list --api
|
|
58
|
+
|
|
59
|
+
# Pass raw args to WP-CLI with --
|
|
60
|
+
instawp wp <site> -- post list --post_type=page --format=json
|
|
61
|
+
|
|
62
|
+
# eval / PHP payloads — wrap in single quotes; args are shell-escaped for you
|
|
63
|
+
instawp wp <site> eval '\MyClass::init(["force" => true]);'
|
|
64
|
+
|
|
65
|
+
# Escape hatch for non-WP commands
|
|
45
66
|
instawp exec <site> ls -la
|
|
46
67
|
instawp exec <site> php -v
|
|
47
68
|
instawp exec <site> cat wp-config.php
|
|
48
69
|
|
|
49
|
-
#
|
|
50
|
-
instawp exec <site>
|
|
51
|
-
|
|
52
|
-
# WP-CLI shorthand (prepends `wp` automatically)
|
|
53
|
-
instawp wp <site> plugin list
|
|
54
|
-
instawp wp <site> option get siteurl
|
|
55
|
-
instawp wp <site> user list --api
|
|
70
|
+
# --api transport (no SSH setup required)
|
|
71
|
+
instawp exec <site> php -v --api
|
|
56
72
|
```
|
|
57
73
|
|
|
58
74
|
`<site>` can be a site **ID**, **name**, or **domain** — the CLI resolves it automatically.
|
|
@@ -79,6 +95,37 @@ instawp sync pull <site>
|
|
|
79
95
|
instawp sync push <site> --dry-run
|
|
80
96
|
```
|
|
81
97
|
|
|
98
|
+
### Database (mysqldump)
|
|
99
|
+
|
|
100
|
+
`db push` always backs up the remote database before overwriting (use `--no-backup` to skip).
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Pull remote DB to a gzipped SQL dump
|
|
104
|
+
instawp db pull <site>
|
|
105
|
+
instawp db pull <site> --output ./backup.sql.gz
|
|
106
|
+
instawp db pull <site> --no-compress # write .sql instead of .sql.gz
|
|
107
|
+
|
|
108
|
+
# Push a local dump back (auto-backs up the remote first)
|
|
109
|
+
instawp db push <site> ./backup.sql.gz
|
|
110
|
+
instawp db push <site> ./backup.sql --force # skip confirmation
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Logs
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Tail the WP debug.log (default)
|
|
117
|
+
instawp logs <site>
|
|
118
|
+
instawp logs <site> --follow # tail -f
|
|
119
|
+
|
|
120
|
+
# Tail PHP-FPM or nginx error logs
|
|
121
|
+
instawp logs <site> --php
|
|
122
|
+
instawp logs <site> --nginx
|
|
123
|
+
instawp logs <site> --php --nginx -f # multi-tail
|
|
124
|
+
|
|
125
|
+
# Custom line count
|
|
126
|
+
instawp logs <site> --lines 500
|
|
127
|
+
```
|
|
128
|
+
|
|
82
129
|
### Teams
|
|
83
130
|
|
|
84
131
|
```bash
|
|
@@ -93,7 +140,8 @@ Add `--json` to any command for machine-readable output:
|
|
|
93
140
|
```bash
|
|
94
141
|
instawp sites list --json
|
|
95
142
|
instawp create --name test-site --json
|
|
96
|
-
instawp
|
|
143
|
+
instawp sites creds <site> --json
|
|
144
|
+
instawp wp <site> option get siteurl --json
|
|
97
145
|
```
|
|
98
146
|
|
|
99
147
|
## Environment Variables
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Bundled Windows binaries
|
|
2
|
+
|
|
3
|
+
Files in this directory are third-party binaries bundled into the npm package
|
|
4
|
+
so that `instawp local clone`, `instawp local push/pull`, and `instawp sync`
|
|
5
|
+
work on Windows without requiring users to install rsync, awk, or sqlite3.
|
|
6
|
+
|
|
7
|
+
Populate this directory by running `scripts/fetch-windows-binaries.sh`.
|
|
8
|
+
|
|
9
|
+
## Components and licenses
|
|
10
|
+
|
|
11
|
+
### busybox.exe
|
|
12
|
+
- **Source**: BusyBox-w64 by Ron Yorston — https://frippery.org/busybox/
|
|
13
|
+
- **License**: GPL-2.0
|
|
14
|
+
- **Used for**: provides `awk` (invoked as `busybox.exe awk -f ...`) for
|
|
15
|
+
converting MySQL dumps to SQLite via the vendored `scripts/mysql2sqlite`
|
|
16
|
+
awk script.
|
|
17
|
+
|
|
18
|
+
### rsync.exe + msys-*.dll
|
|
19
|
+
- **Source**: Git for Windows portable distribution — https://gitforwindows.org/
|
|
20
|
+
- **License**: rsync is GPL-3.0; msys2-runtime DLLs are mixed (mostly LGPL/MIT)
|
|
21
|
+
- **Used for**: file sync between local and remote sites in `sync push/pull`
|
|
22
|
+
and `local push/pull/clone`.
|
|
23
|
+
|
|
24
|
+
The `msys-2.0.dll` and other `msys-*.dll` files must remain colocated with
|
|
25
|
+
rsync.exe — rsync.exe links against them at runtime.
|
|
26
|
+
|
|
27
|
+
## License compliance
|
|
28
|
+
|
|
29
|
+
The CLI is MIT-licensed, but the bundled GPL binaries impose obligations on
|
|
30
|
+
**redistribution**:
|
|
31
|
+
|
|
32
|
+
- Users who receive the binaries are entitled to the corresponding source.
|
|
33
|
+
- BusyBox source: https://busybox.net/downloads/
|
|
34
|
+
- rsync source: https://download.samba.org/pub/rsync/src/
|
|
35
|
+
- Git for Windows source: https://github.com/git-for-windows/git
|
|
36
|
+
|
|
37
|
+
The maintainer's responsibility is to keep this NOTICE.md shipped alongside
|
|
38
|
+
the binaries in the npm tarball.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { join, dirname, basename } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { existsSync, mkdirSync, statSync, createReadStream, createWriteStream, unlinkSync } from 'node:fs';
|
|
5
|
+
import { createGunzip } from 'node:zlib';
|
|
6
|
+
import { pipeline } from 'node:stream/promises';
|
|
7
|
+
import { randomBytes } from 'node:crypto';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { requireAuth } from '../lib/api.js';
|
|
10
|
+
import { resolveSite } from '../lib/site-resolver.js';
|
|
11
|
+
import { ensureSshAccess } from '../lib/ssh-keys.js';
|
|
12
|
+
import { execViaSsh, execViaSshToFile } from '../lib/ssh-connection.js';
|
|
13
|
+
import { success, error, spinner, info, isJsonMode } from '../lib/output.js';
|
|
14
|
+
const KNOWN_HOSTS = join(homedir(), '.instawp', 'known_hosts');
|
|
15
|
+
/**
|
|
16
|
+
* Build scp args matching the ssh-connection.ts ssh args (key, known-hosts,
|
|
17
|
+
* StrictHostKeyChecking). Note `scp` uses uppercase `-P` for port.
|
|
18
|
+
*/
|
|
19
|
+
function scpArgs(conn) {
|
|
20
|
+
return [
|
|
21
|
+
'-i', conn.privateKeyPath,
|
|
22
|
+
'-P', String(conn.port),
|
|
23
|
+
'-o', 'StrictHostKeyChecking=accept-new',
|
|
24
|
+
'-o', `UserKnownHostsFile=${KNOWN_HOSTS}`,
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
function scpUpload(conn, localPath, remotePath) {
|
|
28
|
+
const target = `${conn.username}@${conn.host}:${remotePath}`;
|
|
29
|
+
const result = spawnSync('scp', [...scpArgs(conn), localPath, target], {
|
|
30
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
31
|
+
encoding: 'utf-8',
|
|
32
|
+
});
|
|
33
|
+
if ((result.status ?? 1) !== 0 && result.stderr) {
|
|
34
|
+
console.error(result.stderr);
|
|
35
|
+
}
|
|
36
|
+
return result.status ?? 1;
|
|
37
|
+
}
|
|
38
|
+
/** Timestamp like `2026-05-23T12-34-56` (filename-safe — `:` is illegal on Windows). */
|
|
39
|
+
function isoTimestamp() {
|
|
40
|
+
return new Date().toISOString().replace(/[:.]/g, '-').replace(/-\d{3}Z$/, '');
|
|
41
|
+
}
|
|
42
|
+
function sanitizeForFilename(s) {
|
|
43
|
+
return s.replace(/[^a-zA-Z0-9_.-]/g, '-');
|
|
44
|
+
}
|
|
45
|
+
function formatBytes(bytes) {
|
|
46
|
+
if (bytes < 1024)
|
|
47
|
+
return `${bytes} B`;
|
|
48
|
+
if (bytes < 1024 * 1024)
|
|
49
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
50
|
+
if (bytes < 1024 * 1024 * 1024)
|
|
51
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
52
|
+
return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
|
53
|
+
}
|
|
54
|
+
async function gunzipFile(src, dest) {
|
|
55
|
+
await pipeline(createReadStream(src), createGunzip(), createWriteStream(dest));
|
|
56
|
+
}
|
|
57
|
+
async function promptYesNo(question) {
|
|
58
|
+
const readline = await import('node:readline');
|
|
59
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
60
|
+
const answer = await new Promise((resolve) => {
|
|
61
|
+
rl.question(question, resolve);
|
|
62
|
+
});
|
|
63
|
+
rl.close();
|
|
64
|
+
return answer.trim().toLowerCase() === 'y' || answer.trim().toLowerCase() === 'yes';
|
|
65
|
+
}
|
|
66
|
+
export function registerDbCommand(program) {
|
|
67
|
+
const db = program
|
|
68
|
+
.command('db')
|
|
69
|
+
.description('Push/pull MySQL database dumps to/from a remote site');
|
|
70
|
+
// db pull <site>
|
|
71
|
+
db
|
|
72
|
+
.command('pull <site>')
|
|
73
|
+
.description('Pull remote MySQL database to a local SQL dump')
|
|
74
|
+
.option('--output <path>', 'Output file path (default: ./db-<site>-<timestamp>.sql.gz)')
|
|
75
|
+
.option('--no-compress', 'Write uncompressed .sql instead of .sql.gz')
|
|
76
|
+
.action(async (siteIdentifier, opts) => {
|
|
77
|
+
requireAuth();
|
|
78
|
+
const resolveSpin = spinner('Resolving site...');
|
|
79
|
+
resolveSpin.start();
|
|
80
|
+
let site;
|
|
81
|
+
try {
|
|
82
|
+
site = await resolveSite(siteIdentifier);
|
|
83
|
+
resolveSpin.succeed(`Site: ${site.name || site.sub_domain} (ID: ${site.id})`);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
resolveSpin.fail('Site resolution failed');
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
const conn = await ensureSshAccess(site.id);
|
|
90
|
+
const wpPath = `/home/${conn.username}/web/${conn.domain}/public_html`;
|
|
91
|
+
const compress = opts.compress !== false;
|
|
92
|
+
const siteLabel = sanitizeForFilename(site.name || site.sub_domain || `site-${site.id}`);
|
|
93
|
+
const ext = compress ? 'sql.gz' : 'sql';
|
|
94
|
+
const outputPath = opts.output || `./db-${siteLabel}-${isoTimestamp()}.${ext}`;
|
|
95
|
+
// Make sure the output directory exists
|
|
96
|
+
const outDir = dirname(outputPath);
|
|
97
|
+
if (outDir && outDir !== '.' && !existsSync(outDir)) {
|
|
98
|
+
mkdirSync(outDir, { recursive: true });
|
|
99
|
+
}
|
|
100
|
+
const dumpSpin = spinner(`Exporting database from ${conn.domain}...`);
|
|
101
|
+
dumpSpin.start();
|
|
102
|
+
// Stream `wp db export -` from remote. If --compress, pipe through gzip on
|
|
103
|
+
// the remote side so we never materialize the uncompressed dump locally.
|
|
104
|
+
const remoteCmd = compress
|
|
105
|
+
? `cd ${wpPath} && wp db export --single-transaction - | gzip`
|
|
106
|
+
: `cd ${wpPath} && wp db export --single-transaction -`;
|
|
107
|
+
try {
|
|
108
|
+
const { exitCode, stderr } = execViaSshToFile(conn, remoteCmd, outputPath);
|
|
109
|
+
if (exitCode !== 0) {
|
|
110
|
+
dumpSpin.fail('Database export failed');
|
|
111
|
+
if (stderr)
|
|
112
|
+
error(stderr.trim());
|
|
113
|
+
// Clean up empty/partial file
|
|
114
|
+
try {
|
|
115
|
+
if (existsSync(outputPath))
|
|
116
|
+
unlinkSync(outputPath);
|
|
117
|
+
}
|
|
118
|
+
catch { /* ignore */ }
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const sizeBytes = statSync(outputPath).size;
|
|
122
|
+
if (sizeBytes === 0) {
|
|
123
|
+
dumpSpin.fail('Database export produced an empty file');
|
|
124
|
+
try {
|
|
125
|
+
unlinkSync(outputPath);
|
|
126
|
+
}
|
|
127
|
+
catch { /* ignore */ }
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
dumpSpin.succeed(`Database exported (${formatBytes(sizeBytes)})`);
|
|
131
|
+
success('Pull complete', {
|
|
132
|
+
file: outputPath,
|
|
133
|
+
size_bytes: sizeBytes,
|
|
134
|
+
site_id: site.id,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
dumpSpin.fail('Database export failed');
|
|
139
|
+
error(err.message || String(err));
|
|
140
|
+
try {
|
|
141
|
+
if (existsSync(outputPath))
|
|
142
|
+
unlinkSync(outputPath);
|
|
143
|
+
}
|
|
144
|
+
catch { /* ignore */ }
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
// db push <site> <file>
|
|
149
|
+
db
|
|
150
|
+
.command('push <site> <file>')
|
|
151
|
+
.description('Push local SQL dump to remote site database (creates a backup first)')
|
|
152
|
+
.option('--force', 'Skip confirmation prompt')
|
|
153
|
+
.option('--no-backup', 'Skip taking a remote backup before overwrite (DANGEROUS)')
|
|
154
|
+
.addHelpText('after', `
|
|
155
|
+
Notes:
|
|
156
|
+
- Always takes a remote backup first unless --no-backup is passed.
|
|
157
|
+
- After pushing to a site on a different domain, you may want to run:
|
|
158
|
+
instawp wp <site> search-replace <old-url> <new-url>
|
|
159
|
+
Auto-replacing site URLs is out of scope for this command.
|
|
160
|
+
`)
|
|
161
|
+
.action(async (siteIdentifier, file, opts) => {
|
|
162
|
+
requireAuth();
|
|
163
|
+
// Validate input file
|
|
164
|
+
if (!existsSync(file)) {
|
|
165
|
+
error(`File not found: ${file}`);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
const localSize = statSync(file).size;
|
|
169
|
+
if (localSize === 0) {
|
|
170
|
+
error(`File is empty: ${file}`);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
// In JSON mode, can't prompt — require --force
|
|
174
|
+
if (isJsonMode() && !opts.force) {
|
|
175
|
+
error('--force is required when using --json (cannot prompt for confirmation)');
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
const resolveSpin = spinner('Resolving site...');
|
|
179
|
+
resolveSpin.start();
|
|
180
|
+
let site;
|
|
181
|
+
try {
|
|
182
|
+
site = await resolveSite(siteIdentifier);
|
|
183
|
+
resolveSpin.succeed(`Site: ${site.name || site.sub_domain} (ID: ${site.id})`);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
resolveSpin.fail('Site resolution failed');
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
const conn = await ensureSshAccess(site.id);
|
|
190
|
+
const wpPath = `/home/${conn.username}/web/${conn.domain}/public_html`;
|
|
191
|
+
const remoteHome = `/home/${conn.username}`;
|
|
192
|
+
const timestamp = isoTimestamp();
|
|
193
|
+
const backupFilename = `db-backup-${timestamp}.sql.gz`;
|
|
194
|
+
const backupRemotePath = `${remoteHome}/${backupFilename}`;
|
|
195
|
+
const takeBackup = opts.backup !== false;
|
|
196
|
+
// Confirmation
|
|
197
|
+
if (!opts.force) {
|
|
198
|
+
const backupLine = takeBackup
|
|
199
|
+
? `A backup will be saved to ~/${backupFilename} on the remote.`
|
|
200
|
+
: chalk.red('NO BACKUP will be taken (--no-backup). This is irreversible.');
|
|
201
|
+
console.log(`\nThis will ${chalk.bold.red('OVERWRITE')} the database on ${chalk.bold(conn.domain)}.`);
|
|
202
|
+
console.log(backupLine);
|
|
203
|
+
const ok = await promptYesNo('Continue? (y/N) ');
|
|
204
|
+
if (!ok) {
|
|
205
|
+
info('Cancelled.');
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Step 1: Backup
|
|
210
|
+
if (takeBackup) {
|
|
211
|
+
const backupSpin = spinner(`Backing up remote database to ~/${backupFilename}...`);
|
|
212
|
+
backupSpin.start();
|
|
213
|
+
const backupCmd = `cd ${wpPath} && wp db export --single-transaction - | gzip > ${backupRemotePath}`;
|
|
214
|
+
const backupResult = execViaSsh(conn, backupCmd);
|
|
215
|
+
if (backupResult.exitCode !== 0) {
|
|
216
|
+
backupSpin.fail('Backup failed — aborting push');
|
|
217
|
+
if (backupResult.stderr)
|
|
218
|
+
error(backupResult.stderr.trim());
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
backupSpin.succeed(`Backup saved: ~/${backupFilename}`);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
info('Skipping backup (--no-backup)');
|
|
225
|
+
}
|
|
226
|
+
// Step 2: Prepare local SQL (gunzip if needed)
|
|
227
|
+
const isGzipped = file.endsWith('.gz') || file.endsWith('.gzip');
|
|
228
|
+
let uploadSource = file;
|
|
229
|
+
let tempLocalDecompressed = null;
|
|
230
|
+
if (isGzipped) {
|
|
231
|
+
const decompressSpin = spinner('Decompressing local dump...');
|
|
232
|
+
decompressSpin.start();
|
|
233
|
+
try {
|
|
234
|
+
const tmpName = `instawp-db-push-${randomBytes(6).toString('hex')}.sql`;
|
|
235
|
+
tempLocalDecompressed = join(process.env.TMPDIR || '/tmp', tmpName);
|
|
236
|
+
await gunzipFile(file, tempLocalDecompressed);
|
|
237
|
+
uploadSource = tempLocalDecompressed;
|
|
238
|
+
const decompressedSize = statSync(uploadSource).size;
|
|
239
|
+
decompressSpin.succeed(`Decompressed (${formatBytes(decompressedSize)})`);
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
decompressSpin.fail('Decompression failed');
|
|
243
|
+
error(err.message || String(err));
|
|
244
|
+
if (tempLocalDecompressed) {
|
|
245
|
+
try {
|
|
246
|
+
unlinkSync(tempLocalDecompressed);
|
|
247
|
+
}
|
|
248
|
+
catch { /* ignore */ }
|
|
249
|
+
}
|
|
250
|
+
if (takeBackup) {
|
|
251
|
+
info(`Remote backup preserved: ~/${backupFilename}`);
|
|
252
|
+
}
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Step 3: Upload via scp to /tmp on remote
|
|
257
|
+
const remoteTempName = `db-import-${randomBytes(6).toString('hex')}.sql`;
|
|
258
|
+
const remoteTempPath = `/tmp/${remoteTempName}`;
|
|
259
|
+
const uploadSpin = spinner(`Uploading ${basename(uploadSource)} to remote...`);
|
|
260
|
+
uploadSpin.start();
|
|
261
|
+
const scpExit = scpUpload(conn, uploadSource, remoteTempPath);
|
|
262
|
+
if (scpExit !== 0) {
|
|
263
|
+
uploadSpin.fail(`Upload failed (scp exit ${scpExit})`);
|
|
264
|
+
if (tempLocalDecompressed) {
|
|
265
|
+
try {
|
|
266
|
+
unlinkSync(tempLocalDecompressed);
|
|
267
|
+
}
|
|
268
|
+
catch { /* ignore */ }
|
|
269
|
+
}
|
|
270
|
+
if (takeBackup) {
|
|
271
|
+
info(`Remote backup preserved: ~/${backupFilename}`);
|
|
272
|
+
}
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
uploadSpin.succeed('Upload complete');
|
|
276
|
+
// Clean up local temp file (we have it on remote now)
|
|
277
|
+
if (tempLocalDecompressed) {
|
|
278
|
+
try {
|
|
279
|
+
unlinkSync(tempLocalDecompressed);
|
|
280
|
+
}
|
|
281
|
+
catch { /* ignore */ }
|
|
282
|
+
}
|
|
283
|
+
// Step 4: Import on remote
|
|
284
|
+
const importSpin = spinner(`Importing database on ${conn.domain}...`);
|
|
285
|
+
importSpin.start();
|
|
286
|
+
const importResult = execViaSsh(conn, `cd ${wpPath} && wp db import ${remoteTempPath}`);
|
|
287
|
+
if (importResult.exitCode !== 0) {
|
|
288
|
+
importSpin.fail('Import failed');
|
|
289
|
+
if (importResult.stderr)
|
|
290
|
+
error(importResult.stderr.trim());
|
|
291
|
+
else if (importResult.stdout)
|
|
292
|
+
error(importResult.stdout.trim());
|
|
293
|
+
// Clean up temp file on remote even on failure (best effort)
|
|
294
|
+
execViaSsh(conn, `rm -f ${remoteTempPath}`);
|
|
295
|
+
if (takeBackup) {
|
|
296
|
+
console.log('');
|
|
297
|
+
info(`Remote backup preserved at: ~/${backupFilename}`);
|
|
298
|
+
info('To restore:');
|
|
299
|
+
console.log(` ssh ${conn.username}@${conn.host} 'cd ${wpPath} && gunzip -c ${backupRemotePath} | wp db import -'`);
|
|
300
|
+
console.log(` ${chalk.dim('# or pull the backup down and re-push:')}`);
|
|
301
|
+
console.log(` scp ${conn.username}@${conn.host}:${backupRemotePath} ./`);
|
|
302
|
+
console.log(` instawp db push ${siteIdentifier} ./${backupFilename}`);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
error('No backup was taken — database state may be inconsistent.');
|
|
306
|
+
}
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
importSpin.succeed('Database imported');
|
|
310
|
+
// Step 5: Cleanup remote temp file
|
|
311
|
+
const cleanupSpin = spinner('Cleaning up...');
|
|
312
|
+
cleanupSpin.start();
|
|
313
|
+
const cleanupResult = execViaSsh(conn, `rm -f ${remoteTempPath}`);
|
|
314
|
+
if (cleanupResult.exitCode !== 0) {
|
|
315
|
+
cleanupSpin.fail(`Could not remove ${remoteTempPath} (non-fatal)`);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
cleanupSpin.succeed('Cleanup complete');
|
|
319
|
+
}
|
|
320
|
+
success('Push complete', {
|
|
321
|
+
site_id: site.id,
|
|
322
|
+
backup_path: takeBackup ? backupRemotePath : null,
|
|
323
|
+
restored_from: file,
|
|
324
|
+
size_bytes: localSize,
|
|
325
|
+
});
|
|
326
|
+
if (!isJsonMode() && takeBackup) {
|
|
327
|
+
console.log(`\n ${chalk.dim('Backup:')} ~/${backupFilename} ${chalk.dim('(on remote)')}`);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
//# sourceMappingURL=db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/commands/db.ts"],"names":[],"mappings":"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"}
|