From 35f1b0ebb91912c3a38b735d2d845e3b0739e8f0 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Thu, 3 Sep 2026 19:26:51 -0400 Subject: [PATCH] fix(release): validate minified telemetry and prune target natives first --- config/electron-builder.config.cjs | 17 +++++++---------- .../telemetry-bundle-constant-patterns.mjs | 8 ++++++-- ...telemetry-bundle-constant-patterns.test.mjs | 11 ++++++++++- config/scripts/verify-telemetry-constants.mjs | 18 ++++++++++++++---- 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/config/electron-builder.config.cjs b/config/electron-builder.config.cjs index 06d41bad344..c478741a32e 100644 --- a/config/electron-builder.config.cjs +++ b/config/electron-builder.config.cjs @@ -266,16 +266,6 @@ module.exports = { } }, afterPack: async (context) => { - // Why: a Linux runner-image glibc bump silently shipped a node-pty pty.node - // requiring GLIBC_2.34, crashing the app on startup on Ubuntu 20.04 (#9902). - // Fail packaging if any bundled native binary exceeds the supported floor. - if (context.electronPlatformName === 'linux') { - // Why the arch is passed: symbol-version checks pass happily on a wrong-architecture binary, - // so a cross-built slice could ship the host's pty.node and only fail at runtime. - verifyLinuxGlibcFloor(context.appOutDir, { - targetArch: { 1: 'x64', 3: 'arm64' }[context.arch] - }) - } const resourcesDir = context.electronPlatformName === 'darwin' ? join( @@ -313,6 +303,13 @@ module.exports = { } stampPackagedCliVersion(resourcesDir, context.packager.appInfo.version) prunePackagedRuntimeNodeModules(resourcesDir, context.electronPlatformName, context.arch) + // Prune optional native variants before checking the glibc/architecture + // floor; cross-builds intentionally install all optional packages. + if (context.electronPlatformName === 'linux') { + verifyLinuxGlibcFloor(context.appOutDir, { + targetArch: { 1: 'x64', 3: 'arm64' }[context.arch] + }) + } verifyPackagedMainRuntimeDeps(resourcesDir) // Why: boot the packaged daemon-entry under plain Node, but only for the // slice matching the packaging host's arch — daemon-entry.js is JS, yet it diff --git a/config/scripts/telemetry-bundle-constant-patterns.mjs b/config/scripts/telemetry-bundle-constant-patterns.mjs index 04944b7b156..3dd64425d6f 100644 --- a/config/scripts/telemetry-bundle-constant-patterns.mjs +++ b/config/scripts/telemetry-bundle-constant-patterns.mjs @@ -1,2 +1,6 @@ -export const BUILD_IDENTITY_RE = /\b(?:const|let|var)\s+BUILD_IDENTITY\s*=\s*"(rc|stable)"/ -export const WRITE_KEY_RE = /\b(?:const|let|var)\s+WRITE_KEY\s*=\s*"(phc_[A-Za-z0-9_-]+)"/ +// The unminified bundle keeps these names. Production minification may rename +// them, but the injected identity and key remain adjacent in the declaration. +export const BUILD_IDENTITY_RE = /\b(?:const|let|var)\s+BUILD_IDENTITY\s*=\s*["`](rc|stable)["`]/ +export const WRITE_KEY_RE = /\b(?:const|let|var)\s+WRITE_KEY\s*=\s*["`](phc_[A-Za-z0-9_-]+)["`]/ +export const MINIFIED_TELEMETRY_RE = + /\b(?:const|let|var)\s+[$\w]+\s*=\s*["`](rc|stable)["`]\s*,\s*[$\w]+\s*=\s*["`](phc_[A-Za-z0-9_-]+)["`]/ diff --git a/config/scripts/telemetry-bundle-constant-patterns.test.mjs b/config/scripts/telemetry-bundle-constant-patterns.test.mjs index b8df5ec3d0f..ca87e3ee971 100644 --- a/config/scripts/telemetry-bundle-constant-patterns.test.mjs +++ b/config/scripts/telemetry-bundle-constant-patterns.test.mjs @@ -1,5 +1,9 @@ import { describe, expect, it } from 'vitest' -import { BUILD_IDENTITY_RE, WRITE_KEY_RE } from './telemetry-bundle-constant-patterns.mjs' +import { + BUILD_IDENTITY_RE, + MINIFIED_TELEMETRY_RE, + WRITE_KEY_RE +} from './telemetry-bundle-constant-patterns.mjs' describe('telemetry bundle constant patterns', () => { it.each(['const', 'let', 'var'])('accepts %s declarations', (declaration) => { @@ -13,4 +17,9 @@ describe('telemetry bundle constant patterns', () => { expect('const WRITE_KEY = null').not.toMatch(WRITE_KEY_RE) expect('const WRITE_KEY = "example-key"').not.toMatch(WRITE_KEY_RE) }) + + it('accepts minified adjacent declarations', () => { + const bundle = 'var dde=`stable`,fde=`phc_example-key_123`,pde=(dde===`stable`)' + expect(bundle).toMatch(MINIFIED_TELEMETRY_RE) + }) }) diff --git a/config/scripts/verify-telemetry-constants.mjs b/config/scripts/verify-telemetry-constants.mjs index 3bdcd8b3ec6..836a3e4546f 100644 --- a/config/scripts/verify-telemetry-constants.mjs +++ b/config/scripts/verify-telemetry-constants.mjs @@ -38,7 +38,11 @@ import { join, resolve } from 'node:path' // `node_modules`). If electron-builder ever drops it, promote this to a // direct devDependency in package.json. import { extractFile, listPackage } from '@electron/asar' -import { BUILD_IDENTITY_RE, WRITE_KEY_RE } from './telemetry-bundle-constant-patterns.mjs' +import { + BUILD_IDENTITY_RE, + MINIFIED_TELEMETRY_RE, + WRITE_KEY_RE +} from './telemetry-bundle-constant-patterns.mjs' // Why resolve from import.meta.url instead of cwd: a release runner (or a // developer debugging locally) may invoke this script from a non-root cwd. @@ -156,8 +160,14 @@ function verifyAsar(asarPath) { const buildIdentityMatch = BUILD_IDENTITY_RE.exec(indexJs) const writeKeyMatch = WRITE_KEY_RE.exec(indexJs) + const minifiedTelemetryMatch = MINIFIED_TELEMETRY_RE.exec(indexJs) - if (!buildIdentityMatch) { + // Rolldown renames module-local constants in production output. In that + // form, verify the adjacent injected identity/key declaration instead. + const verifiedIdentity = buildIdentityMatch?.[1] ?? minifiedTelemetryMatch?.[1] + const verifiedWriteKey = writeKeyMatch?.[1] ?? minifiedTelemetryMatch?.[2] + + if (!verifiedIdentity) { console.error(`::error::BUILD_IDENTITY constant missing or unexpected value in ${asarPath}`) const sample = indexJs.match(/.{0,80}BUILD_IDENTITY.{0,80}/g)?.slice(0, 5) ?? [] for (const line of sample) { @@ -165,7 +175,7 @@ function verifyAsar(asarPath) { } return null } - if (!writeKeyMatch) { + if (!verifiedWriteKey) { console.error(`::error::PostHog WRITE_KEY missing from ${asarPath}`) const sample = indexJs.match(/.{0,80}WRITE_KEY.{0,80}/g)?.slice(0, 5) ?? [] for (const line of sample) { @@ -174,7 +184,7 @@ function verifyAsar(asarPath) { return null } - return { asarPath, buildIdentity: buildIdentityMatch[1], writeKey: writeKeyMatch[1] } + return { asarPath, buildIdentity: verifiedIdentity, writeKey: verifiedWriteKey } } // Why verify every match (not just the first): macOS dual-arch produces one