Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow custom stderr and stdout in server #564

Merged
merged 3 commits into from
Jan 16, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 30 additions & 9 deletions node/bridge.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { promises as fs } from 'fs'
import { promises as fs, type WriteStream } from 'fs'
import path from 'path'
import process from 'process'

Expand Down Expand Up @@ -35,9 +35,11 @@ interface ProcessRef {
}

interface RunOptions {
pipeOutput?: boolean
env?: NodeJS.ProcessEnv
extendEnv?: boolean
pipeOutput?: boolean
stderr?: WriteStream
stdout?: WriteStream
rejectOnExitCode?: boolean
}

Expand Down Expand Up @@ -159,14 +161,30 @@ class DenoBridge {
return this.currentDownload
}

private static runWithBinary(binaryPath: string, args: string[], options?: Options, pipeOutput?: boolean) {
private static runWithBinary(
binaryPath: string,
args: string[],
{
options,
pipeOutput,
stderr,
stdout,
}: { options?: Options; pipeOutput?: boolean; stderr?: WriteStream; stdout?: WriteStream },
) {
const runDeno = execa(binaryPath, args, options)

if (pipeOutput) {
runDeno.stdout?.pipe(process.stdout)
if (stderr) {
runDeno.stderr?.pipe(stderr)
} else if (pipeOutput) {
runDeno.stderr?.pipe(process.stderr)
}

if (stdout) {
runDeno.stdout?.pipe(stdout)
} else if (pipeOutput) {
runDeno.stdout?.pipe(process.stdout)
}

return runDeno
}

Expand Down Expand Up @@ -219,25 +237,28 @@ class DenoBridge {

// Runs the Deno CLI in the background and returns a reference to the child
// process, awaiting its execution.
async run(args: string[], { pipeOutput, env: inputEnv, extendEnv = true, rejectOnExitCode = true }: RunOptions = {}) {
async run(
args: string[],
{ env: inputEnv, extendEnv = true, rejectOnExitCode = true, stderr, stdout }: RunOptions = {},
) {
const { path: binaryPath } = await this.getBinaryPath()
const env = this.getEnvironmentVariables(inputEnv)
const options: Options = { env, extendEnv, reject: rejectOnExitCode }

return DenoBridge.runWithBinary(binaryPath, args, options, pipeOutput)
return DenoBridge.runWithBinary(binaryPath, args, { options, stderr, stdout })
}

// Runs the Deno CLI in the background, assigning a reference of the child
// process to a `ps` property in the `ref` argument, if one is supplied.
async runInBackground(
args: string[],
ref?: ProcessRef,
{ pipeOutput, env: inputEnv, extendEnv = true }: RunOptions = {},
{ env: inputEnv, extendEnv = true, stderr, stdout }: RunOptions = {},
) {
const { path: binaryPath } = await this.getBinaryPath()
const env = this.getEnvironmentVariables(inputEnv)
const options: Options = { env, extendEnv }
const ps = DenoBridge.runWithBinary(binaryPath, args, options, pipeOutput)
const ps = DenoBridge.runWithBinary(binaryPath, args, { options, stderr, stdout })

if (ref !== undefined) {
// eslint-disable-next-line no-param-reassign
Expand Down
12 changes: 12 additions & 0 deletions node/server/server.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { createWriteStream } from 'fs'
import { readFile } from 'fs/promises'
import { join } from 'path'
import process from 'process'

import getPort from 'get-port'
import fetch from 'node-fetch'
import tmp from 'tmp-promise'
import { v4 as uuidv4 } from 'uuid'
import { test, expect } from 'vitest'

Expand Down Expand Up @@ -107,6 +109,9 @@ test('Starts a server and serves requests for edge functions', async () => {
})

test('Serves edge functions in a monorepo setup', async () => {
const tmpFile = await tmp.file()
const stderr = createWriteStream(tmpFile.path)

const rootPath = join(fixturesDir, 'monorepo_npm_module')
const basePath = join(rootPath, 'packages', 'frontend')
const paths = {
Expand All @@ -121,6 +126,7 @@ test('Serves edge functions in a monorepo setup', async () => {
port,
rootPath,
servePath,
stderr,
})

const functions = [
Expand All @@ -141,6 +147,7 @@ test('Serves edge functions in a monorepo setup', async () => {
},
options,
)

expect(features).toEqual({ npmModules: true })
expect(success).toBe(true)
expect(functionsConfig).toEqual([{ path: '/func1' }])
Expand All @@ -161,8 +168,13 @@ test('Serves edge functions in a monorepo setup', async () => {
'X-NF-Request-ID': uuidv4(),
},
})

expect(response1.status).toBe(200)
expect(await response1.text()).toBe(
`<parent-1><child-1>JavaScript</child-1></parent-1>, <parent-2><child-2><grandchild-1>APIs<cwd>${process.cwd()}</cwd></grandchild-1></child-2></parent-2>, <parent-3><child-2><grandchild-1>Markup<cwd>${process.cwd()}</cwd></grandchild-1></child-2></parent-3>`,
)

expect(await readFile(tmpFile.path, 'utf8')).toContain('[func1] Something is on fire')

await tmpFile.cleanup()
})
25 changes: 24 additions & 1 deletion node/server/server.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { WriteStream } from 'fs'
import { readdir, unlink } from 'fs/promises'
import { join } from 'path'

Expand Down Expand Up @@ -28,6 +29,8 @@ interface PrepareServerOptions {
formatExportTypeError?: FormatFunction
formatImportError?: FormatFunction
logger: Logger
stderr?: WriteStream
stdout?: WriteStream
port: number
rootPath?: string
}
Expand Down Expand Up @@ -60,6 +63,8 @@ const prepareServer = ({
logger,
port,
rootPath,
stderr,
stdout,
}: PrepareServerOptions) => {
const processRef: ProcessRef = {}
const startServer = async (
Expand Down Expand Up @@ -134,9 +139,11 @@ const prepareServer = ({
// with variables from the user's system, since those will not be available
// in the production environment.
await deno.runInBackground(['run', ...denoFlags, ...extraDenoFlags, stage2Path, ...applicationFlags], processRef, {
pipeOutput: true,
env,
extendEnv: false,
pipeOutput: true,
stderr,
stdout,
})

let functionsConfig: FunctionConfig[] = []
Expand Down Expand Up @@ -190,6 +197,8 @@ interface ServeOptions {
port: number
rootPath?: string
servePath: string
stderr?: WriteStream
stdout?: WriteStream
userLogger?: LogFunction
systemLogger?: LogFunction
}
Expand Down Expand Up @@ -274,6 +283,18 @@ export const serve = async ({
*/
servePath,

/**
* Writable stream to receive the stderr of the server process. If not set,
* the stderr of the parent process will be used.
*/
stderr,

/**
* Writable stream to receive the stdout of the server process. If not set,
* the stdout of the parent process will be used.
*/
stdout,

/**
* Custom logging function to be used for user-facing messages. Defaults to
* `console.log`.
Expand Down Expand Up @@ -330,6 +351,8 @@ export const serve = async ({
formatExportTypeError,
formatImportError,
logger,
stderr,
stdout,
port,
rootPath,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ await Promise.resolve()
new HTMLRewriter()

export default async () => {
console.error('Something is on fire')

const text = [parent1('JavaScript'), parent2('APIs'), parent3('Markup')].join(', ')

return new Response(echo(text))
Expand Down