Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ jobs:
node-version-file: '.nvmrc'
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Generate Prisma
run: pnpm prisma:generate
- name: Lint
run: pnpm run lint
- name: Test
Expand All @@ -30,4 +32,3 @@ jobs:
with:
directory: ./coverage/
token: ${{ secrets.CODECOV_TOKEN }}

68 changes: 68 additions & 0 deletions src/meta/__tests__/globals-banner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { execFile } from 'node:child_process';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { promisify } from 'node:util';

import { afterAll, beforeAll, describe, expect, it } from 'vitest';

import { GLOBALS_BANNER } from '../build.js';

const execFileAsync = promisify(execFile);

/**
* The banner that `@prisma/client` >= 6.19.0 prepends to its ESM runtime, which esbuild inlines into
* the application bundle verbatim. The assignment to `globalThis` is the part that matters.
*/
const DEPENDENCY_BANNER = [
'import * as __banner_node_path from "node:path";',
'import * as __banner_node_url from "node:url";',
'const __filename = __banner_node_url.fileURLToPath(import.meta.url);',
"globalThis['__dirname'] = __banner_node_path.dirname(__filename);"
].join('\n');

describe('GLOBALS_BANNER', () => {
let outdir: string;

beforeAll(async () => {
outdir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'globals-banner-'));
});

afterAll(async () => {
await fs.promises.rm(outdir, { force: true, recursive: true });
});

it('should define the commonjs globals', () => {
expect(GLOBALS_BANNER).toContain('__dirname');
expect(GLOBALS_BANNER).toContain('__filename');
expect(GLOBALS_BANNER).toContain('require');
});

it('should not leave a bundled dependency unable to assign to globalThis', { timeout: 30000 }, async () => {
const esbuild = await import('esbuild');
const sourceDir = path.join(outdir, 'src');
await fs.promises.mkdir(sourceDir, { recursive: true });
await fs.promises.writeFile(
path.join(sourceDir, 'dependency.js'),
`${DEPENDENCY_BANNER}\nexport const value = 'ok';\n`
);
await fs.promises.writeFile(
path.join(sourceDir, 'entry.js'),
"import { value } from './dependency.js';\nconsole.log(value);\n"
);

const outfile = path.join(outdir, 'server.js');
await esbuild.build({
banner: { js: GLOBALS_BANNER },
bundle: true,
entryPoints: [path.join(sourceDir, 'entry.js')],
format: 'esm',
outfile,
platform: 'node'
});

// a non-writable property makes the inlined assignment throw, so the bundle dies before this resolves
const { stdout } = await execFileAsync(process.execPath, [outfile]);
expect(stdout.trim()).toBe('ok');
});
});
15 changes: 14 additions & 1 deletion src/meta/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@ import { swcPlugin } from './plugins/swc.js';

import type { UserConfigOptions } from '../user-config.js';

/**
* The banner prepended to the production bundle, which backfills the CommonJS globals that some
* dependencies expect to exist at runtime.
*
* These are defined `writable: true` deliberately. Some dependencies ship an esbuild-style banner of
* their own that assigns to `globalThis['__dirname']` (`@prisma/client` >= 6.19.0 does). Once inlined
* into this bundle, that assignment runs in module scope, where a non-writable property makes it throw
* `TypeError: Cannot assign to read only property` instead of failing silently, killing the process
* before the app bootstraps.
*/
export const GLOBALS_BANNER =
"Object.defineProperties(globalThis, { __dirname: { value: import.meta.dirname, writable: true }, __filename: { value: import.meta.filename, writable: true }, require: { value: (await import('module')).createRequire(import.meta.url), writable: true } });";

export function buildProd({
configFile,
verbose
Expand Down Expand Up @@ -63,7 +76,7 @@ export function buildProd({

await esbuild.build({
banner: {
js: "Object.defineProperties(globalThis, { __dirname: { value: import.meta.dirname, writable: false }, __filename: { value: import.meta.filename, writable: false }, require: { value: (await import('module')).createRequire(import.meta.url), writable: false } });"
js: GLOBALS_BANNER
},
bundle: true,
define,
Expand Down
Loading