Compare commits

...

2 Commits

Author SHA1 Message Date
diegosouzapw
3f3cb0392a fix(release): make the packaged-app smoke open the database before asserting the SQLite driver
The packaged app opens SQLite lazily and the smoke's readiness URL (/login) never
touches it, so the logs carried no "[DB] Driver: ..." line and
assertNativeDriverSelected failed on the v3.8.50 re-attach (run 33251755872) with
every leg green up to that point — the assertion could not distinguish a native
driver from no database at all. After readiness the smoke now requests
/api/monitoring/health (DB-backed) and waits up to 15 s for the driver line; the
cold-restart assertion is unchanged. Unit tests cover the wait and the timeout.
2026-08-29 09:32:57 -03:00
diegosouzapw
767d846a44 fix(release): drop the build_ref input — a dispatch builds the ref it is dispatched on
Same as the release/v3.8.51 twin (#12022): CodeQL flags an input-controlled checkout
next to setup-node's npm cache on the default branch as cache poisoning
(actions/cache-poisoning/poisonable-step), and it tracks the taint through any job
output. The ref is not an input any more; checkouts use github.ref, so
`gh workflow run electron-release.yml --ref v3.8.50 -f version=v3.8.50` rebuilds the
tag and `--ref main` builds the repaired line (which is how the v3.8.50 assets were
rebuilt). The tag-push path is unchanged.
2026-08-29 09:12:42 -03:00
3 changed files with 84 additions and 15 deletions

View File

@@ -4,6 +4,10 @@ on:
push:
tags:
- "v*"
# A dispatch builds the ref it is dispatched ON (`gh workflow run … --ref v3.8.50` rebuilds
# that tag; `--ref main` builds the repaired line). The ref is deliberately NOT an input:
# CodeQL flags an input-controlled checkout next to the npm cache on the default branch as
# cache poisoning (actions/cache-poisoning/poisonable-step), and `github.ref` is trusted.
workflow_dispatch:
inputs:
version:
@@ -15,11 +19,6 @@ on:
required: false
default: true
type: boolean
build_ref:
description: "Git ref to BUILD from (default: the version tag). Set to a branch when the tag itself cannot build — e.g. a lockfile that was already broken when it was cut — and the assets must come from the repaired line"
required: false
default: ""
type: string
# Least-privilege default: read-only at the top level; each job grants the writes it
# needs (build/release upload assets, publish-npm forwards npm provenance / packages
@@ -86,9 +85,6 @@ jobs:
- uses: actions/checkout@v7
with:
persist-credentials: false
# workflow_dispatch: build the tag being (re)built, not the dispatching branch. On a
# tag push this resolves to the same commit.
ref: ${{ inputs.build_ref || needs.validate.outputs.version }}
- name: Setup Node
uses: actions/setup-node@v7
with:
@@ -174,9 +170,6 @@ jobs:
- uses: actions/checkout@v7
with:
persist-credentials: false
# workflow_dispatch: build the tag being (re)built, not the dispatching branch. On a
# tag push this resolves to the same commit.
ref: ${{ inputs.build_ref || needs.validate.outputs.version }}
- name: Setup Node
uses: actions/setup-node@v7
with:
@@ -363,8 +356,6 @@ jobs:
with:
persist-credentials: false
fetch-depth: 0
# Source archives + SBOM come from the tag being released, not the dispatching branch.
ref: ${{ inputs.build_ref || needs.validate.outputs.version }}
# `merge-multiple` is deliberately OFF. It resolves same-name collisions by ARRIVAL
# ORDER, and the two macOS jobs each emit their own `latest-mac.yml` listing only their

View File

@@ -123,6 +123,46 @@ function discoverPackagedExecutable() {
throw new Error(`Packaged Electron smoke check does not support ${platform()}.`);
}
/**
* The packaged app opens SQLite lazily: `/login` (the readiness URL) never touches the
* database, so a smoke that only waits for readiness sees no "[DB] Driver: ..." line at
* all and `assertNativeDriverSelected` cannot tell a native driver from nothing (v3.8.50
* re-attach, run 33251755872: every leg green up to the smoke, then this). After readiness
* the smoke now requests a DB-backed endpoint and waits for the driver line to appear.
*/
export const DB_TOUCH_PATH = "/api/monitoring/health";
const DB_DRIVER_LINE_PATTERN = /\[DB\] Driver: /;
export async function waitForDriverLine(getLogs, { timeoutMs = 15_000, pollMs = 250 } = {}) {
const startedAt = Date.now();
while (Date.now() - startedAt < timeoutMs) {
const logs = getLogs();
assertNoFatalLogs(logs);
if (DB_DRIVER_LINE_PATTERN.test(logs)) return logs;
await sleep(pollMs);
}
throw new Error(
`Packaged Electron app logged no "[DB] Driver: ..." line within ${timeoutMs}ms of touching ` +
`${DB_TOUCH_PATH} — the database never opened, so the SQLite driver cannot be verified.`
);
}
async function openDatabaseForSmoke({ logs, smokeUrl }) {
const touchUrl = new URL(DB_TOUCH_PATH, smokeUrl).toString();
try {
const response = await fetchWithTimeout(touchUrl, 5_000);
console.log(
`[electron-smoke] touched ${touchUrl} (HTTP ${response.status}) to open the database`
);
} catch (error) {
console.log(
`[electron-smoke] touching ${touchUrl} failed (${error instanceof Error ? error.message : String(error)}) — waiting for the driver line anyway`
);
}
await waitForDriverLine(() => logs.value);
console.log("[electron-smoke] database opened — driver line captured");
}
async function fetchWithTimeout(url, timeoutMs) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
@@ -477,6 +517,7 @@ async function waitForReady({ logs, smokeUrl, timeoutMs, settleMs, exitState })
if (response.status === 200) {
assertNoFatalLogs(logs.value);
console.log(`[electron-smoke] ready: ${smokeUrl} returned HTTP 200`);
await openDatabaseForSmoke({ logs, smokeUrl });
await settleAfterReady({
getExitState: () => ({ exitCode: exitState.exitCode, signalCode: exitState.signalCode }),
logs,
@@ -506,7 +547,14 @@ async function waitForReady({ logs, smokeUrl, timeoutMs, settleMs, exitState })
* by the single-launch path and the cold-restart (two-launch) path so both
* exercise identical spawn/readiness/shutdown behavior.
*/
async function launchAndCollectLogs({ appExecutable, smokeUrl, dataDir, timeoutMs, settleMs, streamLogs }) {
async function launchAndCollectLogs({
appExecutable,
smokeUrl,
dataDir,
timeoutMs,
settleMs,
streamLogs,
}) {
const smokeEnv = buildSmokeEnv({ dataDir });
await assertPortIsFree(smokeUrl);
await ensureSmokeEnvDirs(smokeEnv, dataDir);
@@ -568,7 +616,14 @@ async function main() {
!process.env.ELECTRON_SMOKE_DATA_DIR && process.env.ELECTRON_SMOKE_KEEP_DATA !== "1";
try {
await launchAndCollectLogs({ appExecutable, smokeUrl, dataDir, timeoutMs, settleMs, streamLogs });
await launchAndCollectLogs({
appExecutable,
smokeUrl,
dataDir,
timeoutMs,
settleMs,
streamLogs,
});
if (!coldRestart) return;

View File

@@ -7,6 +7,8 @@ import {
FATAL_LOG_PATTERNS,
LINUX_EXECUTABLE_NAMES,
stopApp,
waitForDriverLine,
DB_TOUCH_PATH,
} from "../../scripts/dev/smoke-electron-packaged.mjs";
test("electron smoke discovers the default Linux executable name", () => {
@@ -97,3 +99,24 @@ test("electron smoke flags startup logs missing any driver selection line", () =
/no '\[DB\] Driver: \.\.\.' line/
);
});
test("electron smoke waits for the [DB] Driver line after touching a DB-backed endpoint", async () => {
assert.equal(DB_TOUCH_PATH, "/api/monitoring/health");
let logs = "[electron] [Server] [STARTUP] ready\n";
setTimeout(() => {
logs += "[electron] [Server] [DB] Driver: better-sqlite3 | file: /tmp/x/storage.sqlite\n";
}, 60);
const seen = await waitForDriverLine(() => logs, { timeoutMs: 2_000, pollMs: 20 });
assert.match(seen, /\[DB\] Driver: better-sqlite3/);
});
test("electron smoke fails clearly when the database never opens", async () => {
await assert.rejects(
() =>
waitForDriverLine(() => "[electron] [Server] [STARTUP] ready\n", {
timeoutMs: 120,
pollMs: 20,
}),
/logged no "\[DB\] Driver: \.\.\." line within 120ms/
);
});