@instawp/cli 0.0.1-beta.1 → 0.0.1-beta.11
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 +138 -0
- package/README.md +58 -10
- 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 +62 -3
- package/dist/commands/exec.js.map +1 -1
- package/dist/commands/local.d.ts +2 -0
- package/dist/commands/local.js +750 -0
- package/dist/commands/local.js.map +1 -0
- package/dist/commands/login.js +23 -7
- package/dist/commands/login.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 +266 -10
- package/dist/commands/sites.js.map +1 -1
- package/dist/commands/sync.js +6 -3
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/teams.js +52 -3
- package/dist/commands/teams.js.map +1 -1
- package/dist/index.js +54 -8
- package/dist/index.js.map +1 -1
- package/dist/lib/api.js +6 -1
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/config.d.ts +10 -1
- package/dist/lib/config.js +47 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/local-env.d.ts +35 -0
- package/dist/lib/local-env.js +299 -0
- package/dist/lib/local-env.js.map +1 -0
- 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/sftp-sync.d.ts +29 -0
- package/dist/lib/sftp-sync.js +266 -0
- package/dist/lib/sftp-sync.js.map +1 -0
- package/dist/lib/site-resolver.js +32 -11
- package/dist/lib/site-resolver.js.map +1 -1
- package/dist/lib/ssh-connection.d.ts +17 -0
- package/dist/lib/ssh-connection.js +103 -5
- package/dist/lib/ssh-connection.js.map +1 -1
- package/dist/lib/ssh-keys.js +12 -5
- package/dist/lib/ssh-keys.js.map +1 -1
- package/dist/lib/windows-binaries.d.ts +10 -0
- package/dist/lib/windows-binaries.js +34 -0
- package/dist/lib/windows-binaries.js.map +1 -0
- package/dist/types.d.ts +8 -0
- package/package.json +11 -3
- package/scripts/mysql2sqlite +289 -0
- package/vendor/win32/NOTICE.md +31 -0
- package/vendor/win32/busybox.exe +0 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.0.1-beta.11 (2026-05-26)
|
|
4
|
+
|
|
5
|
+
### Improved — parallel SFTP transfers on Windows
|
|
6
|
+
- Windows file sync now transfers files across a **pool of parallel SSH connections** instead of one-at-a-time. Measured ~2.9× speedup (a 238-file wp-content pull dropped from ~369s to ~129s).
|
|
7
|
+
- Concurrency defaults to 4, configurable via `INSTAWP_SFTP_CONCURRENCY` (capped at 8).
|
|
8
|
+
- Two-phase design: a single control connection walks the tree and pre-creates directories, then files transfer in parallel. Per-file errors are collected and reported without aborting the whole sync.
|
|
9
|
+
|
|
10
|
+
## 0.0.1-beta.10 (2026-05-26)
|
|
11
|
+
|
|
12
|
+
### Fixed — Windows file sync now works
|
|
13
|
+
- `instawp sync push/pull`, `local push/pull`, and `local clone` failed on Windows with `rsync: connection unexpectedly closed (0 bytes)` + `sigpacket: Suppressing signal 30 to win32 process`. Root cause: the bundled **msys2 rsync.exe couldn't drive native Windows OpenSSH** (incompatible pipe/signal semantics). The DLL "entry point" fix in beta.6 got rsync.exe to *load*, but the SSH transport still died instantly.
|
|
14
|
+
- **Fix**: Windows now transfers files over a **pure-JS SFTP client** (`ssh2-sftp-client`) instead of rsync-over-ssh. macOS/Linux are unchanged (still rsync, with delta sync). New `syncFiles()` dispatcher picks the transport per-platform.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- **Removed `rsync.exe` + all msys2 runtime DLLs from the bundle** (~11 MB). The Windows bundle is now just `busybox.exe` (660 KB, statically linked, for the `mysql2sqlite` awk step in `local clone`). Total package shrinks accordingly.
|
|
18
|
+
- SFTP transfer honors the same exclude/include patterns as the rsync paths (`.git`, `node_modules`, `cache`, `backup*`, etc.).
|
|
19
|
+
|
|
20
|
+
### Trade-off
|
|
21
|
+
- SFTP does full-file copy (no rsync delta algorithm). Fine for typical wp-content; repeat syncs of large sites are slower than rsync on macOS/Linux. We chose this over bundling an msys ssh (which would have dragged in the ~3.5 MB Heimdal/Kerberos DLL chain).
|
|
22
|
+
|
|
23
|
+
## 0.0.1-beta.9 (2026-05-23)
|
|
24
|
+
|
|
25
|
+
### Internals
|
|
26
|
+
- CI smoke test now verifies the bundle by extracting the packed tarball directly (via `tar -xzf`) and running `rsync.exe` from the extract dir. Replaces the `npm install -g` step, which was failing on the GHA Windows runner due to Defender quarantine interactions (tamper protection prevented our exclusion settings from taking effect). Real-user installs are not affected — Defender on individual developer machines is configurable and the first reported Windows install showed the bundle landing at the correct path.
|
|
27
|
+
|
|
28
|
+
## 0.0.1-beta.8 (2026-05-23)
|
|
29
|
+
|
|
30
|
+
### Bug Fixes (Windows)
|
|
31
|
+
- Moved bundled Windows binaries from `bin/win32/` to `vendor/win32/`. With `bin/` and the `bin` field in package.json both set, npm's global install on Windows dropped the `bin/win32/` subdirectory — leaving the CLI unable to find rsync.exe at runtime. macOS/Linux installs were unaffected. Renaming sidesteps the collision entirely.
|
|
32
|
+
|
|
33
|
+
## 0.0.1-beta.7 (2026-05-23)
|
|
34
|
+
|
|
35
|
+
### Internals
|
|
36
|
+
- Smoke-windows CI job now runs the bundled `rsync.exe` and `busybox.exe` directly from the workspace bundle **before** the npm-install step, so a passing smoke test proves the DLL chain is correct independent of whether antivirus interferes with the global install path.
|
|
37
|
+
- Adds Windows Defender exclusions before `npm i -g` to prevent msys DLLs from being quarantined during install.
|
|
38
|
+
- Publish job now skips on `workflow_dispatch` (manual triggers), so maintainers can re-test the smoke job without bumping the version.
|
|
39
|
+
|
|
40
|
+
## 0.0.1-beta.6 (2026-05-23)
|
|
41
|
+
|
|
42
|
+
### Bug Fixes (Windows)
|
|
43
|
+
- Bundled `rsync.exe` now actually loads. beta.4/beta.5 shipped with `msys-2.0.dll` from the legacy `msys2-runtime-3.3` fork, which is missing the `fallocate` symbol that rsync 3.4 needs — produced `Entry Point Not Found: fallocate` on launch and exit code `3221225785` (`STATUS_DLL_INIT_FAILED`) when invoked indirectly via `sync push/pull` or `local clone`.
|
|
44
|
+
- Rebuilt the Windows bundle against current MSYS2 packages: `msys2-runtime-3.6.9-1`, `libopenssl-3.6.2-1`, `libiconv-1.19-1`, `libxxhash-0.8.3-1`, `libzstd-1.5.7-1`, `popt-1.19-1`, `libintl-0.22.5-1`. Includes `msys-popt-0.dll` and `msys-intl-8.dll` which the newer rsync now requires.
|
|
45
|
+
- Upgraded bundled rsync from 3.4.0 → 3.4.2-2.
|
|
46
|
+
|
|
47
|
+
### Internals
|
|
48
|
+
- `scripts/fetch-windows-binaries.sh` now verifies DLL closure (every referenced `msys-*.dll` is present) and asserts `fallocate` is exported from `msys-2.0.dll` before declaring the bundle valid. Catches "wrong runtime fork" regressions at build time.
|
|
49
|
+
- Added `smoke-windows` job to the publish workflow — runs on `windows-latest` and actually executes the bundled `rsync.exe` and `busybox.exe` before npm publish. Publish is now gated on this passing.
|
|
50
|
+
|
|
51
|
+
## 0.0.1-beta.5 (2026-05-23)
|
|
52
|
+
|
|
53
|
+
### New Commands
|
|
54
|
+
- `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.
|
|
55
|
+
- `db pull <site>` — Stream the remote MySQL database to a local gzipped dump. `--output <path>` and `--no-compress` flags.
|
|
56
|
+
- `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.
|
|
57
|
+
- `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.
|
|
58
|
+
- `sites creds <site>` — Re-fetch WP admin credentials + Magic Login URL for an existing site (previously only available in the `create` output).
|
|
59
|
+
|
|
60
|
+
### Improvements
|
|
61
|
+
- `wp <site>` is now positioned as the primary remote-access command; `exec` is documented as the escape hatch for non-WP shell commands.
|
|
62
|
+
- `wp` / `exec` accept POSIX `--` to forward raw args verbatim: `instawp wp my-site -- post list --post_type=page`.
|
|
63
|
+
- 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.
|
|
64
|
+
|
|
65
|
+
### Bug Fixes
|
|
66
|
+
- `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 '('`).
|
|
67
|
+
|
|
68
|
+
### Docs
|
|
69
|
+
- 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.
|
|
70
|
+
- README + CLAUDE.md updated with `wp`-primary positioning and examples for all new commands.
|
|
71
|
+
|
|
72
|
+
## 0.0.1-beta.4 (2026-05-22)
|
|
73
|
+
|
|
74
|
+
### Windows — Zero-Install Support
|
|
75
|
+
- 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.
|
|
76
|
+
- Replaced the external `sqlite3` CLI dependency with the `better-sqlite3` Node module.
|
|
77
|
+
- New `src/lib/windows-binaries.ts` resolves bundled binaries; falls back to PATH then common Git-for-Windows install dirs.
|
|
78
|
+
|
|
79
|
+
### Bug Fixes (Windows)
|
|
80
|
+
- `instawp local clone` now resolves the bundled `mysql2sqlite` script correctly (was broken by `new URL(import.meta.url).pathname` returning `/C:/...`).
|
|
81
|
+
- `mysql2sqlite` is invoked as `awk -f script` explicitly; no longer relies on shebang interpretation.
|
|
82
|
+
- `rsync` no longer treats Windows drive paths (`C:\...`) as remote hostnames — paths are converted to msys style (`/c/...`) inside `rsyncViaSsh`.
|
|
83
|
+
- `-e ssh -i <key>` argument uses forward slashes + quoted paths so msys/cygwin sh inside rsync parses the key path correctly.
|
|
84
|
+
- Eliminated the SQL injection risk in `local clone`'s URL search-replace (now uses bound parameters via better-sqlite3).
|
|
85
|
+
|
|
86
|
+
### Internals
|
|
87
|
+
- New `scripts/fetch-windows-binaries.sh` (maintainer-only) refreshes the Windows bundle from MSYS2 + frippery.org.
|
|
88
|
+
- 32 new tests covering path conversion and bundled-binary resolution.
|
|
89
|
+
|
|
90
|
+
## 0.0.1-beta.3 (2026-04-12)
|
|
91
|
+
|
|
92
|
+
### New Commands
|
|
93
|
+
- `local create` — Create local WordPress sites (powered by WordPress Playground, no Docker needed)
|
|
94
|
+
- `local clone <site>` — Clone an InstaWP cloud site to local (files + database)
|
|
95
|
+
- `local start/stop` — Start in foreground or `--background` mode
|
|
96
|
+
- `local push/pull` — Sync wp-content between local and cloud (incremental rsync)
|
|
97
|
+
- `local list` — Show local sites with running/stopped status
|
|
98
|
+
- `local delete` — Remove local sites
|
|
99
|
+
- `sites php <site>` — View or update PHP version and settings
|
|
100
|
+
- `sites update <site>` — Update site label, description, or expiration
|
|
101
|
+
- `teams switch <team>` — Switch active team context
|
|
102
|
+
|
|
103
|
+
### Improvements
|
|
104
|
+
- `create --wp <version>` — Specify WordPress version when creating sites
|
|
105
|
+
- `sites list` — 50 per page default, `--all` flag, pagination hints
|
|
106
|
+
- Login now shows user name and team after success
|
|
107
|
+
- Site resolver caches name-to-ID lookups for 10 minutes
|
|
108
|
+
- rsync only shows actually changed files (`--itemize-changes`)
|
|
109
|
+
- Magic login URL fixed to use correct `/wordpress-auto-login` endpoint
|
|
110
|
+
|
|
111
|
+
### Bug Fixes
|
|
112
|
+
- Windows: SSH key generation now works (removed Unix-specific shell commands)
|
|
113
|
+
- Windows: command detection uses `where` instead of `which`
|
|
114
|
+
- `exec/wp --api` flag now works at any position in the command
|
|
115
|
+
- Terminal restored after local site Ctrl+C (`stty sane`)
|
|
116
|
+
|
|
117
|
+
## 0.0.1-beta.2 (2026-03-23)
|
|
118
|
+
|
|
119
|
+
### New Commands
|
|
120
|
+
- `local create/clone/start/stop/push/pull/list/delete` — Full local development workflow
|
|
121
|
+
- `teams switch` — Client-side team context
|
|
122
|
+
|
|
123
|
+
### Improvements
|
|
124
|
+
- Site resolver caching
|
|
125
|
+
- Incremental rsync output
|
|
126
|
+
|
|
127
|
+
## 0.0.1-beta.1 (2026-03-02)
|
|
128
|
+
|
|
129
|
+
### Initial Release
|
|
130
|
+
- `login` — OAuth browser flow or `--token`
|
|
131
|
+
- `whoami` — Show current session
|
|
132
|
+
- `create` — Create WordPress sites with provisioning progress
|
|
133
|
+
- `sites list/delete` — Manage sites
|
|
134
|
+
- `exec/wp` — Run commands via SSH or API
|
|
135
|
+
- `ssh` — Interactive SSH sessions
|
|
136
|
+
- `sync push/pull` — rsync wp-content via SSH
|
|
137
|
+
- `teams list/members` — View teams
|
|
138
|
+
- `--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,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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"}
|