0z2i6v3u5t 1.0.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 (211) hide show
  1. package/.devcontainer/devcontainer.json +4 -0
  2. package/.devcontainer/setup.sh +11 -0
  3. package/.dockerignore +2 -0
  4. package/.github/CONTRIBUTING.md +52 -0
  5. package/.github/FUNDING.yml +3 -0
  6. package/.github/ISSUE_TEMPLATE/bug_report.yml +59 -0
  7. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  8. package/.github/ISSUE_TEMPLATE/feature_request.yml +43 -0
  9. package/.github/dependabot.yml +17 -0
  10. package/.github/workflows/codeql.yml +76 -0
  11. package/.github/workflows/publish_docs.yml +25 -0
  12. package/.github/workflows/test.yml +78 -0
  13. package/.nvmrc +1 -0
  14. package/.prettierignore +1 -0
  15. package/.prettierrc +1 -0
  16. package/.vscode/launch.json +42 -0
  17. package/CODE_OF_CONDUCT.md +76 -0
  18. package/Dockerfile +17 -0
  19. package/LICENSE +21 -0
  20. package/README.md +3 -0
  21. package/SECURITY.md +5 -0
  22. package/__tests__/actions/cacheTest.ts +58 -0
  23. package/__tests__/actions/randomNumber.ts +26 -0
  24. package/__tests__/actions/recursiveAction.ts +16 -0
  25. package/__tests__/actions/sleepTest.ts +24 -0
  26. package/__tests__/actions/status.ts +17 -0
  27. package/__tests__/actions/swagger.ts +76 -0
  28. package/__tests__/actions/validationTest.ts +63 -0
  29. package/__tests__/cli/cli.ts +126 -0
  30. package/__tests__/core/api.ts +632 -0
  31. package/__tests__/core/cache.ts +400 -0
  32. package/__tests__/core/chatRoom.ts +589 -0
  33. package/__tests__/core/cli.ts +349 -0
  34. package/__tests__/core/cluster.ts +132 -0
  35. package/__tests__/core/config.ts +78 -0
  36. package/__tests__/core/errors.ts +112 -0
  37. package/__tests__/core/log.ts +23 -0
  38. package/__tests__/core/middleware.ts +427 -0
  39. package/__tests__/core/plugins/partialPlugin.ts +94 -0
  40. package/__tests__/core/plugins/withPlugin.ts +88 -0
  41. package/__tests__/core/plugins/withoutPlugin.ts +81 -0
  42. package/__tests__/core/process.ts +42 -0
  43. package/__tests__/core/specHelper.ts +330 -0
  44. package/__tests__/core/staticFile/compression.ts +99 -0
  45. package/__tests__/core/staticFile/staticFile.ts +180 -0
  46. package/__tests__/core/tasks/customQueueFunction.ts +67 -0
  47. package/__tests__/core/tasks/fullWorkerFlow.ts +199 -0
  48. package/__tests__/core/tasks/tasks.ts +605 -0
  49. package/__tests__/integration/browser.ts +133 -0
  50. package/__tests__/integration/ioredis-mock.ts +194 -0
  51. package/__tests__/integration/sendBuffer.ts +97 -0
  52. package/__tests__/integration/sendFile.ts +24 -0
  53. package/__tests__/integration/sharedFingerprint.ts +82 -0
  54. package/__tests__/integration/taskFlow.ts +110 -0
  55. package/__tests__/jest.ts +5 -0
  56. package/__tests__/modules/action.ts +103 -0
  57. package/__tests__/modules/config.ts +19 -0
  58. package/__tests__/modules/utils/ensureNoTsHeaderOrSpecFiles.ts +24 -0
  59. package/__tests__/servers/web/allowedRequestHosts.ts +88 -0
  60. package/__tests__/servers/web/enableMultiples.ts +83 -0
  61. package/__tests__/servers/web/fileUpload.ts +79 -0
  62. package/__tests__/servers/web/jsonp.ts +57 -0
  63. package/__tests__/servers/web/nonMultiples.ts +83 -0
  64. package/__tests__/servers/web/rawBody.ts +208 -0
  65. package/__tests__/servers/web/returnErrorCodes.ts +55 -0
  66. package/__tests__/servers/web/routes/deepRoutes.ts +96 -0
  67. package/__tests__/servers/web/routes/routes.ts +579 -0
  68. package/__tests__/servers/web/routes/veryDeepRoutes.ts +92 -0
  69. package/__tests__/servers/web/web.ts +1031 -0
  70. package/__tests__/servers/websocket.ts +795 -0
  71. package/__tests__/tasks/runAction.ts +37 -0
  72. package/__tests__/template.ts.example +20 -0
  73. package/__tests__/testCliCommands/hello.ts +44 -0
  74. package/__tests__/testPlugin/public/plugin.html +1 -0
  75. package/__tests__/testPlugin/src/actions/pluginAction.ts +14 -0
  76. package/__tests__/testPlugin/src/bin/hello.ts +22 -0
  77. package/__tests__/testPlugin/src/initializers/pluginInitializer.ts +17 -0
  78. package/__tests__/testPlugin/src/tasks/pluginTask.ts +15 -0
  79. package/__tests__/testPlugin/tsconfig.json +10 -0
  80. package/__tests__/utils/utils.ts +492 -0
  81. package/app.json +23 -0
  82. package/bin/deploy-docs +39 -0
  83. package/client/ActionheroWebsocketClient.js +277 -0
  84. package/docker-compose.yml +73 -0
  85. package/package.json +24 -0
  86. package/public/chat.html +194 -0
  87. package/public/css/cosmo.css +12 -0
  88. package/public/favicon.ico +0 -0
  89. package/public/index.html +115 -0
  90. package/public/javascript/.gitkeep +0 -0
  91. package/public/linkedSession.html +80 -0
  92. package/public/logo/actionhero-small.png +0 -0
  93. package/public/logo/actionhero.png +0 -0
  94. package/public/pixel.gif +0 -0
  95. package/public/simple.html +2 -0
  96. package/public/swagger.html +32 -0
  97. package/public/websocketLoadTest.html +322 -0
  98. package/src/actions/cacheTest.ts +58 -0
  99. package/src/actions/createChatRoom.ts +20 -0
  100. package/src/actions/randomNumber.ts +17 -0
  101. package/src/actions/recursiveAction.ts +13 -0
  102. package/src/actions/sendFile.ts +12 -0
  103. package/src/actions/sleepTest.ts +40 -0
  104. package/src/actions/status.ts +73 -0
  105. package/src/actions/swagger.ts +155 -0
  106. package/src/actions/validationTest.ts +36 -0
  107. package/src/bin/actionhero.ts +225 -0
  108. package/src/bin/methods/actions/list.ts +30 -0
  109. package/src/bin/methods/console.ts +26 -0
  110. package/src/bin/methods/generate/action.ts +58 -0
  111. package/src/bin/methods/generate/cli.ts +51 -0
  112. package/src/bin/methods/generate/initializer.ts +54 -0
  113. package/src/bin/methods/generate/plugin.ts +57 -0
  114. package/src/bin/methods/generate/server.ts +38 -0
  115. package/src/bin/methods/generate/task.ts +68 -0
  116. package/src/bin/methods/generate.ts +176 -0
  117. package/src/bin/methods/task/enqueue.ts +35 -0
  118. package/src/classes/action.ts +98 -0
  119. package/src/classes/actionProcessor.ts +463 -0
  120. package/src/classes/api.ts +51 -0
  121. package/src/classes/cli.ts +67 -0
  122. package/src/classes/config.ts +15 -0
  123. package/src/classes/connection.ts +321 -0
  124. package/src/classes/exceptionReporter.ts +9 -0
  125. package/src/classes/initializer.ts +59 -0
  126. package/src/classes/initializers.ts +5 -0
  127. package/src/classes/input.ts +9 -0
  128. package/src/classes/inputs.ts +34 -0
  129. package/src/classes/process/actionheroVersion.ts +15 -0
  130. package/src/classes/process/env.ts +16 -0
  131. package/src/classes/process/id.ts +34 -0
  132. package/src/classes/process/pid.ts +32 -0
  133. package/src/classes/process/projectRoot.ts +16 -0
  134. package/src/classes/process/typescript.ts +47 -0
  135. package/src/classes/process.ts +479 -0
  136. package/src/classes/server.ts +251 -0
  137. package/src/classes/task.ts +87 -0
  138. package/src/config/api.ts +107 -0
  139. package/src/config/errors.ts +162 -0
  140. package/src/config/logger.ts +113 -0
  141. package/src/config/plugins.ts +37 -0
  142. package/src/config/redis.ts +78 -0
  143. package/src/config/routes.ts +44 -0
  144. package/src/config/tasks.ts +84 -0
  145. package/src/config/web.ts +136 -0
  146. package/src/config/websocket.ts +62 -0
  147. package/src/index.ts +46 -0
  148. package/src/initializers/actions.ts +125 -0
  149. package/src/initializers/chatRoom.ts +214 -0
  150. package/src/initializers/connections.ts +124 -0
  151. package/src/initializers/exceptions.ts +155 -0
  152. package/src/initializers/params.ts +52 -0
  153. package/src/initializers/redis.ts +191 -0
  154. package/src/initializers/resque.ts +248 -0
  155. package/src/initializers/routes.ts +229 -0
  156. package/src/initializers/servers.ts +134 -0
  157. package/src/initializers/specHelper.ts +195 -0
  158. package/src/initializers/staticFile.ts +253 -0
  159. package/src/initializers/tasks.ts +188 -0
  160. package/src/modules/action.ts +89 -0
  161. package/src/modules/cache.ts +326 -0
  162. package/src/modules/chatRoom.ts +321 -0
  163. package/src/modules/config.ts +246 -0
  164. package/src/modules/log.ts +62 -0
  165. package/src/modules/redis.ts +93 -0
  166. package/src/modules/route.ts +59 -0
  167. package/src/modules/specHelper.ts +182 -0
  168. package/src/modules/task.ts +527 -0
  169. package/src/modules/utils/argv.ts +3 -0
  170. package/src/modules/utils/arrayStartingMatch.ts +21 -0
  171. package/src/modules/utils/arrayUnique.ts +15 -0
  172. package/src/modules/utils/collapseObjectToArray.ts +33 -0
  173. package/src/modules/utils/deepCopy.ts +3 -0
  174. package/src/modules/utils/ensureNoTsHeaderOrSpecFiles.ts +19 -0
  175. package/src/modules/utils/eventLoopDelay.ts +34 -0
  176. package/src/modules/utils/fileUtils.ts +119 -0
  177. package/src/modules/utils/filterObjectForLogging.ts +51 -0
  178. package/src/modules/utils/filterResponseForLogging.ts +53 -0
  179. package/src/modules/utils/getExternalIPAddress.ts +17 -0
  180. package/src/modules/utils/hashMerge.ts +63 -0
  181. package/src/modules/utils/isPlainObject.ts +45 -0
  182. package/src/modules/utils/isRunning.ts +7 -0
  183. package/src/modules/utils/parseCookies.ts +20 -0
  184. package/src/modules/utils/parseHeadersForClientAddress.ts +53 -0
  185. package/src/modules/utils/parseIPv6URI.ts +24 -0
  186. package/src/modules/utils/replaceDistWithSrc.ts +9 -0
  187. package/src/modules/utils/safeGlob.ts +6 -0
  188. package/src/modules/utils/sleep.ts +8 -0
  189. package/src/modules/utils/sortGlobalMiddleware.ts +17 -0
  190. package/src/modules/utils/sourceRelativeLinkPath.ts +29 -0
  191. package/src/modules/utils.ts +66 -0
  192. package/src/server.ts +20 -0
  193. package/src/servers/web.ts +894 -0
  194. package/src/servers/websocket.ts +304 -0
  195. package/src/tasks/runAction.ts +29 -0
  196. package/tea.yaml +9 -0
  197. package/templates/README.md.template +17 -0
  198. package/templates/action.ts.template +15 -0
  199. package/templates/boot.js.template +9 -0
  200. package/templates/cli.ts.template +15 -0
  201. package/templates/gitignore.template +23 -0
  202. package/templates/initializer.ts.template +17 -0
  203. package/templates/package-plugin.json.template +12 -0
  204. package/templates/package.json.template +45 -0
  205. package/templates/projectMap.txt +39 -0
  206. package/templates/projectServer.ts.template +20 -0
  207. package/templates/server.ts.template +37 -0
  208. package/templates/task.ts.template +16 -0
  209. package/templates/test/action.ts.template +13 -0
  210. package/templates/test/task.ts.template +20 -0
  211. package/tsconfig.json +11 -0
@@ -0,0 +1,321 @@
1
+ import { AsyncReturnType } from "type-fest";
2
+ import { api, config, id, redis, Connection } from "./../index";
3
+ import * as RedisModule from "../modules/redis";
4
+
5
+ export namespace chatRoom {
6
+ /**
7
+ * Middleware definition for processing chat events. Can be of the
8
+ *
9
+ * ```js
10
+ * var chatMiddleware = {
11
+ * name: 'chat middleware',
12
+ * priority: 1000,
13
+ * join: (connection, room) => {
14
+ * // announce all connections entering a room
15
+ * api.chatRoom.broadcast(null, room, 'I have joined the room: ' + connection.id, callback)
16
+ * },
17
+ * leave:(connection, room, callback) => {
18
+ * // announce all connections leaving a room
19
+ * api.chatRoom.broadcast(null, room, 'I have left the room: ' + connection.id, callback)
20
+ * },
21
+ * // Will be executed once per client connection before delivering the message.
22
+ * say: (connection, room, messagePayload) => {
23
+ * // do stuff
24
+ * log(messagePayload)
25
+ * },
26
+ * // Will be executed only once, when the message is sent to the server.
27
+ * onSayReceive: (connection, room, messagePayload) => {
28
+ * // do stuff
29
+ * log(messagePayload)
30
+ * }
31
+ * }
32
+ * api.chatRoom.addMiddleware(chatMiddleware)
33
+ * ```
34
+ */
35
+
36
+ export interface ChatMiddleware {
37
+ /**Unique name for the middleware. */
38
+ name: string;
39
+ /**Module load order. Defaults to `api.config.general.defaultMiddlewarePriority`. */
40
+ priority?: number;
41
+ /**Called when a connection joins a room. */
42
+ join?: Function;
43
+ /**Called when a connection leaves a room. */
44
+ leave?: Function;
45
+ /**Called when a connection says a message to a room. */
46
+ onSayReceive?: Function;
47
+ /**Called when a connection is about to receive a say message. */
48
+ say?: Function;
49
+ }
50
+
51
+ export interface ChatPubSubMessage extends RedisModule.redis.PubSubMessage {
52
+ messageType: string;
53
+ serverToken: string;
54
+ serverId: string | number;
55
+ message: any;
56
+ sentAt: number;
57
+ connection: {
58
+ id: string;
59
+ room: string;
60
+ };
61
+ }
62
+
63
+ export function client() {
64
+ if (api.redis.clients && api.redis.clients.client) {
65
+ return api.redis.clients.client;
66
+ } else {
67
+ throw new Error("redis not connected, chatRoom cannot be used");
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Add a middleware component to connection handling.
73
+ */
74
+ export async function addMiddleware(data: ChatMiddleware) {
75
+ if (!data.name) {
76
+ throw new Error("middleware.name is required");
77
+ }
78
+ if (!data.priority) {
79
+ data.priority = config.general.defaultMiddlewarePriority;
80
+ }
81
+ data.priority = Number(data.priority);
82
+ api.chatRoom.middleware[data.name] = data;
83
+
84
+ api.chatRoom.globalMiddleware.push(data.name);
85
+ api.chatRoom.globalMiddleware.sort((a, b) => {
86
+ if (
87
+ api.chatRoom.middleware[a].priority >
88
+ api.chatRoom.middleware[b].priority
89
+ ) {
90
+ return 1;
91
+ } else {
92
+ return -1;
93
+ }
94
+ });
95
+ }
96
+
97
+ /**
98
+ * List all chat rooms created
99
+ */
100
+ export async function list(): Promise<Array<string>> {
101
+ return client().smembers(api.chatRoom.keys.rooms);
102
+ }
103
+
104
+ /**
105
+ * Add a new chat room. Throws an error if the room already exists.
106
+ */
107
+ export async function add(room: string) {
108
+ const found = await chatRoom.exists(room);
109
+ if (found === false) {
110
+ return client().sadd(api.chatRoom.keys.rooms, room);
111
+ } else {
112
+ throw new Error(await config.errors.connectionRoomExists(room));
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Remove an existing chat room. All connections in the room will be removed. Throws an error if the room does not exist.
118
+ */
119
+ export async function destroy(room: string) {
120
+ const found = await chatRoom.exists(room);
121
+ if (found === true) {
122
+ await api.chatRoom.broadcast(
123
+ null,
124
+ room,
125
+ await config.errors.connectionRoomHasBeenDeleted(room),
126
+ );
127
+ const membersHash = await client().hgetall(
128
+ api.chatRoom.keys.members + room,
129
+ );
130
+
131
+ for (const id in membersHash) {
132
+ await chatRoom.removeMember(id, room, false);
133
+ }
134
+
135
+ await client().srem(api.chatRoom.keys.rooms, room);
136
+ await client().del(api.chatRoom.keys.members + room);
137
+ } else {
138
+ throw new Error(await config.errors.connectionRoomNotExist(room));
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Check if a room exists.
144
+ */
145
+ export async function exists(room: string): Promise<boolean> {
146
+ const isMember = await client().sismember(api.chatRoom.keys.rooms, room);
147
+ let found = false;
148
+ if (isMember === 1 || isMember.toString() === "true") found = true;
149
+ return found;
150
+ }
151
+
152
+ /**
153
+ * Configures what properties of connections in a room to return via `api.chatRoom.roomStatus`
154
+ */
155
+ export async function sanitizeMemberDetails(memberData: {
156
+ id: string;
157
+ joinedAt: number;
158
+ [key: string]: any;
159
+ }) {
160
+ return {
161
+ id: memberData.id,
162
+ joinedAt: memberData.joinedAt,
163
+ };
164
+ }
165
+
166
+ /**
167
+ * Learn about the connections in the room.
168
+ * Returns a hash of the form { room: room, members: cleanedMembers, membersCount: count }. Members is an array of connections in the room sanitized via `api.chatRoom.sanitizeMemberDetails`
169
+ */
170
+ export async function roomStatus(room: string): Promise<{
171
+ room: string;
172
+ membersCount: number;
173
+ members: Record<string, AsyncReturnType<typeof sanitizeMemberDetails>>;
174
+ }> {
175
+ if (room) {
176
+ const found = await chatRoom.exists(room);
177
+ if (found === true) {
178
+ const key = api.chatRoom.keys.members + room;
179
+ const members = (await api.redis.clients.client.hgetall(key)) as {
180
+ [key: string]: string;
181
+ };
182
+ const cleanedMembers: Record<string, any> = {};
183
+ let count = 0;
184
+
185
+ for (const id in members) {
186
+ const data = JSON.parse(members[id]);
187
+ cleanedMembers[id] = await chatRoom.sanitizeMemberDetails(data);
188
+ count++;
189
+ }
190
+
191
+ return {
192
+ room: room,
193
+ members: cleanedMembers,
194
+ membersCount: count,
195
+ };
196
+ } else {
197
+ throw new Error(await config.errors.connectionRoomNotExist(room));
198
+ }
199
+ } else {
200
+ throw new Error(await config.errors.connectionRoomRequired());
201
+ }
202
+ }
203
+
204
+ /**
205
+ * An overwrite-able method which configures what properties of connections in a room are initially stored about a connection when added via `api.chatRoom.addMember`
206
+ */
207
+ export async function generateMemberDetails(connection: Connection) {
208
+ return {
209
+ id: connection.id,
210
+ joinedAt: new Date().getTime(),
211
+ host: id,
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Add a connection (via id) to a room. Throws errors if the room does not exist, or the connection is already in the room. Middleware errors also throw.
217
+ */
218
+ export async function addMember(
219
+ connectionId: string,
220
+ room: string,
221
+ ): Promise<any> {
222
+ const connection = api.connections.connections[connectionId];
223
+ if (!connection) {
224
+ return redis.doCluster(
225
+ "api.chatRoom.addMember",
226
+ [connectionId, room],
227
+ connectionId,
228
+ true,
229
+ );
230
+ }
231
+
232
+ if (connection.rooms.includes(room)) {
233
+ throw new Error(
234
+ await config.errors.connectionAlreadyInRoom(connection, room),
235
+ );
236
+ }
237
+
238
+ if (!connection.rooms.includes(room)) {
239
+ const found = await chatRoom.exists(room);
240
+ if (!found) {
241
+ throw new Error(await config.errors.connectionRoomNotExist(room));
242
+ }
243
+
244
+ await api.chatRoom.runMiddleware(connection, room, "join");
245
+
246
+ if (!connection.rooms.includes(room)) {
247
+ connection.rooms.push(room);
248
+ }
249
+
250
+ const memberDetails = await chatRoom.generateMemberDetails(connection);
251
+ await client().hset(
252
+ api.chatRoom.keys.members + room,
253
+ connection.id,
254
+ JSON.stringify(memberDetails),
255
+ );
256
+ }
257
+
258
+ return true;
259
+ }
260
+
261
+ /**
262
+ * Remote a connection (via id) from a room. Throws errors if the room does not exist, or the connection is not in the room. Middleware errors also throw.
263
+ * toWaitRemote: Should this method wait until the remote Actionhero server (the one the connection is connected too) responds?
264
+ */
265
+ export async function removeMember(
266
+ connectionId: string,
267
+ room: string,
268
+ toWaitRemote: boolean = true,
269
+ ): Promise<any> {
270
+ const connection = api.connections.connections[connectionId];
271
+ if (!connection) {
272
+ return redis.doCluster(
273
+ "api.chatRoom.removeMember",
274
+ [connectionId, room],
275
+ connectionId,
276
+ toWaitRemote,
277
+ );
278
+ }
279
+
280
+ if (!connection.rooms.includes(room)) {
281
+ throw new Error(
282
+ await config.errors.connectionNotInRoom(connection, room),
283
+ );
284
+ }
285
+
286
+ if (connection.rooms.includes(room)) {
287
+ const found = await chatRoom.exists(room);
288
+ if (!found) {
289
+ throw new Error(await config.errors.connectionRoomNotExist(room));
290
+ }
291
+
292
+ await api.chatRoom.runMiddleware(connection, room, "leave");
293
+
294
+ if (connection.rooms.includes(room)) {
295
+ const index = connection.rooms.indexOf(room);
296
+ connection.rooms.splice(index, 1);
297
+ }
298
+
299
+ await client().hdel(api.chatRoom.keys.members + room, connection.id);
300
+ }
301
+
302
+ return true;
303
+ }
304
+
305
+ /**
306
+ * Send a message to all clients connected to this room
307
+ * - connection:
308
+ * - {} send to every connections
309
+ * - should either be a real client you are emulating (found in api.connections)
310
+ * - a mock
311
+ * - room is the string name of an already-existing room
312
+ * - message can be anything: string, json, object, etc
313
+ */
314
+ export async function broadcast(
315
+ connection: Partial<Connection>,
316
+ room: string,
317
+ message: any,
318
+ ) {
319
+ return api.chatRoom.broadcast(connection, room, message);
320
+ }
321
+ }
@@ -0,0 +1,246 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { utils } from "./utils";
4
+ import { ensureNoTsHeaderOrSpecFiles } from "./utils/ensureNoTsHeaderOrSpecFiles";
5
+
6
+ import { env, recalculateEnv } from "./../classes/process/env";
7
+ import { id, recalcuateId } from "./../classes/process/id";
8
+ import {
9
+ actionheroVersion,
10
+ recalculateActionheroVersion,
11
+ } from "./../classes/process/actionheroVersion";
12
+ import {
13
+ recalculateIsTypescript,
14
+ typescript,
15
+ } from "./../classes/process/typescript";
16
+ import {
17
+ projectRoot,
18
+ recalculateProjectRoot,
19
+ } from "./../classes/process/projectRoot";
20
+ import { RouteMethod, RoutesConfig, RouteType } from "..";
21
+ import { ActionheroConfigInterface } from "../classes/config";
22
+ import { safeGlobSync } from "./utils/safeGlob";
23
+
24
+ export function buildConfig() {
25
+ const configPaths: string[] = [];
26
+
27
+ let config: Partial<ActionheroConfigInterface> = {
28
+ process: {
29
+ env,
30
+ id,
31
+ typescript,
32
+ projectRoot,
33
+ actionheroVersion,
34
+ },
35
+ };
36
+
37
+ // We support multiple configuration paths as follows:
38
+ //
39
+ // 1. Use the project 'config' folder, if it exists.
40
+ // 2. "actionhero --config=PATH1 --config=PATH2 --config=PATH3,PATH4"
41
+ // 3. "ACTIONHERO_CONFIG=PATH1,PATH2 npm start"
42
+ // 4. "ACTIONHERO_CONFIG_OVERRIDES" (stringified JSON) can partially override any of the config objects loaded from the above
43
+ //
44
+ // Note that if --config or ACTIONHERO_CONFIG are used, they _overwrite_ the use of the default "config" folder. If
45
+ // you wish to use both, you need to re-specify "config", e.g. "--config=config,local-config". Also, note that
46
+ // specifying multiple --config options on the command line does exactly the same thing as using one parameter with
47
+ // comma separators, however the environment variable method only supports the comma-delimited syntax.
48
+
49
+ function addConfigPath(
50
+ pathToCheck: string | Array<string>,
51
+ alreadySplit: boolean,
52
+ ) {
53
+ if (typeof pathToCheck === "string") {
54
+ if (!alreadySplit) {
55
+ addConfigPath(pathToCheck.split(","), true);
56
+ } else {
57
+ if (pathToCheck.charAt(0) !== "/") {
58
+ pathToCheck = path.resolve(projectRoot, pathToCheck);
59
+ }
60
+ if (fs.existsSync(pathToCheck)) {
61
+ configPaths.push(pathToCheck);
62
+ }
63
+ }
64
+ } else if (Array.isArray(pathToCheck)) {
65
+ pathToCheck.map((entry) => {
66
+ addConfigPath(entry, alreadySplit);
67
+ });
68
+ }
69
+ }
70
+
71
+ [utils.argv.config?.toString(), process.env.ACTIONHERO_CONFIG].map(
72
+ (entry) => {
73
+ addConfigPath(entry, false);
74
+ },
75
+ );
76
+
77
+ if (configPaths.length < 1 && typescript) {
78
+ addConfigPath("src/config", false);
79
+ }
80
+
81
+ if (configPaths.length < 1) {
82
+ addConfigPath("dist/config", false);
83
+ }
84
+
85
+ if (configPaths.length < 1) {
86
+ throw new Error(
87
+ configPaths +
88
+ "No config directory found in this project. Did you compile your typescript project?",
89
+ );
90
+ }
91
+
92
+ const loadConfigFile = (f: string) => {
93
+ const localConfig = require(f);
94
+ if (f.includes("routes.js") || f.includes("routes.ts")) {
95
+ // We don't want to merge in routes from Actionhero core unless we are running core directly
96
+ // Routes can be loaded by plugins via `registerRoute`
97
+ if (
98
+ f.includes(`${path.sep}node_modules${path.sep}actionhero${path.sep}`)
99
+ ) {
100
+ return;
101
+ }
102
+
103
+ let localRoutes: { routes: Partial<RoutesConfig> } = { routes: {} };
104
+
105
+ if (localConfig.DEFAULT) {
106
+ // @ts-ignore
107
+ localRoutes = utils.hashMerge(localRoutes, localConfig.DEFAULT, config);
108
+ }
109
+
110
+ if (localConfig[env]) {
111
+ // @ts-ignore
112
+ localRoutes = utils.hashMerge(localRoutes, localConfig[env], config);
113
+ }
114
+
115
+ (Object.keys(localRoutes.routes) as RouteMethod[]).forEach((v) => {
116
+ if (config.routes && config.routes[v]) {
117
+ config.routes[v].push(...localRoutes.routes[v]);
118
+ } else {
119
+ if (!config.routes) config.routes = {};
120
+ config.routes[v] = localRoutes.routes[v];
121
+ }
122
+ });
123
+ } else {
124
+ if (localConfig.DEFAULT) {
125
+ config = utils.hashMerge(config, localConfig.DEFAULT, config);
126
+ }
127
+
128
+ if (localConfig[env]) {
129
+ config = utils.hashMerge(config, localConfig[env], config);
130
+ }
131
+ }
132
+ };
133
+
134
+ const loadConfigDirectory = (configPath: string, watch: boolean) => {
135
+ const configFiles = ensureNoTsHeaderOrSpecFiles(
136
+ safeGlobSync(path.join(configPath, "**", "**/*(*.js|*.ts)")),
137
+ );
138
+
139
+ let loadRetries = 0;
140
+ let loadErrors: Record<
141
+ string,
142
+ { error: NodeJS.ErrnoException; msg: string }
143
+ > = {};
144
+ for (let i = 0, limit = configFiles.length; i < limit; i++) {
145
+ const f = configFiles[i];
146
+ try {
147
+ // attempt configuration file load
148
+ loadConfigFile(f);
149
+
150
+ // configuration file load success: clear retries and
151
+ // errors since progress has been made
152
+ loadRetries = 0;
153
+ loadErrors = {};
154
+ } catch (error) {
155
+ // error loading configuration, abort if all remaining
156
+ // configuration files have been tried and failed
157
+ // indicating inability to progress
158
+ loadErrors[f] = { error: error, msg: error.toString() };
159
+ if (++loadRetries === limit - i) {
160
+ Object.keys(loadErrors).forEach((e) => {
161
+ console.log(loadErrors[e].error.stack);
162
+ console.log("");
163
+ delete loadErrors[e].error;
164
+ });
165
+
166
+ throw new Error(
167
+ "Unable to load configurations, errors: " +
168
+ JSON.stringify(loadErrors),
169
+ );
170
+ }
171
+ // adjust configuration files list: remove and push
172
+ // failed configuration to the end of the list and
173
+ // continue with next file at same index
174
+ configFiles.push(configFiles.splice(i--, 1)[0]);
175
+ continue;
176
+ }
177
+ }
178
+
179
+ // We load the config twice. Utilize configuration files load order that succeeded on the first pass.
180
+ // This is to allow 'literal' values to be loaded whenever possible, and then for references to be resolved
181
+ configFiles.forEach(loadConfigFile);
182
+
183
+ // Remove duplicate routes since we might be loading from multiple config directories, also we load every
184
+ // config directory twice.
185
+ if (config.routes) {
186
+ (Object.keys(config.routes) as RouteMethod[]).forEach((v) => {
187
+ config.routes[v] = config.routes[v].filter(
188
+ (route: RouteType, index: number, self: RouteType[]) =>
189
+ index ===
190
+ self.findIndex(
191
+ (r) =>
192
+ r.path === route.path &&
193
+ r.action === route.action &&
194
+ r.apiVersion === route.apiVersion &&
195
+ r.matchTrailingPathParts === route.matchTrailingPathParts &&
196
+ r.dir === route.dir,
197
+ ),
198
+ );
199
+ });
200
+ }
201
+ };
202
+
203
+ // load the default config of actionhero
204
+ loadConfigDirectory(path.join(__dirname, "/../config"), false);
205
+
206
+ // load the project specific config
207
+ configPaths.map((p) => loadConfigDirectory(p, false));
208
+
209
+ if (process.env.ACTIONHERO_CONFIG_OVERRIDES) {
210
+ try {
211
+ config = utils.hashMerge(
212
+ config,
213
+ JSON.parse(process.env.ACTIONHERO_CONFIG_OVERRIDES),
214
+ );
215
+ } catch (error) {
216
+ throw new Error(`could not parse ACTIONHERO_CONFIG_OVERRIDES: ${error}`);
217
+ }
218
+ }
219
+
220
+ if (utils.argv.ACTIONHERO_CONFIG_OVERRIDES) {
221
+ try {
222
+ config = utils.hashMerge(
223
+ config,
224
+ JSON.parse(utils.argv.ACTIONHERO_CONFIG_OVERRIDES.toString()),
225
+ );
226
+ } catch (error) {
227
+ throw new Error(`could not parse ACTIONHERO_CONFIG_OVERRIDES: ${error}`);
228
+ }
229
+ }
230
+
231
+ return config;
232
+ }
233
+
234
+ export let config = buildConfig();
235
+
236
+ /**
237
+ * Rebuild Actionhero's `config` object. Useful when Environment variables effecting the config may have changed.
238
+ */
239
+ export const rebuildConfig = () => {
240
+ recalculateEnv();
241
+ recalculateActionheroVersion();
242
+ recalcuateId();
243
+ recalculateProjectRoot();
244
+ recalculateIsTypescript();
245
+ config = buildConfig();
246
+ };
@@ -0,0 +1,62 @@
1
+ import * as winston from "winston";
2
+ import { config } from "./config";
3
+ import { utils } from "./utils";
4
+
5
+ // exported as `import { loggers } from "actionhero"`
6
+ export let loggers: winston.Logger[] = [];
7
+
8
+ config.general.paths.log.forEach((p: string) => {
9
+ try {
10
+ utils.fileUtils.createDirSafely(p);
11
+ } catch (error) {
12
+ if (error.code !== "EEXIST") {
13
+ throw error;
14
+ }
15
+ }
16
+ });
17
+
18
+ loggers = config.logger.loggers.map((loggerBuilder: Function) => {
19
+ const resolvedLogger = loggerBuilder(config);
20
+ return winston.createLogger(resolvedLogger);
21
+ });
22
+
23
+ export type ActionheroLogLevel =
24
+ | "emerg"
25
+ | "alert"
26
+ | "crit"
27
+ | "error"
28
+ | "warning"
29
+ | "notice"
30
+ | "info"
31
+ | "debug";
32
+
33
+ /**
34
+ * Log a message, with optional metadata. The message can be logged to a number of locations (stdio, files, etc) as configured via config/logger.js
35
+ *
36
+ * The most basic use. Will assume 'info' as the severity: `log('hello')`
37
+ * Custom severity: `log('OH NO!', 'warning')`
38
+ * Custom severity with a metadata object: `log('OH NO, something went wrong', 'warning', { error: new Error('things are busted') })`
39
+ *
40
+ * The default log levels are: `emerg: 0, alert: 1, crit: 2, error: 3, warning: 4, notice: 5, info: 6, debug: 7`.
41
+ * Logging levels in winston conform to the severity ordering specified by RFC5424: severity of all levels is assumed to be numerically ascending from most important to least important.
42
+ * Learn more at https://github.com/winstonjs/winston
43
+ */
44
+ export function log(
45
+ message: string,
46
+ severity: ActionheroLogLevel = "info",
47
+ data?: any,
48
+ ) {
49
+ loggers.map((logger) => {
50
+ if (logger.levels[severity] === undefined) {
51
+ severity = "info";
52
+ }
53
+
54
+ const args = [severity, message];
55
+
56
+ if (data !== null && data !== undefined) {
57
+ args.push(data);
58
+ }
59
+
60
+ return logger.log.apply(logger, args);
61
+ });
62
+ }