@omen.foundation/node-microservice-runtime 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/.env +13 -0
  2. package/dist/auth.cjs +97 -0
  3. package/dist/auth.d.ts +14 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +93 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/cli/index.d.ts +3 -0
  8. package/dist/cli/index.d.ts.map +1 -0
  9. package/dist/cli/index.js +588 -0
  10. package/dist/cli/index.js.map +1 -0
  11. package/dist/decorators.cjs +181 -0
  12. package/dist/decorators.d.ts +23 -0
  13. package/dist/decorators.d.ts.map +1 -0
  14. package/dist/decorators.js +155 -0
  15. package/dist/decorators.js.map +1 -0
  16. package/dist/dependency.cjs +165 -0
  17. package/dist/dependency.d.ts +56 -0
  18. package/dist/dependency.d.ts.map +1 -0
  19. package/dist/dependency.js +162 -0
  20. package/dist/dependency.js.map +1 -0
  21. package/dist/dev.cjs +34 -0
  22. package/dist/dev.d.ts +9 -0
  23. package/dist/dev.d.ts.map +1 -0
  24. package/dist/dev.js +32 -0
  25. package/dist/dev.js.map +1 -0
  26. package/dist/discovery.cjs +79 -0
  27. package/dist/discovery.d.ts +20 -0
  28. package/dist/discovery.d.ts.map +1 -0
  29. package/dist/discovery.js +75 -0
  30. package/dist/discovery.js.map +1 -0
  31. package/dist/docs.cjs +206 -0
  32. package/dist/docs.d.ts +30 -0
  33. package/dist/docs.d.ts.map +1 -0
  34. package/dist/docs.js +209 -0
  35. package/dist/docs.js.map +1 -0
  36. package/dist/env.cjs +106 -0
  37. package/dist/env.d.ts +4 -0
  38. package/dist/env.d.ts.map +1 -0
  39. package/dist/env.js +108 -0
  40. package/dist/env.js.map +1 -0
  41. package/dist/errors.cjs +58 -0
  42. package/dist/errors.d.ts +26 -0
  43. package/dist/errors.d.ts.map +1 -0
  44. package/dist/errors.js +48 -0
  45. package/dist/errors.js.map +1 -0
  46. package/dist/federation.cjs +356 -0
  47. package/dist/federation.d.ts +108 -0
  48. package/dist/federation.d.ts.map +1 -0
  49. package/dist/federation.js +341 -0
  50. package/dist/federation.js.map +1 -0
  51. package/dist/index.cjs +42 -0
  52. package/dist/index.d.ts +13 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +10 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/inventory.cjs +361 -0
  57. package/dist/inventory.d.ts +116 -0
  58. package/dist/inventory.d.ts.map +1 -0
  59. package/dist/inventory.js +351 -0
  60. package/dist/inventory.js.map +1 -0
  61. package/dist/logger.cjs +62 -0
  62. package/dist/logger.d.ts +9 -0
  63. package/dist/logger.d.ts.map +1 -0
  64. package/dist/logger.js +29 -0
  65. package/dist/logger.js.map +1 -0
  66. package/dist/message.cjs +19 -0
  67. package/dist/message.d.ts +5 -0
  68. package/dist/message.d.ts.map +1 -0
  69. package/dist/message.js +15 -0
  70. package/dist/message.js.map +1 -0
  71. package/dist/requester.cjs +100 -0
  72. package/dist/requester.d.ts +20 -0
  73. package/dist/requester.d.ts.map +1 -0
  74. package/dist/requester.js +99 -0
  75. package/dist/requester.js.map +1 -0
  76. package/dist/routing.cjs +39 -0
  77. package/dist/routing.d.ts +2 -0
  78. package/dist/routing.d.ts.map +1 -0
  79. package/dist/routing.js +36 -0
  80. package/dist/routing.js.map +1 -0
  81. package/dist/runtime.cjs +735 -0
  82. package/dist/runtime.d.ts +40 -0
  83. package/dist/runtime.d.ts.map +1 -0
  84. package/dist/runtime.js +825 -0
  85. package/dist/runtime.js.map +1 -0
  86. package/dist/services.cjs +346 -0
  87. package/dist/services.d.ts +46 -0
  88. package/dist/services.d.ts.map +1 -0
  89. package/dist/services.js +343 -0
  90. package/dist/services.js.map +1 -0
  91. package/dist/storage.cjs +147 -0
  92. package/dist/storage.d.ts +46 -0
  93. package/dist/storage.d.ts.map +1 -0
  94. package/dist/storage.js +144 -0
  95. package/dist/storage.js.map +1 -0
  96. package/dist/types.cjs +2 -0
  97. package/dist/types.d.ts +108 -0
  98. package/dist/types.d.ts.map +1 -0
  99. package/dist/types.js +2 -0
  100. package/dist/types.js.map +1 -0
  101. package/dist/utils/urls.cjs +55 -0
  102. package/dist/utils/urls.d.ts +5 -0
  103. package/dist/utils/urls.d.ts.map +1 -0
  104. package/dist/utils/urls.js +50 -0
  105. package/dist/utils/urls.js.map +1 -0
  106. package/dist/websocket.cjs +142 -0
  107. package/dist/websocket.d.ts +33 -0
  108. package/dist/websocket.d.ts.map +1 -0
  109. package/dist/websocket.js +139 -0
  110. package/dist/websocket.js.map +1 -0
  111. package/env.sample +13 -0
  112. package/package.json +49 -0
  113. package/scripts/generate-openapi.mjs +114 -0
  114. package/scripts/lib/cli-utils.mjs +58 -0
  115. package/scripts/prepare-cjs.mjs +44 -0
  116. package/scripts/publish-service.mjs +1126 -0
  117. package/scripts/validate-service.mjs +103 -0
  118. package/scripts/ws-test.mjs +25 -0
  119. package/src/auth.ts +117 -0
  120. package/src/cli/index.ts +699 -0
  121. package/src/decorators.ts +207 -0
  122. package/src/dependency.ts +211 -0
  123. package/src/dev.ts +17 -0
  124. package/src/discovery.ts +88 -0
  125. package/src/docs.ts +262 -0
  126. package/src/env.ts +125 -0
  127. package/src/errors.ts +55 -0
  128. package/src/federation.ts +559 -0
  129. package/src/index.ts +51 -0
  130. package/src/inventory.ts +491 -0
  131. package/src/logger.ts +38 -0
  132. package/src/message.ts +19 -0
  133. package/src/requester.ts +126 -0
  134. package/src/routing.ts +42 -0
  135. package/src/runtime.ts +967 -0
  136. package/src/services.ts +459 -0
  137. package/src/storage.ts +206 -0
  138. package/src/types/beamable-sdk-api.d.ts +5 -0
  139. package/src/types.ts +117 -0
  140. package/src/utils/urls.ts +53 -0
  141. package/src/websocket.ts +170 -0
  142. package/tsconfig.base.json +31 -0
  143. package/tsconfig.build.json +10 -0
  144. package/tsconfig.cjs.json +16 -0
  145. package/tsconfig.dev.json +14 -0
@@ -0,0 +1,139 @@
1
+ import { setTimeout as sleep } from 'node:timers/promises';
2
+ import { EventEmitter } from 'node:events';
3
+ import WebSocket from 'ws';
4
+ export class BeamableWebSocket {
5
+ logger;
6
+ url;
7
+ maxRetries;
8
+ retryDelayMs;
9
+ emitter = new EventEmitter();
10
+ socket = null;
11
+ isDisposed = false;
12
+ constructor(options) {
13
+ this.logger = options.logger.child({ component: 'BeamableWebSocket' });
14
+ this.url = options.url;
15
+ this.maxRetries = options.maxRetries ?? 20;
16
+ this.retryDelayMs = options.retryDelayMs ?? 1000;
17
+ }
18
+ on(event, listener) {
19
+ this.emitter.on(event, listener);
20
+ return this;
21
+ }
22
+ off(event, listener) {
23
+ this.emitter.off(event, listener);
24
+ return this;
25
+ }
26
+ emit(event, ...args) {
27
+ this.emitter.emit(event, ...args);
28
+ }
29
+ async connect() {
30
+ let attempt = 0;
31
+ while (!this.isDisposed) {
32
+ try {
33
+ attempt += 1;
34
+ await this.createSocket();
35
+ return;
36
+ }
37
+ catch (error) {
38
+ const err = error instanceof Error ? error : new Error(String(error));
39
+ this.logger.warn({ err, attempt }, 'WebSocket connection attempt failed.');
40
+ if (attempt >= this.maxRetries) {
41
+ throw err;
42
+ }
43
+ await sleep(this.retryDelayMs * attempt);
44
+ }
45
+ }
46
+ throw new Error('WebSocket disposed before connection could be established.');
47
+ }
48
+ async createSocket() {
49
+ if (this.socket) {
50
+ this.socket.removeAllListeners();
51
+ this.socket.terminate();
52
+ this.socket = null;
53
+ }
54
+ return new Promise((resolve, reject) => {
55
+ const ws = new WebSocket(this.url, {
56
+ perMessageDeflate: true,
57
+ handshakeTimeout: 30_000,
58
+ });
59
+ const handleOpen = () => {
60
+ this.logger.info({ url: this.url }, 'WebSocket connected.');
61
+ this.socket = ws;
62
+ ws.off('error', handleError);
63
+ ws.off('close', handlePrematureClose);
64
+ this.registerSocketHandlers(ws);
65
+ this.emit('open');
66
+ resolve();
67
+ };
68
+ const handleError = (error) => {
69
+ ws.off('open', handleOpen);
70
+ ws.off('close', handlePrematureClose);
71
+ reject(error);
72
+ };
73
+ const handlePrematureClose = (code, reason) => {
74
+ ws.off('open', handleOpen);
75
+ ws.off('error', handleError);
76
+ const err = new Error(`WebSocket closed before open handshake. code=${code} reason=${reason.toString()}`);
77
+ reject(err);
78
+ };
79
+ ws.on('open', handleOpen);
80
+ ws.on('error', handleError);
81
+ ws.on('close', handlePrematureClose);
82
+ });
83
+ }
84
+ registerSocketHandlers(ws) {
85
+ ws.on('message', (data) => {
86
+ const payload = typeof data === 'string' ? data : data.toString('utf8');
87
+ this.logger.debug({ payload }, 'WebSocket received frame.');
88
+ this.emit('message', payload);
89
+ });
90
+ ws.on('close', (code, reason) => {
91
+ this.logger.warn({ code, reason: reason.toString() }, 'WebSocket closed.');
92
+ this.emit('close', code === 1000);
93
+ if (!this.isDisposed) {
94
+ void this.reconnect();
95
+ }
96
+ });
97
+ ws.on('error', (error) => {
98
+ const err = error instanceof Error ? error : new Error(String(error));
99
+ this.logger.error({ err }, 'WebSocket error.');
100
+ this.emit('error', err);
101
+ });
102
+ }
103
+ async reconnect() {
104
+ this.logger.info('Attempting to reconnect websocket.');
105
+ let attempt = 0;
106
+ while (!this.isDisposed) {
107
+ try {
108
+ attempt += 1;
109
+ await this.createSocket();
110
+ this.emit('open');
111
+ return;
112
+ }
113
+ catch (error) {
114
+ const err = error instanceof Error ? error : new Error(String(error));
115
+ this.logger.warn({ err, attempt }, 'Retrying websocket connection.');
116
+ await sleep(this.retryDelayMs * Math.min(attempt, 10));
117
+ }
118
+ }
119
+ }
120
+ async send(text) {
121
+ if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
122
+ throw new Error('WebSocket is not connected.');
123
+ }
124
+ this.logger.debug({ message: text }, 'Sending websocket frame.');
125
+ this.socket.send(text);
126
+ }
127
+ async close() {
128
+ this.isDisposed = true;
129
+ if (!this.socket) {
130
+ return;
131
+ }
132
+ const ws = this.socket;
133
+ if (ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CLOSING) {
134
+ return;
135
+ }
136
+ ws.close(1000, 'shutdown');
137
+ }
138
+ }
139
+ //# sourceMappingURL=websocket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.js","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"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","sourcesContent":["import { setTimeout as sleep } from 'node:timers/promises';\r\nimport { EventEmitter } from 'node:events';\r\nimport WebSocket from 'ws';\r\nimport type { Logger } from 'pino';\r\n\r\nexport interface WebSocketOptions {\r\n logger: Logger;\r\n url: string;\r\n maxRetries?: number;\r\n retryDelayMs?: number;\r\n}\r\n\r\nexport interface WebSocketEvents {\r\n open: [];\r\n close: [wasClean: boolean];\r\n message: [payload: string];\r\n error: [error: Error];\r\n}\r\n\r\nexport class BeamableWebSocket {\r\n private readonly logger: Logger;\r\n private readonly url: string;\r\n private readonly maxRetries: number;\r\n private readonly retryDelayMs: number;\r\n private readonly emitter = new EventEmitter();\r\n\r\n private socket: WebSocket | null = null;\r\n private isDisposed = false;\r\n\r\n constructor(options: WebSocketOptions) {\r\n this.logger = options.logger.child({ component: 'BeamableWebSocket' });\r\n this.url = options.url;\r\n this.maxRetries = options.maxRetries ?? 20;\r\n this.retryDelayMs = options.retryDelayMs ?? 1000;\r\n }\r\n\r\n on(event: string, listener: (...args: unknown[]) => void): this {\r\n this.emitter.on(event, listener);\r\n return this;\r\n }\r\n\r\n off(event: string, listener: (...args: unknown[]) => void): this {\r\n this.emitter.off(event, listener);\r\n return this;\r\n }\r\n\r\n private emit<Event extends keyof WebSocketEvents>(event: Event, ...args: WebSocketEvents[Event]): void {\r\n this.emitter.emit(event, ...args);\r\n }\r\n\r\n async connect(): Promise<void> {\r\n let attempt = 0;\r\n while (!this.isDisposed) {\r\n try {\r\n attempt += 1;\r\n await this.createSocket();\r\n return;\r\n } catch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error));\r\n this.logger.warn({ err, attempt }, 'WebSocket connection attempt failed.');\r\n if (attempt >= this.maxRetries) {\r\n throw err;\r\n }\r\n await sleep(this.retryDelayMs * attempt);\r\n }\r\n }\r\n throw new Error('WebSocket disposed before connection could be established.');\r\n }\r\n\r\n private async createSocket(): Promise<void> {\r\n if (this.socket) {\r\n this.socket.removeAllListeners();\r\n this.socket.terminate();\r\n this.socket = null;\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const ws = new WebSocket(this.url, {\r\n perMessageDeflate: true,\r\n handshakeTimeout: 30_000,\r\n });\r\n\r\n const handleOpen = () => {\r\n this.logger.info({ url: this.url }, 'WebSocket connected.');\r\n this.socket = ws;\r\n ws.off('error', handleError);\r\n ws.off('close', handlePrematureClose);\r\n this.registerSocketHandlers(ws);\r\n this.emit('open');\r\n resolve();\r\n };\r\n\r\n const handleError = (error: Error) => {\r\n ws.off('open', handleOpen);\r\n ws.off('close', handlePrematureClose);\r\n reject(error);\r\n };\r\n\r\n const handlePrematureClose = (code: number, reason: Buffer) => {\r\n ws.off('open', handleOpen);\r\n ws.off('error', handleError);\r\n const err = new Error(`WebSocket closed before open handshake. code=${code} reason=${reason.toString()}`);\r\n reject(err);\r\n };\r\n\r\n ws.on('open', handleOpen);\r\n ws.on('error', handleError);\r\n ws.on('close', handlePrematureClose);\r\n });\r\n }\r\n\r\n private registerSocketHandlers(ws: WebSocket): void {\r\n ws.on('message', (data) => {\r\n const payload = typeof data === 'string' ? data : data.toString('utf8');\r\n this.logger.debug({ payload }, 'WebSocket received frame.');\r\n this.emit('message', payload);\r\n });\r\n\r\n ws.on('close', (code, reason) => {\r\n this.logger.warn({ code, reason: reason.toString() }, 'WebSocket closed.');\r\n this.emit('close', code === 1000);\r\n if (!this.isDisposed) {\r\n void this.reconnect();\r\n }\r\n });\r\n\r\n ws.on('error', (error) => {\r\n const err = error instanceof Error ? error : new Error(String(error));\r\n this.logger.error({ err }, 'WebSocket error.');\r\n this.emit('error', err);\r\n });\r\n }\r\n\r\n private async reconnect(): Promise<void> {\r\n this.logger.info('Attempting to reconnect websocket.');\r\n let attempt = 0;\r\n while (!this.isDisposed) {\r\n try {\r\n attempt += 1;\r\n await this.createSocket();\r\n this.emit('open');\r\n return;\r\n } catch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error));\r\n this.logger.warn({ err, attempt }, 'Retrying websocket connection.');\r\n await sleep(this.retryDelayMs * Math.min(attempt, 10));\r\n }\r\n }\r\n }\r\n\r\n async send(text: string): Promise<void> {\r\n if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {\r\n throw new Error('WebSocket is not connected.');\r\n }\r\n this.logger.debug({ message: text }, 'Sending websocket frame.');\r\n this.socket.send(text);\r\n }\r\n\r\n async close(): Promise<void> {\r\n this.isDisposed = true;\r\n if (!this.socket) {\r\n return;\r\n }\r\n const ws = this.socket;\r\n if (ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CLOSING) {\r\n return;\r\n }\r\n ws.close(1000, 'shutdown');\r\n }\r\n}\r\n"]}
package/env.sample ADDED
@@ -0,0 +1,13 @@
1
+ # Beamable credentials
2
+ CID=1890069218625633
3
+ PID=DE_1934633364747296
4
+ HOST=wss://portal.beamable.com/socket
5
+ SECRET=6a24a8a1-f9f1-4e4a-b487-a446896f9b9f
6
+ # Alternatively, you can use a refresh token instead of SECRET
7
+ # REFRESH_TOKEN=
8
+
9
+ # Optional developer settings
10
+ NAME_PREFIX=
11
+ LOG_LEVEL=info
12
+ WATCH_TOKEN=false
13
+ BEAM_INSTANCE_COUNT=1
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@omen.foundation/node-microservice-runtime",
3
+ "version": "0.1.0",
4
+ "description": "Beamable microservice runtime for Node.js/TypeScript services.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.cjs"
12
+ }
13
+ },
14
+ "bin": {
15
+ "beamo-node": "./dist/cli/index.js"
16
+ },
17
+ "scripts": {
18
+ "clean": "rimraf dist dist-cjs",
19
+ "build": "npm run clean && tsc -p tsconfig.build.json",
20
+ "build:cjs": "tsc -p tsconfig.cjs.json && node ./scripts/prepare-cjs.mjs",
21
+ "compile": "npm run build && npm run build:cjs",
22
+ "dev": "tsx --env-file .env src/dev.ts",
23
+ "start": "node dist/index.js"
24
+ },
25
+ "engines": {
26
+ "node": ">=22.14.0"
27
+ },
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "beamable-sdk": "^0.6.0",
31
+ "dotenv": "^16.4.7",
32
+ "eventemitter3": "^5.0.1",
33
+ "keytar": "^7.9.0",
34
+ "mongodb": "^6.10.0",
35
+ "pino": "^9.0.0",
36
+ "reflect-metadata": "^0.2.1",
37
+ "tar-stream": "^2.2.0",
38
+ "undici": "^6.19.6",
39
+ "ws": "^8.17.1"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^20.11.30",
43
+ "@types/ws": "^8.5.10",
44
+ "rimraf": "^5.0.5",
45
+ "ts-node": "^10.9.2",
46
+ "tsx": "^4.19.2",
47
+ "typescript": "^5.4.5"
48
+ }
49
+ }
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { pathToFileURL, fileURLToPath } from 'node:url';
6
+ import dotenv from 'dotenv';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+
10
+ function parseArgs(argv) {
11
+ const args = {
12
+ entry: 'dist/main.js',
13
+ output: 'beam_openApi.json',
14
+ envFile: undefined,
15
+ cid: undefined,
16
+ pid: undefined,
17
+ host: undefined,
18
+ namePrefix: undefined,
19
+ service: undefined,
20
+ };
21
+
22
+ const queue = [...argv];
23
+ while (queue.length > 0) {
24
+ const current = queue.shift();
25
+ switch (current) {
26
+ case '--entry':
27
+ args.entry = queue.shift();
28
+ break;
29
+ case '--output':
30
+ args.output = queue.shift();
31
+ break;
32
+ case '--env-file':
33
+ args.envFile = queue.shift();
34
+ break;
35
+ case '--cid':
36
+ args.cid = queue.shift();
37
+ break;
38
+ case '--pid':
39
+ args.pid = queue.shift();
40
+ break;
41
+ case '--host':
42
+ args.host = queue.shift();
43
+ break;
44
+ case '--routing-key':
45
+ case '--name-prefix':
46
+ args.namePrefix = queue.shift();
47
+ break;
48
+ case '--service':
49
+ args.service = queue.shift();
50
+ break;
51
+ default:
52
+ throw new Error(`Unknown argument: ${current}`);
53
+ }
54
+ }
55
+
56
+ return args;
57
+ }
58
+
59
+ async function main() {
60
+ const args = parseArgs(process.argv.slice(2));
61
+
62
+ if (args.envFile) {
63
+ const envPath = path.resolve(args.envFile);
64
+ dotenv.config({ path: envPath });
65
+ }
66
+
67
+ if (args.cid) {
68
+ process.env.CID = args.cid;
69
+ }
70
+ if (args.pid) {
71
+ process.env.PID = args.pid;
72
+ }
73
+ if (args.host) {
74
+ process.env.HOST = args.host;
75
+ }
76
+ if (args.namePrefix) {
77
+ process.env.NAME_PREFIX = args.namePrefix;
78
+ }
79
+
80
+ process.env.BEAMABLE_SKIP_RUNTIME = 'true';
81
+
82
+ const entryPath = path.resolve(args.entry);
83
+ const outputPath = path.resolve(args.output);
84
+
85
+ const runtimeEntry = path.resolve(__dirname, '../dist/runtime.js');
86
+ try {
87
+ await fs.access(runtimeEntry);
88
+ } catch (error) {
89
+ throw new Error('Runtime distribution not found. Run "npm run compile" in @omen.foundation/node-microservice-runtime.');
90
+ }
91
+
92
+ await import(pathToFileURL(entryPath).href);
93
+
94
+ const { generateOpenApiDocumentForRegisteredServices } = await import(pathToFileURL(runtimeEntry).href);
95
+
96
+ const document = generateOpenApiDocumentForRegisteredServices(
97
+ {
98
+ cid: process.env.CID,
99
+ pid: process.env.PID,
100
+ host: process.env.HOST,
101
+ routingKey: process.env.NAME_PREFIX,
102
+ },
103
+ { serviceName: args.service },
104
+ );
105
+
106
+ await fs.writeFile(outputPath, `${JSON.stringify(document, null, 2)}\n`, 'utf8');
107
+
108
+ console.log(`OpenAPI documentation generated at ${outputPath}`);
109
+ }
110
+
111
+ main().catch((error) => {
112
+ console.error(error instanceof Error ? error.message : error);
113
+ process.exit(1);
114
+ });
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from 'node:child_process';
4
+
5
+ export function runCommand(
6
+ command,
7
+ args,
8
+ { cwd = process.cwd(), env = process.env, stdio = 'inherit', capture = false, shell, silent = false } = {},
9
+ ) {
10
+ return new Promise((resolve, reject) => {
11
+ const stdioOption = capture ? 'pipe' : (silent ? 'ignore' : stdio);
12
+ const child = spawn(command, args, {
13
+ cwd,
14
+ env,
15
+ stdio: stdioOption,
16
+ shell: shell ?? (process.platform === 'win32'),
17
+ });
18
+
19
+ const stdoutChunks = [];
20
+ const stderrChunks = [];
21
+
22
+ if (capture) {
23
+ child.stdout?.on('data', (chunk) => stdoutChunks.push(chunk));
24
+ child.stderr?.on('data', (chunk) => stderrChunks.push(chunk));
25
+ }
26
+
27
+ child.on('error', (error) => {
28
+ reject(error);
29
+ });
30
+
31
+ child.on('exit', (code, signal) => {
32
+ if (code === 0) {
33
+ resolve({
34
+ code,
35
+ signal,
36
+ stdout: capture ? Buffer.concat(stdoutChunks).toString('utf8').trim() : undefined,
37
+ stderr: capture ? Buffer.concat(stderrChunks).toString('utf8').trim() : undefined,
38
+ });
39
+ } else {
40
+ const error = new Error(`Command failed: ${command} ${args.join(' ')}`);
41
+ error.code = code;
42
+ error.signal = signal;
43
+ if (capture) {
44
+ error.stdout = Buffer.concat(stdoutChunks).toString('utf8');
45
+ error.stderr = Buffer.concat(stderrChunks).toString('utf8');
46
+ }
47
+ reject(error);
48
+ }
49
+ });
50
+ });
51
+ }
52
+
53
+ export function parseBooleanEnv(value, fallback = false) {
54
+ if (value === undefined) {
55
+ return fallback;
56
+ }
57
+ return ['1', 'true', 'yes', 'on'].includes(value.toString().trim().toLowerCase());
58
+ }
@@ -0,0 +1,44 @@
1
+ import { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+
4
+ async function ensureDir(path) {
5
+ await mkdir(path, { recursive: true });
6
+ }
7
+
8
+ async function copyCjsFiles() {
9
+ const srcRoot = join(process.cwd(), 'dist-cjs');
10
+ const dstRoot = join(process.cwd(), 'dist');
11
+
12
+ await ensureDir(dstRoot);
13
+
14
+ const stack = [''];
15
+ while (stack.length > 0) {
16
+ const current = stack.pop();
17
+ const srcDir = join(srcRoot, current);
18
+ const entries = await readdir(srcDir, { withFileTypes: true });
19
+ for (const entry of entries) {
20
+ const relativePath = join(current, entry.name);
21
+ if (entry.isDirectory()) {
22
+ stack.push(relativePath);
23
+ continue;
24
+ }
25
+
26
+ if (!entry.name.endsWith('.js')) {
27
+ continue;
28
+ }
29
+
30
+ const sourceFile = join(srcRoot, relativePath);
31
+ const destFile = join(dstRoot, current, entry.name.replace(/\.js$/, '.cjs'));
32
+ await ensureDir(dirname(destFile));
33
+ const contents = await readFile(sourceFile);
34
+ await writeFile(destFile, contents);
35
+ }
36
+ }
37
+
38
+ await rm(srcRoot, { recursive: true, force: true });
39
+ }
40
+
41
+ copyCjsFiles().catch((error) => {
42
+ console.error('Failed to prepare CommonJS artifacts:', error);
43
+ process.exitCode = 1;
44
+ });