DIYgod/RSSHub · error · Error

JavaScript file not found.

Error message

JavaScript file not found.

What it means

Thrown when the scoop.sh index HTML contains no <script type="module" crossorigin src="..."> tag from which the route extracts the JS bundle URL. The route scrapes the JS bundle to recover the embedded Azure Search API key, so a missing module script aborts the whole flow.

Source

Thrown at lib/routes/scoop/apps.tsx:52

export const handler = async (ctx: Context): Promise<Data> => {
    const { query = 's=2&d=1&n=true&dm=true&o=true' } = ctx.req.param();
    const limit = Number(ctx.req.query('limit') ?? '50');

    const baseUrl = 'https://scoop.sh';
    const apiBaseUrl = 'https://scoopsearch.search.windows.net';
    const targetUrl: string = new URL(`/#/apps?${query}`, baseUrl).href;
    const apiUrl: string = new URL('indexes/apps/docs/search', apiBaseUrl).href;

    const targetResponse = await ofetch(targetUrl);
    const $: CheerioAPI = load(targetResponse);
    const language = $('html').attr('lang') ?? 'en';

    const scriptRegExp = /<script type="module" crossorigin src="(.*?)"><\/script>/;
    const scriptUrl: string = scriptRegExp.test(targetResponse) ? new URL(targetResponse.match(scriptRegExp)?.[1], baseUrl).href : '';

    if (!scriptUrl) {
        throw new Error('JavaScript file not found.');
    }

    const scriptResponse = await ofetch(scriptUrl, {
        parseResponse: (txt) => txt,
    });

    const key: string = scriptResponse.match(/VITE_APP_AZURESEARCH_KEY:"(.*?)"/)?.[1];

    if (!key) {
        throw new Error('Key not found.');
    }

    const isOffcial = !query.includes('o=false');
    const isDistinct = !query.includes('dm=false');
    const sort: string = query.match(/s=(\d+)/)?.[1] ?? '2';
    const desc: string = query.match(/d=(\d+)/)?.[1] ?? '1';

    const response = await ofetch(apiUrl, {

View on GitHub (pinned to bed535e087)

Solutions

  1. View-source https://scoop.sh and confirm the module script tag's current attributes.
  2. Update scriptRegExp to match the new tag shape (e.g. dropped 'crossorigin', changed type).
  3. If an anti-bot page is served, add browser-like headers via config.trueUA.
  4. Detect a non-HTML response early and throw a clearer 'unexpected response type' error.

Example fix

// before
const scriptRegExp = /<script type="module" crossorigin src="(.*?)"><\/script>/;
const scriptUrl: string = scriptRegExp.test(targetResponse) ? new URL(targetResponse.match(scriptRegExp)?.[1], baseUrl).href : '';
if (!scriptUrl) {
    throw new Error('JavaScript file not found.');
}

// after — tolerate the optional crossorigin attribute
const scriptRegExp = /<script[^>]*type="module"[^>]*src="([^"]+)"[^>]*><\/script>/;
const scriptUrl = new URL(targetResponse.match(scriptRegExp)?.[1] ?? '', baseUrl).href;
if (!scriptUrl) {
    throw new Error('JavaScript module bundle not found in scoop.sh index HTML');
}
Defensive patterns

Strategy: fallback

Validate before calling

// Tolerate attribute changes by matching the module script more loosely.
function extractScriptUrl(html: string, baseUrl: string): string {
    const m = html.match(/<script[^>]*\btype="module"[^>]*\bsrc="([^"]+)"/);
    return m ? new URL(m[1], baseUrl).href : '';
}

Type guard

const isHtmlWithModuleScript = (s: string): boolean =>
    /<script[^>]*type="module"[^>]*src=/.test(s);

Try / catch

let scriptUrl = extractScriptUrl(targetResponse, baseUrl);
if (!scriptUrl) {
    // retry once with a browser UA in case an anti-bot page was served
    const retry = await ofetch(targetUrl, { headers: { 'user-agent': config.trueUA }, parseResponse: (t) => t });
    scriptUrl = extractScriptUrl(retry, baseUrl);
}
if (!scriptUrl) throw new Error('JavaScript module bundle not found in scoop.sh index HTML');

Prevention

When it happens

Trigger: ofetch(targetUrl) returns HTML where scriptRegExp.test(targetResponse) is false — i.e. the Vite module script tag is absent (renamed attribute, different bundler, or an anti-bot page was returned). scriptUrl stays '' and the guard throws.

Common situations: scoop.sh rebuilt its frontend and changed the script tag attributes (no longer 'module crossorigin'); a CDN/anti-bot layer returned a challenge page instead of the app shell; the response was gzip/binary and the regex ran against a non-string (mitigated by ofetch default text handling).

Related errors


AI-assisted analysis of DIYgod/RSSHub@bed535e087 (2026-08-12). Data as JSON: /api/errors/61631c3e8183e6e8. Report an issue: GitHub.