mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 10:06:15 -08:00
feat(about): show build commit next to the app version
Settings > About now renders "<version> (<short-sha>)" with the full SHA in the tooltip, so bug reports from test and nightly builds identify the exact commit. The commit is injected at CI build time into apps/web/src/environments/build-commit.ts (same placeholder pattern as the TMDB key inject); PR builds use the real head SHA instead of the ephemeral merge commit. Local/dev builds keep the plain version. The semver version itself deliberately stays untouched: a "-sha" suffix would flip electron-updater into prerelease mode and leak into installer/artifact version fields. Requested by WolfganP in #1202. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2f8aee72df
commit
2546bb9bfd
9 files changed
+155
-2
No files matched your search
@@ -396,6 +396,14 @@ jobs:
|
||||
TMDB_API_KEY: ${{ secrets.TMDB_API_KEY }}
|
||||
run: node tools/tmdb/inject-tmdb-key.mjs
|
||||
|
||||
- name: Inject build commit
|
||||
# Shows "<version> (<sha>)" in Settings > About so bug reports
|
||||
# from test builds identify the exact commit. PR builds use the
|
||||
# head SHA — github.sha would be the ephemeral merge commit.
|
||||
env:
|
||||
BUILD_COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
run: node tools/build/inject-build-commit.mjs
|
||||
|
||||
- name: Build frontend
|
||||
run: pnpm nx build web --skip-nx-cache
|
||||
|
||||
|
||||
@@ -866,6 +866,9 @@ Build configurations in `apps/web/project.json`:
|
||||
**Factory Pattern Implementation**:
|
||||
The factory pattern ensures a single codebase works in both environments without conditional checks scattered throughout the application. All environment-specific logic is encapsulated in the service implementations.
|
||||
|
||||
**Build Commit In About**:
|
||||
CI injects the git commit into `apps/web/src/environments/build-commit.ts` via `tools/build/inject-build-commit.mjs` (same placeholder pattern as the TMDB key inject); `Settings > About` then shows `"<version> (<short-sha>)"`. The semver version itself deliberately stays untouched — a `-sha` suffix would flip electron-updater into prerelease mode and leak into installer/artifact version fields. Local/dev builds keep the placeholder empty and show the plain version.
|
||||
|
||||
### Testing Strategy
|
||||
|
||||
- **Unit tests**: Jest with `jest-preset-angular` and `ng-mocks`
|
||||
|
||||
@@ -19,7 +19,17 @@
|
||||
<p>{{ 'SETTINGS.VERSION_DESCRIPTION' | translate }}</p>
|
||||
</div>
|
||||
<div class="setting-item__control version-block">
|
||||
<span>{{ version() }}</span>
|
||||
<span data-test-id="app-version">
|
||||
{{ version() }}
|
||||
@if (buildCommitShort(); as commit) {
|
||||
<span
|
||||
class="build-commit"
|
||||
data-test-id="app-build-commit"
|
||||
[title]="buildCommit()"
|
||||
>({{ commit }})</span
|
||||
>
|
||||
}
|
||||
</span>
|
||||
<small>{{ updateMessage() }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -132,3 +132,60 @@ describe('SettingsAboutSectionComponent app updates', () => {
|
||||
expect(getButton(fixture, 'app-update-open-release')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('SettingsAboutSectionComponent version display', () => {
|
||||
let fixture: ComponentFixture<SettingsAboutSectionComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SettingsAboutSectionComponent,
|
||||
TranslateModule.forRoot(),
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SettingsAboutSectionComponent);
|
||||
fixture.componentRef.setInput('activeSection', 'about');
|
||||
fixture.componentRef.setInput('version', '0.23.0');
|
||||
});
|
||||
|
||||
function getVersionBlock() {
|
||||
return fixture.nativeElement.querySelector(
|
||||
'[data-test-id="app-version"]'
|
||||
) as HTMLElement;
|
||||
}
|
||||
|
||||
function getCommitMarker() {
|
||||
return fixture.nativeElement.querySelector(
|
||||
'[data-test-id="app-build-commit"]'
|
||||
) as HTMLElement | null;
|
||||
}
|
||||
|
||||
it('shows the shortened build commit next to the version on CI builds', () => {
|
||||
fixture.componentRef.setInput(
|
||||
'buildCommit',
|
||||
'949ea520aa11bb22cc33dd44ee55ff6677889900'
|
||||
);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getVersionBlock().textContent).toContain('0.23.0');
|
||||
expect(getCommitMarker()?.textContent).toBe('(949ea52)');
|
||||
expect(getCommitMarker()?.getAttribute('title')).toBe(
|
||||
'949ea520aa11bb22cc33dd44ee55ff6677889900'
|
||||
);
|
||||
});
|
||||
|
||||
it('shows the plain version when no build commit was injected', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getVersionBlock().textContent).toContain('0.23.0');
|
||||
expect(getCommitMarker()).toBeNull();
|
||||
});
|
||||
|
||||
it('treats a whitespace-only commit as absent', () => {
|
||||
fixture.componentRef.setInput('buildCommit', ' ');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getCommitMarker()).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -18,15 +18,25 @@ import {
|
||||
imports: [MatButtonModule, MatIconModule, TranslateModule],
|
||||
templateUrl: './settings-about-section.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
styles: [':host { display: contents; }'],
|
||||
styles: [
|
||||
':host { display: contents; }',
|
||||
'.version-block .build-commit { opacity: 0.65; font-size: 0.85em; }',
|
||||
],
|
||||
})
|
||||
export class SettingsAboutSectionComponent {
|
||||
readonly activeSection = input.required<string>();
|
||||
readonly isDesktop = input(false);
|
||||
readonly version = input<string | undefined>();
|
||||
readonly buildCommit = input<string | undefined>();
|
||||
readonly updateMessage = input<string | undefined>();
|
||||
readonly appUpdateStatus = input<ElectronBridgeAppUpdateStatus | null>(null);
|
||||
|
||||
readonly buildCommitShort = computed(() => {
|
||||
const commit = this.buildCommit()?.trim();
|
||||
|
||||
return commit ? commit.slice(0, 7) : undefined;
|
||||
});
|
||||
|
||||
readonly checkForAppUpdate = output<void>();
|
||||
readonly downloadAppUpdate = output<void>();
|
||||
readonly installAppUpdate = output<void>();
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
[activeSection]="activeSection()"
|
||||
[isDesktop]="isDesktop"
|
||||
[version]="version"
|
||||
[buildCommit]="buildCommit"
|
||||
[updateMessage]="updateMessage"
|
||||
[appUpdateStatus]="appUpdateStatus()"
|
||||
(checkForAppUpdate)="checkForAppUpdate()"
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
selectIsEpgAvailable,
|
||||
} from '@iptvnator/m3u-state';
|
||||
import { take } from 'rxjs';
|
||||
import { BUILD_COMMIT } from '../../environments/build-commit';
|
||||
import { DataService, RuntimeCapabilitiesService } from '@iptvnator/services';
|
||||
import {
|
||||
EmbeddedMpvSupport,
|
||||
@@ -200,6 +201,9 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
/** Current version of the app */
|
||||
version = '';
|
||||
|
||||
/** Git commit the app was built from (CI builds only) */
|
||||
readonly buildCommit = BUILD_COMMIT;
|
||||
|
||||
/** Update message to show */
|
||||
updateMessage = '';
|
||||
readonly appUpdateStatus = signal<ElectronBridgeAppUpdateStatus | null>(
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Git commit the app was built from. Populated by
|
||||
* tools/build/inject-build-commit.mjs during CI builds; stays empty for
|
||||
* local/dev builds, in which case Settings > About shows the plain version.
|
||||
*
|
||||
* Intentionally separate from AppConfig.version: appending the SHA to the
|
||||
* semver itself would flip electron-updater into prerelease mode and leak
|
||||
* into installer/artifact version fields.
|
||||
*/
|
||||
export const BUILD_COMMIT = '';
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Injects the git commit SHA from the BUILD_COMMIT environment variable into
|
||||
* the frontend BUILD_COMMIT constant before a CI build.
|
||||
*
|
||||
* The commit is shown next to the app version in Settings > About so bug
|
||||
* reports from test and nightly builds identify the exact commit. The semver
|
||||
* version itself intentionally stays untouched: a `-sha` suffix would flip
|
||||
* electron-updater into prerelease mode and leak into installer/artifact
|
||||
* version fields. Without BUILD_COMMIT the script is a no-op — local and dev
|
||||
* builds show the plain version.
|
||||
*
|
||||
* Usage (CI, before `nx build web`):
|
||||
* BUILD_COMMIT=<git-sha> node tools/build/inject-build-commit.mjs
|
||||
*/
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
|
||||
const CONFIG_PATH = 'apps/web/src/environments/build-commit.ts';
|
||||
const MARKER = "export const BUILD_COMMIT = '';";
|
||||
|
||||
const commit = (process.env.BUILD_COMMIT ?? '').trim().toLowerCase();
|
||||
|
||||
if (!commit) {
|
||||
console.warn(
|
||||
'BUILD_COMMIT is not set — Settings > About will show the plain version.'
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!/^[0-9a-f]{7,40}$/.test(commit)) {
|
||||
console.error(
|
||||
'BUILD_COMMIT must be a 7-40 character hex git SHA; aborting.'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const source = readFileSync(CONFIG_PATH, 'utf8');
|
||||
|
||||
if (!source.includes(MARKER)) {
|
||||
console.error(
|
||||
`Expected placeholder not found in ${CONFIG_PATH}. ` +
|
||||
'Update tools/build/inject-build-commit.mjs if the constant moved.'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
writeFileSync(
|
||||
CONFIG_PATH,
|
||||
source.replace(MARKER, `export const BUILD_COMMIT = '${commit}';`)
|
||||
);
|
||||
console.log(`Injected build commit ${commit} into ${CONFIG_PATH}.`);
|
||||
Reference in new issue
Block a user