mirror of
https://github.com/zoriya/v10.git
synced 2026-08-04 21:27:10 +00:00
feat: add a CDN media-availability manifest and read it across install surfaces (#1737)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,7 @@ site/src/content/generated-feature-reference/
|
||||
site/src/content/generated-media-reference/
|
||||
site/src/content/generated-preset-reference/
|
||||
site/src/content/ejected-skins.json
|
||||
site/src/content/cdn-media.json
|
||||
packages/cli/docs/
|
||||
packages/html/docs/
|
||||
packages/react/docs/
|
||||
|
||||
@@ -5,7 +5,13 @@ import type { Framework } from '../utils/config.js';
|
||||
import { getConfigValue } from '../utils/config.js';
|
||||
import { docExistsInAnyFramework, readBundledDoc, readLlmsTxt } from '../utils/docs.js';
|
||||
import { formatInstallationCode } from '../utils/format.js';
|
||||
import { mapRawSkin, type PartialInstallFlags, promptFramework, promptInstallOptions } from '../utils/prompts.js';
|
||||
import {
|
||||
mapRawSkin,
|
||||
type PartialInstallFlags,
|
||||
promptFramework,
|
||||
promptInstallOptions,
|
||||
supportsCdnInstall,
|
||||
} from '../utils/prompts.js';
|
||||
import { replaceMarker, stripOmitMarkers } from '../utils/replace.js';
|
||||
|
||||
interface ParsedFlags {
|
||||
@@ -177,6 +183,14 @@ export async function handleDocs(flags: ParsedFlags, positionals: string[]): Pro
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// The interactive prompt hides CDN for renderers without a CDN build; guard
|
||||
// the non-interactive flag path so a `--install-method cdn` request for one
|
||||
// can't emit a broken snippet.
|
||||
if (opts.installMethod === 'cdn' && !supportsCdnInstall(opts.renderer)) {
|
||||
console.error('Error: this source type has no CDN build. Install it with npm, pnpm, yarn, or bun.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const generated = formatInstallationCode(opts);
|
||||
const output = stripOmitMarkers(replaceMarker(markdown, 'installation', generated));
|
||||
printVersionHeader();
|
||||
|
||||
Vendored
+12
@@ -59,3 +59,15 @@ declare module '@/utils/installation/detect-renderer' {
|
||||
|
||||
export function detectRenderer(url: string, useCase: UseCase): DetectionResult | null;
|
||||
}
|
||||
|
||||
declare module '@/utils/installation/cdn-code' {
|
||||
import type { Renderer, Skin, UseCase } from '@/utils/installation/types';
|
||||
|
||||
export function generateCdnCode(useCase: UseCase, skin: Skin, renderer: Renderer): string;
|
||||
export function rendererSupportsCdn(renderer: Renderer, cdnMediaSubpaths: readonly string[]): boolean;
|
||||
}
|
||||
|
||||
declare module '@/content/cdn-media.json' {
|
||||
const entries: Array<{ id: string }>;
|
||||
export default entries;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import * as p from '@clack/prompts';
|
||||
import cdnMedia from '@/content/cdn-media.json';
|
||||
import { rendererSupportsCdn } from '@/utils/installation/cdn-code';
|
||||
import type { InstallationOptions } from '@/utils/installation/codegen';
|
||||
import { detectRenderer } from '@/utils/installation/detect-renderer';
|
||||
import type { InstallMethod, Renderer, Skin, UseCase } from '@/utils/installation/types';
|
||||
import { VALID_RENDERERS } from '@/utils/installation/types';
|
||||
import type { Framework } from './config.js';
|
||||
|
||||
const CDN_MEDIA_SUBPATHS = cdnMedia.map((entry) => entry.id);
|
||||
|
||||
export function supportsCdnInstall(renderer: Renderer): boolean {
|
||||
return rendererSupportsCdn(renderer, CDN_MEDIA_SUBPATHS);
|
||||
}
|
||||
|
||||
export async function promptFramework(): Promise<Framework> {
|
||||
const value = await p.select({
|
||||
message: 'Which framework?',
|
||||
@@ -50,14 +58,19 @@ function skinOptionsForUseCase(useCase: UseCase): Array<{ value: Skin; label: st
|
||||
];
|
||||
}
|
||||
|
||||
function installMethodOptions(framework: Framework): Array<{ value: InstallMethod; label: string }> {
|
||||
function installMethodOptions(
|
||||
framework: Framework,
|
||||
renderer: Renderer
|
||||
): Array<{ value: InstallMethod; label: string }> {
|
||||
const options: Array<{ value: InstallMethod; label: string }> = [
|
||||
{ value: 'npm', label: 'npm' },
|
||||
{ value: 'pnpm', label: 'pnpm' },
|
||||
{ value: 'yarn', label: 'yarn' },
|
||||
{ value: 'bun', label: 'bun' },
|
||||
];
|
||||
if (framework === 'html') {
|
||||
// CDN is HTML-only, and only when the renderer ships a CDN build — matching
|
||||
// the install page, which hides the CDN tab for renderers without one.
|
||||
if (framework === 'html' && supportsCdnInstall(renderer)) {
|
||||
options.unshift({ value: 'cdn', label: 'CDN' });
|
||||
}
|
||||
return options;
|
||||
@@ -154,7 +167,7 @@ export async function promptInstallOptions(
|
||||
(await (async () => {
|
||||
const value = await p.select({
|
||||
message: 'Install method',
|
||||
options: installMethodOptions(framework),
|
||||
options: installMethodOptions(framework, media),
|
||||
});
|
||||
if (p.isCancel(value)) process.exit(0);
|
||||
return value;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
[
|
||||
{ "id": "dash-video" },
|
||||
{ "id": "hls-video" },
|
||||
{ "id": "mux-audio" },
|
||||
{ "id": "mux-video" },
|
||||
{ "id": "native-hls-video" },
|
||||
{ "id": "simple-hls-audio-only" },
|
||||
{ "id": "simple-hls-video" }
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { supportsCdnInstall } from '../prompts.js';
|
||||
|
||||
// Wires the cdn-media manifest into the CLI the same way the install page reads
|
||||
// the cdnMedia collection. Every current renderer ships (or is covered by) a
|
||||
// CDN build, so all resolve true; the no-CDN path (e.g. Vimeo) arrives with the
|
||||
// new rendering engines. `rendererSupportsCdn`'s false branch is unit-tested in
|
||||
// cdn-code.test.ts.
|
||||
describe('supportsCdnInstall', () => {
|
||||
it('returns true for preset renderers (covered by the preset bundle)', () => {
|
||||
expect(supportsCdnInstall('html5-video')).toBe(true);
|
||||
expect(supportsCdnInstall('html5-audio')).toBe(true);
|
||||
expect(supportsCdnInstall('background-video')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for hls, whose media bundle ships a CDN build', () => {
|
||||
expect(supportsCdnInstall('hls')).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -23,6 +23,7 @@ export default defineConfig({
|
||||
'@/utils/installation/types': resolve(__dirname, '../../site/src/utils/installation/types.ts'),
|
||||
'@/utils/installation/cdn-code': resolve(__dirname, '../../site/src/utils/installation/cdn-code.ts'),
|
||||
'@/utils/installation/detect-renderer': resolve(__dirname, '../../site/src/utils/installation/detect-renderer.ts'),
|
||||
'@/content/cdn-media.json': resolve(__dirname, '../../site/src/content/cdn-media.json'),
|
||||
'@/consts': resolve(__dirname, '../../site/src/consts.ts'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -17,6 +17,11 @@ export default defineConfig({
|
||||
__dirname,
|
||||
'../../site/src/utils/installation/detect-renderer.ts'
|
||||
),
|
||||
// The real manifest is generated at build time (gitignored) and bundled
|
||||
// by tsdown. CLI tests are intentionally hermetic (`test` has no turbo
|
||||
// build dependency), so they resolve a committed fixture that mirrors the
|
||||
// manifest's shape and contents instead of forcing a CDN build.
|
||||
'@/content/cdn-media.json': resolve(__dirname, 'src/utils/tests/fixtures/cdn-media.json'),
|
||||
'@/consts': resolve(__dirname, '../../site/src/consts.ts'),
|
||||
},
|
||||
},
|
||||
|
||||
+2
-1
@@ -5,9 +5,10 @@
|
||||
"scripts": {
|
||||
"api-docs": "tsx scripts/api-docs-builder/src/index.ts",
|
||||
"ejected-skins": "tsx scripts/build-ejected-skins.ts",
|
||||
"cdn-manifest": "tsx scripts/build-cdn-manifest.ts",
|
||||
"dev": "NETLIFY_DEV=1 astro dev",
|
||||
"build": "astro build",
|
||||
"clean": "rm -rf dist .netlify src/content/generated-component-reference src/content/generated-util-reference src/content/ejected-skins.json",
|
||||
"clean": "rm -rf dist .netlify src/content/generated-component-reference src/content/generated-util-reference src/content/ejected-skins.json src/content/cdn-media.json",
|
||||
"astro": "astro",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Build the CDN media manifest for the installation guide.
|
||||
*
|
||||
* Scans the built `@videojs/html` CDN media bundles and records which media
|
||||
* subpaths actually ship a CDN build. The installation page uses this to hide
|
||||
* the CDN install option for renderers that have no CDN bundle (e.g. Vimeo).
|
||||
*
|
||||
* Produces `site/src/content/cdn-media.json` as an array of `{ id }` entries
|
||||
* (one per media subpath), consumed via the `cdnMedia` content collection.
|
||||
*
|
||||
* Source of truth: the built output of `@videojs/html`'s `build:cdn` task
|
||||
* (configured in `packages/html/tsdown.cdn.config.ts`). Reading the build
|
||||
* output — rather than a hand-maintained list — means a renderer that fails to
|
||||
* ship a CDN bundle correctly shows as no-CDN.
|
||||
*
|
||||
* Prerequisites: `@videojs/html`'s `build:cdn` (wired as a turbo dependency).
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { z } from 'astro/zod';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = resolve(__dirname, '../..');
|
||||
const CDN_MEDIA_DIR = resolve(ROOT, 'packages/html/cdn/media');
|
||||
const OUTPUT = resolve(ROOT, 'site/src/content/cdn-media.json');
|
||||
|
||||
const PREFIX = '\x1b[35m[cdn-manifest]\x1b[0m';
|
||||
const log = {
|
||||
info: (...args: unknown[]) => console.log(PREFIX, ...args),
|
||||
warn: (...args: unknown[]) => console.warn(PREFIX, '\x1b[33mwarn:\x1b[0m', ...args),
|
||||
error: (...args: unknown[]) => console.error(PREFIX, '\x1b[31merror:\x1b[0m', ...args),
|
||||
};
|
||||
|
||||
const ManifestSchema = z.array(z.object({ id: z.string() }));
|
||||
|
||||
function main() {
|
||||
if (!existsSync(CDN_MEDIA_DIR)) {
|
||||
log.error(`CDN media build not found at ${CDN_MEDIA_DIR}.`);
|
||||
log.error("Run @videojs/html's build:cdn first (it's wired as a turbo dependency).");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Production bundles are `<subpath>.js`; skip dev (`.dev.js`), sourcemaps,
|
||||
// and type stubs.
|
||||
const subpaths = readdirSync(CDN_MEDIA_DIR)
|
||||
.filter((file) => file.endsWith('.js') && !file.endsWith('.dev.js'))
|
||||
.map((file) => file.replace(/\.js$/, ''))
|
||||
.sort();
|
||||
|
||||
if (subpaths.length === 0) {
|
||||
log.error(`No CDN media bundles found in ${CDN_MEDIA_DIR}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const entries = ManifestSchema.parse(subpaths.map((id) => ({ id })));
|
||||
|
||||
mkdirSync(dirname(OUTPUT), { recursive: true });
|
||||
writeFileSync(OUTPUT, `${JSON.stringify(entries, null, 2)}\n`);
|
||||
|
||||
log.info(`✅ Wrote ${entries.length} CDN media entries to ${OUTPUT}`);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
import { getCollection } from 'astro:content';
|
||||
import HTMLInstallTabsClient from './HTMLInstallTabsClient';
|
||||
|
||||
// Read the generated CDN media manifest server-side and hand the list to the
|
||||
// client island, which decides per-renderer whether to offer the CDN option.
|
||||
const cdnMedia = (await getCollection('cdnMedia')).map((entry) => entry.id);
|
||||
---
|
||||
|
||||
<HTMLInstallTabsClient client:idle cdnMedia={cdnMedia} />
|
||||
@@ -1,64 +0,0 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import ClientCode from '@/components/Code/ClientCode';
|
||||
import { Tab, TabsList, TabsPanel, TabsRoot } from '@/components/Tabs';
|
||||
import { installMethod } from '@/stores/installation';
|
||||
import type { InstallMethod } from '@/utils/installation/types';
|
||||
import HTMLCdnCodeBlock from './HTMLCdnCodeBlock';
|
||||
|
||||
export default function HTMLInstallTabs() {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Observe tab changes and sync to installMethod store
|
||||
useEffect(() => {
|
||||
const root = ref.current?.querySelector('[data-tabs-root]');
|
||||
if (!root) return;
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
const activeTab = root.querySelector('[role="tab"][data-tab-active="true"]');
|
||||
if (activeTab) {
|
||||
const value = activeTab.getAttribute('data-value');
|
||||
if (value) {
|
||||
installMethod.set(value as InstallMethod);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const tabs = root.querySelectorAll('[role="tab"]');
|
||||
tabs.forEach((tab) => {
|
||||
observer.observe(tab, { attributes: true, attributeFilter: ['data-tab-active'] });
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<TabsRoot>
|
||||
<TabsList label="Installation">
|
||||
<Tab value="cdn" initial>
|
||||
cdn
|
||||
</Tab>
|
||||
<Tab value="npm">npm</Tab>
|
||||
<Tab value="pnpm">pnpm</Tab>
|
||||
<Tab value="yarn">yarn</Tab>
|
||||
<Tab value="bun">bun</Tab>
|
||||
</TabsList>
|
||||
<TabsPanel value="cdn" initial>
|
||||
<HTMLCdnCodeBlock />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="npm">
|
||||
<ClientCode code="npm install @videojs/html" lang="bash" />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="pnpm">
|
||||
<ClientCode code="pnpm add @videojs/html" lang="bash" />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="yarn">
|
||||
<ClientCode code="yarn add @videojs/html" lang="bash" />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="bun">
|
||||
<ClientCode code="bun add @videojs/html" lang="bash" />
|
||||
</TabsPanel>
|
||||
</TabsRoot>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import ClientCode from '@/components/Code/ClientCode';
|
||||
import { Tab, TabsList, TabsPanel, TabsRoot } from '@/components/Tabs';
|
||||
import { shared } from '@/components/typography/styles';
|
||||
import { installMethod, renderer } from '@/stores/installation';
|
||||
import { rendererSupportsCdn } from '@/utils/installation/cdn-code';
|
||||
import type { InstallMethod } from '@/utils/installation/types';
|
||||
import HTMLCdnCodeBlock from './HTMLCdnCodeBlock';
|
||||
|
||||
interface HTMLInstallTabsProps {
|
||||
/** Media subpaths that ship a CDN build, from the cdn-media manifest. */
|
||||
cdnMedia: string[];
|
||||
}
|
||||
|
||||
export default function HTMLInstallTabs({ cdnMedia }: HTMLInstallTabsProps) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const $renderer = useStore(renderer);
|
||||
|
||||
const supportsCdn = rendererSupportsCdn($renderer, cdnMedia);
|
||||
|
||||
// Mirror the active install-method tab into the store so the usage code block
|
||||
// can react (e.g. CDN omits the JS imports). Observing from the stable
|
||||
// wrapper rather than the tabs root means the observer survives the keyed
|
||||
// remount below, so it never needs to re-attach.
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
const value = el.querySelector('[role="tab"][data-tab-active="true"]')?.getAttribute('data-value');
|
||||
if (value) installMethod.set(value as InstallMethod);
|
||||
});
|
||||
|
||||
observer.observe(el, { subtree: true, attributes: true, attributeFilter: ['data-tab-active'] });
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
{/* Remount the tab set when CDN availability changes so the active tab
|
||||
resets cleanly to its initial. Without this, flipping the npm tab's
|
||||
`initial` while the CDN tab mounts/unmounts can leave two tabs active
|
||||
at once and desync installMethod from the visible tab. */}
|
||||
<TabsRoot key={supportsCdn ? 'with-cdn' : 'without-cdn'}>
|
||||
<TabsList label="Installation">
|
||||
{supportsCdn && (
|
||||
<Tab value="cdn" initial>
|
||||
cdn
|
||||
</Tab>
|
||||
)}
|
||||
<Tab value="npm" initial={!supportsCdn}>
|
||||
npm
|
||||
</Tab>
|
||||
<Tab value="pnpm">pnpm</Tab>
|
||||
<Tab value="yarn">yarn</Tab>
|
||||
<Tab value="bun">bun</Tab>
|
||||
</TabsList>
|
||||
{supportsCdn && (
|
||||
<TabsPanel value="cdn" initial>
|
||||
<HTMLCdnCodeBlock />
|
||||
</TabsPanel>
|
||||
)}
|
||||
<TabsPanel value="npm" initial={!supportsCdn}>
|
||||
<ClientCode code="npm install @videojs/html" lang="bash" />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="pnpm">
|
||||
<ClientCode code="pnpm add @videojs/html" lang="bash" />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="yarn">
|
||||
<ClientCode code="yarn add @videojs/html" lang="bash" />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="bun">
|
||||
<ClientCode code="bun add @videojs/html" lang="bash" />
|
||||
</TabsPanel>
|
||||
</TabsRoot>
|
||||
{!supportsCdn && (
|
||||
<p className={clsx(shared.p, shared.prose)}>
|
||||
This source type isn't available via CDN — install it with a package manager (npm, pnpm, yarn, or bun).
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
import type { HTMLTag, Polymorphic } from 'astro/types';
|
||||
import { clsx } from 'clsx';
|
||||
import { twMerge } from '@/utils/twMerge';
|
||||
import { shared } from './styles';
|
||||
|
||||
type Props<Tag extends HTMLTag = 'p'> = Polymorphic<{ as: Tag }> & {
|
||||
maxWidth?: boolean;
|
||||
@@ -13,7 +14,7 @@ const { as: Tag = 'p', maxWidth = true, class: className, ...props } = Astro.pro
|
||||
---
|
||||
|
||||
<Tag
|
||||
class={twMerge(clsx("my-4", maxWidth && "mx-auto max-w-3xl"), className)}
|
||||
class={twMerge(clsx(shared.p, maxWidth && shared.prose), className)}
|
||||
{...props}
|
||||
>
|
||||
<slot />
|
||||
|
||||
@@ -13,6 +13,8 @@ export const shared = {
|
||||
em: 'italic',
|
||||
li: '',
|
||||
ol: 'list-decimal list-outside pl-4 space-y-1',
|
||||
p: 'my-4',
|
||||
prose: 'mx-auto max-w-3xl',
|
||||
strong: 'font-bold',
|
||||
ul: 'list-disc list-outside pl-4 space-y-1',
|
||||
} as const;
|
||||
|
||||
@@ -170,6 +170,16 @@ const ejectedSkins = defineCollection({
|
||||
}),
|
||||
});
|
||||
|
||||
// Media subpaths that ship a CDN build, generated by scripts/build-cdn-manifest.ts.
|
||||
// Each entry's id is a media subpath (e.g. `hls-video`). Used by the installation
|
||||
// guide to hide the CDN install option for renderers with no CDN bundle.
|
||||
const cdnMedia = defineCollection({
|
||||
loader: file('./src/content/cdn-media.json'),
|
||||
schema: z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = {
|
||||
blog,
|
||||
docs,
|
||||
@@ -180,4 +190,5 @@ export const collections = {
|
||||
mediaReference,
|
||||
presetReference,
|
||||
ejectedSkins,
|
||||
cdnMedia,
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ import JSPicker from '@/components/installation/JSPicker.astro';
|
||||
import RendererPicker from '@/components/installation/RendererPicker';
|
||||
import SkinPickerSection from '@/components/installation/SkinPickerSection.astro';
|
||||
import UseCasePicker from '@/components/installation/UseCasePicker';
|
||||
import HTMLInstallTabs from '@/components/installation/HTMLInstallTabs';
|
||||
import HTMLInstallTabs from '@/components/installation/HTMLInstallTabs.astro';
|
||||
import HTMLUsageCodeBlock from '@/components/installation/HTMLUsageCodeBlock';
|
||||
import ReactCreateCodeBlock from '@/components/installation/ReactCreateCodeBlock';
|
||||
import ReactUsageCodeBlock from '@/components/installation/ReactUsageCodeBlock';
|
||||
@@ -112,7 +112,7 @@ Video.js supports a wide range of file types and hosting services. It's easy to
|
||||
## Install Video.js
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
<HTMLInstallTabs client:idle />
|
||||
<HTMLInstallTabs />
|
||||
</FrameworkCase>
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
<TabsRoot client:idle>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { generateCdnCode } from '../cdn-code';
|
||||
import { generateCdnCode, rendererSupportsCdn } from '../cdn-code';
|
||||
|
||||
describe('generateCdnCode', () => {
|
||||
it('generates video preset CDN tags for html5-video', () => {
|
||||
@@ -33,3 +33,21 @@ describe('generateCdnCode', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rendererSupportsCdn', () => {
|
||||
const manifest = ['hls-video'];
|
||||
|
||||
it('returns true for preset renderers (covered by the preset bundle, no media subpath)', () => {
|
||||
expect(rendererSupportsCdn('html5-video', manifest)).toBe(true);
|
||||
expect(rendererSupportsCdn('html5-audio', manifest)).toBe(true);
|
||||
expect(rendererSupportsCdn('background-video', manifest)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for a media renderer whose subpath is in the manifest', () => {
|
||||
expect(rendererSupportsCdn('hls', manifest)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for a media renderer whose subpath is absent from the manifest', () => {
|
||||
expect(rendererSupportsCdn('hls', [])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,14 +10,29 @@ function getCdnFileName(useCase: UseCase, skin: Skin): string {
|
||||
return skin;
|
||||
}
|
||||
|
||||
function getCdnMediaSubpath(renderer: Renderer): string | null {
|
||||
// Renderer → media subpath name, independent of whether a CDN build exists.
|
||||
// Preset renderers (html5-video/audio, background-video) are covered by the
|
||||
// preset bundle and have no separate media script, so they map to null.
|
||||
function getMediaSubpath(renderer: Renderer): string | null {
|
||||
const map: Partial<Record<Renderer, string>> = {
|
||||
hls: 'hls-video',
|
||||
};
|
||||
|
||||
return map[renderer] ?? null;
|
||||
}
|
||||
|
||||
// Whether a renderer can be installed via CDN, given the set of media subpaths
|
||||
// that ship a CDN build (from the cdn-media manifest). Preset renderers always
|
||||
// can (no separate media script); a media renderer can only if its subpath is
|
||||
// in the manifest.
|
||||
export function rendererSupportsCdn(renderer: Renderer, cdnMediaSubpaths: readonly string[]): boolean {
|
||||
const subpath = getMediaSubpath(renderer);
|
||||
return subpath === null || cdnMediaSubpaths.includes(subpath);
|
||||
}
|
||||
|
||||
function getCdnMediaSubpath(renderer: Renderer): string | null {
|
||||
return getMediaSubpath(renderer);
|
||||
}
|
||||
|
||||
export function generateCdnCode(useCase: UseCase, skin: Skin, renderer: Renderer): string {
|
||||
const name = getCdnFileName(useCase, skin);
|
||||
const mediaSubpath = getCdnMediaSubpath(renderer);
|
||||
|
||||
+6
-2
@@ -10,8 +10,12 @@
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["src/content/ejected-skins.json"]
|
||||
},
|
||||
"cdn-manifest": {
|
||||
"dependsOn": ["^build:cdn"],
|
||||
"outputs": ["src/content/cdn-media.json"]
|
||||
},
|
||||
"build": {
|
||||
"dependsOn": ["$TURBO_EXTENDS$", "api-docs", "ejected-skins"],
|
||||
"dependsOn": ["$TURBO_EXTENDS$", "api-docs", "ejected-skins", "cdn-manifest"],
|
||||
"env": [
|
||||
"OAUTH_CLIENT_ID",
|
||||
"OAUTH_CLIENT_SECRET",
|
||||
@@ -23,7 +27,7 @@
|
||||
]
|
||||
},
|
||||
"dev": {
|
||||
"dependsOn": ["$TURBO_EXTENDS$", "api-docs", "ejected-skins"],
|
||||
"dependsOn": ["$TURBO_EXTENDS$", "api-docs", "ejected-skins", "cdn-manifest"],
|
||||
"env": [
|
||||
"OAUTH_CLIENT_ID",
|
||||
"OAUTH_CLIENT_SECRET",
|
||||
|
||||
Reference in New Issue
Block a user