Skip to content

feat: add a fake req to the repl to make life a little easier #35

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

Merged
merged 1 commit into from
Jun 14, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ coverage
.nyc_output
*.log
.tap_output
.vscode

.yarn/*
!.yarn/patches
Expand Down
25 changes: 13 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,18 @@
"dependencies": {
"@godaddy/terminus": "^4.12.1",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/auto-instrumentations-node": "^0.60.0",
"@opentelemetry/auto-instrumentations-node": "^0.60.1",
"@opentelemetry/exporter-prometheus": "^0.202.0",
"@opentelemetry/instrumentation-dns": "^0.46.0",
"@opentelemetry/instrumentation-express": "^0.51.0",
"@opentelemetry/instrumentation-generic-pool": "^0.46.0",
"@opentelemetry/instrumentation-graphql": "^0.50.0",
"@opentelemetry/instrumentation-http": "^0.202.0",
"@opentelemetry/instrumentation-ioredis": "^0.50.0",
"@opentelemetry/instrumentation-net": "^0.46.0",
"@opentelemetry/instrumentation-net": "^0.46.1",
"@opentelemetry/instrumentation-pg": "^0.54.0",
"@opentelemetry/instrumentation-pino": "^0.49.0",
"@opentelemetry/instrumentation-undici": "^0.13.0",
"@opentelemetry/instrumentation-undici": "^0.13.1",
"@opentelemetry/resource-detector-container": "^0.7.2",
"@opentelemetry/resource-detector-gcp": "^0.36.0",
"@opentelemetry/sdk-node": "^0.202.0",
Expand All @@ -93,9 +93,9 @@
"cookie-parser": "^1.4.7",
"dotenv": "^16.5.0",
"express": "^5.1.0",
"express-openapi-validator": "^5.5.3",
"glob": "^11.0.2",
"import-in-the-middle": "^1.14.0",
"express-openapi-validator": "^5.5.7",
"glob": "^11.0.3",
"import-in-the-middle": "^1.14.2",
"minimist": "^1.2.8",
"moderndash": "^4.0.0",
"opentelemetry-resource-detector-sync-api": "^0.30.0",
Expand All @@ -111,26 +111,27 @@
"@semantic-release/exec": "^7.1.0",
"@semantic-release/github": "^11.0.3",
"@semantic-release/release-notes-generator": "^14.0.3",
"@types/cookie-parser": "^1.4.8",
"@types/express": "^5.0.2",
"@types/cookie-parser": "^1.4.9",
"@types/express": "^5.0.3",
"@types/minimist": "^1.2.5",
"@types/node": "^22.15.29",
"@types/node": "^22.15.31",
"@types/request-ip": "^0.0.41",
"@types/supertest": "^6.0.3",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"coconfig": "^1.6.2",
"eslint": "^8.57.1",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^4.4.2",
"eslint-import-resolver-typescript": "^4.4.3",
"eslint-plugin-import": "^2.31.0",
"pino-pretty": "^13.0.0",
"pinst": "^3.0.0",
"prettier": "^3.5.3",
"supertest": "^7.1.1",
"tsconfig-paths": "^4.2.0",
"tsx": "^4.19.4",
"tsx": "^4.20.3",
"typescript": "^5.8.3",
"vitest": "^3.2.1"
"vitest": "^3.2.3"
},
"resolutions": {
"qs": "^6.11.0"
Expand Down
7 changes: 6 additions & 1 deletion src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import { config } from 'dotenv';
import { readPackageUp } from 'read-package-up';
import type { NormalizedPackageJson } from 'read-package-up';

import type { AnyServiceLocals, RequestLocals, ServiceLocals, ServiceStartOptions } from './types.js';
import type {
AnyServiceLocals,
RequestLocals,
ServiceLocals,
ServiceStartOptions,
} from './types.js';
import { isDev } from './env.js';
import { startWithTelemetry } from './telemetry/index.js';
import { ConfigurationSchema } from './config/schema.js';
Expand Down
2 changes: 1 addition & 1 deletion src/development/port-finder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,5 @@ async function getEphemeralPort(): Promise<number> {
}

export async function getAvailablePort(basePort: number): Promise<number> {
return (isTest() || process.env.TEST_RUNNER) ? getEphemeralPort() : findPort(basePort);
return isTest() || process.env.TEST_RUNNER ? getEphemeralPort() : findPort(basePort);
}
22 changes: 16 additions & 6 deletions src/development/repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,23 @@ export function serviceRepl<SLocals extends AnyServiceLocals = ServiceLocals<Con
codepath: string | undefined,
onExit: () => void,
) {
class FakeReq {
locals: { app: ServiceExpress<SLocals> } = { app };
headers: Record<string, string | string[] | undefined> = {};
query = new URLSearchParams();
body: unknown = {};

constructor(public path: string) {
this.locals.app = app;
}
}

const rl = repl.start({
prompt: '> ',
});
Object.assign(rl.context, app.locals, {
app,
req: new FakeReq('/'),
dump(o: unknown) {
// eslint-disable-next-line no-console
console.log(JSON.stringify(o, null, '\t'));
Expand All @@ -47,11 +59,9 @@ export function serviceRepl<SLocals extends AnyServiceLocals = ServiceLocals<Con
rl.on('exit', onExit);
}

async function loadReplFunctions<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>>(
app: ServiceExpress<SLocals>,
codepath: string | undefined,
rl: REPLServer,
) {
async function loadReplFunctions<
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
>(app: ServiceExpress<SLocals>, codepath: string | undefined, rl: REPLServer) {
if (!codepath) {
return;
}
Expand Down Expand Up @@ -104,7 +114,7 @@ type ReplAny = any;
*/
export function repl$<
S extends ServiceExpress<ReplAny>,
T extends (app: S, ...args: ReplAny[]) => ReplAny
T extends (app: S, ...args: ReplAny[]) => ReplAny,
>(fn: T, name?: string) {
const functionName = name || fn.name;
if (!functionName) {
Expand Down
54 changes: 33 additions & 21 deletions src/express-app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export async function startApp<
Object.assign(mergeObject, {
trace_id: ctx.traceId,
span_id: ctx.spanId,
trace_flags: ctx.traceFlags
trace_flags: ctx.traceFlags,
});
}
}
Expand All @@ -70,28 +70,28 @@ export async function startApp<

const logger = shouldPrettyPrint
? pino(
{
transport: {
target: 'pino-pretty',
options: {
colorize: true,
{
transport: {
target: 'pino-pretty',
options: {
colorize: true,
},
},
mixin: poorMansOtlp,
},
mixin: poorMansOtlp,
},
destination,
)
destination,
)
: pino(
{
formatters: {
level(label) {
return { level: label };
{
formatters: {
level(label) {
return { level: label };
},
},
mixin: poorMansOtlp,
},
mixin: poorMansOtlp,
},
destination,
);
destination,
);

const serviceImpl = service();
assert(serviceImpl?.start, 'Service function did not return a conforming object');
Expand Down Expand Up @@ -192,7 +192,11 @@ export async function startApp<
);
}
if (routing?.bodyParsers?.form) {
app.use(express.urlencoded(typeof routing.bodyParsers.form === 'object' ? routing.bodyParsers.form : {}));
app.use(
express.urlencoded(
typeof routing.bodyParsers.form === 'object' ? routing.bodyParsers.form : {},
),
);
}

if (serviceImpl.authorize) {
Expand Down Expand Up @@ -259,7 +263,13 @@ export async function startApp<
);
}
if (routing?.openapi) {
const openApiMiddleware = await openApi(app, rootDirectory, codepath, codePattern, options.openApiOptions);
const openApiMiddleware = await openApi(
app,
rootDirectory,
codepath,
codePattern,
options.openApiOptions,
);
app.use(openApiMiddleware);
}

Expand Down Expand Up @@ -352,7 +362,9 @@ export async function listen<SLocals extends AnyServiceLocals = ServiceLocals<Co
onShutdown() {
return Promise.resolve()
.then(() => service.stop?.(app))
.then(() => { logger.info('Service stop complete'); })
.then(() => {
logger.info('Service stop complete');
})
.then(shutdownHandler || (() => Promise.resolve()))
.then(() => logger.info('Graceful shutdown complete'))
.catch((error) => logger.error(error, 'Error terminating tracing'))
Expand Down
20 changes: 10 additions & 10 deletions src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export async function openApi<

try {
app.locals.openApiSpecification = await new OpenAPIFramework({ apiDoc: apiSpec })
.initialize({ visitApi() { } })
.initialize({ visitApi() {} })
.then((docs) => docs.apiDoc)
.catch((error) => {
app.locals.logger.error(error, 'Failed to parse and load OpenAPI spec');
Expand Down Expand Up @@ -130,15 +130,15 @@ export async function openApi<
// by setting validateResponses to false in the config.
...(getNodeEnv() === 'test'
? {
validateResponses: {
onError(error: Error, body: unknown, req: Request) {
console.log('Response body fails validation: ', error);
console.log('Emitted from:', req.originalUrl);
console.debug(body);
throw error;
validateResponses: {
onError(error: Error, body: unknown, req: Request) {
console.log('Response body fails validation: ', error);
console.log('Emitted from:', req.originalUrl);
console.debug(body);
throw error;
},
},
},
}
}
: {}),
...(typeof routing.openapi === 'object' ? routing.openapi : {}),
...openApiOptions,
Expand All @@ -148,6 +148,6 @@ export async function openApi<
} finally {
if (_window) {
(global as { window: unknown }).window = _window;
};
}
}
}
12 changes: 1 addition & 11 deletions src/telemetry/hook-modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,6 @@ import module from 'node:module';
module.register('import-in-the-middle/hook.mjs', import.meta.url, {
parentURL: import.meta.url,
data: {
include: [
'express',
'pino',
'http',
'dns',
'net',
'pg',
'ioredis',
'undici',
'generic-pool',
],
include: ['express', 'pino', 'http', 'dns', 'net', 'pg', 'ioredis', 'undici', 'generic-pool'],
},
});
2 changes: 1 addition & 1 deletion src/telemetry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export async function startWithTelemetry<
await startGlobalTelemetry(options.name);

// eslint-disable-next-line import/no-unresolved, @typescript-eslint/no-var-requires
const { startApp, listen } = await import('../express-app/app.js') as {
const { startApp, listen } = (await import('../express-app/app.js')) as {
startApp: StartAppFn<SLocals, RLocals>;
listen: ListenFn<SLocals>;
};
Expand Down
5 changes: 4 additions & 1 deletion src/telemetry/instrumentations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { DnsInstrumentation } from '@opentelemetry/instrumentation-dns';
import { ExpressInstrumentation, SpanNameHook } from '@opentelemetry/instrumentation-express';
import { UndiciInstrumentation } from '@opentelemetry/instrumentation-undici';
import { GenericPoolInstrumentation } from '@opentelemetry/instrumentation-generic-pool';
import { HttpInstrumentation, IgnoreIncomingRequestFunction } from '@opentelemetry/instrumentation-http';
import {
HttpInstrumentation,
IgnoreIncomingRequestFunction,
} from '@opentelemetry/instrumentation-http';
import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis';
import { NetInstrumentation } from '@opentelemetry/instrumentation-net';
import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql';
Expand Down
Loading
Loading