Skip to content

Commit 8108dbf

Browse files
committed
feat: export addToRepl function that will cause load into repl
1 parent c495a96 commit 8108dbf

File tree

8 files changed

+256
-163
lines changed

8 files changed

+256
-163
lines changed

__tests__/fake-serv/src/handlers/hello.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ServiceHandler } from '../../../../src/index';
1+
import { addToRepl, ServiceHandler } from '../../../../src/index';
22
import { FakeServLocals } from '../index';
33

44
export const get: ServiceHandler<FakeServLocals> = async (req, res) => {
@@ -8,3 +8,9 @@ export const get: ServiceHandler<FakeServLocals> = async (req, res) => {
88
export const post: ServiceHandler<FakeServLocals> = async (req, res) => {
99
res.sendStatus(204);
1010
};
11+
12+
export function thisIsATest() {
13+
return 'Yes, true';
14+
}
15+
16+
addToRepl(thisIsATest);

package.json

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,25 +62,25 @@
6262
"@opentelemetry/api": "^1.9.0",
6363
"@opentelemetry/exporter-prometheus": "^0.53.0",
6464
"@opentelemetry/instrumentation-dns": "^0.39.0",
65-
"@opentelemetry/instrumentation-express": "^0.42.0",
65+
"@opentelemetry/instrumentation-express": "^0.43.0",
6666
"@opentelemetry/instrumentation-generic-pool": "^0.39.0",
6767
"@opentelemetry/instrumentation-graphql": "^0.43.0",
6868
"@opentelemetry/instrumentation-http": "^0.53.0",
6969
"@opentelemetry/instrumentation-ioredis": "^0.43.0",
7070
"@opentelemetry/instrumentation-net": "^0.39.0",
71-
"@opentelemetry/instrumentation-pg": "^0.44.0",
71+
"@opentelemetry/instrumentation-pg": "^0.45.1",
7272
"@opentelemetry/instrumentation-pino": "^0.42.0",
7373
"@opentelemetry/instrumentation-undici": "^0.6.0",
74-
"@opentelemetry/resource-detector-container": "^0.4.1",
75-
"@opentelemetry/resource-detector-gcp": "^0.29.11",
74+
"@opentelemetry/resource-detector-container": "^0.4.3",
75+
"@opentelemetry/resource-detector-gcp": "^0.29.12",
7676
"@opentelemetry/sdk-node": "^0.53.0",
7777
"@opentelemetry/semantic-conventions": "^1.27.0",
7878
"@sesamecare-oss/confit": "^2.2.1",
7979
"@sesamecare-oss/opentelemetry-node-metrics": "^1.1.0",
8080
"ajv": "^8.17.1",
81-
"cookie-parser": "^1.4.6",
81+
"cookie-parser": "^1.4.7",
8282
"dotenv": "^16.4.5",
83-
"express": "^5.0.0",
83+
"express": "^5.0.1",
8484
"express-openapi-validator": "^5.3.7",
8585
"glob": "^8.1.0",
8686
"lodash": "^4.17.21",
@@ -95,29 +95,29 @@
9595
"@openapi-typescript-infra/coconfig": "^4.4.0",
9696
"@semantic-release/commit-analyzer": "^13.0.0",
9797
"@semantic-release/exec": "^6.0.3",
98-
"@semantic-release/github": "^10.3.5",
98+
"@semantic-release/github": "^11.0.0",
9999
"@semantic-release/release-notes-generator": "^14.0.1",
100100
"@types/cookie-parser": "^1.4.7",
101-
"@types/express": "^4.17.21",
101+
"@types/express": "^5.0.0",
102102
"@types/glob": "^8.1.0",
103-
"@types/lodash": "^4.17.7",
103+
"@types/lodash": "^4.17.10",
104104
"@types/minimist": "^1.2.5",
105-
"@types/node": "^20.16.5",
105+
"@types/node": "^20.16.11",
106106
"@types/request-ip": "^0.0.41",
107107
"@types/supertest": "^6.0.2",
108108
"@typescript-eslint/eslint-plugin": "^6.21.0",
109109
"@typescript-eslint/parser": "^6.21.0",
110110
"coconfig": "^1.5.2",
111111
"eslint": "^8.57.1",
112112
"eslint-config-prettier": "^9.1.0",
113-
"eslint-plugin-import": "^2.30.0",
113+
"eslint-plugin-import": "^2.31.0",
114114
"pino-pretty": "^11.2.2",
115115
"pinst": "^3.0.0",
116116
"supertest": "^7.0.0",
117117
"ts-node": "^10.9.2",
118118
"tsconfig-paths": "^4.2.0",
119-
"typescript": "^5.6.2",
120-
"vitest": "^2.1.1"
119+
"typescript": "^5.6.3",
120+
"vitest": "^2.1.2"
121121
},
122122
"resolutions": {
123123
"qs": "^6.11.0"

src/bin/start-service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ const noTelemetry = (argv.repl || isDev()) && !argv.telemetry;
1919
bootstrap({
2020
...argv,
2121
telemetry: !noTelemetry,
22-
}).then(({ app, server }) => {
22+
}).then(({ app, codepath, server }) => {
2323
if (argv.repl) {
24-
serviceRepl(app, () => {
24+
serviceRepl(app, codepath, () => {
2525
server?.close();
2626
});
2727
}

src/bootstrap.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,5 +129,5 @@ export async function bootstrap<
129129
const { startApp, listen } = await import('./express-app/app.js');
130130
const app = await startApp<SLocals, RLocals>(opts);
131131
const server = argv?.nobind ? undefined : await listen(app);
132-
return { server, app };
132+
return { server, app, codepath };
133133
}

src/development/repl.ts

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
import repl from 'repl';
1+
import repl, { REPLServer } from 'repl';
2+
import fs from 'fs';
23
import path from 'path';
34

5+
import { glob } from 'glob';
6+
47
import { AnyServiceLocals, ServiceExpress, ServiceLocals } from '../types';
58
import { ConfigurationSchema } from '../config/schema';
69

710
export function serviceRepl<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>>(
811
app: ServiceExpress<SLocals>,
12+
codepath: string | undefined,
913
onExit: () => void,
1014
) {
1115
const rl = repl.start({
@@ -25,5 +29,66 @@ export function serviceRepl<SLocals extends AnyServiceLocals = ServiceLocals<Con
2529
}
2630
});
2731
app.locals.service.attachRepl?.(app, rl);
32+
33+
loadReplFunctions(app, codepath, rl);
34+
2835
rl.on('exit', onExit);
2936
}
37+
38+
function loadReplFunctions<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>>(
39+
app: ServiceExpress<SLocals>,
40+
codepath: string | undefined,
41+
rl: REPLServer,
42+
) {
43+
if (!codepath) {
44+
return;
45+
}
46+
47+
const files = glob.sync(path.join(codepath, '**/*.{js,ts}'));
48+
49+
files.forEach((file) => {
50+
try {
51+
// Read the file content as text
52+
const fileContent = fs.readFileSync(file, 'utf-8');
53+
54+
// Check if @repl is present
55+
if (/addToRepl\(/.test(fileContent)) {
56+
// eslint-disable-next-line global-require, import/no-dynamic-require, @typescript-eslint/no-var-requires
57+
const module = require(file); // Only require if @repl is found
58+
59+
// Look for functions with the __isReplFunction marker
60+
Object.values(module).forEach((exported) => {
61+
if (!exported) {
62+
return;
63+
}
64+
if (typeof exported === 'function' || typeof exported === 'object') {
65+
const obj = exported as Record<string, unknown>;
66+
for (const key of Object.keys(obj)) {
67+
if ((obj[key] as { __openApiServiceReplFunction?: boolean }).__openApiServiceReplFunction) {
68+
const fn = obj[key] as (app: ServiceExpress<SLocals>, ...args: unknown[]) => unknown;
69+
rl.context[key] = (...args: unknown[]) => fn(app, ...args);
70+
}
71+
}
72+
}
73+
});
74+
}
75+
} catch (err) {
76+
console.error(`Failed to load REPL functions from ${file}:`, err);
77+
}
78+
});
79+
}
80+
81+
/**
82+
* This decorator-like function can be applied to functions and the service will load and expose
83+
* the function when the repl is engaged.
84+
*/
85+
export function addToRepl<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>>(
86+
fn: (app: ServiceExpress<SLocals>, ...args: unknown[]) => unknown,
87+
name?: string,
88+
) {
89+
const functionName = name || fn.name;
90+
if (!functionName) {
91+
throw new Error('Function must have a name or a name must be provided.');
92+
}
93+
(fn as unknown as { __openApiServiceReplFunction: string }).__openApiServiceReplFunction = functionName;
94+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export * from './config';
66
export * from './error';
77
export * from './bootstrap';
88
export * from './hook';
9+
export { addToRepl } from './development/repl';

src/telemetry/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,5 +123,5 @@ export async function startWithTelemetry<
123123
await shutdownGlobalTelemetry();
124124
app.locals.logger.info('OpenTelemetry shut down');
125125
});
126-
return { app, server };
126+
return { app, codepath: options.codepath, server };
127127
}

0 commit comments

Comments
 (0)