import {
  createMethod,
  createProdHandler,
  createRpcHandler,
  createRpcServer
} from "./chunk-LSPXE4QP.mjs";
import {
  initProject,
  resolveProjectDir
} from "./chunk-XSVX7YWK.mjs";
import {
  APP_URL_WINDOW_PROPERTY,
  asyncHandler
} from "./chunk-PPE2X6R3.mjs";
import "./chunk-CLKKQB2X.mjs";

// cli/index.ts
import "dotenv/config";
import path2 from "path";
import yargs from "yargs";
import * as url2 from "node:url";
import chalk2 from "chalk";
import { execaNode } from "execa";

// src/server/index.ts
import * as path from "path";
import { createServer } from "http";
import * as fs from "fs/promises";
import { Worker, MessageChannel } from "worker_threads";
import express from "express";
import getPort from "get-port";
import { createProxyMiddleware } from "http-proxy-middleware";
import { mapValues } from "@mui/toolpad-utils/collections";
import prettyBytes from "pretty-bytes";
import { WebSocket, WebSocketServer } from "ws";
import { listen } from "@mui/toolpad-utils/http";
import openBrowser from "react-dev-utils/openBrowser.js";
import chalk from "chalk";
import { serveRpc } from "@mui/toolpad-utils/workerRpc";
import * as url from "node:url";
import cors from "cors";

// src/server/projectRpcServer.ts
function createRpcServer2(project) {
  return {
    dataSourceFetchPrivate: createMethod(
      ({ params }) => {
        return project.dataManager.dataSourceFetchPrivate(...params);
      }
    ),
    execQuery: createMethod(({ params }) => {
      return project.dataManager.execDataNodeQuery(...params);
    }),
    loadDom: createMethod(({ params }) => {
      return project.loadDom(...params);
    }),
    getVersionInfo: createMethod(({ params }) => {
      return project.getVersionInfo(...params);
    }),
    introspect: createMethod(({ params }) => {
      return project.functionsManager.introspect(...params);
    }),
    getPrettierConfig: createMethod(({ params }) => {
      return project.getPrettierConfig(...params);
    }),
    saveDom: createMethod(({ params }) => {
      return project.saveDom(...params);
    }),
    applyDomDiff: createMethod(({ params }) => {
      return project.applyDomDiff(...params);
    }),
    openCodeEditor: createMethod(({ params }) => {
      return project.openCodeEditor(...params);
    }),
    createComponent: createMethod(({ params }) => {
      return project.createComponent(...params);
    }),
    deletePage: createMethod(({ params }) => {
      return project.deletePage(...params);
    }),
    dataSourceExecPrivate: createMethod(
      ({ params }) => {
        return project.dataManager.dataSourceExecPrivate(...params);
      }
    ),
    createDataProvider: createMethod(({ params }) => {
      return project.createDataProvider(...params);
    }),
    getRuntimeConfig: createMethod(({ params }) => {
      return project.getRuntimeConfig(...params);
    }),
    getComponents: createMethod(({ params }) => {
      return project.getComponentsManifest(...params);
    })
  };
}

// src/server/index.ts
import.meta.url ??= url.pathToFileURL(__filename).toString();
var currentDirectory = url.fileURLToPath(new URL(".", import.meta.url));
var DEFAULT_PORT = 3e3;
function* getPreferredPorts(port = DEFAULT_PORT) {
  while (true) {
    yield port;
    port += 1;
  }
}
async function createDevHandler(project) {
  const handler = express.Router();
  handler.use((req, res, next) => {
    res.setHeader("X-Toolpad-Base", project.options.base);
    next();
  });
  handler.use(cors());
  const appServerPath = path.resolve(currentDirectory, "../cli/appServerWorker.mjs");
  const [wsPort, devPort, runtimeConfig] = await Promise.all([
    getPort(),
    getPort(),
    project.getRuntimeConfig()
  ]);
  const mainThreadRpcChannel = new MessageChannel();
  const worker = new Worker(appServerPath, {
    workerData: {
      outDir: project.getAppOutputFolder(),
      base: project.options.base,
      config: runtimeConfig,
      root: project.getRoot(),
      port: devPort,
      mainThreadRpcPort: mainThreadRpcChannel.port1,
      customServer: project.options.customServer
    },
    transferList: [mainThreadRpcChannel.port1],
    env: {
      ...process.env,
      NODE_ENV: "development"
    }
  });
  worker.once("exit", (code) => {
    console.error(`App dev server failed ${code}`);
    process.exit(1);
  });
  let resolveReadyPromise;
  const readyPromise = new Promise((resolve2) => {
    resolveReadyPromise = resolve2;
  });
  serveRpc(mainThreadRpcChannel.port2, {
    notifyReady: async () => resolveReadyPromise?.(),
    loadDom: async () => project.loadDom(),
    getComponents: async () => project.getComponentsManifest(),
    getPagesManifest: async () => project.getPagesManifest()
  });
  project.events.on("componentsListChanged", () => {
    worker.postMessage({ kind: "reload-components" });
  });
  const rpcServer = createRpcServer2(project);
  handler.use("/__toolpad_dev__/rpc", createRpcHandler(rpcServer));
  handler.use(
    "/__toolpad_dev__/reactDevtools",
    express.static(path.resolve(currentDirectory, "../../dist/reactDevtools"))
  );
  handler.use(
    "/__toolpad_dev__/manifest.json",
    asyncHandler(async (req, res) => {
      const wsProtocol = req.protocol === "http" ? "ws" : "wss";
      res.json({
        rootDir: project.getRoot(),
        wsUrl: `${wsProtocol}://${req.hostname}:${wsPort}`
      });
    })
  );
  handler.use("/api/data", project.dataManager.createDataHandler());
  const runtimeRpcServer = createRpcServer(project);
  handler.use("/api/runtime-rpc", createRpcHandler(runtimeRpcServer));
  handler.use(
    (req, res, next) => {
      readyPromise.then(next, next);
    },
    createProxyMiddleware({
      logLevel: "silent",
      ws: true,
      target: {
        host: "localhost",
        port: devPort
      }
    })
  );
  const wsServer = new WebSocketServer({ port: wsPort });
  project.events.on("*", (event, payload) => {
    wsServer.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify({ kind: "projectEvent", event, payload }));
      }
    });
  });
  wsServer.on("connection", (ws, _request) => {
    ws.on("error", console.error);
  });
  return {
    handler,
    async dispose() {
      worker.postMessage({ kind: "exit" });
    }
  };
}
async function createToolpadAppHandler(project) {
  const appHandler = project.options.dev ? await createDevHandler(project) : await createProdHandler(project);
  return appHandler;
}
async function createEditorHandler(appUrl, { toolpadDevMode = false }) {
  const router = express.Router();
  const transformIndexHtml = (html) => {
    return html.replace(
      "<!-- __TOOLPAD_SCRIPTS__ -->",
      `<script>window[${JSON.stringify(APP_URL_WINDOW_PROPERTY)}] = ${JSON.stringify(
        appUrl
      )}</script>
      `
    );
  };
  let viteApp;
  if (toolpadDevMode) {
    console.log(`${chalk.blue("info")}  - Running Toolpad editor in dev mode`);
    const vite = await import("vite");
    viteApp = await vite.createServer({
      configFile: path.resolve(currentDirectory, "../../src/toolpad/vite.config.mts"),
      root: path.resolve(currentDirectory, "../../src/toolpad"),
      server: { middlewareMode: true },
      plugins: [
        {
          name: "toolpad:transform-index-html",
          transformIndexHtml
        }
      ]
    });
    router.use("/", viteApp.middlewares);
  } else {
    router.use(
      "/",
      express.static(path.resolve(currentDirectory, "../../dist/editor"), { index: false }),
      asyncHandler(async (req, res) => {
        const htmlFilePath = path.resolve(currentDirectory, "../../dist/editor/index.html");
        let html = await fs.readFile(htmlFilePath, { encoding: "utf-8" });
        html = transformIndexHtml(html);
        res.setHeader("Content-Type", "text/html").status(200).end(html);
      })
    );
  }
  return {
    handler: router,
    async dispose() {
      await viteApp?.close();
    }
  };
}
async function createToolpadHandler({
  dev,
  externalUrl,
  base,
  dir,
  toolpadDevMode
}) {
  const editorBasename = "/_toolpad";
  const project = await initProject({ dev, dir, externalUrl, base });
  await project.start();
  const router = express.Router();
  router.use((req, res, expressNext) => {
    res.setHeader("X-Content-Type-Options", "nosniff");
    res.setHeader("X-XSS-Protection", "1; mode=block");
    res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
    expressNext();
  });
  router.get("/", (req, res) => {
    const redirectUrl = dev ? editorBasename : project.options.base;
    res.redirect(302, redirectUrl);
  });
  const publicPath = path.resolve(currentDirectory, "../../public");
  router.use(express.static(publicPath, { index: false }));
  const appHandler = await createToolpadAppHandler(project);
  router.use(project.options.base, appHandler.handler);
  let editorHandler;
  if (dev) {
    editorHandler = await createEditorHandler(project.options.base, { toolpadDevMode });
    router.use(editorBasename, editorHandler.handler);
  }
  return {
    handler: router,
    dispose: async () => {
      await Promise.allSettled([
        project.dispose(),
        appHandler?.dispose(),
        editorHandler?.dispose()
      ]);
    }
  };
}
async function startToolpadServer({ port, ...config }) {
  const gitSha1 = process.env.GIT_SHA1 || null;
  const circleBuildNum = process.env.CIRCLE_BUILD_NUM || null;
  const app = express();
  const httpServer = createServer(app);
  app.get("/health-check", (req, res) => {
    const memoryUsage = process.memoryUsage();
    res.json({
      gitSha1,
      circleBuildNum,
      memoryUsage,
      memoryUsagePretty: mapValues(memoryUsage, (usage) => prettyBytes(usage))
    });
  });
  const toolpadHandler = await createToolpadHandler(config);
  app.use(toolpadHandler.handler);
  const runningServer = await listen(httpServer, port);
  return {
    port: runningServer.port,
    async dispose() {
      await Promise.allSettled([runningServer.close(), toolpadHandler?.dispose?.()]);
    }
  };
}
async function fetchAppUrl(appUrl) {
  const res = await fetch(appUrl);
  if (!res.ok) {
    throw new Error(`HTTP ${res.status}: ${res.statusText}`);
  }
  const appBase = res.headers.get("X-Toolpad-Base");
  if (!appBase) {
    throw new Error(`Not a Toolpad app or not running in dev mode`);
  }
  return new URL(appBase, appUrl).toString();
}
async function runEditor(appUrl, options = {}) {
  console.log(`${chalk.blue("info")}  - starting Toolpad editor...`);
  let appRootUrl;
  try {
    appRootUrl = await fetchAppUrl(appUrl);
  } catch (err) {
    console.error(
      `${chalk.red("error")} - No Toolpad application found running under ${chalk.cyan(appUrl)}
        Find more information about running a custom server at ${chalk.cyan(
        "https://mui.com/toolpad/concepts/custom-server/"
      )}`
    );
    process.exit(1);
  }
  const app = express();
  const editorBasename = "/_toolpad";
  const { pathname, origin } = new URL(appRootUrl);
  const editorHandler = await createEditorHandler(pathname, options);
  app.use(
    pathname,
    createProxyMiddleware({
      logLevel: "silent",
      ws: true,
      target: origin
    })
  );
  app.use(editorBasename, editorHandler.handler);
  const port = options.port || await getPort({ port: getPreferredPorts(DEFAULT_PORT) });
  const server = await listen(app, port);
  console.log(
    `${chalk.green("ready")} - Toolpad editor ready on ${chalk.cyan(
      `http://localhost:${server.port}${editorBasename}`
    )}`
  );
}
async function runApp({
  dev = false,
  dir = ".",
  base = "/prod",
  port,
  toolpadDevMode = false
}) {
  const projectDir = resolveProjectDir(dir);
  if (!port) {
    port = dev ? await getPort({ port: getPreferredPorts(DEFAULT_PORT) }) : DEFAULT_PORT;
  } else {
    const availablePort = await getPort({ port });
    if (availablePort !== port) {
      console.error(`${chalk.red("error")} - Port ${port} is not available. Aborted.`);
      process.exit(1);
    }
  }
  const externalUrl = process.env.TOOLPAD_EXTERNAL_URL || `http://localhost:${port}`;
  const server = await startToolpadServer({
    dev,
    base,
    dir,
    port,
    toolpadDevMode: !!process.env.TOOLPAD_NEXT_DEV || toolpadDevMode,
    externalUrl
  });
  const toolpadUrl = `http://localhost:${server.port}/`;
  console.log(
    `${chalk.green("ready")} - toolpad project ${chalk.cyan(projectDir)} ready on ${chalk.cyan(
      toolpadUrl
    )}`
  );
  if (dev) {
    try {
      openBrowser(toolpadUrl);
    } catch (err) {
      console.error(`${chalk.red("error")} - Failed to open browser: ${err.message}`);
    }
  }
  return {
    async dispose() {
      await Promise.allSettled([server.dispose()]);
    }
  };
}

// cli/index.ts
import.meta.url ??= url2.pathToFileURL(__filename).toString();
var currentDirectory2 = url2.fileURLToPath(new URL(".", import.meta.url));
async function runCommand(cmd, { dir, dev: toolpadDevMode, ...args }) {
  const projectDir = path2.resolve(process.cwd(), dir);
  const app = await runApp({
    ...args,
    dir: projectDir,
    dev: cmd !== "start",
    toolpadDevMode
  });
  process.once("SIGINT", () => {
    app.dispose().finally(() => {
      process.exit(0);
    });
  });
}
async function devCommand(args) {
  console.log(`${chalk2.blue("info")}  - starting Toolpad application in dev mode...`);
  await runCommand("dev", args);
}
async function editorCommand({ dev: toolpadDevMode, ...args }) {
  await runEditor(args.url, { toolpadDevMode, ...args });
}
async function buildCommand({ dir, base }) {
  const projectDir = path2.resolve(process.cwd(), dir);
  console.log(`${chalk2.blue("info")}  - building Toolpad application...`);
  const builderPath = path2.resolve(currentDirectory2, "./appBuilderWorker.mjs");
  await execaNode(builderPath, [], {
    stdio: "inherit",
    env: {
      NODE_ENV: "production",
      FORCE_COLOR: "1",
      TOOLPAD_PROJECT_DIR: projectDir,
      TOOLPAD_BASE: base
    }
  });
  console.log(`${chalk2.green("success")} - build done.`);
}
async function startCommand(args) {
  console.log(`${chalk2.blue("info")}  - Starting Toolpad application...`);
  await runCommand("start", args);
}
async function cli(argv) {
  const sharedOptions = {
    dir: {
      type: "string",
      describe: "Directory of the Toolpad application",
      default: "./toolpad"
    },
    base: {
      type: "string",
      describe: "Public base path of the Toolpad application",
      default: "/prod"
    }
  };
  const sharedRunOptions = {
    ...sharedOptions,
    port: {
      type: "number",
      describe: "Port to run the Toolpad application on",
      demandOption: false,
      alias: "p"
    },
    dev: {
      type: "boolean",
      describe: "Run the Toolpad editor app in development mode",
      demandOption: false,
      default: false,
      hidden: true
    }
  };
  const parsedArgs = yargs(argv).scriptName("toolpad").command(
    ["$0 [dir]", "dev [dir]"],
    "Run Toolpad in development mode",
    {
      ...sharedRunOptions
    },
    (args) => devCommand(args)
  ).command(
    "start [dir]",
    "Run built Toolpad application in production mode",
    {
      ...sharedRunOptions
    },
    (args) => startCommand(args)
  ).command(
    "build [dir]",
    "Build Toolpad app for production",
    {
      ...sharedOptions
    },
    (args) => buildCommand(args)
  ).command(
    "editor [url]",
    "Run the Toolpad editor for a custom server",
    {
      url: {
        type: "string",
        describe: "URL of the Toolpad application",
        demandOption: true
      },
      dev: {
        type: "boolean",
        describe: "Run the Toolpad editor app in development mode",
        demandOption: false,
        default: false,
        hidden: true
      }
    },
    (args) => editorCommand(args)
  ).command("help", "Show help", {}, async () => {
    console.log(await parsedArgs.getHelp());
  }).help();
  parsedArgs.argv;
}
export {
  cli as default
};
//# sourceMappingURL=index.mjs.map