From 52fe6d58c46562628163361c8796ee0684f6903b Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 19 Oct 2026 14:54:20 -0700 Subject: [PATCH 1/3] Keep released lock-sidecar cleanup buildable with MSVC Use the MSVC stack allocator when preparing released amalgamations that contain a variable-length lock-path array. Preserve the source everywhere else and keep existing releases usable with the Windows publishing toolchain. Co-Authored-By: OpenAI Codex --- scripts/build-amalgamation.js | 16 ++++++++++++++++ test/amalgamation.test.ts | 23 ++++++++++++++++++----- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/scripts/build-amalgamation.js b/scripts/build-amalgamation.js index 9cca418..b8fc66a 100644 --- a/scripts/build-amalgamation.js +++ b/scripts/build-amalgamation.js @@ -32,6 +32,22 @@ console.log("Preparing released doltlite amalgamation...") fs.mkdirSync(outDir, { recursive: true }) let amalgamation = fs.readFileSync(inC, "utf8") +const lockPathVla = ' char zLock[nDir + (nPath - nDir) + sizeof(".-lock") + 1];' +if (amalgamation.includes(lockPathVla)) { + amalgamation = `/* DOLTLITE_NODE_MSVC_LOCK_PATH_FALLBACK */ +#ifdef _MSC_VER +# include +#endif + +${amalgamation.replace(lockPathVla, [ + "#ifdef _MSC_VER", + ' char *zLock = (char*)_alloca(nPath + sizeof(".-lock"));', + "#else", + lockPathVla, + "#endif", + ].join("\n"))}` +} + // DoltLite releases before v0.11.37 emitted the Winsock 2 headers after // SQLite's Windows VFS had already included windows.h. Keep those releases // buildable while newer release amalgamations carry the ordering fix directly. diff --git a/test/amalgamation.test.ts b/test/amalgamation.test.ts index 79b2caa..f12cdd4 100644 --- a/test/amalgamation.test.ts +++ b/test/amalgamation.test.ts @@ -27,14 +27,27 @@ describe("released amalgamation preparation", () => { expect(winsock).toBeLessThan(windows) }) - test("uses an unmodified fixed release amalgamation", () => { + test("keeps lock-path allocation on the stack with MSVC", () => { const source = readFileSync(sourcePath, "utf8") const output = readFileSync(outputPath, "utf8") + const vla = ' char zLock[nDir + (nPath - nDir) + sizeof(".-lock") + 1];' - if (source.includes("DOLTLITE_AMALGAMATION_WINSOCK2_EARLY")) { - expect(output).toBe(source) - } else { - expect(output).toContain("DOLTLITE_NODE_WINSOCK2_EARLY_FALLBACK") + if (source.includes(vla)) { + expect(output).toContain("DOLTLITE_NODE_MSVC_LOCK_PATH_FALLBACK") + expect(output).toContain('#ifdef _MSC_VER\n char *zLock = (char*)_alloca(nPath + sizeof(".-lock"));') + expect(output).toContain(`#else\n${vla}\n#endif`) } }) + + test("preserves release source outside compiler compatibility fixes", () => { + const source = readFileSync(sourcePath, "utf8") + const output = readFileSync(outputPath, "utf8") + const vla = ' char zLock[nDir + (nPath - nDir) + sizeof(".-lock") + 1];' + const restored = output + .replace(/^\/\* DOLTLITE_NODE_WINSOCK2_EARLY_FALLBACK \*\/\n#ifdef _WIN32\n# include \n# include \n#endif\n\n/, "") + .replace(/^\/\* DOLTLITE_NODE_MSVC_LOCK_PATH_FALLBACK \*\/\n#ifdef _MSC_VER\n# include \n#endif\n\n/, "") + .replace(`#ifdef _MSC_VER\n char *zLock = (char*)_alloca(nPath + sizeof(".-lock"));\n#else\n${vla}\n#endif`, vla) + + expect(restored).toBe(source) + }) }) From 45964131fc803de726be884d4ada9c44fde2536c Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 19 Oct 2026 14:57:37 -0700 Subject: [PATCH 2/3] Use the Windows compiler build as the regression gate Keep source-preservation coverage while relying on the existing Windows release-toolchain job to exercise the actual compiler failure. Co-Authored-By: OpenAI Codex --- test/amalgamation.test.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/test/amalgamation.test.ts b/test/amalgamation.test.ts index f12cdd4..1c01a70 100644 --- a/test/amalgamation.test.ts +++ b/test/amalgamation.test.ts @@ -27,18 +27,6 @@ describe("released amalgamation preparation", () => { expect(winsock).toBeLessThan(windows) }) - test("keeps lock-path allocation on the stack with MSVC", () => { - const source = readFileSync(sourcePath, "utf8") - const output = readFileSync(outputPath, "utf8") - const vla = ' char zLock[nDir + (nPath - nDir) + sizeof(".-lock") + 1];' - - if (source.includes(vla)) { - expect(output).toContain("DOLTLITE_NODE_MSVC_LOCK_PATH_FALLBACK") - expect(output).toContain('#ifdef _MSC_VER\n char *zLock = (char*)_alloca(nPath + sizeof(".-lock"));') - expect(output).toContain(`#else\n${vla}\n#endif`) - } - }) - test("preserves release source outside compiler compatibility fixes", () => { const source = readFileSync(sourcePath, "utf8") const output = readFileSync(outputPath, "utf8") From 092f1b4c2fb7af71ccc9ae10b470c91b25ffbcdd Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 19 Oct 2026 15:23:25 -0700 Subject: [PATCH 3/3] Restore bundled CLI permissions before publishing npm archives Artifact downloads strip executable modes. Restore both Unix CLI modes before packing, then smoke-test the actual npm archive and its native addon and CLI. The current 0.51.1 archive fails this check before the permission repair and passes afterward; the published npm version is immutable. Co-Authored-By: OpenAI Codex --- .github/workflows/publish.yml | 6 ++++++ scripts/test-package.js | 37 +++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 scripts/test-package.js diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 742b874..7ac2569 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -105,6 +105,12 @@ jobs: - name: Install dependencies (skip build — we have prebuilts) run: npm install --ignore-scripts + - name: Restore bundled CLI permissions + run: chmod 755 prebuilds/linux-x64/doltlite prebuilds/darwin-arm64/doltlite + + - name: Smoke test npm archive + run: node scripts/test-package.js + - name: Verify npm publish identity run: | npm config get registry diff --git a/scripts/test-package.js b/scripts/test-package.js new file mode 100644 index 0000000..09c3de1 --- /dev/null +++ b/scripts/test-package.js @@ -0,0 +1,37 @@ +"use strict" + +const assert = require("node:assert/strict") +const fs = require("node:fs") +const os = require("node:os") +const path = require("node:path") +const { execFileSync } = require("node:child_process") + +const root = path.resolve(__dirname, "..") +const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "doltlite-package-")) + +try { + const packed = JSON.parse(execFileSync("npm", [ + "pack", "--ignore-scripts", "--json", "--pack-destination", tmp, + ], { cwd: root, encoding: "utf8" })) + execFileSync("tar", ["xzf", path.join(tmp, packed[0].filename), "-C", tmp]) + const packageRoot = path.join(tmp, "package") + for (const platform of ["linux-x64", "darwin-arm64"]) { + const cli = path.join(packageRoot, "prebuilds", platform, "doltlite") + assert.notEqual(fs.statSync(cli).mode & 0o111, 0, `${platform} CLI is not executable`) + } + const { DatabaseSync, binPath } = require(packageRoot) + const db = new DatabaseSync(":memory:") + try { + assert.equal(db.prepare("SELECT doltlite_engine() AS engine").get().engine, "prolly") + db.exec("CREATE TABLE t(id INTEGER PRIMARY KEY); INSERT INTO t VALUES(1)") + assert.equal(db.prepare("SELECT count(*) AS n FROM t").get().n, 1) + } finally { + db.close() + } + assert.equal(execFileSync(binPath(), [":memory:", "SELECT doltlite_engine();"], { + encoding: "utf8", + }).trim(), "prolly") + console.log("npm archive: native addon and executable bundled CLIs PASS") +} finally { + fs.rmSync(tmp, { recursive: true, force: true }) +}