@instawp/cli 0.0.1-beta.2 → 0.0.1-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +69 -0
- 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/exec.js +26 -0
- package/dist/commands/exec.js.map +1 -1
- package/dist/commands/local.js +168 -91
- package/dist/commands/local.js.map +1 -1
- package/dist/commands/login.js +23 -7
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/sites.js +167 -2
- package/dist/commands/sites.js.map +1 -1
- package/dist/commands/sync.js +5 -1
- package/dist/commands/sync.js.map +1 -1
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/local-env.js +5 -2
- package/dist/lib/local-env.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/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 +31 -0
- package/dist/lib/windows-binaries.js.map +1 -0
- package/package.json +6 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.0.1-beta.4 (2026-05-22)
|
|
4
|
+
|
|
5
|
+
### Windows — Zero-Install Support
|
|
6
|
+
- 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.
|
|
7
|
+
- Replaced the external `sqlite3` CLI dependency with the `better-sqlite3` Node module.
|
|
8
|
+
- New `src/lib/windows-binaries.ts` resolves bundled binaries; falls back to PATH then common Git-for-Windows install dirs.
|
|
9
|
+
|
|
10
|
+
### Bug Fixes (Windows)
|
|
11
|
+
- `instawp local clone` now resolves the bundled `mysql2sqlite` script correctly (was broken by `new URL(import.meta.url).pathname` returning `/C:/...`).
|
|
12
|
+
- `mysql2sqlite` is invoked as `awk -f script` explicitly; no longer relies on shebang interpretation.
|
|
13
|
+
- `rsync` no longer treats Windows drive paths (`C:\...`) as remote hostnames — paths are converted to msys style (`/c/...`) inside `rsyncViaSsh`.
|
|
14
|
+
- `-e ssh -i <key>` argument uses forward slashes + quoted paths so msys/cygwin sh inside rsync parses the key path correctly.
|
|
15
|
+
- Eliminated the SQL injection risk in `local clone`'s URL search-replace (now uses bound parameters via better-sqlite3).
|
|
16
|
+
|
|
17
|
+
### Internals
|
|
18
|
+
- New `scripts/fetch-windows-binaries.sh` (maintainer-only) refreshes the Windows bundle from MSYS2 + frippery.org.
|
|
19
|
+
- 32 new tests covering path conversion and bundled-binary resolution.
|
|
20
|
+
|
|
21
|
+
## 0.0.1-beta.3 (2026-04-12)
|
|
22
|
+
|
|
23
|
+
### New Commands
|
|
24
|
+
- `local create` — Create local WordPress sites (powered by WordPress Playground, no Docker needed)
|
|
25
|
+
- `local clone <site>` — Clone an InstaWP cloud site to local (files + database)
|
|
26
|
+
- `local start/stop` — Start in foreground or `--background` mode
|
|
27
|
+
- `local push/pull` — Sync wp-content between local and cloud (incremental rsync)
|
|
28
|
+
- `local list` — Show local sites with running/stopped status
|
|
29
|
+
- `local delete` — Remove local sites
|
|
30
|
+
- `sites php <site>` — View or update PHP version and settings
|
|
31
|
+
- `sites update <site>` — Update site label, description, or expiration
|
|
32
|
+
- `teams switch <team>` — Switch active team context
|
|
33
|
+
|
|
34
|
+
### Improvements
|
|
35
|
+
- `create --wp <version>` — Specify WordPress version when creating sites
|
|
36
|
+
- `sites list` — 50 per page default, `--all` flag, pagination hints
|
|
37
|
+
- Login now shows user name and team after success
|
|
38
|
+
- Site resolver caches name-to-ID lookups for 10 minutes
|
|
39
|
+
- rsync only shows actually changed files (`--itemize-changes`)
|
|
40
|
+
- Magic login URL fixed to use correct `/wordpress-auto-login` endpoint
|
|
41
|
+
|
|
42
|
+
### Bug Fixes
|
|
43
|
+
- Windows: SSH key generation now works (removed Unix-specific shell commands)
|
|
44
|
+
- Windows: command detection uses `where` instead of `which`
|
|
45
|
+
- `exec/wp --api` flag now works at any position in the command
|
|
46
|
+
- Terminal restored after local site Ctrl+C (`stty sane`)
|
|
47
|
+
|
|
48
|
+
## 0.0.1-beta.2 (2026-03-23)
|
|
49
|
+
|
|
50
|
+
### New Commands
|
|
51
|
+
- `local create/clone/start/stop/push/pull/list/delete` — Full local development workflow
|
|
52
|
+
- `teams switch` — Client-side team context
|
|
53
|
+
|
|
54
|
+
### Improvements
|
|
55
|
+
- Site resolver caching
|
|
56
|
+
- Incremental rsync output
|
|
57
|
+
|
|
58
|
+
## 0.0.1-beta.1 (2026-03-02)
|
|
59
|
+
|
|
60
|
+
### Initial Release
|
|
61
|
+
- `login` — OAuth browser flow or `--token`
|
|
62
|
+
- `whoami` — Show current session
|
|
63
|
+
- `create` — Create WordPress sites with provisioning progress
|
|
64
|
+
- `sites list/delete` — Manage sites
|
|
65
|
+
- `exec/wp` — Run commands via SSH or API
|
|
66
|
+
- `ssh` — Interactive SSH sessions
|
|
67
|
+
- `sync push/pull` — rsync wp-content via SSH
|
|
68
|
+
- `teams list/members` — View teams
|
|
69
|
+
- `--json` mode for all commands
|
|
@@ -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
|
package/dist/commands/exec.js
CHANGED
|
@@ -108,6 +108,19 @@ export function registerExecCommand(program) {
|
|
|
108
108
|
.option('--api', 'Use API transport instead of SSH')
|
|
109
109
|
.option('--timeout <seconds>', 'Command timeout in seconds (API mode only)', '30')
|
|
110
110
|
.action(async (siteIdentifier, args, opts) => {
|
|
111
|
+
// passThroughOptions may swallow --api/--timeout into args — extract them
|
|
112
|
+
const extractedApi = args.includes('--api');
|
|
113
|
+
const timeoutIdx = args.indexOf('--timeout');
|
|
114
|
+
let extractedTimeout;
|
|
115
|
+
if (timeoutIdx !== -1 && args[timeoutIdx + 1]) {
|
|
116
|
+
extractedTimeout = args[timeoutIdx + 1];
|
|
117
|
+
args = args.filter((_, i) => i !== timeoutIdx && i !== timeoutIdx + 1);
|
|
118
|
+
}
|
|
119
|
+
args = args.filter(a => a !== '--api');
|
|
120
|
+
if (extractedApi)
|
|
121
|
+
opts.api = true;
|
|
122
|
+
if (extractedTimeout)
|
|
123
|
+
opts.timeout = extractedTimeout;
|
|
111
124
|
await execAction(siteIdentifier, args, opts);
|
|
112
125
|
});
|
|
113
126
|
}
|
|
@@ -120,6 +133,19 @@ export function registerWpCommand(program) {
|
|
|
120
133
|
.option('--api', 'Use API transport instead of SSH')
|
|
121
134
|
.option('--timeout <seconds>', 'Command timeout in seconds (API mode only)', '30')
|
|
122
135
|
.action(async (siteIdentifier, args, opts) => {
|
|
136
|
+
// passThroughOptions may swallow --api/--timeout into args — extract them
|
|
137
|
+
const extractedApi = args.includes('--api');
|
|
138
|
+
const timeoutIdx = args.indexOf('--timeout');
|
|
139
|
+
let extractedTimeout;
|
|
140
|
+
if (timeoutIdx !== -1 && args[timeoutIdx + 1]) {
|
|
141
|
+
extractedTimeout = args[timeoutIdx + 1];
|
|
142
|
+
args = args.filter((_, i) => i !== timeoutIdx && i !== timeoutIdx + 1);
|
|
143
|
+
}
|
|
144
|
+
args = args.filter(a => a !== '--api');
|
|
145
|
+
if (extractedApi)
|
|
146
|
+
opts.api = true;
|
|
147
|
+
if (extractedTimeout)
|
|
148
|
+
opts.timeout = extractedTimeout;
|
|
123
149
|
await execAction(siteIdentifier, ['wp', ...args], opts);
|
|
124
150
|
});
|
|
125
151
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/commands/exec.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/commands/exec.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/local.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { join
|
|
2
|
+
import { join } from 'node:path';
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import open from 'open';
|
|
6
|
+
import Database from 'better-sqlite3';
|
|
7
|
+
import { resolveFromModule } from '../lib/paths.js';
|
|
8
|
+
import { bundledBusybox, bundledRsync } from '../lib/windows-binaries.js';
|
|
6
9
|
import { getLocalInstances, getLocalInstance, setLocalInstance, removeLocalInstance, } from '../lib/config.js';
|
|
7
10
|
import { getNextPort, createInstanceDir, deleteInstanceDir, startServer, startServerBackground, stopServer as stopServerProcess, isServerRunning, checkPlaygroundConnectivity, ensureAutoLogin, } from '../lib/local-env.js';
|
|
8
11
|
import { requireAuth, getClient } from '../lib/api.js';
|
|
@@ -54,11 +57,11 @@ export function registerLocalCommand(program) {
|
|
|
54
57
|
createdAt: new Date().toISOString(),
|
|
55
58
|
};
|
|
56
59
|
setLocalInstance(instance);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
${chalk.dim('#')} Starting WordPress ${opts.wp} with PHP ${opts.php}
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
if (!isJsonMode()) {
|
|
61
|
+
success(`Instance "${name}" created`);
|
|
62
|
+
console.log(`\n${chalk.dim('#')} Starting WordPress ${opts.wp} with PHP ${opts.php}...`);
|
|
63
|
+
console.log(`${chalk.dim('#')} Data stored at: ${chalk.dim(dir)}\n`);
|
|
64
|
+
}
|
|
62
65
|
await launchServer(instance, opts);
|
|
63
66
|
}
|
|
64
67
|
catch (err) {
|
|
@@ -176,6 +179,7 @@ ${chalk.dim('#')} Data stored at: ${chalk.dim(dir)}
|
|
|
176
179
|
local
|
|
177
180
|
.command('push <local-name> [cloud-site]')
|
|
178
181
|
.description('Push local wp-content to an InstaWP cloud site')
|
|
182
|
+
.option('--include <pattern...>', 'Include patterns (e.g. .git)')
|
|
179
183
|
.option('--exclude <pattern...>', 'Additional exclude patterns')
|
|
180
184
|
.option('--dry-run', 'Show what would be transferred')
|
|
181
185
|
.action(async (localName, cloudSiteArg, opts) => {
|
|
@@ -186,7 +190,7 @@ ${chalk.dim('#')} Data stored at: ${chalk.dim(dir)}
|
|
|
186
190
|
process.exit(1);
|
|
187
191
|
}
|
|
188
192
|
if (!checkRsync()) {
|
|
189
|
-
error('rsync is required. Install: brew install rsync');
|
|
193
|
+
error('rsync is required.' + (process.platform === 'win32' ? ' Reinstall the CLI — the bundled rsync.exe is missing.' : ' Install: brew install rsync'));
|
|
190
194
|
process.exit(1);
|
|
191
195
|
}
|
|
192
196
|
const localWpContent = join(instance.path, 'wp-content') + '/';
|
|
@@ -282,6 +286,7 @@ ${chalk.dim('#')} Data stored at: ${chalk.dim(dir)}
|
|
|
282
286
|
local
|
|
283
287
|
.command('pull <local-name> <cloud-site>')
|
|
284
288
|
.description('Pull wp-content from an InstaWP cloud site to local')
|
|
289
|
+
.option('--include <pattern...>', 'Include patterns (e.g. .git)')
|
|
285
290
|
.option('--exclude <pattern...>', 'Additional exclude patterns')
|
|
286
291
|
.option('--dry-run', 'Show what would be transferred')
|
|
287
292
|
.action(async (localName, cloudSiteArg, opts) => {
|
|
@@ -292,7 +297,7 @@ ${chalk.dim('#')} Data stored at: ${chalk.dim(dir)}
|
|
|
292
297
|
process.exit(1);
|
|
293
298
|
}
|
|
294
299
|
if (!checkRsync()) {
|
|
295
|
-
error('rsync is required. Install: brew install rsync');
|
|
300
|
+
error('rsync is required.' + (process.platform === 'win32' ? ' Reinstall the CLI — the bundled rsync.exe is missing.' : ' Install: brew install rsync'));
|
|
296
301
|
process.exit(1);
|
|
297
302
|
}
|
|
298
303
|
const localWpContent = join(instance.path, 'wp-content') + '/';
|
|
@@ -309,11 +314,14 @@ ${chalk.dim('#')} Data stored at: ${chalk.dim(dir)}
|
|
|
309
314
|
}
|
|
310
315
|
const conn = await ensureSshAccess(site.id);
|
|
311
316
|
const remotePath = `/home/${conn.username}/web/${conn.domain}/public_html/wp-content/`;
|
|
312
|
-
const extraArgs = [
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
+
const extraArgs = [];
|
|
318
|
+
if (opts.include) {
|
|
319
|
+
for (const pattern of opts.include) {
|
|
320
|
+
extraArgs.push(`--include=${pattern}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
extraArgs.push('--exclude=database', // Don't overwrite local SQLite database
|
|
324
|
+
'--exclude=db.php', '--exclude=mu-plugins');
|
|
317
325
|
if (opts.exclude) {
|
|
318
326
|
for (const pattern of opts.exclude) {
|
|
319
327
|
extraArgs.push(`--exclude=${pattern}`);
|
|
@@ -338,10 +346,12 @@ ${chalk.dim('#')} Data stored at: ${chalk.dim(dir)}
|
|
|
338
346
|
.description('Clone a complete InstaWP cloud site to local')
|
|
339
347
|
.option('--name <name>', 'Local instance name (defaults to cloud site name)')
|
|
340
348
|
.option('--no-start', 'Do not start the local site after cloning')
|
|
349
|
+
.option('--force', 'Overwrite existing local instance')
|
|
350
|
+
.option('--include <pattern...>', 'Include patterns for rsync (e.g. .git)')
|
|
341
351
|
.action(async (cloudSiteArg, opts) => {
|
|
342
352
|
requireAuth();
|
|
343
353
|
if (!checkRsync()) {
|
|
344
|
-
error('rsync is required. Install: brew install rsync');
|
|
354
|
+
error('rsync is required.' + (process.platform === 'win32' ? ' Reinstall the CLI — the bundled rsync.exe is missing.' : ' Install: brew install rsync'));
|
|
345
355
|
process.exit(1);
|
|
346
356
|
}
|
|
347
357
|
// 1. Resolve cloud site
|
|
@@ -360,8 +370,15 @@ ${chalk.dim('#')} Data stored at: ${chalk.dim(dir)}
|
|
|
360
370
|
const instances = getLocalInstances();
|
|
361
371
|
const name = sanitizeName(opts.name || site.name || site.sub_domain || `site-${site.id}`);
|
|
362
372
|
if (instances[name]) {
|
|
363
|
-
|
|
364
|
-
|
|
373
|
+
if (!opts.force) {
|
|
374
|
+
error(`Local instance "${name}" already exists. Use --force to overwrite or --name to pick a different name.`);
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
// Force: delete existing instance first
|
|
378
|
+
stopServerProcess(instances[name]);
|
|
379
|
+
deleteInstanceDir(name);
|
|
380
|
+
removeLocalInstance(name);
|
|
381
|
+
info(`Existing instance "${name}" removed.`);
|
|
365
382
|
}
|
|
366
383
|
const port = await getNextPort(instances);
|
|
367
384
|
const dir = createInstanceDir(name);
|
|
@@ -402,7 +419,14 @@ ${chalk.dim('#')} Data stored at: ${chalk.dim(dir)}
|
|
|
402
419
|
const remotePath = `/home/${conn.username}/web/${conn.domain}/public_html/wp-content/`;
|
|
403
420
|
const remoteSource = `${conn.username}@${conn.host}:${remotePath}`;
|
|
404
421
|
info(`Pulling wp-content from ${chalk.dim(conn.domain)}...`);
|
|
422
|
+
const includeArgs = [];
|
|
423
|
+
if (opts.include) {
|
|
424
|
+
for (const pattern of opts.include) {
|
|
425
|
+
includeArgs.push(`--include=${pattern}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
405
428
|
const rsyncExit = rsyncViaSsh(conn, remoteSource, localWpContent, [
|
|
429
|
+
...includeArgs,
|
|
406
430
|
'--exclude=cache',
|
|
407
431
|
'--exclude=upgrade',
|
|
408
432
|
'--exclude=wflogs',
|
|
@@ -433,7 +457,7 @@ ${chalk.dim('#')} Data stored at: ${chalk.dim(dir)}
|
|
|
433
457
|
const dbSpin2 = spinner('Importing database...');
|
|
434
458
|
dbSpin2.start();
|
|
435
459
|
try {
|
|
436
|
-
const mysql2sqlitePath =
|
|
460
|
+
const mysql2sqlitePath = resolveFromModule(import.meta.url, '..', '..', 'scripts', 'mysql2sqlite');
|
|
437
461
|
const dbDir = join(dir, 'wp-content', 'database');
|
|
438
462
|
const sqliteDbPath = join(dbDir, '.ht.sqlite');
|
|
439
463
|
// Clean slate for database dir
|
|
@@ -446,8 +470,15 @@ ${chalk.dim('#')} Data stored at: ${chalk.dim(dir)}
|
|
|
446
470
|
if (sqlStart > 0) {
|
|
447
471
|
writeFileSync(dumpPath, rawDump.substring(sqlStart));
|
|
448
472
|
}
|
|
449
|
-
// Convert MySQL → SQLite
|
|
450
|
-
|
|
473
|
+
// Convert MySQL → SQLite via awk (mysql2sqlite is an awk script).
|
|
474
|
+
// Windows doesn't honor shebangs, so invoke awk explicitly.
|
|
475
|
+
const awk = findAwk();
|
|
476
|
+
if (!awk) {
|
|
477
|
+
throw new Error('awk not found. ' + (process.platform === 'win32'
|
|
478
|
+
? 'Reinstall the CLI — the bundled busybox.exe is missing.'
|
|
479
|
+
: 'Install awk/gawk.'));
|
|
480
|
+
}
|
|
481
|
+
const convertResult = spawnSync(awk.cmd, [...awk.prefixArgs, '-f', mysql2sqlitePath, dumpPath], {
|
|
451
482
|
encoding: 'utf-8',
|
|
452
483
|
maxBuffer: 500 * 1024 * 1024,
|
|
453
484
|
timeout: 120000,
|
|
@@ -458,69 +489,54 @@ ${chalk.dim('#')} Data stored at: ${chalk.dim(dir)}
|
|
|
458
489
|
// Add DROP TABLE before each CREATE TABLE
|
|
459
490
|
let sqliteSql = convertResult.stdout;
|
|
460
491
|
sqliteSql = sqliteSql.replace(/^(CREATE TABLE `([^`]+)`)/gm, 'DROP TABLE IF EXISTS `$2`;\n$1');
|
|
461
|
-
//
|
|
462
|
-
const
|
|
463
|
-
writeFileSync(tmpSql, sqliteSql);
|
|
464
|
-
spawnSync('sqlite3', [sqliteDbPath], {
|
|
465
|
-
input: `.read ${tmpSql}\n`,
|
|
466
|
-
encoding: 'utf-8',
|
|
467
|
-
timeout: 120000,
|
|
468
|
-
});
|
|
469
|
-
// Find the table prefix and rename to wp_
|
|
470
|
-
const tablesResult = spawnSync('sqlite3', [sqliteDbPath, '.tables'], { encoding: 'utf-8' });
|
|
471
|
-
const allTables = (tablesResult.stdout || '').split(/\s+/).filter(Boolean);
|
|
472
|
-
const optionsTable = allTables.find((t) => t.endsWith('_options'));
|
|
473
|
-
const oldPrefix = optionsTable ? optionsTable.replace('options', '') : 'wp_';
|
|
474
|
-
if (oldPrefix !== 'wp_') {
|
|
475
|
-
// Rename tables
|
|
476
|
-
const renameStatements = allTables
|
|
477
|
-
.filter((t) => t.startsWith(oldPrefix))
|
|
478
|
-
.map((t) => `ALTER TABLE \`${t}\` RENAME TO \`wp_${t.substring(oldPrefix.length)}\`;`)
|
|
479
|
-
.join('\n');
|
|
480
|
-
spawnSync('sqlite3', [sqliteDbPath, renameStatements], { encoding: 'utf-8' });
|
|
481
|
-
// Rename meta keys and option names that contain the old prefix
|
|
482
|
-
const fixPrefixSql = [
|
|
483
|
-
`UPDATE wp_usermeta SET meta_key = REPLACE(meta_key, '${oldPrefix}', 'wp_') WHERE meta_key LIKE '${oldPrefix}%';`,
|
|
484
|
-
`UPDATE wp_options SET option_name = REPLACE(option_name, '${oldPrefix}', 'wp_') WHERE option_name LIKE '${oldPrefix}%';`,
|
|
485
|
-
].join('\n');
|
|
486
|
-
spawnSync('sqlite3', [sqliteDbPath, fixPrefixSql], { encoding: 'utf-8' });
|
|
487
|
-
}
|
|
488
|
-
// Search-replace old cloud URL → localhost
|
|
489
|
-
const localUrl = `http://127.0.0.1:${instance.port}`;
|
|
490
|
-
const oldDomain = site.url || site.sub_domain || '';
|
|
491
|
-
const oldUrls = [
|
|
492
|
-
oldDomain,
|
|
493
|
-
oldDomain.replace('https://', 'http://'),
|
|
494
|
-
].filter(Boolean);
|
|
495
|
-
for (const oldUrl of oldUrls) {
|
|
496
|
-
const replaceSql = [
|
|
497
|
-
`UPDATE wp_options SET option_value = REPLACE(option_value, '${oldUrl}', '${localUrl}') WHERE option_value LIKE '%${oldUrl}%';`,
|
|
498
|
-
`UPDATE wp_posts SET post_content = REPLACE(post_content, '${oldUrl}', '${localUrl}') WHERE post_content LIKE '%${oldUrl}%';`,
|
|
499
|
-
`UPDATE wp_posts SET guid = REPLACE(guid, '${oldUrl}', '${localUrl}') WHERE guid LIKE '%${oldUrl}%';`,
|
|
500
|
-
`UPDATE wp_postmeta SET meta_value = REPLACE(meta_value, '${oldUrl}', '${localUrl}') WHERE meta_value LIKE '%${oldUrl}%';`,
|
|
501
|
-
`UPDATE wp_comments SET comment_content = REPLACE(comment_content, '${oldUrl}', '${localUrl}') WHERE comment_content LIKE '%${oldUrl}%';`,
|
|
502
|
-
].join('\n');
|
|
503
|
-
spawnSync('sqlite3', [sqliteDbPath, replaceSql], { encoding: 'utf-8' });
|
|
504
|
-
}
|
|
505
|
-
// Ensure siteurl/home are correct
|
|
506
|
-
spawnSync('sqlite3', [sqliteDbPath,
|
|
507
|
-
`UPDATE wp_options SET option_value='${localUrl}' WHERE option_name IN ('siteurl','home');`,
|
|
508
|
-
], { encoding: 'utf-8' });
|
|
509
|
-
// Get admin username for blueprint login step
|
|
510
|
-
const adminResult = spawnSync('sqlite3', [sqliteDbPath,
|
|
511
|
-
"SELECT user_login FROM wp_users WHERE ID = (SELECT user_id FROM wp_usermeta WHERE meta_key = 'wp_capabilities' AND meta_value LIKE '%administrator%' LIMIT 1);",
|
|
512
|
-
], { encoding: 'utf-8' });
|
|
513
|
-
adminUsername = (adminResult.stdout || '').trim() || 'admin';
|
|
514
|
-
// Count tables for output
|
|
515
|
-
const countResult = spawnSync('sqlite3', [sqliteDbPath,
|
|
516
|
-
"SELECT COUNT(*) FROM sqlite_master WHERE type='table';",
|
|
517
|
-
], { encoding: 'utf-8' });
|
|
518
|
-
// Clean up temp file
|
|
492
|
+
// Import directly via better-sqlite3 (no external sqlite3 CLI needed)
|
|
493
|
+
const db = new Database(sqliteDbPath);
|
|
519
494
|
try {
|
|
520
|
-
|
|
495
|
+
db.exec(sqliteSql);
|
|
496
|
+
// Find the table prefix and rename to wp_
|
|
497
|
+
const tableRows = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").all();
|
|
498
|
+
const allTables = tableRows.map(r => r.name);
|
|
499
|
+
const optionsTable = allTables.find(t => t.endsWith('_options'));
|
|
500
|
+
const oldPrefix = optionsTable ? optionsTable.replace('options', '') : 'wp_';
|
|
501
|
+
if (oldPrefix !== 'wp_') {
|
|
502
|
+
const renames = allTables
|
|
503
|
+
.filter(t => t.startsWith(oldPrefix))
|
|
504
|
+
.map(t => `ALTER TABLE \`${t}\` RENAME TO \`wp_${t.substring(oldPrefix.length)}\``);
|
|
505
|
+
db.exec(renames.join(';\n') + ';');
|
|
506
|
+
// Rename meta keys and option names that contain the old prefix
|
|
507
|
+
db.prepare('UPDATE wp_usermeta SET meta_key = REPLACE(meta_key, ?, ?) WHERE meta_key LIKE ?').run(oldPrefix, 'wp_', oldPrefix + '%');
|
|
508
|
+
db.prepare('UPDATE wp_options SET option_name = REPLACE(option_name, ?, ?) WHERE option_name LIKE ?').run(oldPrefix, 'wp_', oldPrefix + '%');
|
|
509
|
+
}
|
|
510
|
+
// Search-replace old cloud URL → localhost (bound params: no SQL injection)
|
|
511
|
+
const localUrl = `http://127.0.0.1:${instance.port}`;
|
|
512
|
+
const oldDomain = site.url || site.sub_domain || '';
|
|
513
|
+
const oldUrls = [
|
|
514
|
+
oldDomain,
|
|
515
|
+
oldDomain.replace('https://', 'http://'),
|
|
516
|
+
].filter(Boolean);
|
|
517
|
+
const replaceStmts = [
|
|
518
|
+
'UPDATE wp_options SET option_value = REPLACE(option_value, ?, ?) WHERE option_value LIKE ?',
|
|
519
|
+
'UPDATE wp_posts SET post_content = REPLACE(post_content, ?, ?) WHERE post_content LIKE ?',
|
|
520
|
+
'UPDATE wp_posts SET guid = REPLACE(guid, ?, ?) WHERE guid LIKE ?',
|
|
521
|
+
'UPDATE wp_postmeta SET meta_value = REPLACE(meta_value, ?, ?) WHERE meta_value LIKE ?',
|
|
522
|
+
'UPDATE wp_comments SET comment_content = REPLACE(comment_content, ?, ?) WHERE comment_content LIKE ?',
|
|
523
|
+
].map(s => db.prepare(s));
|
|
524
|
+
for (const oldUrl of oldUrls) {
|
|
525
|
+
const likePattern = '%' + oldUrl + '%';
|
|
526
|
+
for (const stmt of replaceStmts) {
|
|
527
|
+
stmt.run(oldUrl, localUrl, likePattern);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
db.prepare("UPDATE wp_options SET option_value = ? WHERE option_name IN ('siteurl','home')").run(localUrl);
|
|
531
|
+
// Get admin username for blueprint login step
|
|
532
|
+
const adminRow = db.prepare("SELECT user_login FROM wp_users WHERE ID = (SELECT user_id FROM wp_usermeta WHERE meta_key = 'wp_capabilities' AND meta_value LIKE '%administrator%' LIMIT 1)").get();
|
|
533
|
+
adminUsername = adminRow?.user_login || 'admin';
|
|
534
|
+
const tableCount = db.prepare("SELECT COUNT(*) AS c FROM sqlite_master WHERE type='table'").get().c;
|
|
535
|
+
dbSpin2.succeed(`Database imported (${tableCount} tables, admin: ${adminUsername})`);
|
|
536
|
+
}
|
|
537
|
+
finally {
|
|
538
|
+
db.close();
|
|
521
539
|
}
|
|
522
|
-
catch { }
|
|
523
|
-
dbSpin2.succeed(`Database imported (${(countResult.stdout || '').trim()} tables, admin: ${adminUsername})`);
|
|
524
540
|
}
|
|
525
541
|
catch (err) {
|
|
526
542
|
dbSpin2.fail('Database import failed: ' + err.message);
|
|
@@ -579,9 +595,46 @@ ${chalk.bold.green('Clone complete!')}
|
|
|
579
595
|
});
|
|
580
596
|
}
|
|
581
597
|
function checkRsync() {
|
|
582
|
-
|
|
598
|
+
if (bundledRsync())
|
|
599
|
+
return true;
|
|
600
|
+
const cmd = process.platform === 'win32' ? 'where' : 'which';
|
|
601
|
+
const result = spawnSync(cmd, ['rsync'], { stdio: 'ignore' });
|
|
583
602
|
return result.status === 0;
|
|
584
603
|
}
|
|
604
|
+
/**
|
|
605
|
+
* Locate an awk-compatible interpreter. Resolution order:
|
|
606
|
+
* 1. Bundled BusyBox-w64 in bin/win32/ (Windows only — invoked as `busybox awk`)
|
|
607
|
+
* 2. `awk` or `gawk` in PATH
|
|
608
|
+
* 3. Common Git-for-Windows install dirs (Windows only)
|
|
609
|
+
*
|
|
610
|
+
* Returns the command path plus any arg-prefix that must precede the awk
|
|
611
|
+
* arguments (busybox uses `busybox awk -f script input`).
|
|
612
|
+
*/
|
|
613
|
+
function findAwk() {
|
|
614
|
+
const bb = bundledBusybox();
|
|
615
|
+
if (bb)
|
|
616
|
+
return { cmd: bb, prefixArgs: ['awk'] };
|
|
617
|
+
const cmd = process.platform === 'win32' ? 'where' : 'which';
|
|
618
|
+
for (const name of ['awk', 'gawk']) {
|
|
619
|
+
const r = spawnSync(cmd, [name], { stdio: 'pipe' });
|
|
620
|
+
if (r.status === 0)
|
|
621
|
+
return { cmd: name, prefixArgs: [] };
|
|
622
|
+
}
|
|
623
|
+
if (process.platform === 'win32') {
|
|
624
|
+
const candidates = [
|
|
625
|
+
'C:\\Program Files\\Git\\usr\\bin\\awk.exe',
|
|
626
|
+
'C:\\Program Files (x86)\\Git\\usr\\bin\\awk.exe',
|
|
627
|
+
];
|
|
628
|
+
if (process.env.PROGRAMFILES) {
|
|
629
|
+
candidates.push(process.env.PROGRAMFILES + '\\Git\\usr\\bin\\awk.exe');
|
|
630
|
+
}
|
|
631
|
+
for (const c of candidates) {
|
|
632
|
+
if (existsSync(c))
|
|
633
|
+
return { cmd: c, prefixArgs: [] };
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
585
638
|
function sanitizeName(name) {
|
|
586
639
|
return name.replace(/[^a-zA-Z0-9_-]/g, '-').toLowerCase();
|
|
587
640
|
}
|
|
@@ -611,27 +664,51 @@ function printUrls(port) {
|
|
|
611
664
|
}
|
|
612
665
|
async function launchServer(instance, opts) {
|
|
613
666
|
const shouldOpen = opts.open !== false;
|
|
667
|
+
const json = isJsonMode();
|
|
614
668
|
if (opts.background) {
|
|
615
|
-
const spin = spinner(`Starting "${instance.name}" in background...`);
|
|
616
|
-
spin
|
|
669
|
+
const spin = json ? null : spinner(`Starting "${instance.name}" in background...`);
|
|
670
|
+
spin?.start();
|
|
617
671
|
try {
|
|
618
672
|
const { pid, url } = await startServerBackground(instance, opts.blueprint);
|
|
619
|
-
|
|
620
|
-
|
|
673
|
+
if (json) {
|
|
674
|
+
console.log(JSON.stringify({
|
|
675
|
+
success: true,
|
|
676
|
+
data: {
|
|
677
|
+
name: instance.name,
|
|
678
|
+
url,
|
|
679
|
+
port: instance.port,
|
|
680
|
+
pid,
|
|
681
|
+
wp: instance.wp,
|
|
682
|
+
php: instance.php,
|
|
683
|
+
path: instance.path,
|
|
684
|
+
},
|
|
685
|
+
}));
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
spin?.succeed(`Running in background (PID: ${pid})`);
|
|
689
|
+
printUrls(instance.port);
|
|
690
|
+
info(`Stop with: instawp local stop ${instance.name}`);
|
|
691
|
+
info(`Logs: ${instance.path}/server.log`);
|
|
692
|
+
}
|
|
621
693
|
if (shouldOpen)
|
|
622
694
|
await openWpAdmin(url);
|
|
623
|
-
info(`Stop with: instawp local stop ${instance.name}`);
|
|
624
|
-
info(`Logs: ${instance.path}/server.log`);
|
|
625
695
|
}
|
|
626
696
|
catch (err) {
|
|
627
|
-
|
|
628
|
-
|
|
697
|
+
if (json) {
|
|
698
|
+
console.log(JSON.stringify({ success: false, error: err.message }));
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
spin?.fail('Failed to start');
|
|
702
|
+
error(err.message);
|
|
703
|
+
}
|
|
629
704
|
process.exit(1);
|
|
630
705
|
}
|
|
631
706
|
}
|
|
632
707
|
else {
|
|
633
|
-
|
|
634
|
-
|
|
708
|
+
if (!json) {
|
|
709
|
+
printUrls(instance.port);
|
|
710
|
+
console.log(chalk.dim('\nPress Ctrl+C to stop.\n'));
|
|
711
|
+
}
|
|
635
712
|
try {
|
|
636
713
|
await startServer(instance, {
|
|
637
714
|
blueprint: opts.blueprint,
|