From 06bcfd58ba9cb5ebf424290bd4f746f3021082d1 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Mon, 3 Aug 2026 22:36:32 -0700 Subject: [PATCH] refactor: Stamp build information in as "defines" (#2654) --- package.json | 1 - rollup.config.js | 17 ++--------- scripts/build-defines.js | 44 +++++++++++++++++++++++++++ scripts/release-version-plugin.js | 25 ---------------- scripts/release-version.js | 12 -------- src/card.ts | 2 +- src/components/loading.ts | 2 +- src/utils/build-info.ts | 41 +++++++++++++++++++++++++ src/utils/diagnostics.ts | 26 ++++------------ tests/utils/build-info.test.ts | 18 +++++++++++ tests/utils/diagnostics.test.ts | 40 +++++++++++-------------- vitest.browser.config.ts | 3 +- yarn.lock | 50 +------------------------------ 13 files changed, 133 insertions(+), 148 deletions(-) create mode 100644 scripts/build-defines.js delete mode 100644 scripts/release-version-plugin.js delete mode 100644 scripts/release-version.js create mode 100644 src/utils/build-info.ts create mode 100644 tests/utils/build-info.test.ts diff --git a/package.json b/package.json index 6dd3c413..24e0f3b0 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,6 @@ "playwright": "1.62.0", "prettier": "^3.3.2", "rollup": "^3.29.4", - "rollup-plugin-git-info": "^1.0.0", "rollup-plugin-serve": "^1.1.1", "rollup-plugin-styler": "^1.8.0", "rollup-plugin-visualizer": "^5.12.0", diff --git a/rollup.config.js b/rollup.config.js index 9dd57a5d..adf789fd 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -5,13 +5,12 @@ import { nodeResolve } from '@rollup/plugin-node-resolve'; import replace from '@rollup/plugin-replace'; import terser from '@rollup/plugin-terser'; import typescript from '@rollup/plugin-typescript'; -import gitInfo from 'rollup-plugin-git-info'; import serve from 'rollup-plugin-serve'; import styles from 'rollup-plugin-styler'; import { visualizer } from 'rollup-plugin-visualizer'; +import { getBuildDefines } from './scripts/build-defines.js'; import { cleanDist } from './scripts/clean-dist-plugin.js'; -import { RELEASE_VERSION_TOKEN } from './scripts/release-version.js'; import { svgPath } from './scripts/svg-path-plugin.js'; const watch = process.env.ROLLUP_WATCH === 'true' || process.env.ROLLUP_WATCH === '1'; @@ -39,16 +38,6 @@ const serveopts = { */ const plugins = [ cleanDist(), - gitInfo.default( - // Limit git-info to the project's own package.json. Without this it also - // rewrites any dependency's imported package.json into ESM, which then breaks - // the json() plugin downstream (ha-nunjucks imports its own package.json). - { - enableBuildDate: true, - updateVersion: false, - include: 'package.json', - }, - ), styles({ modules: false, // Behavior of inject mode, without actually injecting style @@ -72,12 +61,12 @@ const plugins = [ inlineSources: dev, exclude: ['dist/**', 'tests/**/*.test.ts'], }), - json({ exclude: 'package.json' }), + json(), replace({ preventAssignment: true, values: { 'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production'), - [RELEASE_VERSION_TOKEN]: process.env.RELEASE_VERSION ?? (dev ? 'dev' : 'pkg'), + ...getBuildDefines({ dev, releaseVersion: process.env.RELEASE_VERSION }), }, }), serveEnabled && serve(serveopts), diff --git a/scripts/build-defines.js b/scripts/build-defines.js new file mode 100644 index 00000000..71091d55 --- /dev/null +++ b/scripts/build-defines.js @@ -0,0 +1,44 @@ +import { execFileSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; + +/** + * Asks git something or gives back nothing when it cannot be asked. + */ +const askGit = (...args) => { + try { + return execFileSync('git', args, { + encoding: 'utf-8', + stdio: ['ignore', 'pipe', 'ignore'], + }).trim(); + } catch { + return ''; + } +}; + +const getPackageVersion = () => + JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8')).version; + +/** + * What the build stamps into the card, as names for the bundler to substitute. + * + * `releaseVersion` is the version being released, which only the release + * workflow knows; a build without one reports the version in `package.json`, or + * a development build the commit it was made from. + * + * The values are JSON so that a bundler can drop them in as written. + */ +export const getBuildDefines = ({ dev, releaseVersion }) => { + const gitHash = askGit('rev-parse', '--short', 'HEAD'); + const developmentVersion = gitHash ? `dev+${gitHash}` : 'dev'; + + return { + __ADVANCED_CAMERA_CARD_RELEASE_VERSION__: JSON.stringify( + releaseVersion ?? (dev ? developmentVersion : getPackageVersion()), + ), + __ADVANCED_CAMERA_CARD_GIT_HASH__: JSON.stringify(gitHash), + __ADVANCED_CAMERA_CARD_GIT_DATE__: JSON.stringify( + askGit('log', '-1', '--format=%cI'), + ), + __ADVANCED_CAMERA_CARD_BUILD_DATE__: JSON.stringify(new Date().toISOString()), + }; +}; diff --git a/scripts/release-version-plugin.js b/scripts/release-version-plugin.js deleted file mode 100644 index cbaa1f4a..00000000 --- a/scripts/release-version-plugin.js +++ /dev/null @@ -1,25 +0,0 @@ -import { RELEASE_VERSION_TOKEN } from './release-version.js'; - -// What the card substitutes to mean "the version in package.json". The -// development substitution is not used here because it appends a git hash that -// only a build step knows. -const PACKAGE_VERSION = 'pkg'; - -/** - * Substitutes the release version the way the build does. - * - * The card reads it out of a string literal that Rollup rewrites, so without - * this it renders the placeholder itself: the loading screen shows the raw - * token, which then appears in every failure screenshot. - */ -export const releaseVersion = () => ({ - name: 'release-version', - - transform(code, id) { - if (!id.includes('src/utils/diagnostics.ts')) { - return null; - } - - return { code: code.replace(RELEASE_VERSION_TOKEN, PACKAGE_VERSION), map: null }; - }, -}); diff --git a/scripts/release-version.js b/scripts/release-version.js deleted file mode 100644 index 4b63b24c..00000000 --- a/scripts/release-version.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * The literal the card carries in place of its version, for the build to - * rewrite. - * - * `getReleaseVersion` cannot import this: it has to sit in that source as a - * plain string for the build to have something to replace. It lives in its own - * module because the build, the browser tests and a unit test all have to agree - * on it, and none of them should have to depend on either of the others. - * - * It must be in a JS file as Node's loader cannot import TypeScript. - */ -export const RELEASE_VERSION_TOKEN = '__ADVANCED_CAMERA_CARD_RELEASE_VERSION__'; diff --git a/src/card.ts b/src/card.ts index 7898d790..e1eca4f5 100644 --- a/src/card.ts +++ b/src/card.ts @@ -65,7 +65,7 @@ import { localize } from './localize/localize.js'; import cardStyle from './scss/card.scss'; import type { MediaLoadedInfoEventDetail } from './types.js'; import { hasAction } from './utils/action.js'; -import { getReleaseVersion } from './utils/diagnostics'; +import { getReleaseVersion } from './utils/build-info.js'; // *************************************************************************** // General Card-Wide Notes diff --git a/src/components/loading.ts b/src/components/loading.ts index 75ecda9e..86b0c6f5 100644 --- a/src/components/loading.ts +++ b/src/components/loading.ts @@ -9,7 +9,7 @@ import { customElement, property } from 'lit/decorators.js'; import loadingStyle from '../scss/loading.scss'; import type { EffectName, EffectsManagerInterface } from '../types'; -import { getReleaseVersion } from '../utils/diagnostics'; +import { getReleaseVersion } from '../utils/build-info.js'; import './icon'; diff --git a/src/utils/build-info.ts b/src/utils/build-info.ts new file mode 100644 index 00000000..92eb6067 --- /dev/null +++ b/src/utils/build-info.ts @@ -0,0 +1,41 @@ +declare const __ADVANCED_CAMERA_CARD_RELEASE_VERSION__: string | undefined; +declare const __ADVANCED_CAMERA_CARD_GIT_HASH__: string | undefined; +declare const __ADVANCED_CAMERA_CARD_GIT_DATE__: string | undefined; +declare const __ADVANCED_CAMERA_CARD_BUILD_DATE__: string | undefined; + +const DEVELOPMENT_VERSION = 'dev'; + +/* v8 ignore start: substituted by the build -- @preserve */ +const RELEASE_VERSION = + typeof __ADVANCED_CAMERA_CARD_RELEASE_VERSION__ === 'undefined' + ? DEVELOPMENT_VERSION + : __ADVANCED_CAMERA_CARD_RELEASE_VERSION__; + +const GIT_HASH = + typeof __ADVANCED_CAMERA_CARD_GIT_HASH__ === 'undefined' + ? undefined + : __ADVANCED_CAMERA_CARD_GIT_HASH__; + +const GIT_DATE = + typeof __ADVANCED_CAMERA_CARD_GIT_DATE__ === 'undefined' + ? undefined + : __ADVANCED_CAMERA_CARD_GIT_DATE__; + +const BUILD_DATE = + typeof __ADVANCED_CAMERA_CARD_BUILD_DATE__ === 'undefined' + ? undefined + : __ADVANCED_CAMERA_CARD_BUILD_DATE__; +/* v8 ignore stop -- @preserve */ + +export interface GitInfo { + hash?: string; + commitDate?: string; + buildDate?: string; +} + +export const getReleaseVersion = (): string => RELEASE_VERSION; +export const getGitInfo = (): GitInfo => ({ + hash: GIT_HASH, + commitDate: GIT_DATE, + buildDate: BUILD_DATE, +}); diff --git a/src/utils/diagnostics.ts b/src/utils/diagnostics.ts index f0672e68..1f7b87a1 100644 --- a/src/utils/diagnostics.ts +++ b/src/utils/diagnostics.ts @@ -1,4 +1,3 @@ -import pkg from '../../package.json'; import type { IssueKey, IssuePresence } from '../card-controller/issues/types'; import type { RawAdvancedCameraCardConfig } from '../config/types'; import { getIntegrationManifest } from '../ha/integration'; @@ -7,6 +6,7 @@ import type { DeviceRegistryManager } from '../ha/registry/device'; import type { HomeAssistant } from '../ha/types'; import { HASS_WEB_PROXY_DOMAIN } from '../ha/web-proxy'; import { getLanguage } from '../localize/localize'; +import { getGitInfo, getReleaseVersion } from './build-info'; type FrigateDevices = Record; @@ -21,22 +21,6 @@ interface IntegrationDiagnostics { version?: string; } -export const getReleaseVersion = (): string => { - const releaseVersion: string = '__ADVANCED_CAMERA_CARD_RELEASE_VERSION__'; - - /* v8 ignore if: depends on rollup substitution -- @preserve */ - if (releaseVersion === 'pkg') { - return pkg.version; - } - - /* v8 ignore if: depends on rollup substitution -- @preserve */ - if (releaseVersion === 'dev') { - return `dev+${pkg['gitAbbrevHash']}`; - } - - return releaseVersion; -}; - interface Diagnostics { card_version: string; browser: string; @@ -103,6 +87,8 @@ export const getDiagnostics = async ( }); }); + const gitInfo = getGitInfo(); + return { card_version: getReleaseVersion(), browser: navigator.userAgent, @@ -110,9 +96,9 @@ export const getDiagnostics = async ( lang: getLanguage(), timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, git: { - ...(pkg['gitAbbrevHash'] && { hash: pkg['gitAbbrevHash'] }), - ...(pkg['buildDate'] && { build_date: pkg['buildDate'] }), - ...(pkg['gitDate'] && { commit_date: pkg['gitDate'] }), + ...(gitInfo.hash && { hash: gitInfo.hash }), + ...(gitInfo.buildDate && { build_date: gitInfo.buildDate }), + ...(gitInfo.commitDate && { commit_date: gitInfo.commitDate }), }, ...(hass && { ha_version: hass.config.version }), custom_integrations: { diff --git a/tests/utils/build-info.test.ts b/tests/utils/build-info.test.ts new file mode 100644 index 00000000..3d258fc9 --- /dev/null +++ b/tests/utils/build-info.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest'; + +import { getGitInfo, getReleaseVersion } from '../../src/utils/build-info'; + +// As these are running as tests, the won't be build substitutes so this only +// tests default/fallback values. + +describe('getReleaseVersion', () => { + it('should report an unbuilt card as a development one', () => { + expect(getReleaseVersion()).toBe('dev'); + }); +}); + +describe('getGitInfo', () => { + it('should report nothing about an unbuilt card', () => { + expect(getGitInfo()).toEqual({}); + }); +}); diff --git a/tests/utils/diagnostics.test.ts b/tests/utils/diagnostics.test.ts index dca8633f..6259260a 100644 --- a/tests/utils/diagnostics.test.ts +++ b/tests/utils/diagnostics.test.ts @@ -2,31 +2,25 @@ import type { HassConfig } from 'home-assistant-js-websocket'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { mock } from 'vitest-mock-extended'; -import { RELEASE_VERSION_TOKEN } from '../../scripts/release-version.js'; import type { DeviceRegistryManager } from '../../src/ha/registry/device'; import { homeAssistantWSRequest } from '../../src/ha/ws-request'; import { getLanguage } from '../../src/localize/localize'; -import { getDiagnostics, getReleaseVersion } from '../../src/utils/diagnostics.js'; +import { getDiagnostics } from '../../src/utils/diagnostics.js'; import { createHASS, createRegistryDevice } from '../test-utils'; -vi.mock('../../package.json', () => ({ - default: { - gitAbbrevHash: 'g4cf13b1', - buildDate: 'Tue, 19 Sep 2023 04:59:27 GMT', - gitDate: 'Wed, 6 Sep 2023 21:27:28 -0700', - }, +vi.mock('../../src/utils/build-info.js', () => ({ + getReleaseVersion: () => '1.2.3', + getGitInfo: () => ({ + hash: 'g4cf13b1', + buildDate: '2023-09-19T04:59:27.000Z', + commitDate: '2023-09-06T21:27:28-07:00', + }), })); vi.mock('../../src/ha'); vi.mock('../../src/localize/localize.js'); vi.mock('../../src/ha/registry/device/index.js'); vi.mock('../../src/ha/ws-request.js'); -describe('getReleaseVersion', () => { - it('should get release version', () => { - expect(getReleaseVersion()).toBe(RELEASE_VERSION_TOKEN); - }); -}); - describe('getDiagnostics', () => { const now = new Date('2023-10-01T21:53Z'); const hass = createHASS(); @@ -78,13 +72,13 @@ describe('getDiagnostics', () => { }), ).toEqual({ browser: 'AdvancedCameraCardTest/1.0', - card_version: '__ADVANCED_CAMERA_CARD_RELEASE_VERSION__', + card_version: '1.2.3', config: { cameras: [{ camera_entity: 'camera.office' }], }, git: { - build_date: 'Tue, 19 Sep 2023 04:59:27 GMT', - commit_date: 'Wed, 6 Sep 2023 21:27:28 -0700', + build_date: '2023-09-19T04:59:27.000Z', + commit_date: '2023-09-06T21:27:28-07:00', hash: 'g4cf13b1', }, custom_integrations: { @@ -133,10 +127,10 @@ describe('getDiagnostics', () => { it('should fetch diagnostics without hass or config', async () => { expect(await getDiagnostics()).toEqual({ browser: 'AdvancedCameraCardTest/1.0', - card_version: '__ADVANCED_CAMERA_CARD_RELEASE_VERSION__', + card_version: '1.2.3', git: { - build_date: 'Tue, 19 Sep 2023 04:59:27 GMT', - commit_date: 'Wed, 6 Sep 2023 21:27:28 -0700', + build_date: '2023-09-19T04:59:27.000Z', + commit_date: '2023-09-06T21:27:28-07:00', hash: 'g4cf13b1', }, custom_integrations: { @@ -185,10 +179,10 @@ describe('getDiagnostics', () => { expect(await getDiagnostics(hass, deviceRegistryManager)).toEqual({ browser: 'AdvancedCameraCardTest/1.0', - card_version: '__ADVANCED_CAMERA_CARD_RELEASE_VERSION__', + card_version: '1.2.3', git: { - build_date: 'Tue, 19 Sep 2023 04:59:27 GMT', - commit_date: 'Wed, 6 Sep 2023 21:27:28 -0700', + build_date: '2023-09-19T04:59:27.000Z', + commit_date: '2023-09-06T21:27:28-07:00', hash: 'g4cf13b1', }, custom_integrations: { diff --git a/vitest.browser.config.ts b/vitest.browser.config.ts index 8e2eec56..ac58f078 100644 --- a/vitest.browser.config.ts +++ b/vitest.browser.config.ts @@ -2,7 +2,6 @@ import { playwright } from '@vitest/browser-playwright'; import { defineConfig } from 'vitest/config'; import { getBrowsers, type Browser } from './scripts/browsers.js'; -import { releaseVersion } from './scripts/release-version-plugin.js'; import { scssString } from './scripts/scss-string-plugin.js'; import { svgPath } from './scripts/svg-path-plugin.js'; @@ -14,7 +13,7 @@ export default defineConfig({ // supply the same asset shapes the build's plugins do: an SVG becomes the // `{ path, viewBox }` a custom iconset serves, SCSS the string `unsafeCSS` // takes. `svgPath` is the build's own plugin, reused unchanged. - plugins: [releaseVersion(), scssString(), svgPath()], + plugins: [scssString(), svgPath()], // Where the Mock Service Worker script is served from, which Vite serves at // the root of the page. Named rather than left at its default of `public/` in diff --git a/yarn.lock b/yarn.lock index 6e45e364..65097e94 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1610,17 +1610,6 @@ __metadata: languageName: node linkType: hard -"@rollup/plugin-json@npm:^4.1.0": - version: 4.1.0 - resolution: "@rollup/plugin-json@npm:4.1.0" - dependencies: - "@rollup/pluginutils": "npm:^3.0.8" - peerDependencies: - rollup: ^1.20.0 || ^2.0.0 - checksum: 10c0/9fc4a3ee60929afcb5269ebda602914d1cf5dc020808f85be90c0a5a2ba9ca26136b0284a1935984861f0549a1e1db30fc372906c14425f5da4909f0fd21e5ea - languageName: node - linkType: hard - "@rollup/plugin-json@npm:^6.1.0": version: 6.1.0 resolution: "@rollup/plugin-json@npm:6.1.0" @@ -1704,19 +1693,6 @@ __metadata: languageName: node linkType: hard -"@rollup/pluginutils@npm:^3.0.8": - version: 3.1.0 - resolution: "@rollup/pluginutils@npm:3.1.0" - dependencies: - "@types/estree": "npm:0.0.39" - estree-walker: "npm:^1.0.1" - picomatch: "npm:^2.2.2" - peerDependencies: - rollup: ^1.20.0||^2.0.0 - checksum: 10c0/7151753160d15ba2b259461a6c25b3932150994ea52dba8fd3144f634c7647c2e56733d986e2c15de67c4d96a9ee7d6278efa6d2e626a7169898fd64adc0f90c - languageName: node - linkType: hard - "@rollup/pluginutils@npm:^5.0.0, @rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.1.0": version: 5.1.0 resolution: "@rollup/pluginutils@npm:5.1.0" @@ -2009,13 +1985,6 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:0.0.39": - version: 0.0.39 - resolution: "@types/estree@npm:0.0.39" - checksum: 10c0/f0af6c95ac1988c4827964bd9d3b51d24da442e2188943f6dfcb1e1559103d5d024d564b2e9d3f84c53714a02a0a7435c7441138eb63d9af5de4dfc66cdc0d92 - languageName: node - linkType: hard - "@types/estree@npm:^1.0.6": version: 1.0.7 resolution: "@types/estree@npm:1.0.7" @@ -2547,7 +2516,6 @@ __metadata: propagating-hammerjs: "npm:^2.0.1" quick-lru: "npm:^6.1.2" rollup: "npm:^3.29.4" - rollup-plugin-git-info: "npm:^1.0.0" rollup-plugin-serve: "npm:^1.1.1" rollup-plugin-styler: "npm:^1.8.0" rollup-plugin-visualizer: "npm:^5.12.0" @@ -4415,13 +4383,6 @@ __metadata: languageName: node linkType: hard -"estree-walker@npm:^1.0.1": - version: 1.0.1 - resolution: "estree-walker@npm:1.0.1" - checksum: 10c0/fa9e5f8c1bbe8d01e314c0f03067b64a4f22d4c58410fc5237060d0c15b81e58c23921c41acc60abbdab490f1fdfcbd6408ede2d03ca704454272e0244d61a55 - languageName: node - linkType: hard - "estree-walker@npm:^2.0.2": version: 2.0.2 resolution: "estree-walker@npm:2.0.2" @@ -8221,7 +8182,7 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.2, picomatch@npm:^2.3.1": +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be @@ -9188,15 +9149,6 @@ __metadata: languageName: node linkType: hard -"rollup-plugin-git-info@npm:^1.0.0": - version: 1.0.0 - resolution: "rollup-plugin-git-info@npm:1.0.0" - dependencies: - "@rollup/plugin-json": "npm:^4.1.0" - checksum: 10c0/d0d6d20d47da9abf6452a76932066d2fb831a33a1f5618f7bc876ad1a290abdcfe5065c761a256f13c433fd98bc6264cfb096cad08d348684399521f6d637724 - languageName: node - linkType: hard - "rollup-plugin-serve@npm:^1.1.1": version: 1.1.1 resolution: "rollup-plugin-serve@npm:1.1.1"