All files / src/servicenow dev-server-plugins.ts

100% Statements 26/26
82.6% Branches 19/23
100% Functions 5/5
100% Lines 26/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111                                  3x           26x 26x 2x     2x                           24x   24x         17x 17x 17x     2x         17x 11x                     7x 2x     5x 2x         24x 48x 23x   1x         23x 46x 22x   1x       22x                        
import { Credential } from "./types";
 
/**
 * Creates dev server plugins for Rollup with live reload and optional proxy support.
 * Returns an array of [devServer, livereload] plugins, or undefined if no watch paths are provided.
 * @param config Configuration object for the dev server
 * @param config.watchPaths Array of directories to serve and watch for live reload
 * @param config.proxyPaths Array of paths to proxy to the ServiceNow instance
 * @param config.credential ServiceNow authentication credentials, or undefined
 * @param config.httpProxy Optional HTTP proxy server URL for tunnelling upstream requests (e.g. `http://proxy.corp:8080`)
 * @returns Array of Rollup plugins [devServer, livereload], or undefined if watchPaths is empty
 *
 * Note: auth headers are fetched once via `credential.getHeaders()` at call time (before
 * the dev server starts). They are reused for the lifetime of the session. If the credential
 * token expires during a long dev session, proxied requests will silently fail with auth
 * errors from the instance. Restart the dev server to refresh credentials.
 */
export async function devServerPlugins(config: {
  watchPaths: string[];
  proxyPaths: string[];
  credential: Credential | undefined;
  httpProxy?: string;
}) {
  const { credential, watchPaths, proxyPaths, httpProxy } = config;
  if (watchPaths.length === 0) {
    console.warn(
      "No watch paths provided for dev server. Please provide at least one path to watch for changes.",
    );
    return;
  }
  const proxy: Array<{
    from: string;
    to: string;
    opts: {
      replyOptions: {
        rewriteRequestHeaders: (
          _req: Request,
          headers: Record<string, string>,
        ) => Record<string, string>;
      };
      websocket?: boolean;
    };
  }> = [];
 
  if (
    credential &&
    typeof credential.getUrl === "function" &&
    typeof credential.getHeaders === "function"
  ) {
    const authHeaders = await credential.getHeaders();
    const proxyTarget = credential.getUrl().href.replace(/\/$/, "");
    const rewriteRequestHeaders = (
      _req: Request,
      headers: Record<string, string>,
    ) => ({
      ...headers,
      ...authHeaders,
    });
 
    proxy.push(
      ...proxyPaths.map((proxyPath) => ({
        from: proxyPath,
        to: `${proxyTarget}${proxyPath}`,
        opts: {
          replyOptions: {
            rewriteRequestHeaders,
          },
          ...(proxyPath === "/amb" ? { websocket: true } : {}),
        },
      })),
    );
  } else if (credential) {
    console.warn(
      "credential is invalid. It must have getUrl and getHeaders methods. Dev server will start without proxying to ServiceNow instance.",
    );
  } else if (proxyPaths.length > 0) {
    console.warn(
      "credential not provided. Dev server will start without proxying to ServiceNow instance.",
    );
  }
  let devServer: any;
  try {
    const imported = await import("../dev-server");
    devServer = imported.default || imported;
  } catch (error: any) {
    throw new Error(
      `dev-server failed to load. Please ensure all optional dev-server dependencies (fastify, @fastify/static, @fastify/http-proxy) are installed.${error.message ? ` Original error: ${error.message}` : ""}`,
    );
  }
  let livereload: any;
  try {
    const imported = await import("rollup-plugin-livereload");
    livereload = imported.default || imported;
  } catch (error: any) {
    throw new Error(
      `rollup-plugin-livereload is not installed or failed to load. Please install it to enable the dev server.${error.message ? ` Original error: ${error.message}` : ""}`,
    );
  }
  return [
    devServer({
      dirs: watchPaths,
      port: 3000,
      proxy,
      ...(httpProxy ? { httpProxy } : {}),
    }),
    livereload({
      watch: watchPaths,
    }),
  ];
}