import path from "node:path"; import * as esbuild from "esbuild"; import { defineConfig } from "tsup"; import { EXTERNAL_DEPENDENCIES } from "./scripts/deps"; import type { Options } from "tsup"; const TEMPLATES_DIR = path.join(__dirname, "templates"); const workersContexts = new Map(); function embedWorkersPlugin({ isWatch, }: { isWatch: boolean; }): Exclude[0] { return { name: "embed-workers", setup(build) { const namespace = "embed-worker"; build.onResolve({ filter: /^worker:/ }, async (args) => { const name = args.path.substring("worker:".length); // Use `build.resolve()` API so Workers can be written as `m?[jt]s` files const result = await build.resolve("./" + name, { kind: "import-statement", resolveDir: TEMPLATES_DIR, }); if (result.errors.length > 0) { return { errors: result.errors }; } return { path: result.path, namespace }; }); build.onLoad({ filter: /.*/, namespace }, async (args) => { const ctx = workersContexts.get(args.path) ?? (await esbuild.context({ platform: "node", // Marks `node:*` imports as external conditions: ["workerd", "worker", "browser"], format: "esm", target: "esnext", bundle: true, sourcemap: process.env.SOURCEMAPS !== "false", sourcesContent: false, metafile: true, external: ["cloudflare:email", "cloudflare:workers"], entryPoints: [args.path], outdir: build.initialOptions.outdir, })); const result = await ctx.rebuild(); workersContexts.set(args.path, ctx); const watchFiles = Object.keys(result?.metafile?.inputs ?? {}); const scriptPath = Object.keys(result?.metafile?.outputs ?? {}).find( (filepath) => filepath.endsWith(".js") ); const contents = ` import path from "node:path"; const scriptPath = path.resolve(__dirname, "..", "${scriptPath}"); export default scriptPath; `; return { contents, loader: "js", watchFiles }; }); // If we don't dispose the contexts, they will keep running and block the build from exiting. // But we must not dispose them in watch mode, as that would break the incremental builds. if (!isWatch) { build.onDispose(async () => { for (const ctx of workersContexts.values()) { await ctx.dispose(); } }); } }, }; } export default defineConfig((options) => [ { treeshake: true, keepNames: true, entry: ["src/cli.ts"], platform: "node", format: "cjs", dts: { resolve: ["@cloudflare/workflows-shared/src/types"], }, outDir: "wrangler-dist", tsconfig: "tsconfig.json", metafile: true, external: EXTERNAL_DEPENDENCIES, sourcemap: process.env.SOURCEMAPS !== "false", inject: [path.join(__dirname, "import_meta_url.js")], // mainFields: ["module", "main"], define: { __RELATIVE_PACKAGE_PATH__: '".."', "import.meta.url": "import_meta_url", "process.env.NODE_ENV": `'${process.env.NODE_ENV || "production"}'`, "process.env.SPARROW_SOURCE_KEY": JSON.stringify( process.env.SPARROW_SOURCE_KEY ?? "" ), ...(process.env.ALGOLIA_APP_ID ? { ALGOLIA_APP_ID: `"${process.env.ALGOLIA_APP_ID}"` } : {}), ...(process.env.ALGOLIA_PUBLIC_KEY ? { ALGOLIA_PUBLIC_KEY: `"${process.env.ALGOLIA_PUBLIC_KEY}"` } : {}), ...(process.env.SENTRY_DSN ? { SENTRY_DSN: `"${process.env.SENTRY_DSN}"` } : {}), ...(process.env.WRANGLER_PRERELEASE_LABEL ? { WRANGLER_PRERELEASE_LABEL: `"${process.env.WRANGLER_PRERELEASE_LABEL}"`, } : {}), }, esbuildPlugins: [embedWorkersPlugin({ isWatch: !!options.watch })], esbuildOptions(esbuildOptions) { esbuildOptions.logOverride = { ...esbuildOptions.logOverride, // Suppress the warning for the intentional runtime dynamic // `import()` in `@cloudflare/config`'s `loadConfig` // (packages/config/src/load.ts). The import is unanalyzable by // design: the specifier is computed at runtime and the // `with: { cf: "no-cache" }` attribute is consumed by a Node // `registerHooks` resolver. esbuild already preserves the call // verbatim — only the warning is noise. // tsup's *DTS* pipeline (Rollup) also emits a separate // `INVALID_IMPORT_ATTRIBUTE` warning for the same call site // ("…the attribute will be removed"). That warning is also // cosmetic — the DTS step does not write `cli.js`, and `cli.d.ts` // contains no runtime import calls — but tsup 8.3 does not // expose a Rollup `onwarn` hook for the DTS pipeline, so we // currently cannot silence it. // Note: this is not necessary if we publish `@cloudflare/config` and include is as a dependency "unsupported-dynamic-import": "silent", }; }, }, ]);