Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ jobs:
- run: npm run build

- name: Publish to npm
run: npm publish --access public --tag beta --provenance
run: npm publish --access public --tag beta
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
69 changes: 69 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Changelog

## 0.0.1-beta.4 (2026-05-22)

### Windows — Zero-Install Support
- 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.
- Replaced the external `sqlite3` CLI dependency with the `better-sqlite3` Node module.
- New `src/lib/windows-binaries.ts` resolves bundled binaries; falls back to PATH then common Git-for-Windows install dirs.

### Bug Fixes (Windows)
- `instawp local clone` now resolves the bundled `mysql2sqlite` script correctly (was broken by `new URL(import.meta.url).pathname` returning `/C:/...`).
- `mysql2sqlite` is invoked as `awk -f script` explicitly; no longer relies on shebang interpretation.
- `rsync` no longer treats Windows drive paths (`C:\...`) as remote hostnames — paths are converted to msys style (`/c/...`) inside `rsyncViaSsh`.
- `-e ssh -i <key>` argument uses forward slashes + quoted paths so msys/cygwin sh inside rsync parses the key path correctly.
- Eliminated the SQL injection risk in `local clone`'s URL search-replace (now uses bound parameters via better-sqlite3).

### Internals
- New `scripts/fetch-windows-binaries.sh` (maintainer-only) refreshes the Windows bundle from MSYS2 + frippery.org.
- 32 new tests covering path conversion and bundled-binary resolution.

## 0.0.1-beta.3 (2026-04-12)

### New Commands
- `local create` — Create local WordPress sites (powered by WordPress Playground, no Docker needed)
- `local clone <site>` — Clone an InstaWP cloud site to local (files + database)
- `local start/stop` — Start in foreground or `--background` mode
- `local push/pull` — Sync wp-content between local and cloud (incremental rsync)
- `local list` — Show local sites with running/stopped status
- `local delete` — Remove local sites
- `sites php <site>` — View or update PHP version and settings
- `sites update <site>` — Update site label, description, or expiration
- `teams switch <team>` — Switch active team context

### Improvements
- `create --wp <version>` — Specify WordPress version when creating sites
- `sites list` — 50 per page default, `--all` flag, pagination hints
- Login now shows user name and team after success
- Site resolver caches name-to-ID lookups for 10 minutes
- rsync only shows actually changed files (`--itemize-changes`)
- Magic login URL fixed to use correct `/wordpress-auto-login` endpoint

### Bug Fixes
- Windows: SSH key generation now works (removed Unix-specific shell commands)
- Windows: command detection uses `where` instead of `which`
- `exec/wp --api` flag now works at any position in the command
- Terminal restored after local site Ctrl+C (`stty sane`)

## 0.0.1-beta.2 (2026-03-23)

### New Commands
- `local create/clone/start/stop/push/pull/list/delete` — Full local development workflow
- `teams switch` — Client-side team context

### Improvements
- Site resolver caching
- Incremental rsync output

## 0.0.1-beta.1 (2026-03-02)

### Initial Release
- `login` — OAuth browser flow or `--token`
- `whoami` — Show current session
- `create` — Create WordPress sites with provisioning progress
- `sites list/delete` — Manage sites
- `exec/wp` — Run commands via SSH or API
- `ssh` — Interactive SSH sessions
- `sync push/pull` — rsync wp-content via SSH
- `teams list/members` — View teams
- `--json` mode for all commands
125 changes: 111 additions & 14 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,55 @@ src/
│ ├── exec.ts # exec + wp commands (merged, --api/--ssh transport)
│ ├── ssh.ts # Interactive SSH shell
│ ├── sync.ts # rsync push/pull via SSH
│ └── teams.ts # teams list/members
│ ├── teams.ts # teams list/switch/members
│ └── local.ts # local create/clone/start/stop/push/pull/list/delete
├── lib/
│ ├── api.ts # Axios client, auth interceptor, 401/429 handling
│ ├── api.ts # Axios client, auth interceptor, team_id injection
│ ├── auth.ts # OAuth flow (local HTTP server for callback)
│ ├── config.ts # Conf-based persistent config (~/.config/instawp/)
│ ├── local-env.ts # Playground server management, background mode
│ ├── output.ts # chalk/ora output helpers, --json mode
│ ├── site-resolver.ts # Resolve site by ID, name, or domain
│ ├── site-resolver.ts # Resolve site by ID/name/domain with caching
│ ├── ssh-keys.ts # SSH key generation, upload, caching
│ └── ssh-connection.ts # SSH/rsync spawn helpers
└── __tests__/ # Vitest tests (148 tests)
├── __tests__/ # Vitest tests (148 tests)
scripts/
└── mysql2sqlite # MySQL→SQLite dump converter (vendored)
```

## Commands

```
# Auth
instawp login [--token <t>] [--api-url <url>]
instawp whoami
instawp sites list [--status <s>] [--page <n>]
instawp sites create --name <n> [--php <v>] [--config <id>]
instawp create --name <n> # alias for sites create

# Sites (cloud)
instawp sites list [--status <s>] [--page <n>] [--per-page <n>] [--all]
instawp create --name <n> [--php <v>] [--config <id>]
instawp sites delete <site> [--force]

# Remote access
instawp exec <site> <cmd...> [--api] [--timeout <s>]
instawp wp <site> <args...> [--api] # shorthand: prepends `wp` to args
instawp wp <site> <args...> [--api]
instawp ssh <site>
instawp sync push <site> [--path] [--exclude] [--dry-run]
instawp sync pull <site> [--path] [--exclude] [--dry-run]

# Teams
instawp teams list
instawp teams switch [team] # client-side team context
instawp teams members <team>

# Local development (powered by WordPress Playground)
instawp local create [--name <n>] [--wp <v>] [--php <v>] [--background] [--no-open]
instawp local clone <cloud-site> [--name <n>] [--no-start]
instawp local start [name] [--background] [--no-open]
instawp local stop [name]
instawp local push <local-name> [cloud-site] [--dry-run]
instawp local pull <local-name> <cloud-site> [--dry-run]
instawp local list
instawp local delete <name> [--force]
```

All commands support `--json` for machine-readable output.
Expand All @@ -62,11 +83,41 @@ All commands support `--json` for machine-readable output.
- `--api`: uses `POST /sites/{id}/run-cmd` API → cloud-app → InstaCP `v-instawp-run-cmd`
- Both transports can run arbitrary commands (API is not WP-only despite the name)

### Site resolution
### Site resolution + caching
- `resolveSite()` accepts ID (numeric), name, or domain
- Numeric → direct `GET /sites/{id}/details`
- String → fetches list, matches by name/sub_domain/domain, then fetches details
- Errors on zero matches or ambiguous multiple matches
- **Caches** name→ID mappings for 10 minutes (avoids list call on repeat lookups)

### Team context
- `teams switch` stores team_id locally (no server-side change)
- API interceptor injects `team_id` as query param on all requests
- Client-app `SiteService::getList()` already accepts `team_id` parameter

### Local development architecture
- Uses **WordPress Playground** (`@wp-playground/cli`) — WASM PHP + SQLite, no Docker needed
- NOT a hard dependency — auto-downloaded via `npx`, faster if installed globally (`npm i -g @wp-playground/cli`)
- Instance data stored at `~/.instawp/local/<name>/`
- Fresh sites: mount entire `wp-content` before install (`--mount-before-install`)
- Cloned sites: mount subdirs individually after install (`--mount`) so Playground sets up `db.php` internally

### Clone flow (local clone)
1. Export MySQL dump via SSH (`wp db export`)
2. Strip SSH MOTD from dump output
3. Convert MySQL → SQLite using `mysql2sqlite` (awk script)
4. Import directly into `.ht.sqlite` via `sqlite3` CLI
5. Rename table prefix to `wp_` (tables + meta keys + option names)
6. Search-replace cloud URL → `http://127.0.0.1:<port>` across all tables
7. Pull wp-content via rsync (plugins, themes, uploads)
8. Pull non-core root files (CLAUDE.md, .htaccess, etc.)
9. Generate blueprint with `WP_SQLITE_AST_DRIVER=true` + `login` step with actual admin username
10. Write error suppression mu-plugin

### Background mode
- `--background` flag spawns detached process, polls until server responds, returns immediately
- PID stored at `<instance>/server.pid`, logs at `<instance>/server.log`
- `local stop` kills the background process
- `local list` shows `running`/`stopped` status

### SSH key management
- Auto-generates RSA 4096 key at `~/.instawp/cli_key` if needed
Expand All @@ -77,15 +128,57 @@ All commands support `--json` for machine-readable output.
### Config storage
- Uses `conf` package → `~/.config/instawp/config.json`
- Env overrides: `INSTAWP_TOKEN`, `INSTAWP_API_URL`
- SSH cache with TTL stored alongside auth config
- Stores: auth, SSH cache, site cache, team_id, local instances

## Vendored Dependencies

### `scripts/mysql2sqlite`
- **Source**: https://github.com/dumblob/mysql2sqlite
- **License**: MIT
- **What**: AWK script that converts MySQL dump files to SQLite-compatible SQL
- **Used by**: `local clone` for database import
- **Version**: Vendored from master branch (2026-03-23)
- **Update procedure**: Download latest from `https://raw.githubusercontent.com/dumblob/mysql2sqlite/master/mysql2sqlite` and replace `scripts/mysql2sqlite`. Test with `instawp local clone` on a WooCommerce site to verify compatibility.

## Windows Support

Windows ships with `ssh`/`scp` but not `rsync`, `awk`, or `sqlite3`. The CLI works on Windows with zero extra installs via:

- **`better-sqlite3`** (npm dep) — replaces the sqlite3 CLI. Native module, prebuilt binaries for win32-x64.
- **`bin/win32/busybox.exe`** — provides `awk` for the `mysql2sqlite` script (invoked as `busybox awk -f ...`).
- **`bin/win32/rsync.exe`** + `msys-*.dll` — used for sync/push/pull. The DLLs must remain colocated with rsync.exe.

### Resolution order
- `findAwk()` / `bundledRsync()` (`src/lib/windows-binaries.ts`) prefer bundled binaries on Windows, then fall back to system PATH, then check common Git-for-Windows install dirs.
- On macOS/Linux, the bundle is ignored — `rsync`/`awk` come from PATH.

### Refreshing the Windows bundle
```bash
# Maintainer-only — run on macOS/Linux with curl + 7z (`brew install p7zip`)
bash scripts/fetch-windows-binaries.sh
git add bin/win32 && git commit -m 'chore: refresh Windows binaries'
```
See `bin/win32/NOTICE.md` for sources and license obligations (BusyBox is GPL-2.0, rsync is GPL-3.0).

### Cross-platform path handling
- `src/lib/paths.ts` → `toRsyncPath()` converts `C:\foo\bar` → `/c/foo/bar` so msys rsync doesn't interpret `C:` as a hostname.
- `rsyncViaSsh()` applies `toRsyncPath` centrally; the embedded `-e "ssh -i ... -o ..."` string uses forward slashes + quotes so msys/cygwin sh parses key paths correctly.

## Known Limitations

### Local clone + SQLite
- **WP_SQLITE_AST_DRIVER=true** is required for complex plugins (WooCommerce). The new AST-based SQLite driver (v2.2.1+) handles 99% of MySQL queries.
- Some MySQL-specific queries may still fail at runtime (rare edge cases in complex plugins)
- PHP deprecation warnings can crash WASM PHP — suppressed via mu-plugin (`error_reporting(E_ERROR | E_PARSE)`)
- `downloads.w.org` is unreachable on some networks — connectivity pre-check warns the user

## API Endpoints Used

| Endpoint | Used By |
|----------|---------|
| `GET /api/v2/sites` | sites list, site resolver |
| `GET /api/v2/sites/{id}/details` | site resolver |
| `POST /api/v2/sites` | sites create |
| `POST /api/v2/sites` | sites create, local push (auto-create) |
| `DELETE /api/v2/sites/{id}` | sites delete |
| `POST /api/v2/sites/{id}/run-cmd` | exec --api, wp --api |
| `GET /api/v2/tasks/{id}/status` | create (poll provisioning) |
Expand Down Expand Up @@ -113,6 +206,8 @@ npm run build
node dist/index.js --help
node dist/index.js login --token <test-token>
node dist/index.js sites list
node dist/index.js local create --name test --background --no-open
node dist/index.js local stop test
```

Or link globally:
Expand All @@ -127,8 +222,8 @@ instawp --help

```bash
# Bump version in package.json, then:
git tag v0.0.1-beta.1
git push origin v0.0.1-beta.1
git tag v0.0.1-beta.2
git push origin v0.0.1-beta.2
```

- Publishes with `--tag beta` (install via `npm i -g @instawp/cli@beta`)
Expand All @@ -142,3 +237,5 @@ git push origin v0.0.1-beta.1
- Spinners stop before printing output (no interleaved text)
- JSON mode returns `{ success, data }` or `{ success: false, error }`
- Version reads from package.json at runtime (single source of truth)
- rsync uses `--itemize-changes` (only shows actually changed files)
- Terminal restored with `stty sane` after Playground exits
38 changes: 38 additions & 0 deletions bin/win32/NOTICE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Bundled Windows binaries

Files in this directory are third-party binaries bundled into the npm package
so that `instawp local clone`, `instawp local push/pull`, and `instawp sync`
work on Windows without requiring users to install rsync, awk, or sqlite3.

Populate this directory by running `scripts/fetch-windows-binaries.sh`.

## Components and licenses

### busybox.exe
- **Source**: BusyBox-w64 by Ron Yorston — https://frippery.org/busybox/
- **License**: GPL-2.0
- **Used for**: provides `awk` (invoked as `busybox.exe awk -f ...`) for
converting MySQL dumps to SQLite via the vendored `scripts/mysql2sqlite`
awk script.

### rsync.exe + msys-*.dll
- **Source**: Git for Windows portable distribution — https://gitforwindows.org/
- **License**: rsync is GPL-3.0; msys2-runtime DLLs are mixed (mostly LGPL/MIT)
- **Used for**: file sync between local and remote sites in `sync push/pull`
and `local push/pull/clone`.

The `msys-2.0.dll` and other `msys-*.dll` files must remain colocated with
rsync.exe — rsync.exe links against them at runtime.

## License compliance

The CLI is MIT-licensed, but the bundled GPL binaries impose obligations on
**redistribution**:

- Users who receive the binaries are entitled to the corresponding source.
- BusyBox source: https://busybox.net/downloads/
- rsync source: https://download.samba.org/pub/rsync/src/
- Git for Windows source: https://github.com/git-for-windows/git

The maintainer's responsibility is to keep this NOTICE.md shipped alongside
the binaries in the npm tarball.
Binary file added bin/win32/busybox.exe
Binary file not shown.
Binary file added bin/win32/msys-2.0.dll
Binary file not shown.
Binary file added bin/win32/msys-crypto-3.dll
Binary file not shown.
Binary file added bin/win32/msys-iconv-2.dll
Binary file not shown.
Binary file added bin/win32/msys-lz4-1.dll
Binary file not shown.
Binary file added bin/win32/msys-xxhash-0.dll
Binary file not shown.
Binary file added bin/win32/msys-zstd-1.dll
Binary file not shown.
Binary file added bin/win32/rsync.exe
Binary file not shown.
Loading
Loading