import * as esbuild from "esbuild"; import { writeFileSync } from "fs"; import { join } from "path"; import TOML from "@iarna/toml"; import { getResocketDir, getWorkerFramework } from "./getAssets"; import { ReplaceSymbols } from "../constants"; import type { ResocketConfig } from "../types"; import { removeUndefined } from "../utils"; let ctx: esbuild.BuildContext; //we will use it to generate wrangler.toml instead let prevConfig: ResocketConfig; const getDurableBindingsFromRooms = (rooms: string | string[]) => { let roomsArray: string[]; if (Array.isArray(rooms)) { roomsArray = rooms; } else { roomsArray = [rooms]; } const durableBindings = roomsArray.map((room) => ({ name: room, class_name: room, })); const durableMigrations = { tag: "v0", new_classes: roomsArray.map((room) => room), }; return { durableBindings, durableMigrations }; }; const getDevWorkerFilePath = () => { return getResocketDir() + "/gen/worker.js"; }; export const createWranglerDevConfigFromResocketConfig = ( resocketConfig: ResocketConfig ) => { const { name, rooms, main, port, envs: vars, migrationMode: _autoMigs, ...wranglerConfig } = resocketConfig; const { durableBindings, durableMigrations } = getDurableBindingsFromRooms(rooms); const config = { name, main: getDevWorkerFilePath(), vars, dev: { port, }, ...wranglerConfig, durable_objects: { bindings: durableBindings }, migrations: [durableMigrations], }; const toml = TOML.stringify(removeUndefined(config)); return `#auto generated dont change \n` + toml; }; const getGeneratedDevDurableImports = (rooms: string | string[]) => { let gen = ""; const resocketDir = getResocketDir(); const genDurablePath = `${resocketDir.replaceAll("\\", "/")}/gen/durable.js`; Array.isArray(rooms) ? rooms.map((room) => { gen += `import {${room} as ${room}DO} from '${genDurablePath}';\n`; }) : `import {${rooms} as ${rooms}DO} from '${genDurablePath}';\n`; return gen; }; const getGeneratedDurableExports = (rooms: string | string[]) => { let gen = ""; Array.isArray(rooms) ? rooms.map((room) => { gen += `export const ${room} = _createDurable(${room}DO);\n _durables["${room}"] = ${room}DO\n`; }) : `export const ${rooms} = _createDurable(${rooms}DO);\n _durables["${rooms}"] = ${rooms}DO\n`; return gen; }; export const buildDev = async (file: string, config: ResocketConfig) => { const resocketDir = getResocketDir(); const isInitial = !ctx; //if no context is present, that means this is the initial build if (isInitial) { //build durable objects first ctx = await esbuild.context({ entryPoints: [file], bundle: true, outfile: `${resocketDir}/gen/durable.js`, format: "esm", platform: "node", }); } //this means we need to dispose context and rebuild else if (prevConfig && prevConfig.main !== config.main) { await ctx.dispose(); ctx = await esbuild.context({ entryPoints: [file], bundle: true, outfile: `${resocketDir}/gen/durable.js`, format: "esm", }); await ctx.watch(); } //write the wrangler.toml file incase of config changes //?question is that what if the esbuild context doesn not care about resocket.json changes? hmm something to keep in mind that we might still need to keep chokidar around for this use case if (!prevConfig || JSON.stringify(prevConfig) !== JSON.stringify(config)) { //run it after the prev build // const hasEnv = Object.keys(config.) //?hmm... maybe this should be only run after the resocket.json is changed, why? because this actually does not changes even if the core durables change> :genios await esbuild.build({ stdin: { contents: getWorkerFramework() .replace( ReplaceSymbols.ImportDurables, getGeneratedDevDurableImports(config.rooms) ) .replace( ReplaceSymbols.ExportDurables, getGeneratedDurableExports(config.rooms) ), // resolveDir: resocketDir, loader: "ts", }, // minify: true, bundle: false, outfile: `${resocketDir}/gen/worker.js`, format: "esm", }); prevConfig = config; writeFileSync( join(resocketDir, "/gen/wrangler.toml"), //hmm... either change the code to add envs to config or use esbuild degines heh createWranglerDevConfigFromResocketConfig(config) ); } if (isInitial) await ctx.watch(); return ctx; }; export const buildDeployDurable = async (file: string) => { //build durable objects first const data = await esbuild.build({ entryPoints: [file], bundle: true, format: "esm", minify: false, platform: "node", write: false, }); const bundle = data.outputFiles[0]; //should never happen if (!bundle) throw new Error("No output bundle found - should never happen"); return bundle; };