manifest.webmanifest
The manifest feature writes /manifest.webmanifest into the build output during astro build. The JSON output contains the options you provide.
If manifest is omitted, Eminence logs a recommendation. Set manifest: false to disable generation and silence it.
Required fields
Section titled “Required fields”The TypeScript type requires:
nameorshort_namedisplayordisplay_overridestart_url
prefer_related_applications, when present, may only be false.
Manifest icons are explicit. Public favicon discovery does not add entries because 32×32 favicons and 180×180 Apple touch icons are not substitutes for installable application icons.
Place appropriately sized files in public/ and list them in manifest.icons:
eminence({ manifest: { name: "Example", short_name: "Example", start_url: "/", display: "standalone", icons: [ { src: "/icon-192.png", sizes: "192x192", type: "image/png" }, { src: "/icon-512.png", sizes: "512x512", type: "image/png", purpose: "any maskable", }, ], },});Options
Section titled “Options”| option | type | required | description |
|---|---|---|---|
name |
string |
Yes* | Full application name; optional when short_name is present. |
short_name |
string |
Yes* | Compact name; optional when name is present. |
start_url |
string |
Yes | URL loaded when the application launches. |
display |
string |
Yes* | Display mode; optional with display_override. |
display_override |
string[] |
Yes* | Ordered display candidates; optional with display. |
icons |
WebManifestIconItem[] |
No | Explicit installable-application icons. |
description |
string |
No | Application description. |
background_color |
string |
No | Splash-screen background color. |
theme_color |
string |
No | Default browser or OS chrome color. |
scope |
string |
No | Navigation scope. |
orientation |
string |
No | Preferred screen orientation. |
id |
string |
No | Stable application identity. |
categories |
string[] |
No | Application categories. |
screenshots |
WebManifestScreenshotItem[] |
No | Install and store screenshots. |
shortcuts |
WebManifestShortcutItem[] |
No | OS shortcut menu entries. |
related_applications |
WebManifestRelatedApplication[] |
No | Related native applications. |
prefer_related_applications |
false |
No | May only be false. |
file_handlers |
WebManifestFileHandler[] |
No | File associations. |
protocol_handlers |
WebManifestProtocolHandler[] |
No | Custom protocol handlers. |
share_target |
WebManifestShareTarget |
No | OS share target. |
launch_handler |
WebManifestLaunchHandler |
No | Launch behavior. |
note_taking |
{ new_note_shortcut?: { url: string } } |
No | Note-taking integration. |
scope_extensions |
Array<{ origin: string }> |
No | Additional navigation origins. |
serviceworker |
WebManifestServiceWorker |
No | Service worker metadata. |
If a manifest already exists in the output directory, Eminence leaves it untouched and logs a warning.
Source code
Section titled “Source code”import { constants } from "node:fs";import { access, mkdir, writeFile } from "node:fs/promises";import { dirname, join } from "node:path";import { fileURLToPath } from "node:url";import type { IntegrationRuntimeContext } from "..";
export type WebManifestIconItem = { src: string; sizes?: string; type?: string; purpose?: string;};
export type WebManifestScreenshotItem = { src: string; sizes?: string; type?: string; label?: string; form_factor?: string; platform?: string;};
export type WebManifestShortcutItem = { name: string; url: string; short_name?: string; description?: string; icons?: WebManifestIconItem[];};
export type WebManifestRelatedApplication = { platform: string; url?: string; id?: string;};
export type WebManifestFileHandler = { action: string; accept: Record<string, string[]>;};
export type WebManifestProtocolHandler = { protocol: string; url: string;};
export type WebManifestShareTarget = { action: string; method?: string; enctype?: string; params?: Record<string, string>;};
export type WebManifestLaunchHandler = { client_mode?: string | string[];};
export type WebManifestServiceWorker = { src: string; scope?: string; type?: string; update_via_cache?: string;};
type NameOrShortName = { name: string; short_name?: string } | { short_name: string; name?: never };
type DisplayOrDisplayOverride = | { display: string; display_override?: string[] } | { display_override: string[]; display?: never };
type WebManifestBase = { start_url: string; icons?: WebManifestIconItem[]; prefer_related_applications?: false; description?: string; background_color?: string; theme_color?: string; scope?: string; orientation?: string; id?: string; categories?: string[]; screenshots?: WebManifestScreenshotItem[]; shortcuts?: WebManifestShortcutItem[]; related_applications?: WebManifestRelatedApplication[]; file_handlers?: WebManifestFileHandler[]; protocol_handlers?: WebManifestProtocolHandler[]; share_target?: WebManifestShareTarget; launch_handler?: WebManifestLaunchHandler; note_taking?: { new_note_shortcut?: { url: string } }; scope_extensions?: Array<{ origin: string }>; serviceworker?: WebManifestServiceWorker;};
export type WebManifestOptions = NameOrShortName & DisplayOrDisplayOverride & WebManifestBase;
export const WEB_MANIFEST_RECOMMENDATION = "Recommendation: follow eminence-astro-suite.xeffen25.com/recommendations/when-you-should-add-a-manifest-webmanifest to learn when you should add a manifest.webmanifest.";
export const WEB_MANIFEST_RELATIVE_PATH = "/manifest.webmanifest";
const buildManifest = (options: WebManifestOptions): string => { return `${JSON.stringify(options, null, 2)}\n`;};
const exists = async (path: string): Promise<boolean> => { try { await access(path, constants.F_OK); return true; } catch (error) { if ( error && typeof error === "object" && "code" in error && error.code === "ENOENT" ) { return false; }
throw error; }};
export async function generateManifest({ dir, options, logger,}: IntegrationRuntimeContext): Promise<void> { const input = options.manifest; const outputPath = join(fileURLToPath(dir), "manifest.webmanifest"); const outputExists = await exists(outputPath);
if (input === false) { if (outputExists) { logger.info( `No "${WEB_MANIFEST_RELATIVE_PATH}" file was generated nor modified because it already exists.`, ); } else { logger.info( `No "${WEB_MANIFEST_RELATIVE_PATH}" file exists and no file was generated.`, ); }
return; }
if (input === undefined) { logger.warn( `No manifest.webmanifest file was generated because manifest is undefined. ${WEB_MANIFEST_RECOMMENDATION}`, ); return; }
if (typeof input !== "object" || input === null) { logger.error( "Invalid manifest configuration: expected an object with required PWA fields.", ); throw new Error( "Invalid manifest configuration: expected an object with required PWA fields.", ); }
if (outputExists) { logger.warn( `Could not generate "${WEB_MANIFEST_RELATIVE_PATH}" because it already exists. Disabling manifest generation for this build.`, ); options.manifest = false; return; }
try { const content = buildManifest(input); await mkdir(dirname(outputPath), { recursive: true }); await writeFile(outputPath, content, "utf-8"); logger.info(`Generated "${WEB_MANIFEST_RELATIVE_PATH}"`); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error( `Failed to generate "${WEB_MANIFEST_RELATIVE_PATH}": ${message}`, ); throw error; }}