first commit
Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled

This commit is contained in:
Arian Tron
2026-03-10 19:37:31 +03:30
commit 61f56f997c
27684 changed files with 2784175 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
import { NextResponse } from 'next/server'
import pino from 'pino'
export async function GET() {
try {
// Create a pino logger with a transport
// This internally uses thread-stream which creates worker_threads
const logger = pino({
transport: {
target: 'pino/file',
options: { destination: 1 }, // stdout
},
})
// Log a test message - this triggers the transport worker
logger.info('pino test message')
// Flush the logger to ensure the message is sent
// Use nextTick to allow async initialization of the transport
await new Promise((resolve) => process.nextTick(resolve))
return NextResponse.json({
success: true,
message: 'pino logger with transport initialized successfully',
})
} catch (error) {
return NextResponse.json(
{ success: false, error: String(error) },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,46 @@
import { Worker } from 'node:worker_threads'
import { NextResponse } from 'next/server'
interface PngInfo {
url: string
width: number
height: number
}
export async function GET() {
try {
const worker = new Worker('./app/worker-dir/png-worker.ts')
const pngInfo = await new Promise<PngInfo>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Worker timeout'))
}, 5000)
worker.on('message', (msg: PngInfo) => {
clearTimeout(timeout)
resolve(msg)
})
worker.on('error', (err) => {
clearTimeout(timeout)
reject(err)
})
worker.on('exit', (code) => {
if (code !== 0) {
clearTimeout(timeout)
reject(new Error(`Worker stopped with exit code ${code}`))
}
})
worker.postMessage('get-png-info')
})
await worker.terminate()
return NextResponse.json({ success: true, pngInfo })
} catch (error) {
return NextResponse.json(
{ success: false, error: String(error) },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,41 @@
import { Worker } from 'node:worker_threads'
import { NextResponse } from 'next/server'
export async function GET() {
try {
// Test with a relative path instead of __filename
const worker = new Worker('./app/worker-dir/simple-worker.ts')
const message = await new Promise<string>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Worker timeout'))
}, 5000)
worker.on('message', (msg) => {
clearTimeout(timeout)
resolve(msg)
})
worker.on('error', (err) => {
clearTimeout(timeout)
reject(err)
})
worker.on('exit', (code) => {
if (code !== 0) {
clearTimeout(timeout)
reject(new Error(`Worker stopped with exit code ${code}`))
}
})
worker.postMessage('ping')
})
await worker.terminate()
return NextResponse.json({ success: true, message })
} catch (error) {
return NextResponse.json(
{ success: false, error: String(error) },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,62 @@
import { Worker, isMainThread, parentPort } from 'node:worker_threads'
import { NextResponse } from 'next/server'
// This file tests self-referencing worker threads using __filename
// This pattern is used by libraries like @duckdb/duckdb-wasm
if (!isMainThread && parentPort) {
// Worker thread - handle messages
parentPort.on('message', (msg) => {
if (msg === 'ping') {
parentPort!.postMessage('pong')
}
})
}
export async function GET() {
if (!isMainThread) {
// If we're in a worker, don't try to create another worker
return NextResponse.json({ error: 'Already in worker' }, { status: 500 })
}
// Log __filename for debugging
console.log('__filename:', __filename)
console.log('typeof __filename:', typeof __filename)
try {
// Create a worker using __filename - this is the pattern that triggers the cycle bug
const worker = new Worker(__filename)
const message = await new Promise<string>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Worker timeout'))
}, 5000)
worker.on('message', (msg) => {
clearTimeout(timeout)
resolve(msg)
})
worker.on('error', (err) => {
clearTimeout(timeout)
reject(err)
})
worker.on('exit', (code) => {
if (code !== 0) {
clearTimeout(timeout)
reject(new Error(`Worker stopped with exit code ${code}`))
}
})
worker.postMessage('ping')
})
await worker.terminate()
return NextResponse.json({ success: true, message })
} catch (error) {
return NextResponse.json(
{ success: false, error: String(error) },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,28 @@
import { Worker } from 'node:worker_threads'
export async function GET() {
try {
const worker = new Worker('./app/worker-dir/workerdata-check-worker.ts')
const result = await new Promise<{
workerDataKeys: string[]
hasTurbopackKeys: boolean
turbopackKeys: string[]
}>((resolve, reject) => {
worker.on('message', resolve)
worker.on('error', reject)
})
await worker.terminate()
return Response.json({
success: true,
...result,
})
} catch (error) {
return Response.json(
{ success: false, error: String(error) },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,11 @@
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>{children}</body>
</html>
)
}

View File

@@ -0,0 +1,16 @@
import { parentPort } from 'node:worker_threads'
import pngUrl from './test-image.png'
if (parentPort) {
parentPort.on('message', (msg) => {
if (msg === 'get-png-info') {
// Return the PNG info - the URL will be fetched by the client
// to verify it's correctly formed and accessible
parentPort!.postMessage({
url: pngUrl.src,
width: pngUrl.width,
height: pngUrl.height,
})
}
})
}

View File

@@ -0,0 +1,9 @@
import { parentPort } from 'node:worker_threads'
if (parentPort) {
parentPort.on('message', (msg) => {
if (msg === 'ping') {
parentPort!.postMessage('pong from simple worker')
}
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@@ -0,0 +1,14 @@
import { parentPort, workerData } from 'node:worker_threads'
if (parentPort) {
// Check if any __turbopack prefixed keys are visible in workerData
const turbopackKeys = Object.keys(workerData || {}).filter((key) =>
key.startsWith('__turbopack')
)
parentPort.postMessage({
workerDataKeys: Object.keys(workerData || {}),
hasTurbopackKeys: turbopackKeys.length > 0,
turbopackKeys,
})
}

View File

@@ -0,0 +1,8 @@
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
deploymentId: 'test-deployment-id',
}
module.exports = nextConfig

View File

@@ -0,0 +1,88 @@
import { isNextDev, nextTestSetup } from 'e2e-utils'
describe('node-worker-threads', () => {
const { next, skipped, isTurbopack } = nextTestSetup({
files: __dirname,
skipDeployment: true,
dependencies: {
pino: '9.6.0',
},
})
if (skipped) {
return
}
// These tests are Turbopack-specific since they rely on Turbopack's worker bundling
if (!isTurbopack) {
it.skip('webpack doesnt support bundling worker-threads', () => {})
return
}
it('should handle simple worker with relative path', async () => {
const res = await next.fetch('/api/simple-worker-test')
const data = await res.json()
expect(res.status).toBe(200)
expect(data.success).toBe(true)
expect(data.message).toBe('pong from simple worker')
})
it('should handle self-referencing worker with __filename', async () => {
const res = await next.fetch('/api/worker-test')
const data = await res.json()
expect(res.status).toBe(200)
expect(data.success).toBe(true)
expect(data.message).toBe('pong')
})
it('should handle pino logger with transport (thread-stream)', async () => {
// Pino with transports uses thread-stream internally, which creates worker_threads
// with a broad pattern like join(__dirname, 'lib', 'worker.js') that can match
// non-evaluatable files like package.json. This tests that we properly downgrade
// those errors to warnings via loose_errors.
const res = await next.fetch('/api/pino-test')
const data = await res.json()
expect(res.status).toBe(200)
expect(data.success).toBe(true)
})
it('should not expose __turbopack internal workerData to user code', async () => {
// Verify that internal __turbopack_globals__ data used to forward globals
// is not visible to user code accessing workerData
const res = await next.fetch('/api/workerdata-check')
const data = await res.json()
expect(res.status).toBe(200)
expect(data.success).toBe(true)
// The __turbopack_globals__ key should NOT be visible to user code
expect(data.hasTurbopackKeys).toBe(false)
expect(data.turbopackKeys).toEqual([])
})
it('should handle PNG file import in worker', async () => {
// Test that static assets (like PNG images) can be imported and used in workers
// The server worker returns the PNG URL, then we fetch it from the client
// to verify the URL is correctly formed and accessible
const res = await next.fetch('/api/png-worker-test')
const data = await res.json()
expect(res.status).toBe(200)
expect(data.success).toBe(true)
expect(data.pngInfo).toBeDefined()
expect(data.pngInfo.width).toBe(1)
expect(data.pngInfo.height).toBe(1)
const url = new URL(data.pngInfo.url, 'http://localhost')
expect(url.pathname).toMatch(
/\/_next\/static.*\/test-image\.[a-f0-9]+\.png/
)
if (!isNextDev) {
expect(next.assetToken).toMatch(/.+/)
expect(url.searchParams.get('dpl')).toBe(next.assetToken)
}
// Now fetch the PNG URL from the client to verify it's accessible
// This tests that the URL generated by the server worker is correct
const pngRes = await next.fetch(data.pngInfo.url)
expect(pngRes.status).toBe(200)
expect(pngRes.headers.get('content-type')).toBe('image/png')
})
})