Skip to content
Merged
51 changes: 44 additions & 7 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,22 @@ on:

jobs:
smoke-windows:
name: Windows smoke test
name: Windows smoke test (Node ${{ matrix.node }})
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
# Every Node line a user is likely to have. better-sqlite3 is native: a
# Node version with no prebuilt win32 binary falls back to node-gyp, which
# needs Visual Studio Build Tools — users don't have them. Node 20 alone
# (the old single entry) had a prebuild, so it could never catch that.
node: [22, 24, 26]
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20
node-version: ${{ matrix.node }}

- run: npm ci
- run: npm run build
Expand Down Expand Up @@ -46,20 +54,49 @@ jobs:
& $busybox awk 'BEGIN { print "awk works" }'
if ($LASTEXITCODE -ne 0) { throw "busybox awk failed (exit $LASTEXITCODE)" }

- name: Install globally + verify CLI loads (incl. SFTP + native deps)
- name: Install globally + verify CLI loads (incl. SFTP deps)
shell: pwsh
run: |
npm i -g (Get-ChildItem -Filter "instawp-cli-*.tgz").FullName
if ($LASTEXITCODE -ne 0) { throw "npm i -g failed" }

# `instawp --version` eagerly loads the full command import graph:
# sync -> ssh-connection -> sftp-sync -> ssh2-sftp-client + ssh2
# local -> better-sqlite3 (native)
# so a clean exit here proves all of those load on Windows.
Write-Host "--- instawp --version (exercises SFTP + native deps) ---"
# so a clean exit here proves those load on Windows. better-sqlite3 is
# NOT in that graph (lazy-loaded by src/lib/sqlite.ts) — checked below.
Write-Host "--- instawp --version (exercises SFTP deps) ---"
instawp --version
if ($LASTEXITCODE -ne 0) { throw "instawp --version failed (exit $LASTEXITCODE) — a dependency failed to load" }

- name: Verify the native SQLite module installed and loads
shell: pwsh
run: |
# better-sqlite3 is an optionalDependency, so npm SKIPS it silently when
# the native build fails. That must never happen on a supported Node
# version — load it from the global install and open a DB to prove it.
$cli = Join-Path (npm root -g) "@instawp\cli"
node -e "const D=require(require.resolve('better-sqlite3',{paths:[process.argv[1]]}));const db=new D(':memory:');console.log('better-sqlite3', db.prepare('select sqlite_version() v').get().v);db.close()" $cli

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BUG: Wrong process.argv index — should be process.argv[2], not process.argv[1]

When running node -e "script" $cli, the Node process receives:

  • process.argv[0] = 'node'
  • process.argv[1] = the -e script string
  • process.argv[2] = $cli (the CLI path)

The code uses process.argv[1] which is the script itself, not the argument. This works by accident because require.resolve falls back to default resolution, but it's incorrect.

Suggested change
node -e "const D=require(require.resolve('better-sqlite3',{paths:[process.argv[1]]}));const db=new D(':memory:');console.log('better-sqlite3', db.prepare('select sqlite_version() v').get().v);db.close()" $cli
node -e "const D=require(require.resolve('better-sqlite3',{paths:[process.argv[2]]}));const db=new D(':memory:');console.log('better-sqlite3', db.prepare('select sqlite_version() v').get().v);db.close()" $cli

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

if ($LASTEXITCODE -ne 0) { throw "better-sqlite3 is missing or failed to load on Node ${{ matrix.node }}" }
# The runner HAS Visual Studio, so a missing prebuild would be compiled
# silently and still load — while users without build tools fail. A
# prebuild ships only build\Release\better_sqlite3.node; node-gyp
# writes build\config.gypi. Its presence means no prebuild was found.
if (Test-Path (Join-Path $cli "node_modules\better-sqlite3\build\config.gypi")) {
throw "better-sqlite3 was compiled from source on Node ${{ matrix.node }} (no prebuilt binary) — users without VS Build Tools would fail to install"
}

- name: Verify the CLI still starts without the native module
shell: pwsh
run: |
# Simulates a machine where the optional native build was skipped: core
# commands must keep working; only local clone/push may fail.
$mod = Join-Path (npm root -g) "@instawp\cli\node_modules\better-sqlite3"
if (-not (Test-Path $mod)) { throw "expected $mod to exist" }
Rename-Item $mod "$mod.hidden"
instawp --version
if ($LASTEXITCODE -ne 0) { throw "instawp --version failed without better-sqlite3 (exit $LASTEXITCODE) — it is being loaded eagerly again" }
Rename-Item "$mod.hidden" $mod

publish:
name: Publish to npm
needs: smoke-windows
Expand All @@ -74,7 +111,7 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
registry-url: https://registry.npmjs.org

- run: npm ci
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## 0.0.1-beta.34 (2026-10-09)

### Fixed — `npm install -g @instawp/cli` fails on Windows with Node 24+

The install aborted on Windows for anyone on Node 24 LTS, 25 or 26 (`npm warn cleanup Failed to remove some directories` after a native build error).

- `better-sqlite3` 11.10.0 ships prebuilt Windows binaries only for Node 18–23. On newer Node, npm fell back to compiling it with node-gyp, which needs Visual Studio Build Tools — so the whole install failed.
- Upgraded to `better-sqlite3` ^12.11.1 (prebuilt for Node 22, 24, 25 and 26 on Windows, macOS and Linux) and made it an **optional** dependency: if the native module still can't be installed, the CLI installs anyway.
- The module is now loaded only by the commands that use it (`local clone` / `local push`). Before, `local.ts` imported it at startup, so a missing module broke every command, including `login`. If it's missing, those two commands now say how to fix it.
- **Requires Node.js 22+** (was 18+). Node 18 and 20 are end-of-life.
- **npm 12** blocks packages' install scripts unless allowed, so the SQLite binary is never downloaded. The CLI now detects that on first use (not just a missing package) and says to reinstall with `--allow-scripts=better-sqlite3`. `instawp upgrade` passes that flag. The plain install command is unchanged.
- `local clone` checks for the SQLite module before downloading anything and warns (without stopping) if it is missing. When the database couldn't be imported, it ends with "Clone complete — but the database was NOT imported" and the reason, instead of a plain "Clone complete!" over an empty site.
- CI: the Windows smoke test now runs on Node 22, 24 and 26 (it only ran on Node 20, which had a prebuilt binary, so it could never catch this), checks the native module installed and loads, and checks the CLI still starts without it.

## 0.0.1-beta.33 (2026-07-10)

### Fixed — remote docroot resolved as a full path (chroot/jailed SSH nodes)
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

TypeScript CLI for InstaWP. Lets users create/manage WordPress sites from the terminal.

- **Stack**: TypeScript, ESM, Commander.js, Axios, Node 18+
- **Stack**: TypeScript, ESM, Commander.js, Axios, Node 22+
- **Package**: `@instawp/cli` on npm (scoped, public)
- **Entry**: `src/index.ts` → compiled to `dist/index.js`
- **Binary**: `instawp` (registered via `bin` in package.json)
Expand Down Expand Up @@ -209,7 +209,7 @@ The standalone-CLI replacement for the plugin's `wp instawp local push`. Mirrors

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.
- **`better-sqlite3`** (optional npm dep) — replaces the sqlite3 CLI. Native module with prebuilt binaries per Node ABI: a Node version with no prebuild falls back to node-gyp, which fails without Visual Studio Build Tools, so it is an `optionalDependency` and lazy-loaded via `loadSqlite()` (`src/lib/sqlite.ts`) — only `local clone`/`local push --with-db` need it. **Never import it statically** from anything `index.ts` loads, or a skipped install breaks every command. When bumping it, check the release's `node-v<ABI>-win32-x64` assets cover every Node line in `publish.yml`'s smoke matrix.
- **`vendor/win32/busybox.exe`** — provides `awk` for the `mysql2sqlite` script (invoked as `busybox awk -f ...`). Statically linked, no DLLs. This is the **only** bundled binary.
- **Pure-JS SFTP** (`ssh2-sftp-client`) for file transfers — see below.
- **`cross-spawn`** for launching WordPress Playground (`local create/start/clone`). `npx`/`wp-playground-cli` are `.cmd` shims on Windows; Node won't spawn `.cmd` without `shell:true` (CVE-2024-27980), so `local-env.ts` uses `cross-spawn` (resolves the shim + quotes mount-path args safely). Never use bare `child_process.spawn` for npx/.cmd on Windows.
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,8 @@ The CLI checks for a new version **once a day** and prints a one-line hint (to s

## Requirements

- Node.js 18+
- Node.js 22+
- **npm 12:** npm blocks packages' install scripts unless you allow them, so the SQLite module used by `local clone` / `local push --with-db` is never downloaded. Everything else works; for those two commands, install with `npm install -g @instawp/cli --allow-scripts=better-sqlite3`. The CLI tells you this if it happens. (`instawp upgrade` passes the flag for you.)
- `ssh` and `ssh-keygen` (for SSH / `exec` / `sync` / `db` over SSH)
- `rsync` (for `sync` on macOS/Linux; Windows uses built-in SFTP)
- Local development uses WordPress Playground (auto-fetched via `npx`) — no Docker required
Expand Down
Loading
Loading