Skip to content

Commit f4abd19

Browse files
fix: improve Next.js monorepo compatibility (#148)
1 parent b1ad466 commit f4abd19

3 files changed

Lines changed: 44 additions & 11 deletions

File tree

src/instrumentation/libraries/pg/Instrumentation.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ export class PgInstrumentation extends TdInstrumentationBase {
8383
// Submittable queries use EventEmitter pattern (row, end, error events)
8484
// and return the Query object itself, not a Promise
8585
if (self.isSubmittable(args[0])) {
86-
logger.debug(`[PgInstrumentation] Submittable query detected, passing through uninstrumented`);
86+
logger.debug(
87+
`[PgInstrumentation] Submittable query detected, passing through uninstrumented`,
88+
);
8789
return originalQuery.apply(this, args);
8890
}
8991

@@ -365,9 +367,25 @@ export class PgInstrumentation extends TdInstrumentationBase {
365367
return originalQuery.apply(context, args);
366368
} else {
367369
// Promise-based query
368-
const promise = originalQuery.apply(context, args);
370+
const result = originalQuery.apply(context, args);
371+
372+
// ORMs like Prisma, TypeORM, etc. may call pg.query() internally in ways
373+
// that return undefined instead of a Promise (e.g. Prisma's driver adapter).
374+
// Without this guard, we'd crash with "Cannot read properties of undefined
375+
// (reading 'then')".
376+
if (!result || typeof result.then !== "function") {
377+
logger.debug(
378+
`[PgInstrumentation] originalQuery returned non-thenable, passing through (${SpanUtils.getTraceInfo()})`,
379+
);
380+
try {
381+
SpanUtils.endSpan(spanInfo.span, { code: SpanStatusCode.OK });
382+
} catch (error) {
383+
logger.error(`[PgInstrumentation] error ending span:`, error);
384+
}
385+
return result;
386+
}
369387

370-
return promise
388+
return result
371389
.then((result: PgResult) => {
372390
logger.debug(
373391
`[PgInstrumentation] PG query completed successfully (${SpanUtils.getTraceInfo()})`,
@@ -497,23 +515,25 @@ export class PgInstrumentation extends TdInstrumentationBase {
497515
*
498516
* Reference for data type IDs: https://jdbc.postgresql.org/documentation/publicapi/constant-values.html
499517
*/
500-
private convertPostgresTypes(result: any, rowMode?: 'array'): any {
518+
private convertPostgresTypes(result: any, rowMode?: "array"): any {
501519
// Handle multi-statement results (wrapped format from _addOutputAttributesToSpan)
502520
if (result && result.isMultiStatement && Array.isArray(result.results)) {
503-
return result.results.map((singleResult: any) => this.convertPostgresTypes(singleResult, rowMode));
521+
return result.results.map((singleResult: any) =>
522+
this.convertPostgresTypes(singleResult, rowMode),
523+
);
504524
}
505525

506526
// Handle multi-statement results (array of Results - legacy/direct format)
507527
if (Array.isArray(result)) {
508-
return result.map(singleResult => this.convertPostgresTypes(singleResult, rowMode));
528+
return result.map((singleResult) => this.convertPostgresTypes(singleResult, rowMode));
509529
}
510530

511531
if (!result || !result.fields || !result.rows) {
512532
return result;
513533
}
514534

515535
// If rowMode is 'array', handle arrays differently
516-
if (rowMode === 'array') {
536+
if (rowMode === "array") {
517537
// For array mode, rows are arrays of values indexed by column position
518538
const convertedRows = result.rows.map((row: any) => {
519539
if (!Array.isArray(row)) return row; // Safety check
@@ -655,7 +675,7 @@ export class PgInstrumentation extends TdInstrumentationBase {
655675
// Wrap in object with 'results' key to ensure CLI can handle it properly
656676
outputValue = {
657677
isMultiStatement: true,
658-
results: result.map(r => ({
678+
results: result.map((r) => ({
659679
command: r.command,
660680
rowCount: r.rowCount,
661681
oid: r.oid,

src/nextjs/utils.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,25 @@ import type { ParsedVersion } from "./types";
99
* @returns The Next.js version string, or undefined if not found
1010
*/
1111
export function getNextjsVersion(): string | undefined {
12+
// Try cwd-based lookup first (works for standard project layouts)
1213
try {
13-
// Try to read from node_modules/next/package.json
1414
const nextPackageJsonPath = path.join(process.cwd(), "node_modules", "next", "package.json");
1515

1616
if (fs.existsSync(nextPackageJsonPath)) {
1717
const packageJson = JSON.parse(fs.readFileSync(nextPackageJsonPath, "utf-8"));
1818
return packageJson.version;
1919
}
20-
} catch (error) {
21-
// Silent failure - we'll warn the user in the main function
20+
} catch {
21+
// Fall through to require.resolve
22+
}
23+
24+
// Fallback: use Node's module resolution, which handles hoisted packages in monorepos
25+
try {
26+
const resolvedPath = require.resolve("next/package.json", { paths: [process.cwd()] });
27+
const packageJson = JSON.parse(fs.readFileSync(resolvedPath, "utf-8"));
28+
return packageJson.version;
29+
} catch {
30+
// next is not resolvable
2231
}
2332

2433
return undefined;

src/nextjs/withTuskDrift.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ export function withTuskDrift(
140140
// This ensures packages remain external regardless of which bundler is used
141141
serverExternalPackages: [...(config.serverExternalPackages || []), ...coreExternals],
142142

143+
// Preserve user's turbopack config (or set empty) so Next.js 16+ doesn't error
144+
// when it sees our webpack config without a turbopack counterpart
145+
turbopack: (config as any).turbopack || {},
146+
143147
webpack: (webpackConfig: any, webpackOptions: any) => {
144148
if (webpackOptions.isServer) {
145149
// Safely handle different externals formats (array, function, object, or undefined)

0 commit comments

Comments
 (0)