diff --git a/scripts/build-date-plugin.js b/scripts/build-date-plugin.js
new file mode 100644
index 00000000..b516871b
--- /dev/null
+++ b/scripts/build-date-plugin.js
@@ -0,0 +1,40 @@
+/**
+ * Vite plugin: writes the date of the build into the card.
+ */
+
+// Strict length requirement: See below.
+export const BUILD_DATE_PLACEHOLDER = '__BUILD_DATE_SENTINEL___';
+
+/**
+ * The date cannot be handed to the bundler as a substituted name, because the
+ * build configuration is evaluated once and `vite build --watch` reuses it.
+ * As such, every rebuild would report the time the watcher started rather than
+ * the actual build time. Output is generated afresh for each build, so the date
+ * is written here instead.
+ *
+ * @type {() => import('vite').Plugin}
+ */
+export const buildDate = () => ({
+ name: 'build-date',
+
+ renderChunk(code) {
+ if (!code.includes(BUILD_DATE_PLACEHOLDER)) {
+ return null;
+ }
+
+ const date = new Date().toISOString();
+ if (date.length !== BUILD_DATE_PLACEHOLDER.length) {
+ this.error(
+ 'The build date must be exactly as long as the placeholder it replaces.',
+ );
+ }
+
+ return {
+ code: code.replaceAll(BUILD_DATE_PLACEHOLDER, date),
+
+ // The replacement is exactly as long as what it replaces, so the existing
+ // sourcemap still describes the code.
+ map: null,
+ };
+ },
+});
diff --git a/scripts/build-defines.js b/scripts/build-defines.js
index 71091d55..dd01b06e 100644
--- a/scripts/build-defines.js
+++ b/scripts/build-defines.js
@@ -1,6 +1,8 @@
import { execFileSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
+import { BUILD_DATE_PLACEHOLDER } from './build-date-plugin.js';
+
/**
* Asks git something or gives back nothing when it cannot be asked.
*/
@@ -23,7 +25,11 @@ const getPackageVersion = () =>
*
* `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.
+ * a development build the commit it was made from. Its presence is what makes a
+ * build a released one.
+ *
+ * The build date is a placeholder here, written in for by the `buildDate`
+ * plugin as each build's output is generated.
*
* The values are JSON so that a bundler can drop them in as written.
*/
@@ -35,10 +41,11 @@ export const getBuildDefines = ({ dev, releaseVersion }) => {
__ADVANCED_CAMERA_CARD_RELEASE_VERSION__: JSON.stringify(
releaseVersion ?? (dev ? developmentVersion : getPackageVersion()),
),
+ __ADVANCED_CAMERA_CARD_IS_RELEASE_BUILD__: JSON.stringify(!!releaseVersion),
__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()),
+ __ADVANCED_CAMERA_CARD_BUILD_DATE__: JSON.stringify(BUILD_DATE_PLACEHOLDER),
};
};
diff --git a/src/components/loading.ts b/src/components/loading.ts
index f9d76681..30aa811d 100644
--- a/src/components/loading.ts
+++ b/src/components/loading.ts
@@ -9,7 +9,8 @@ import { customElement, property } from 'lit/decorators.js';
import loadingStyle from '../scss/loading.scss?inline';
import type { EffectName, EffectsManagerInterface } from '../types';
-import { getReleaseVersion } from '../utils/build-info.js';
+import { formatDateAndTime } from '../utils/basic.js';
+import { getReleaseVersion, getUnreleasedBuildDate } from '../utils/build-info.js';
import './icon';
@@ -80,10 +81,14 @@ export class AdvancedCameraCardLoading extends LitElement {
}
protected render(): TemplateResult {
+ const buildDate = getUnreleasedBuildDate();
+
return html`${getReleaseVersion()}`;
+ >${getReleaseVersion()} ${buildDate
+ ? html`${formatDateAndTime(buildDate, true)}`
+ : ''}`;
}
static get styles(): CSSResultGroup {
diff --git a/src/scss/loading.scss b/src/scss/loading.scss
index d1077f50..5ff3e7be 100644
--- a/src/scss/loading.scss
+++ b/src/scss/loading.scss
@@ -53,3 +53,8 @@ advanced-camera-card-icon {
span {
font-size: x-large;
}
+
+.build-date {
+ font-size: medium;
+ opacity: 0.7;
+}
diff --git a/src/utils/build-info.ts b/src/utils/build-info.ts
index 92eb6067..5794721a 100644
--- a/src/utils/build-info.ts
+++ b/src/utils/build-info.ts
@@ -1,4 +1,5 @@
declare const __ADVANCED_CAMERA_CARD_RELEASE_VERSION__: string | undefined;
+declare const __ADVANCED_CAMERA_CARD_IS_RELEASE_BUILD__: boolean | 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;
@@ -25,6 +26,14 @@ const BUILD_DATE =
typeof __ADVANCED_CAMERA_CARD_BUILD_DATE__ === 'undefined'
? undefined
: __ADVANCED_CAMERA_CARD_BUILD_DATE__;
+
+const IS_RELEASE_BUILD =
+ typeof __ADVANCED_CAMERA_CARD_IS_RELEASE_BUILD__ === 'undefined'
+ ? false
+ : __ADVANCED_CAMERA_CARD_IS_RELEASE_BUILD__;
+
+const UNRELEASED_BUILD_DATE =
+ !IS_RELEASE_BUILD && BUILD_DATE ? new Date(BUILD_DATE) : null;
/* v8 ignore stop -- @preserve */
export interface GitInfo {
@@ -34,6 +43,8 @@ export interface GitInfo {
}
export const getReleaseVersion = (): string => RELEASE_VERSION;
+export const getUnreleasedBuildDate = (): Date | null => UNRELEASED_BUILD_DATE;
+
export const getGitInfo = (): GitInfo => ({
hash: GIT_HASH,
commitDate: GIT_DATE,
diff --git a/tests/utils/build-info.test.ts b/tests/utils/build-info.test.ts
index 3d258fc9..13fae50d 100644
--- a/tests/utils/build-info.test.ts
+++ b/tests/utils/build-info.test.ts
@@ -1,6 +1,10 @@
import { describe, expect, it } from 'vitest';
-import { getGitInfo, getReleaseVersion } from '../../src/utils/build-info';
+import {
+ getGitInfo,
+ getReleaseVersion,
+ getUnreleasedBuildDate,
+} from '../../src/utils/build-info';
// As these are running as tests, the won't be build substitutes so this only
// tests default/fallback values.
@@ -16,3 +20,9 @@ describe('getGitInfo', () => {
expect(getGitInfo()).toEqual({});
});
});
+
+describe('getUnreleasedBuildDate', () => {
+ it('should report no build date for an unbuilt card', () => {
+ expect(getUnreleasedBuildDate()).toBeNull();
+ });
+});
diff --git a/vite.config.ts b/vite.config.ts
index a6757582..1afbf6a0 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,6 +1,7 @@
import { visualizer } from 'rollup-plugin-visualizer';
import { defineConfig } from 'vite';
+import { buildDate } from './scripts/build-date-plugin.js';
import { getBuildDefines } from './scripts/build-defines.js';
import { cleanDist } from './scripts/clean-dist-plugin.js';
import { facadeEntry } from './scripts/facade-entry-plugin.js';
@@ -24,6 +25,7 @@ export default defineConfig(({ mode }) => {
plugins: [
cleanDist(),
svgPath(),
+ buildDate(),
facadeEntry({ publicFileNames: PUBLIC_ENTRIES }),
visualizer({ filename: 'visualizations/treemap.html', template: 'treemap' }),
],