From fa0dd5d6598466941d7428fea0820dfd77be9405 Mon Sep 17 00:00:00 2001 From: "Engineer (agent-os)" Date: Fri, 9 Oct 2026 11:07:52 +0000 Subject: [PATCH 1/9] fix: npm install fails on Windows with Node 24+ (better-sqlite3 native build) better-sqlite3 11.10.0 ships win32 prebuilds only for Node ABI 108-131 (Node 18-23). On Node 24 LTS/25/26 npm falls back to node-gyp, which needs Visual Studio Build Tools, so the global install fails. - better-sqlite3 ^11.10.0 -> ^12.11.1 (win32/darwin/linux prebuilds for Node 22/24/25/26), moved to optionalDependencies. - Lazy-load it via src/lib/sqlite.ts so a missing module only affects local clone/push, not every command (index.ts loads local.ts eagerly). - engines.node >=18 -> >=22. - Windows smoke test: Node matrix [22, 24, 26]; assert the native module installed and loads; assert the CLI starts without it. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish.yml | 42 +++++++++++++++++++---- CHANGELOG.md | 12 +++++++ CLAUDE.md | 2 +- README.md | 2 +- package-lock.json | 63 +++++++++++++++++++++++++++-------- package.json | 6 ++-- src/commands/local.ts | 6 ++-- src/lib/sqlite-to-mysql.ts | 5 +-- src/lib/sqlite.ts | 28 ++++++++++++++++ 9 files changed, 137 insertions(+), 29 deletions(-) create mode 100644 src/lib/sqlite.ts diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ee1cf8c..34462ce 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -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 @@ -54,12 +62,34 @@ jobs: # `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 + if ($LASTEXITCODE -ne 0) { throw "better-sqlite3 is missing or failed to load on Node ${{ matrix.node }}" } + + - 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 @@ -74,7 +104,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 registry-url: https://registry.npmjs.org - run: npm ci diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f7db95..b9c9bae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## Unreleased + +### 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. +- 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) diff --git a/CLAUDE.md b/CLAUDE.md index cec3ad3..5c8c676 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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) diff --git a/README.md b/README.md index 4aa0897..984dfea 100644 --- a/README.md +++ b/README.md @@ -279,7 +279,7 @@ 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+ - `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 diff --git a/package-lock.json b/package-lock.json index 92526b4..80184fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "archiver": "^7.0.1", "axios": "^1.7.0", - "better-sqlite3": "^11.10.0", "chalk": "^5.3.0", "cli-table3": "^0.6.5", "commander": "^12.1.0", @@ -35,7 +34,10 @@ "vitest": "^4.0.18" }, "engines": { - "node": ">=18" + "node": ">=22" + }, + "optionalDependencies": { + "better-sqlite3": "^12.11.1" } }, "node_modules/@colors/colors": { @@ -1518,14 +1520,18 @@ } }, "node_modules/better-sqlite3": { - "version": "11.10.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz", - "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==", + "version": "12.11.1", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz", + "integrity": "sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==", "hasInstallScript": true, "license": "MIT", + "optional": true, "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" } }, "node_modules/bindings": { @@ -1533,6 +1539,7 @@ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "license": "MIT", + "optional": true, "dependencies": { "file-uri-to-path": "1.0.0" } @@ -1542,6 +1549,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "license": "MIT", + "optional": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -1576,6 +1584,7 @@ } ], "license": "MIT", + "optional": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -1659,7 +1668,8 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/cli-cursor": { "version": "5.0.0", @@ -1955,6 +1965,7 @@ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "license": "MIT", + "optional": true, "dependencies": { "mimic-response": "^3.1.0" }, @@ -1970,6 +1981,7 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "license": "MIT", + "optional": true, "engines": { "node": ">=4.0.0" } @@ -2028,6 +2040,7 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", + "optional": true, "engines": { "node": ">=8" } @@ -2078,6 +2091,7 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", + "optional": true, "dependencies": { "once": "^1.4.0" } @@ -2236,6 +2250,7 @@ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", "license": "(MIT OR WTFPL)", + "optional": true, "engines": { "node": ">=6" } @@ -2300,7 +2315,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/follow-redirects": { "version": "1.15.11", @@ -2358,7 +2374,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -2437,7 +2454,8 @@ "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/glob": { "version": "10.5.0", @@ -2547,7 +2565,8 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/is-docker": { "version": "3.0.0", @@ -2820,6 +2839,7 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "license": "MIT", + "optional": true, "engines": { "node": ">=10" }, @@ -2847,6 +2867,7 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "license": "MIT", + "optional": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2864,7 +2885,8 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/nan": { "version": "2.27.0", @@ -2896,13 +2918,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/node-abi": { "version": "3.92.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", "license": "MIT", + "optional": true, "dependencies": { "semver": "^7.3.5" }, @@ -2935,6 +2959,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", + "optional": true, "dependencies": { "wrappy": "1" } @@ -3111,6 +3136,7 @@ "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", "license": "MIT", + "optional": true, "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", @@ -3171,6 +3197,7 @@ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "license": "MIT", + "optional": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -3181,6 +3208,7 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -3413,7 +3441,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/simple-get": { "version": "4.0.1", @@ -3434,6 +3463,7 @@ } ], "license": "MIT", + "optional": true, "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", @@ -3644,6 +3674,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } @@ -3668,6 +3699,7 @@ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "license": "MIT", + "optional": true, "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -3680,6 +3712,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "license": "MIT", + "optional": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -3758,6 +3791,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "license": "Apache-2.0", + "optional": true, "dependencies": { "safe-buffer": "^5.0.1" }, @@ -4117,7 +4151,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/wsl-utils": { "version": "0.1.0", diff --git a/package.json b/package.json index e93ac54..4f9d053 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "test:watch": "vitest" }, "engines": { - "node": ">=18" + "node": ">=22" }, "keywords": [ "instawp", @@ -44,7 +44,6 @@ "dependencies": { "archiver": "^7.0.1", "axios": "^1.7.0", - "better-sqlite3": "^11.10.0", "chalk": "^5.3.0", "cli-table3": "^0.6.5", "commander": "^12.1.0", @@ -63,5 +62,8 @@ "@types/ssh2-sftp-client": "^9.0.6", "typescript": "^5.5.0", "vitest": "^4.0.18" + }, + "optionalDependencies": { + "better-sqlite3": "^12.11.1" } } diff --git a/src/commands/local.ts b/src/commands/local.ts index 72e64fa..4490b5d 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -6,7 +6,6 @@ import { tmpdir } from 'node:os'; import { randomBytes } from 'node:crypto'; import chalk from 'chalk'; import open from 'open'; -import Database from 'better-sqlite3'; import { resolveFromModule } from '../lib/paths.js'; import { bundledBusybox } from '../lib/windows-binaries.js'; import { @@ -35,6 +34,7 @@ import { syncFiles, execViaSsh, execViaSshToFile, scpUpload } from '../lib/ssh-c import { listLocalFiles } from '../lib/sftp-sync.js'; import { sanitizeName, defaultInstanceName, pushTargetRef, parseTablePrefix, parseSqlTableNames } from '../lib/local-instance.js'; import { generateMysqlDump } from '../lib/sqlite-to-mysql.js'; +import { loadSqlite } from '../lib/sqlite.js'; import { success, error, table, spinner, info, isJsonMode } from '../lib/output.js'; import type { LocalInstance, SshConnection } from '../types.js'; @@ -639,7 +639,7 @@ export function registerLocalCommand(program: Command): void { ); // Import directly via better-sqlite3 (no external sqlite3 CLI needed) - const db = new Database(sqliteDbPath); + const db = new (loadSqlite())(sqliteDbPath); try { db.exec(sqliteSql); @@ -791,7 +791,7 @@ async function pushDatabase(instance: LocalInstance, site: any, conn: SshConnect // Authoritative local URL from the DB (handles port drift); cloud URL from the site. let fromUrl = `http://127.0.0.1:${instance.port}`; try { - const ldb = new Database(sqlitePath, { readonly: true }); + const ldb = new (loadSqlite())(sqlitePath, { readonly: true }); try { const row = ldb.prepare("SELECT option_value AS v FROM wp_options WHERE option_name='siteurl'").get() as { v?: string } | undefined; if (row?.v) fromUrl = String(row.v).replace(/\/+$/, ''); diff --git a/src/lib/sqlite-to-mysql.ts b/src/lib/sqlite-to-mysql.ts index 8d7135b..b799e2b 100644 --- a/src/lib/sqlite-to-mysql.ts +++ b/src/lib/sqlite-to-mysql.ts @@ -1,5 +1,6 @@ -import Database from 'better-sqlite3'; +import type Database from 'better-sqlite3'; import { openSync, writeSync, closeSync } from 'node:fs'; +import { loadSqlite } from './sqlite.js'; /** * Generate a MySQL dump from a WordPress Playground SQLite database so a local @@ -86,7 +87,7 @@ export function generateMysqlDump(params: DumpParams): DumpResult { const result: DumpResult = { tables: [], skipped: [], totalRows: 0 }; try { - db = new Database(sqlitePath, { readonly: true }); + db = new (loadSqlite())(sqlitePath, { readonly: true }); writeSync(fd, '-- InstaWP CLI: local SQLite → MySQL data dump\n'); // Pin a sane sql_mode so our backslash escaping is always honored (a server diff --git a/src/lib/sqlite.ts b/src/lib/sqlite.ts new file mode 100644 index 0000000..db9296e --- /dev/null +++ b/src/lib/sqlite.ts @@ -0,0 +1,28 @@ +import { createRequire } from 'node:module'; +import type BetterSqlite3 from 'better-sqlite3'; + +const require = createRequire(import.meta.url); + +let cached: typeof BetterSqlite3 | undefined; + +/** + * Load better-sqlite3 on first use instead of at startup. + * + * It is a native addon and an optionalDependency: on a platform/Node combination + * with no prebuilt binary (and no C++ toolchain) npm skips it rather than failing + * the whole install. A static import would then crash every command — including + * `login` — because `index.ts` loads `local.ts` eagerly. Only the commands that + * actually open a SQLite file (local clone/push) call this, so only they fail. + */ +export function loadSqlite(): typeof BetterSqlite3 { + if (cached) return cached; + try { + cached = require('better-sqlite3') as typeof BetterSqlite3; + return cached; + } catch (err: any) { + throw new Error( + `Local-site features need the SQLite native module (better-sqlite3), which failed to load (${err?.code || err?.message}). ` + + 'Reinstall the CLI on Node.js 22 or 24 LTS: npm install -g @instawp/cli', + ); + } +} From 413949456de31f78d66bfe338d4a7b6419f780b8 Mon Sep 17 00:00:00 2001 From: "Engineer (agent-os)" Date: Fri, 9 Oct 2026 11:12:13 +0000 Subject: [PATCH 2/9] =?UTF-8?q?fix:=20address=20review=20=E2=80=94=20CI=20?= =?UTF-8?q?catches=20a=20source=20compile;=20preflight=20local=20push=20--?= =?UTF-8?q?with-db?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Windows smoke: fail if better-sqlite3 has build/config.gypi. The runner has Visual Studio, so a missing prebuild would otherwise compile silently and pass while users without build tools fail. - local push --with-db: check the native module before syncing files, so a missing module can't fail after files are pushed and an overwrite prompt. - Error message names Node 26; rename the install step (no native deps now). Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish.yml | 9 ++++++++- CLAUDE.md | 2 +- src/commands/local.ts | 12 ++++++++++++ src/lib/sqlite.ts | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 34462ce..aa59ccb 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -54,7 +54,7 @@ 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 @@ -77,6 +77,13 @@ jobs: $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 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 diff --git a/CLAUDE.md b/CLAUDE.md index 5c8c676..c6318a4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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-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. diff --git a/src/commands/local.ts b/src/commands/local.ts index 4490b5d..f421dbb 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -241,6 +241,18 @@ export function registerLocalCommand(program: Command): void { process.exit(1); } + // --with-db needs the native SQLite module. Check it before anything is + // pushed, so a missing module can't fail halfway (files already synced, + // and an overwrite confirmation for a DB push that cannot happen). + if (opts.withDb) { + try { + loadSqlite(); + } catch (err: any) { + error(err.message); + process.exit(1); + } + } + const localWpContent = join(instance.path, 'wp-content') + '/'; // Where does this push go? Explicit arg → the site this instance was diff --git a/src/lib/sqlite.ts b/src/lib/sqlite.ts index db9296e..613f0ce 100644 --- a/src/lib/sqlite.ts +++ b/src/lib/sqlite.ts @@ -22,7 +22,7 @@ export function loadSqlite(): typeof BetterSqlite3 { } catch (err: any) { throw new Error( `Local-site features need the SQLite native module (better-sqlite3), which failed to load (${err?.code || err?.message}). ` + - 'Reinstall the CLI on Node.js 22 or 24 LTS: npm install -g @instawp/cli', + 'Reinstall the CLI on Node.js 22, 24 or 26: npm install -g @instawp/cli', ); } } From 4239e5bbf06ca4271ccae45f9779100ed927a2e0 Mon Sep 17 00:00:00 2001 From: "Engineer (agent-os)" Date: Fri, 9 Oct 2026 11:28:15 +0000 Subject: [PATCH 3/9] refactor: name the better-sqlite3 type import Database, matching the codebase Type-only rename; the compiled dist/lib/sqlite.js is byte-identical. Co-Authored-By: Claude Opus 5.5 --- src/lib/sqlite.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/sqlite.ts b/src/lib/sqlite.ts index 613f0ce..79cbd5a 100644 --- a/src/lib/sqlite.ts +++ b/src/lib/sqlite.ts @@ -1,9 +1,9 @@ import { createRequire } from 'node:module'; -import type BetterSqlite3 from 'better-sqlite3'; +import type Database from 'better-sqlite3'; const require = createRequire(import.meta.url); -let cached: typeof BetterSqlite3 | undefined; +let cached: typeof Database | undefined; /** * Load better-sqlite3 on first use instead of at startup. @@ -14,10 +14,10 @@ let cached: typeof BetterSqlite3 | undefined; * `login` — because `index.ts` loads `local.ts` eagerly. Only the commands that * actually open a SQLite file (local clone/push) call this, so only they fail. */ -export function loadSqlite(): typeof BetterSqlite3 { +export function loadSqlite(): typeof Database { if (cached) return cached; try { - cached = require('better-sqlite3') as typeof BetterSqlite3; + cached = require('better-sqlite3') as typeof Database; return cached; } catch (err: any) { throw new Error( From 0f1c6a144e9d389926c79a059b06cc55250cc3dd Mon Sep 17 00:00:00 2001 From: "Engineer (agent-os)" Date: Fri, 9 Oct 2026 11:39:36 +0000 Subject: [PATCH 4/9] fix: detect a blocked native binary (npm 12 allowScripts) and say how to fix it npm 12 blocks install scripts by default, so a global install keeps the better-sqlite3 package but never downloads its binary. require() still succeeds; the first `new Database()` throws a raw "Could not locate the bindings file". loadSqlite() now opens an in-memory DB once so that case gets the friendly message, which names --allow-scripts=better-sqlite3. `instawp upgrade` passes the flag (npm 10/11 accept it), and the README install command includes it. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + README.md | 4 +++- src/lib/sqlite.ts | 13 ++++++++++--- src/lib/update-notifier.ts | 9 +++++++-- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9c9bae..4a96233 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The install aborted on Windows for anyone on Node 24 LTS, 25 or 26 (`npm warn cl - 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, and the README install command includes it. - 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) diff --git a/README.md b/README.md index 984dfea..576f85d 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,11 @@ Create and manage WordPress sites from the terminal. ``` -npm install -g @instawp/cli +npm install -g @instawp/cli --allow-scripts=better-sqlite3 ``` +Requires Node.js 22+. `--allow-scripts` lets npm 12+ fetch the prebuilt SQLite module used by `local clone`/`local push --with-db`; without it everything else still works. npm 10 and 11 accept the flag too. + This installs the latest release. The CLI checks for updates once a day and `instawp upgrade` keeps you current (see [Updating](#updating)). > **Beta** — early release. Report issues at [github.com/InstaWP/cli/issues](https://github.com/InstaWP/cli/issues). diff --git a/src/lib/sqlite.ts b/src/lib/sqlite.ts index 79cbd5a..27630f4 100644 --- a/src/lib/sqlite.ts +++ b/src/lib/sqlite.ts @@ -17,12 +17,19 @@ let cached: typeof Database | undefined; export function loadSqlite(): typeof Database { if (cached) return cached; try { - cached = require('better-sqlite3') as typeof Database; + const Sqlite = require('better-sqlite3') as typeof Database; + // Requiring the package does not load the native binary; opening a DB does. + // npm 12 installs the package but BLOCKS its install script by default, so + // the binary is never downloaded and only the first `new Database()` fails. + new Sqlite(':memory:').close(); + cached = Sqlite; return cached; } catch (err: any) { + const reason = err?.code || String(err?.message ?? err).split('\n')[0].replace(/\s*Tried:\s*$/, ''); throw new Error( - `Local-site features need the SQLite native module (better-sqlite3), which failed to load (${err?.code || err?.message}). ` + - 'Reinstall the CLI on Node.js 22, 24 or 26: npm install -g @instawp/cli', + `Local-site features need the SQLite native module (better-sqlite3), which failed to load (${reason}). ` + + 'Reinstall the CLI on Node.js 22, 24 or 26, allowing its install script: ' + + 'npm install -g @instawp/cli --allow-scripts=better-sqlite3', ); } } diff --git a/src/lib/update-notifier.ts b/src/lib/update-notifier.ts index 41d78fa..80c7554 100644 --- a/src/lib/update-notifier.ts +++ b/src/lib/update-notifier.ts @@ -48,9 +48,14 @@ export async function fetchLatestVersion(timeoutMs = 2500): Promise` (inherits stdio). Returns npm's exit code. */ +/** + * Run `npm install -g @instawp/cli@` (inherits stdio). Returns npm's exit code. + * `--allow-scripts=better-sqlite3`: npm 12 blocks install scripts by default, which + * would leave the SQLite native binary undownloaded (local clone/push then fail). + * npm 10 and 11 accept the flag silently. + */ export function performUpgrade(versionTag = 'latest'): number { - const res = spawnSync('npm', ['install', '-g', `${PKG}@${versionTag}`], { stdio: 'inherit' }); + const res = spawnSync('npm', ['install', '-g', `${PKG}@${versionTag}`, '--allow-scripts=better-sqlite3'], { stdio: 'inherit' }); return res.status ?? 1; } From 40efcfedc6d430e68f01f834d9fd950a4d9c48e2 Mon Sep 17 00:00:00 2001 From: "Engineer (agent-os)" Date: Fri, 9 Oct 2026 11:43:10 +0000 Subject: [PATCH 5/9] =?UTF-8?q?chore(release):=200.0.1-beta.34=20=E2=80=94?= =?UTF-8?q?=20Windows=20install=20on=20Node=2024+=20(better-sqlite3=2012.x?= =?UTF-8?q?,=20optional=20+=20lazy-loaded)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a96233..0b4cc98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.0.1-beta.34 (2026-10-09) ### Fixed — `npm install -g @instawp/cli` fails on Windows with Node 24+ diff --git a/package-lock.json b/package-lock.json index 80184fd..91b65eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@instawp/cli", - "version": "0.0.1-beta.33", + "version": "0.0.1-beta.34", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@instawp/cli", - "version": "0.0.1-beta.33", + "version": "0.0.1-beta.34", "license": "MIT", "dependencies": { "archiver": "^7.0.1", diff --git a/package.json b/package.json index 4f9d053..5cfa70d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@instawp/cli", - "version": "0.0.1-beta.33", + "version": "0.0.1-beta.34", "description": "InstaWP CLI - Create and manage WordPress sites from the terminal", "type": "module", "bin": { From 897e0963bb32a3add8086af85706c6827fff8ea4 Mon Sep 17 00:00:00 2001 From: "Engineer (agent-os)" Date: Fri, 9 Oct 2026 11:46:15 +0000 Subject: [PATCH 6/9] =?UTF-8?q?chore:=20review=20nits=20=E2=80=94=20--allo?= =?UTF-8?q?w-scripts=20in=20the=20upgrade=20fallback=20hint=20and=20CI=20i?= =?UTF-8?q?nstall;=20accurate=20npm=2011=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish.yml | 3 ++- src/commands/upgrade.ts | 2 +- src/lib/update-notifier.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index aa59ccb..693ca34 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -57,7 +57,8 @@ jobs: - name: Install globally + verify CLI loads (incl. SFTP deps) shell: pwsh run: | - npm i -g (Get-ChildItem -Filter "instawp-cli-*.tgz").FullName + # Same flag the README tells users to pass (npm 12 blocks install scripts without it). + npm i -g (Get-ChildItem -Filter "instawp-cli-*.tgz").FullName --allow-scripts=better-sqlite3 if ($LASTEXITCODE -ne 0) { throw "npm i -g failed" } # `instawp --version` eagerly loads the full command import graph: diff --git a/src/commands/upgrade.ts b/src/commands/upgrade.ts index ca2cdaf..c9da4b7 100644 --- a/src/commands/upgrade.ts +++ b/src/commands/upgrade.ts @@ -37,7 +37,7 @@ export function registerUpgradeCommand(program: Command): void { if (!json) info(`Updating ${current} → ${latest}…`); const code = performUpgrade(); if (code !== 0) { - error('Upgrade failed. If this is a permissions error, try: sudo npm install -g @instawp/cli@latest'); + error('Upgrade failed. If this is a permissions error, try: sudo npm install -g @instawp/cli@latest --allow-scripts=better-sqlite3'); process.exit(code); } if (json) console.log(JSON.stringify({ success: true, data: { current, latest, upgraded: true } })); diff --git a/src/lib/update-notifier.ts b/src/lib/update-notifier.ts index 80c7554..b280890 100644 --- a/src/lib/update-notifier.ts +++ b/src/lib/update-notifier.ts @@ -52,7 +52,7 @@ export async function fetchLatestVersion(timeoutMs = 2500): Promise` (inherits stdio). Returns npm's exit code. * `--allow-scripts=better-sqlite3`: npm 12 blocks install scripts by default, which * would leave the SQLite native binary undownloaded (local clone/push then fail). - * npm 10 and 11 accept the flag silently. + * npm 10/11 accept it (some 11.x versions print an unknown-config warning). */ export function performUpgrade(versionTag = 'latest'): number { const res = spawnSync('npm', ['install', '-g', `${PKG}@${versionTag}`, '--allow-scripts=better-sqlite3'], { stdio: 'inherit' }); From c1fde6bb405b32e4187e2b1e9374348831fcae75 Mon Sep 17 00:00:00 2001 From: "Engineer (agent-os)" Date: Fri, 9 Oct 2026 11:47:45 +0000 Subject: [PATCH 7/9] docs: keep the plain install command; --allow-scripts only in the failure message Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish.yml | 3 +-- CHANGELOG.md | 2 +- README.md | 4 +--- src/commands/upgrade.ts | 2 +- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 693ca34..aa59ccb 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -57,8 +57,7 @@ jobs: - name: Install globally + verify CLI loads (incl. SFTP deps) shell: pwsh run: | - # Same flag the README tells users to pass (npm 12 blocks install scripts without it). - npm i -g (Get-ChildItem -Filter "instawp-cli-*.tgz").FullName --allow-scripts=better-sqlite3 + 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: diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b4cc98..1d6e513 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ The install aborted on Windows for anyone on Node 24 LTS, 25 or 26 (`npm warn cl - 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, and the README install command includes it. +- **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. - 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) diff --git a/README.md b/README.md index 576f85d..984dfea 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,9 @@ Create and manage WordPress sites from the terminal. ``` -npm install -g @instawp/cli --allow-scripts=better-sqlite3 +npm install -g @instawp/cli ``` -Requires Node.js 22+. `--allow-scripts` lets npm 12+ fetch the prebuilt SQLite module used by `local clone`/`local push --with-db`; without it everything else still works. npm 10 and 11 accept the flag too. - This installs the latest release. The CLI checks for updates once a day and `instawp upgrade` keeps you current (see [Updating](#updating)). > **Beta** — early release. Report issues at [github.com/InstaWP/cli/issues](https://github.com/InstaWP/cli/issues). diff --git a/src/commands/upgrade.ts b/src/commands/upgrade.ts index c9da4b7..ca2cdaf 100644 --- a/src/commands/upgrade.ts +++ b/src/commands/upgrade.ts @@ -37,7 +37,7 @@ export function registerUpgradeCommand(program: Command): void { if (!json) info(`Updating ${current} → ${latest}…`); const code = performUpgrade(); if (code !== 0) { - error('Upgrade failed. If this is a permissions error, try: sudo npm install -g @instawp/cli@latest --allow-scripts=better-sqlite3'); + error('Upgrade failed. If this is a permissions error, try: sudo npm install -g @instawp/cli@latest'); process.exit(code); } if (json) console.log(JSON.stringify({ success: true, data: { current, latest, upgraded: true } })); From 8d995b8fc5e7d56db9b945737304f1696f992fac Mon Sep 17 00:00:00 2001 From: "Engineer (agent-os)" Date: Fri, 9 Oct 2026 12:00:26 +0000 Subject: [PATCH 8/9] docs(readme): npm 12 note under Requirements Co-Authored-By: Claude Opus 5.5 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 984dfea..73cf9d0 100644 --- a/README.md +++ b/README.md @@ -280,6 +280,7 @@ The CLI checks for a new version **once a day** and prints a one-line hint (to s ## Requirements - 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 From adf2270b2c01b073c3d76199be6c99576fa511ba Mon Sep 17 00:00:00 2001 From: "Engineer (agent-os)" Date: Fri, 9 Oct 2026 12:06:02 +0000 Subject: [PATCH 9/9] fix(local clone): warn up front when SQLite is missing; never report a plain success without the DB Before, a missing better-sqlite3 (npm 12 without --allow-scripts) made the DB import fail on one easily-missed line and the command still ended with a green "Clone complete!" over an empty site. Now clone checks the module right after resolving the site (try/catch, warns, continues), and if the import failed for any reason the completion header says so and why. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + src/commands/local.ts | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d6e513..e99fb5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The install aborted on Windows for anyone on Node 24 LTS, 25 or 26 (`npm warn cl - 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) diff --git a/src/commands/local.ts b/src/commands/local.ts index f421dbb..9d4a1f4 100644 --- a/src/commands/local.ts +++ b/src/commands/local.ts @@ -504,6 +504,14 @@ export function registerLocalCommand(program: Command): void { process.exit(1); } + // The database import needs the native SQLite module. Check it now, before + // anything is downloaded, and warn instead of stopping: the files still clone. + try { + loadSqlite(); + } catch (err: any) { + info(chalk.yellow("The site's files will be cloned, but its database can't be imported. ") + err.message); + } + // 2. Create local instance const instances = getLocalInstances(); const name = opts.name ? sanitizeName(opts.name) : defaultInstanceName(site); @@ -607,6 +615,7 @@ export function registerLocalCommand(program: Command): void { // 7. Convert MySQL dump → SQLite, import directly, fix URLs and table prefix const hasDump = existsSync(dumpPath) && statSync(dumpPath).size > 0; let adminUsername = 'admin'; + let dbImportError: string | null = null; if (hasDump) { const dbSpin2 = spinner('Importing database...'); dbSpin2.start(); @@ -719,6 +728,7 @@ export function registerLocalCommand(program: Command): void { db.close(); } } catch (err: any) { + dbImportError = err.message; dbSpin2.fail('Database import failed: ' + err.message); } } @@ -749,8 +759,16 @@ export function registerLocalCommand(program: Command): void { writeFileSync(join(muDir, '0-suppress-errors.php'), "