chore(release): begin 0.25 development and publish 0.24 article (#1674)

This commit is contained in:
4gray authored and GitHub committed 2026-09-26 17:13:13 +02:00
1 parent 512e9787a8
commit 0e4e1d2169
13 files changed
+102 -56

No files matched your search

+6 -5
View File
@@ -73,11 +73,12 @@ the complete 27-asset set documented in `docs/architecture/release-pipeline.md`.
It is read-only, and fails on an already-published release. Still review the
authored text and generated commits by eye.
After verification, manually publish the GitHub release. That publication
automatically verifies its Snap assets and uploads them to `edge`.
Installed-Snap smoke and candidate/stable promotion remain manual. Keep the
blog draft during artifact verification; publish it in a follow-up commit and
verify the website deployment.
Manually publish the release; this verifies and uploads Snaps
to `edge`. Installed-Snap smoke and candidate/stable promotion stay manual.
After public-asset verification, publish the draft blog and update
`apps/website/released-version.json` to the published version together.
Follow the release pipeline's offline-download checks and verify deployment;
never use the development/nightly version for this pin.
## Failure Safety
+6 -5
View File
@@ -73,11 +73,12 @@ the complete 27-asset set documented in `docs/architecture/release-pipeline.md`.
It is read-only, and fails on an already-published release. Still review the
authored text and generated commits by eye.
After verification, manually publish the GitHub release. That publication
automatically verifies its Snap assets and uploads them to `edge`.
Installed-Snap smoke and candidate/stable promotion remain manual. Keep the
blog draft during artifact verification; publish it in a follow-up commit and
verify the website deployment.
Manually publish the release; this verifies and uploads Snaps
to `edge`. Installed-Snap smoke and candidate/stable promotion stay manual.
After public-asset verification, publish the draft blog and update
`apps/website/released-version.json` to the published version together.
Follow the release pipeline's offline-download checks and verify deployment;
never use the development/nightly version for this pin.
## Failure Safety
+6 -2
View File
@@ -11,7 +11,7 @@ this marker — see `.changes/README.md`.
<!-- next-release -->
# [0.24.0](https://github.com/4gray/iptvnator/compare/v0.23.0...v0.24.0) (2026-09-21)
# [0.24.0](https://github.com/4gray/iptvnator/compare/v0.23.0...v0.24.0) (2026-09-24)
![IPTVnator 0.24.0 — programme guide, fullscreen browsing and stream info](https://raw.githubusercontent.com/4gray/iptvnator/v0.24.0/apps/website/public/blog/v0-24/announce.png)
@@ -40,7 +40,11 @@ Before updating, back up your playlists and important data. Desktop startup appl
## Thanks
Thank you to [@Bpl5966](https://github.com/Bpl5966) for MPV reconnects and extra options ([#1515](https://github.com/4gray/iptvnator/pull/1515)), [@mark-jardine](https://github.com/mark-jardine) for the EPG time offset ([#1489](https://github.com/4gray/iptvnator/pull/1489)), and [@larsemig](https://github.com/larsemig) for stream information ([#1578](https://github.com/4gray/iptvnator/pull/1578)). A warm thank-you to everyone who reported issues, tested builds, helped in the [Telegram community](https://t.me/iptvnator), or supported development. 💙
Thank you to [@Bpl5966](https://github.com/Bpl5966) for MPV reconnects and extra options ([#1515](https://github.com/4gray/iptvnator/pull/1515)), [@mark-jardine](https://github.com/mark-jardine) for the EPG time offset ([#1489](https://github.com/4gray/iptvnator/pull/1489)), and [@larsemig](https://github.com/larsemig) for stream information ([#1578](https://github.com/4gray/iptvnator/pull/1578)).
Thank you also to [@thejdubb02](https://github.com/thejdubb02) for the original zoom-persistence, Turkish-search and Xtream EPG-refresh fixes ([#1613](https://github.com/4gray/iptvnator/pull/1613), [#1612](https://github.com/4gray/iptvnator/pull/1612), [#1610](https://github.com/4gray/iptvnator/pull/1610)). This work was incorporated and extended in [#1617](https://github.com/4gray/iptvnator/pull/1617), [#1640](https://github.com/4gray/iptvnator/pull/1640) and [#1647](https://github.com/4gray/iptvnator/pull/1647), with his co-authorship preserved.
A warm thank-you to everyone who reported issues, tested builds, helped in the [Telegram community](https://t.me/iptvnator), or supported development. 💙
If IPTVnator is useful to you, you can help keep it moving through [GitHub Sponsors](https://github.com/sponsors/4gray) or [Ko-fi](https://ko-fi.com/4gray).
+1 -4
View File
@@ -26,10 +26,7 @@
<link rel="icon" type="image/x-icon" href="assets/icons/favicon.ico" />
<script src="assets/app-config.js" defer></script>
<style>
/* Inline splash — paints immediately while the JS bundle and the
styles.css (~300KB) are still downloading and Angular is
bootstrapping. Removed from the DOM by main.ts after
bootstrapApplication() resolves. No assets, zero extra HTTP. */
/* Asset-free splash while bundles load; main.ts removes it after bootstrap. */
#initial-splash {
position: fixed;
inset: 0;
+9 -6
View File
@@ -74,11 +74,12 @@ resolves it at build time:
options whose file is missing are dropped, sizes and the publish date come
from the API. `deploy-website.yml` passes `GITHUB_TOKEN` to the build so
the call is authenticated.
2. Fallback: the root `package.json` version with the asset naming pattern
from `electron-builder.json`. This is deterministic but cannot prove the
files exist yet (a version bump lands on `master` before the release is
published), so a warning is printed. Set `WEBSITE_SKIP_RELEASE_FETCH=1` to
force it for offline or reproducible builds.
2. Fallback: the published version pinned in `released-version.json` with
the asset naming pattern from `electron-builder.json`. Advance this pin
only after that GitHub release is public and its assets are verified, in
the follow-up commit that publishes the article. It must stay independent
of the root `package.json` development/nightly version. Set
`WEBSITE_SKIP_RELEASE_FETCH=1` to force it for offline or reproducible builds.
Both paths produce the same page structure. Adding an artifact means adding a
`DownloadOption` (matcher + fallback name) in `downloads.ts`; the pages and the
@@ -88,7 +89,9 @@ resolved version.
`pnpm nx test website` builds the site and runs
`tools/testing/website-download-pages.test.mjs`, which checks titles,
canonicals, direct asset links, JSON-LD, cross-links and sitemap entries
without depending on a specific version.
without depending on a specific version. Resolver regression tests use the real
Astro build constants and cover offline builds, rate limits and timeouts,
ensuring each platform keeps links to the pinned published release.
Two of the suites drive the built site in a real browser:
`tools/testing/website-screenshot-showcase.test.mjs` (the home page channel
+3 -10
View File
@@ -4,16 +4,9 @@ import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
import mdx from '@astrojs/mdx';
/**
* Repository version for the download pages' offline fallback
* (`src/lib/downloads.ts`). Read from the workspace root through the file
* system and injected as a build-time constant: importing the root
* `package.json` from inside the project trips the Nx module-boundaries rule
* ("external resources cannot be imported using a relative path"), and a
* value fixed at build time is what the fallback needs anyway.
*/
/** Published version for offline downloads; independent of the nightly base. */
const { version } = JSON.parse(
readFileSync(new URL('../../package.json', import.meta.url), 'utf8'),
readFileSync(new URL('./released-version.json', import.meta.url), 'utf8'),
);
// Tailwind (v3) is applied via apps/website/postcss.config.mjs — the
@@ -26,7 +19,7 @@ export default defineConfig({
integrations: [sitemap(), mdx()],
vite: {
define: {
__IPTVNATOR_VERSION__: JSON.stringify(version),
__IPTVNATOR_RELEASED_VERSION__: JSON.stringify(version),
},
},
});
+3
View File
@@ -0,0 +1,3 @@
{
"version": "0.24.0"
}
@@ -2,12 +2,12 @@
title: v0.24 - Release Notes
description: A rebuilt programme guide, channels and episodes within reach in fullscreen, and live stream information — plus more dependable sources, playback and upgrades.
featured: true
pubDate: 2026-09-21
pubDate: 2026-09-24
author: 4gray
heroImage: /iptvnator/blog/v0-24/announce.png
tags:
- release
draft: true
draft: false
---
import BlogImageSlider from '../../components/blog/BlogImageSlider.astro';
@@ -24,7 +24,7 @@ There is plenty around those three changes: local XMLTV files, catch-up download
<ReleaseMeta
version="v0.24.0"
releaseDate="September 21, 2026"
releaseDate="September 24, 2026"
channels={['Desktop', 'PWA']}
/>
@@ -106,7 +106,7 @@ The player overlay now shows stream information: resolution, playback and source
- **AppManager discovery.** AppImages include the repository metadata AppManager needs to find and download complete updates. ([#1559](https://github.com/4gray/iptvnator/pull/1559))
- **Review inactive sources together.** Desktop users can select sources to remove, retain individual entries and see the result for each deletion. ([#1596](https://github.com/4gray/iptvnator/pull/1596))
- **Check source availability.** Desktop playlist lists show background availability checks for Stalker portals and M3U links alongside Xtream accounts. ([#1592](https://github.com/4gray/iptvnator/pull/1592))
- **Try the nightly channel.** Settings → About offers builds from master. Stable remains the default; returning from nightly waits for a newer stable release. ([#1608](https://github.com/4gray/iptvnator/pull/1608))
- **Try the nightly channel.** Settings → About offers builds from master, also available in the [nightly repository](https://github.com/4gray/iptvnator-nightly/releases). Stable remains the default; returning from nightly waits for a newer stable release. ([#1608](https://github.com/4gray/iptvnator/pull/1608))
## Other changes
@@ -235,7 +235,11 @@ Please back up your playlists and important data before updating. Desktop startu
## Thanks
Thank you to [@Bpl5966](https://github.com/Bpl5966) for embedded MPV reconnects and extra options ([#1515](https://github.com/4gray/iptvnator/pull/1515)), [@mark-jardine](https://github.com/mark-jardine) for the EPG time offset ([#1489](https://github.com/4gray/iptvnator/pull/1489)), and [@larsemig](https://github.com/larsemig) for stream information ([#1578](https://github.com/4gray/iptvnator/pull/1578)). Welcome to Bpl5966 and mark-jardine, whose first contributions are included in this release.
Thank you to [@Bpl5966](https://github.com/Bpl5966) for embedded MPV reconnects and extra options ([#1515](https://github.com/4gray/iptvnator/pull/1515)), [@mark-jardine](https://github.com/mark-jardine) for the EPG time offset ([#1489](https://github.com/4gray/iptvnator/pull/1489)), and [@larsemig](https://github.com/larsemig) for stream information ([#1578](https://github.com/4gray/iptvnator/pull/1578)).
Thank you also to [@thejdubb02](https://github.com/thejdubb02) for the original zoom-persistence, Turkish-search and Xtream EPG-refresh fixes ([#1613](https://github.com/4gray/iptvnator/pull/1613), [#1612](https://github.com/4gray/iptvnator/pull/1612), [#1610](https://github.com/4gray/iptvnator/pull/1610)). This work was incorporated and extended in [#1617](https://github.com/4gray/iptvnator/pull/1617), [#1640](https://github.com/4gray/iptvnator/pull/1640) and [#1647](https://github.com/4gray/iptvnator/pull/1647), with his co-authorship preserved.
Welcome to Bpl5966, mark-jardine and thejdubb02, whose first contributions are included in this release.
A warm thank-you to everyone who reported issues, tested builds and helped others in the [Telegram community](https://t.me/iptvnator), and to the sponsors and donors who make time for this project possible. 💙 If IPTVnator is useful to you, you can support its development through [GitHub Sponsors](https://github.com/sponsors/4gray) or [Ko-fi](https://ko-fi.com/4gray).
+2 -6
View File
@@ -1,8 +1,4 @@
/// <reference types="astro/client" />
/**
* Repository version from the workspace root `package.json`, injected at
* build time by `astro.config.mjs` through Vite `define`. Used by the download
* pages' offline fallback (`src/lib/downloads.ts`).
*/
declare const __IPTVNATOR_VERSION__: string;
/** Published release from released-version.json, injected by astro.config.mjs. */
declare const __IPTVNATOR_RELEASED_VERSION__: string;
+9 -9
View File
@@ -7,13 +7,13 @@ import type { DownloadPlatform } from './platforms';
* *published* release so that every direct link points at an asset that
* exists, and so file sizes and the publish date can be shown. When the API
* is unreachable (offline build, rate limit, `WEBSITE_SKIP_RELEASE_FETCH=1`)
* the pages fall back to the repository version from the root `package.json`
* — injected as `__IPTVNATOR_VERSION__` by `astro.config.mjs`, since a
* relative import of the root manifest violates the Nx module boundaries —
* and the known asset naming pattern from `electron-builder.json`.
* the pages fall back to the published version in `released-version.json`,
* injected as `__IPTVNATOR_RELEASED_VERSION__` by `astro.config.mjs`, and the
* known asset naming pattern from `electron-builder.json`. The development
* version in the root package manifest must never select download assets.
*/
const FALLBACK_VERSION = __IPTVNATOR_VERSION__;
const FALLBACK_VERSION = __IPTVNATOR_RELEASED_VERSION__;
export const GITHUB_REPO = '4gray/iptvnator';
export const REPO_URL = `https://github.com/${GITHUB_REPO}`;
@@ -38,8 +38,8 @@ export interface ResolvedRelease {
url: string;
publishedAt: Date | null;
assets: ReleaseAsset[];
/** Where the data came from; the fallback cannot promise that the files exist yet. */
source: 'github-api' | 'package-json';
/** Whether the API or the pinned published version supplied the release. */
source: 'github-api' | 'published-version';
}
export interface DownloadOption {
@@ -321,10 +321,10 @@ function fallbackRelease(): ResolvedRelease {
url: `${RELEASES_URL}/tag/${tag}`,
publishedAt: null,
assets,
source: 'package-json',
source: 'published-version',
};
}
function warnFallback(reason: string): void {
console.warn(`[website] Latest release lookup failed (${reason}); using package.json version ${FALLBACK_VERSION}.`);
console.warn(`[website] Latest release lookup failed (${reason}); using pinned published version ${FALLBACK_VERSION}.`);
}
+7 -2
View File
@@ -436,8 +436,13 @@ Publishing the GitHub release is manual. That publication automatically
verifies its Snap assets and uploads them to `edge`; installed-Snap smoke and
candidate/stable promotion remain manual (see
`tools/packaging/validate-snap-release-boundary.mjs`). Keep the blog post a
draft during artifact verification, then publish it in a follow-up commit and
verify the website deployment.
draft during artifact verification. After the release is public and its assets
are verified, publish the blog and advance
`apps/website/released-version.json` to that published version in the same
follow-up commit. Run `WEBSITE_SKIP_RELEASE_FETCH=1 pnpm nx test website --skip-nx-cache`,
compare the generated download links with the public release assets, and
verify the website deployment. The fallback pin must never follow the
development/nightly version in the root `package.json`.
If a Store upload fails after publication, run `publish-snap.yaml` from
`master` with its `tag` input set to the existing public stable tag, for example
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "iptvnator",
"version": "0.24.0",
"version": "0.25.0",
"engines": {
"node": "^22.22.3 || ^24.15.0"
},
+40 -1
View File
@@ -1,15 +1,19 @@
import { readFile } from 'node:fs/promises';
import { test } from 'node:test';
import assert from 'node:assert/strict';
import vm from 'node:vm';
import ts from 'typescript';
import astroConfig from '../../apps/website/astro.config.mjs';
/**
* Structural checks for the per-OS download pages in the built website.
* The build resolves the latest release from the GitHub API and falls back to
* package.json, so assertions accept any semver version but insist on direct
* the pinned published version, so assertions accept any semver but insist on direct
* asset links, canonical URLs, structured data and internal linking.
*/
const distRoot = new URL('../../dist/apps/website/', import.meta.url);
const { version: publishedVersion } = JSON.parse(await readFile(new URL('../../apps/website/released-version.json', import.meta.url), 'utf8'));
const SITE = 'https://4gray.github.io/iptvnator';
const readDist = (relativePath) => readFile(new URL(relativePath, distRoot), 'utf8');
@@ -152,3 +156,38 @@ test('sitemap lists the download pages', async () => {
assert.match(sitemap, new RegExp(`<loc>${SITE}/${path}</loc>`));
}
});
// Use the real Astro defines so a development-version bump cannot silently
// change the offline download URLs. The API is the only mocked boundary.
for (const failure of ['offline', 'rate-limit', 'timeout']) {
test(`release lookup ${failure}: downloads stay on the published release`, async () => {
const source = await readFile(new URL('../../apps/website/src/lib/downloads.ts', import.meta.url), 'utf8');
const code = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS } }).outputText;
const exports = {};
let fetchCalls = 0;
const warnings = [];
vm.runInNewContext(code, {
exports,
...Object.fromEntries(Object.entries(astroConfig.vite.define).map(([name, value]) => [name, JSON.parse(value)])),
process: { env: { WEBSITE_SKIP_RELEASE_FETCH: failure === 'offline' ? '1' : '0' } },
AbortSignal,
console: { warn(message) { warnings.push(message); } },
fetch: async () => {
fetchCalls++;
if (failure === 'timeout') throw new Error('The operation timed out');
return { ok: false, status: 403 };
},
});
const release = await exports.getLatestRelease();
assert.equal(release.version, publishedVersion, 'The published release is independent of the upcoming development version');
assert.equal(fetchCalls, failure === 'offline' ? 0 : 1);
assert.equal(warnings.length, failure === 'offline' ? 0 : 1);
for (const platform of ['windows', 'macos', 'linux']) {
const downloads = exports.resolveDownloads(release, platform);
assert.ok(downloads.length > 0);
for (const download of downloads) {
assert.ok(download.url.includes(`/releases/download/v${publishedVersion}/iptvnator-${publishedVersion}-`), download.url);
}
}
});
}