mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 13:46:16 -08:00
fix(website): pin downloads to the published release
This commit is contained in:
1 parent
112672b970
commit
d42c931400
7 files changed
+67
-33
No files matched your search
+1
-1
@@ -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)
|
||||
|
||||
|
||||

|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"version": "0.24.0"
|
||||
}
|
||||
Vendored
+2
-6
@@ -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;
|
||||
@@ -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}.`);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user