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.
- package/.devcontainer/devcontainer.json +4 -0
- package/.devcontainer/setup.sh +11 -0
- package/.dockerignore +2 -0
- package/.github/CONTRIBUTING.md +52 -0
- package/.github/FUNDING.yml +3 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +59 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +43 -0
- package/.github/dependabot.yml +17 -0
- package/.github/workflows/codeql.yml +76 -0
- package/.github/workflows/publish_docs.yml +25 -0
- package/.github/workflows/test.yml +78 -0
- package/.nvmrc +1 -0
- package/.prettierignore +1 -0
- package/.prettierrc +1 -0
- package/.vscode/launch.json +42 -0
- package/CODE_OF_CONDUCT.md +76 -0
- package/Dockerfile +17 -0
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/SECURITY.md +5 -0
- package/__tests__/actions/cacheTest.ts +58 -0
- package/__tests__/actions/randomNumber.ts +26 -0
- package/__tests__/actions/recursiveAction.ts +16 -0
- package/__tests__/actions/sleepTest.ts +24 -0
- package/__tests__/actions/status.ts +17 -0
- package/__tests__/actions/swagger.ts +76 -0
- package/__tests__/actions/validationTest.ts +63 -0
- package/__tests__/cli/cli.ts +126 -0
- package/__tests__/core/api.ts +632 -0
- package/__tests__/core/cache.ts +400 -0
- package/__tests__/core/chatRoom.ts +589 -0
- package/__tests__/core/cli.ts +349 -0
- package/__tests__/core/cluster.ts +132 -0
- package/__tests__/core/config.ts +78 -0
- package/__tests__/core/errors.ts +112 -0
- package/__tests__/core/log.ts +23 -0
- package/__tests__/core/middleware.ts +427 -0
- package/__tests__/core/plugins/partialPlugin.ts +94 -0
- package/__tests__/core/plugins/withPlugin.ts +88 -0
- package/__tests__/core/plugins/withoutPlugin.ts +81 -0
- package/__tests__/core/process.ts +42 -0
- package/__tests__/core/specHelper.ts +330 -0
- package/__tests__/core/staticFile/compression.ts +99 -0
- package/__tests__/core/staticFile/staticFile.ts +180 -0
- package/__tests__/core/tasks/customQueueFunction.ts +67 -0
- package/__tests__/core/tasks/fullWorkerFlow.ts +199 -0
- package/__tests__/core/tasks/tasks.ts +605 -0
- package/__tests__/integration/browser.ts +133 -0
- package/__tests__/integration/ioredis-mock.ts +194 -0
- package/__tests__/integration/sendBuffer.ts +97 -0
- package/__tests__/integration/sendFile.ts +24 -0
- package/__tests__/integration/sharedFingerprint.ts +82 -0
- package/__tests__/integration/taskFlow.ts +110 -0
- package/__tests__/jest.ts +5 -0
- package/__tests__/modules/action.ts +103 -0
- package/__tests__/modules/config.ts +19 -0
- package/__tests__/modules/utils/ensureNoTsHeaderOrSpecFiles.ts +24 -0
- package/__tests__/servers/web/allowedRequestHosts.ts +88 -0
- package/__tests__/servers/web/enableMultiples.ts +83 -0
- package/__tests__/servers/web/fileUpload.ts +79 -0
- package/__tests__/servers/web/jsonp.ts +57 -0
- package/__tests__/servers/web/nonMultiples.ts +83 -0
- package/__tests__/servers/web/rawBody.ts +208 -0
- package/__tests__/servers/web/returnErrorCodes.ts +55 -0
- package/__tests__/servers/web/routes/deepRoutes.ts +96 -0
- package/__tests__/servers/web/routes/routes.ts +579 -0
- package/__tests__/servers/web/routes/veryDeepRoutes.ts +92 -0
- package/__tests__/servers/web/web.ts +1031 -0
- package/__tests__/servers/websocket.ts +795 -0
- package/__tests__/tasks/runAction.ts +37 -0
- package/__tests__/template.ts.example +20 -0
- package/__tests__/testCliCommands/hello.ts +44 -0
- package/__tests__/testPlugin/public/plugin.html +1 -0
- package/__tests__/testPlugin/src/actions/pluginAction.ts +14 -0
- package/__tests__/testPlugin/src/bin/hello.ts +22 -0
- package/__tests__/testPlugin/src/initializers/pluginInitializer.ts +17 -0
- package/__tests__/testPlugin/src/tasks/pluginTask.ts +15 -0
- package/__tests__/testPlugin/tsconfig.json +10 -0
- package/__tests__/utils/utils.ts +492 -0
- package/app.json +23 -0
- package/bin/deploy-docs +39 -0
- package/client/ActionheroWebsocketClient.js +277 -0
- package/docker-compose.yml +73 -0
- package/package.json +24 -0
- package/public/chat.html +194 -0
- package/public/css/cosmo.css +12 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +115 -0
- package/public/javascript/.gitkeep +0 -0
- package/public/linkedSession.html +80 -0
- package/public/logo/actionhero-small.png +0 -0
- package/public/logo/actionhero.png +0 -0
- package/public/pixel.gif +0 -0
- package/public/simple.html +2 -0
- package/public/swagger.html +32 -0
- package/public/websocketLoadTest.html +322 -0
- package/src/actions/cacheTest.ts +58 -0
- package/src/actions/createChatRoom.ts +20 -0
- package/src/actions/randomNumber.ts +17 -0
- package/src/actions/recursiveAction.ts +13 -0
- package/src/actions/sendFile.ts +12 -0
- package/src/actions/sleepTest.ts +40 -0
- package/src/actions/status.ts +73 -0
- package/src/actions/swagger.ts +155 -0
- package/src/actions/validationTest.ts +36 -0
- package/src/bin/actionhero.ts +225 -0
- package/src/bin/methods/actions/list.ts +30 -0
- package/src/bin/methods/console.ts +26 -0
- package/src/bin/methods/generate/action.ts +58 -0
- package/src/bin/methods/generate/cli.ts +51 -0
- package/src/bin/methods/generate/initializer.ts +54 -0
- package/src/bin/methods/generate/plugin.ts +57 -0
- package/src/bin/methods/generate/server.ts +38 -0
- package/src/bin/methods/generate/task.ts +68 -0
- package/src/bin/methods/generate.ts +176 -0
- package/src/bin/methods/task/enqueue.ts +35 -0
- package/src/classes/action.ts +98 -0
- package/src/classes/actionProcessor.ts +463 -0
- package/src/classes/api.ts +51 -0
- package/src/classes/cli.ts +67 -0
- package/src/classes/config.ts +15 -0
- package/src/classes/connection.ts +321 -0
- package/src/classes/exceptionReporter.ts +9 -0
- package/src/classes/initializer.ts +59 -0
- package/src/classes/initializers.ts +5 -0
- package/src/classes/input.ts +9 -0
- package/src/classes/inputs.ts +34 -0
- package/src/classes/process/actionheroVersion.ts +15 -0
- package/src/classes/process/env.ts +16 -0
- package/src/classes/process/id.ts +34 -0
- package/src/classes/process/pid.ts +32 -0
- package/src/classes/process/projectRoot.ts +16 -0
- package/src/classes/process/typescript.ts +47 -0
- package/src/classes/process.ts +479 -0
- package/src/classes/server.ts +251 -0
- package/src/classes/task.ts +87 -0
- package/src/config/api.ts +107 -0
- package/src/config/errors.ts +162 -0
- package/src/config/logger.ts +113 -0
- package/src/config/plugins.ts +37 -0
- package/src/config/redis.ts +78 -0
- package/src/config/routes.ts +44 -0
- package/src/config/tasks.ts +84 -0
- package/src/config/web.ts +136 -0
- package/src/config/websocket.ts +62 -0
- package/src/index.ts +46 -0
- package/src/initializers/actions.ts +125 -0
- package/src/initializers/chatRoom.ts +214 -0
- package/src/initializers/connections.ts +124 -0
- package/src/initializers/exceptions.ts +155 -0
- package/src/initializers/params.ts +52 -0
- package/src/initializers/redis.ts +191 -0
- package/src/initializers/resque.ts +248 -0
- package/src/initializers/routes.ts +229 -0
- package/src/initializers/servers.ts +134 -0
- package/src/initializers/specHelper.ts +195 -0
- package/src/initializers/staticFile.ts +253 -0
- package/src/initializers/tasks.ts +188 -0
- package/src/modules/action.ts +89 -0
- package/src/modules/cache.ts +326 -0
- package/src/modules/chatRoom.ts +321 -0
- package/src/modules/config.ts +246 -0
- package/src/modules/log.ts +62 -0
- package/src/modules/redis.ts +93 -0
- package/src/modules/route.ts +59 -0
- package/src/modules/specHelper.ts +182 -0
- package/src/modules/task.ts +527 -0
- package/src/modules/utils/argv.ts +3 -0
- package/src/modules/utils/arrayStartingMatch.ts +21 -0
- package/src/modules/utils/arrayUnique.ts +15 -0
- package/src/modules/utils/collapseObjectToArray.ts +33 -0
- package/src/modules/utils/deepCopy.ts +3 -0
- package/src/modules/utils/ensureNoTsHeaderOrSpecFiles.ts +19 -0
- package/src/modules/utils/eventLoopDelay.ts +34 -0
- package/src/modules/utils/fileUtils.ts +119 -0
- package/src/modules/utils/filterObjectForLogging.ts +51 -0
- package/src/modules/utils/filterResponseForLogging.ts +53 -0
- package/src/modules/utils/getExternalIPAddress.ts +17 -0
- package/src/modules/utils/hashMerge.ts +63 -0
- package/src/modules/utils/isPlainObject.ts +45 -0
- package/src/modules/utils/isRunning.ts +7 -0
- package/src/modules/utils/parseCookies.ts +20 -0
- package/src/modules/utils/parseHeadersForClientAddress.ts +53 -0
- package/src/modules/utils/parseIPv6URI.ts +24 -0
- package/src/modules/utils/replaceDistWithSrc.ts +9 -0
- package/src/modules/utils/safeGlob.ts +6 -0
- package/src/modules/utils/sleep.ts +8 -0
- package/src/modules/utils/sortGlobalMiddleware.ts +17 -0
- package/src/modules/utils/sourceRelativeLinkPath.ts +29 -0
- package/src/modules/utils.ts +66 -0
- package/src/server.ts +20 -0
- package/src/servers/web.ts +894 -0
- package/src/servers/websocket.ts +304 -0
- package/src/tasks/runAction.ts +29 -0
- package/tea.yaml +9 -0
- package/templates/README.md.template +17 -0
- package/templates/action.ts.template +15 -0
- package/templates/boot.js.template +9 -0
- package/templates/cli.ts.template +15 -0
- package/templates/gitignore.template +23 -0
- package/templates/initializer.ts.template +17 -0
- package/templates/package-plugin.json.template +12 -0
- package/templates/package.json.template +45 -0
- package/templates/projectMap.txt +39 -0
- package/templates/projectServer.ts.template +20 -0
- package/templates/server.ts.template +37 -0
- package/templates/task.ts.template +16 -0
- package/templates/test/action.ts.template +13 -0
- package/templates/test/task.ts.template +20 -0
- package/tsconfig.json +11 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
import * as fs from "fs";
|
2
|
+
import * as path from "path";
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Check if a directory exists.
|
6
|
+
*/
|
7
|
+
export function dirExists(dir: string): boolean {
|
8
|
+
try {
|
9
|
+
const stats = fs.lstatSync(dir);
|
10
|
+
return stats.isDirectory() || stats.isSymbolicLink();
|
11
|
+
} catch (e) {
|
12
|
+
return false;
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Check if a file exists.
|
18
|
+
*/
|
19
|
+
export function fileExists(file: string): boolean {
|
20
|
+
try {
|
21
|
+
const stats = fs.lstatSync(file);
|
22
|
+
return stats.isFile() || stats.isSymbolicLink();
|
23
|
+
} catch (e) {
|
24
|
+
return false;
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
/**
|
29
|
+
* Create a directory, only if it doesn't exist yet.
|
30
|
+
* Throws an error if the directory already exists, or encounters a filesystem problem.
|
31
|
+
*/
|
32
|
+
export function createDirSafely(dir: string): string {
|
33
|
+
if (dirExists(dir)) {
|
34
|
+
const error = new Error(
|
35
|
+
`directory '${path.normalize(dir)}' already exists`,
|
36
|
+
);
|
37
|
+
// @ts-ignore
|
38
|
+
error.code = "EEXIST";
|
39
|
+
throw error;
|
40
|
+
} else {
|
41
|
+
fs.mkdirSync(path.normalize(dir), "0766");
|
42
|
+
return `created directory '${path.normalize(dir)}'`;
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
* Create a file, only if it doesn't exist yet.
|
48
|
+
* Throws an error if the file already exists, or encounters a filesystem problem.
|
49
|
+
*/
|
50
|
+
export function createFileSafely(
|
51
|
+
file: string,
|
52
|
+
data: string | NodeJS.ArrayBufferView,
|
53
|
+
overwrite: boolean = false,
|
54
|
+
): string {
|
55
|
+
if (fileExists(file) && !overwrite) {
|
56
|
+
const error = new Error(`file '${path.normalize(file)}' already exists`);
|
57
|
+
// @ts-ignore
|
58
|
+
error.code = "EEXIST";
|
59
|
+
throw error;
|
60
|
+
} else {
|
61
|
+
let message = `wrote file '${path.normalize(file)}'`;
|
62
|
+
if (overwrite && fileExists(file)) {
|
63
|
+
message = ` - overwritten file '${path.normalize(file)}'`;
|
64
|
+
}
|
65
|
+
fs.writeFileSync(path.normalize(file), data);
|
66
|
+
return message;
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
/**
|
71
|
+
* Create an Actionhero LinkFile, only if it doesn't exist yet.
|
72
|
+
* Throws an error if the file already exists, or encounters a filesystem problem.
|
73
|
+
*/
|
74
|
+
export function createLinkfileSafely(filePath: string, type: string): string {
|
75
|
+
if (fileExists(filePath)) {
|
76
|
+
const error = new Error(`link file '${filePath}' already exists`);
|
77
|
+
// @ts-ignore
|
78
|
+
error.code = "EEXIST";
|
79
|
+
throw error;
|
80
|
+
} else {
|
81
|
+
fs.writeFileSync(filePath, type);
|
82
|
+
return `creating linkfile '${filePath}'`;
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
/**
|
87
|
+
* Remove an Actionhero LinkFile, only if it exists.
|
88
|
+
* Throws an error if the file does not exist, or encounters a filesystem problem.
|
89
|
+
*/
|
90
|
+
export function removeLinkfileSafely(filePath: string): string {
|
91
|
+
if (!fileExists(filePath)) {
|
92
|
+
const error = new Error(`link file '${filePath}' doesn't exist`);
|
93
|
+
// @ts-ignore
|
94
|
+
error.code = "ENOEXIST";
|
95
|
+
throw error;
|
96
|
+
} else {
|
97
|
+
fs.unlinkSync(filePath);
|
98
|
+
return `removing linkfile '${filePath}'`;
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
/**
|
103
|
+
* Create a system symbolic link.
|
104
|
+
* Throws an error if it encounters a filesystem problem.
|
105
|
+
*/
|
106
|
+
export function createSymlinkSafely(
|
107
|
+
destination: string,
|
108
|
+
source: string,
|
109
|
+
): string {
|
110
|
+
if (dirExists(destination)) {
|
111
|
+
const error = new Error(`symbolic link '${destination}' already exists`);
|
112
|
+
// @ts-ignore
|
113
|
+
error.code = "EEXIST";
|
114
|
+
throw error;
|
115
|
+
} else {
|
116
|
+
fs.symlinkSync(source, destination, "dir");
|
117
|
+
return `creating symbolic link '${destination}' => '${source}'`;
|
118
|
+
}
|
119
|
+
}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import { isPlainObject } from "./isPlainObject";
|
2
|
+
import { deepCopy } from "./deepCopy";
|
3
|
+
import { config } from "../config";
|
4
|
+
import * as dotProp from "dot-prop";
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Prepares acton params for logging.
|
8
|
+
* Hides any sensitive data as defined by `api.config.general.filteredParams`
|
9
|
+
* Truncates long strings via `api.config.logger.maxLogStringLength`
|
10
|
+
*/
|
11
|
+
export function filterObjectForLogging(
|
12
|
+
params: Record<string, any>,
|
13
|
+
): Record<string, any> {
|
14
|
+
params = deepCopy(params);
|
15
|
+
const sanitizedParams: Record<string, any> = {};
|
16
|
+
|
17
|
+
for (const i in params) {
|
18
|
+
if (
|
19
|
+
Array.isArray(params[i]) &&
|
20
|
+
params[i].length > (config.logger?.maxLogArrayLength ?? 10)
|
21
|
+
) {
|
22
|
+
params[i] = `${params[i].length} items`;
|
23
|
+
}
|
24
|
+
|
25
|
+
if (isPlainObject(params[i])) {
|
26
|
+
sanitizedParams[i] = params[i];
|
27
|
+
} else if (typeof params[i] === "string") {
|
28
|
+
sanitizedParams[i] = params[i].substring(
|
29
|
+
0,
|
30
|
+
config.logger.maxLogStringLength,
|
31
|
+
);
|
32
|
+
} else {
|
33
|
+
sanitizedParams[i] = params[i];
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
let filteredParams: string[];
|
38
|
+
if (typeof config.general.filteredParams === "function") {
|
39
|
+
filteredParams = config.general.filteredParams();
|
40
|
+
} else {
|
41
|
+
filteredParams = config.general.filteredParams;
|
42
|
+
}
|
43
|
+
|
44
|
+
filteredParams.forEach((configParam) => {
|
45
|
+
if (dotProp.get(params, configParam) !== undefined) {
|
46
|
+
dotProp.set(sanitizedParams, configParam, "[FILTERED]");
|
47
|
+
}
|
48
|
+
});
|
49
|
+
|
50
|
+
return sanitizedParams;
|
51
|
+
}
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import { isPlainObject } from "./isPlainObject";
|
2
|
+
import { deepCopy } from "./deepCopy";
|
3
|
+
import { config } from "../config";
|
4
|
+
import * as dotProp from "dot-prop";
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Prepares acton response for logging.
|
8
|
+
* Hides any sensitive data as defined by `api.config.general.filteredResponse`
|
9
|
+
* Truncates long strings via `api.config.logger.maxLogStringLength`
|
10
|
+
*/
|
11
|
+
export function filterResponseForLogging(response: Record<string, any>): {
|
12
|
+
[key: string]: any;
|
13
|
+
} {
|
14
|
+
response = deepCopy(response);
|
15
|
+
const sanitizedResponse: Record<string, any> = {};
|
16
|
+
|
17
|
+
for (const i in response) {
|
18
|
+
if (
|
19
|
+
Array.isArray(response[i]) &&
|
20
|
+
response[i].length > (config.logger?.maxLogArrayLength ?? 10)
|
21
|
+
) {
|
22
|
+
response[i] = `${response[i].length} items`;
|
23
|
+
}
|
24
|
+
|
25
|
+
if (isPlainObject(response[i])) {
|
26
|
+
sanitizedResponse[i] = response[i];
|
27
|
+
} else if (typeof response[i] === "string") {
|
28
|
+
sanitizedResponse[i] = response[i].substring(
|
29
|
+
0,
|
30
|
+
config.logger.maxLogStringLength,
|
31
|
+
);
|
32
|
+
} else if (response[i] instanceof Error) {
|
33
|
+
sanitizedResponse[i] = response[i].message ?? String(response[i]);
|
34
|
+
} else {
|
35
|
+
sanitizedResponse[i] = response[i];
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
let filteredResponse: string[];
|
40
|
+
if (typeof config.general.filteredResponse === "function") {
|
41
|
+
filteredResponse = config.general.filteredResponse();
|
42
|
+
} else {
|
43
|
+
filteredResponse = config.general.filteredResponse;
|
44
|
+
}
|
45
|
+
|
46
|
+
filteredResponse.forEach((configParam) => {
|
47
|
+
if (dotProp.get(response, configParam) !== undefined) {
|
48
|
+
dotProp.set(sanitizedResponse, configParam, "[FILTERED]");
|
49
|
+
}
|
50
|
+
});
|
51
|
+
|
52
|
+
return sanitizedResponse;
|
53
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import * as os from "os";
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Returns this server's external/public IP address
|
5
|
+
*/
|
6
|
+
export function getExternalIPAddress(): string {
|
7
|
+
const interfaces = os.networkInterfaces();
|
8
|
+
let ip = null;
|
9
|
+
for (const dev in interfaces) {
|
10
|
+
interfaces[dev].forEach((details) => {
|
11
|
+
if (details.family === "IPv4" && details.address !== "127.0.0.1") {
|
12
|
+
ip = details.address;
|
13
|
+
}
|
14
|
+
});
|
15
|
+
}
|
16
|
+
return ip;
|
17
|
+
}
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import { isPlainObject } from "./isPlainObject";
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Recursively merge 2 Objects together. Will resolve functions if they are present, unless the parent Object has the property `_toExpand = false`.
|
5
|
+
* Actionhero uses this internally to construct and resolve the config.
|
6
|
+
* Matching keys in B override A.
|
7
|
+
*/
|
8
|
+
export function hashMerge(
|
9
|
+
a: Record<string, any>,
|
10
|
+
b: Record<string, any>,
|
11
|
+
arg?: Record<string, any>,
|
12
|
+
): { [key: string]: any } {
|
13
|
+
const c: Record<string, any> = {};
|
14
|
+
let i: string;
|
15
|
+
let response: object;
|
16
|
+
|
17
|
+
for (i in a) {
|
18
|
+
if (isPlainObject(a[i])) {
|
19
|
+
// can't be added into above condition, or empty objects will overwrite and not merge
|
20
|
+
// also make sure empty objects are created
|
21
|
+
c[i] = Object.keys(a[i]).length > 0 ? hashMerge(c[i], a[i], arg) : {};
|
22
|
+
} else {
|
23
|
+
if (typeof a[i] === "function") {
|
24
|
+
response = a[i](arg);
|
25
|
+
if (isPlainObject(response)) {
|
26
|
+
c[i] = hashMerge(c[i], response, arg);
|
27
|
+
} else {
|
28
|
+
c[i] = response;
|
29
|
+
}
|
30
|
+
} else {
|
31
|
+
// don't create first term if it is undefined or null
|
32
|
+
if (a[i] === undefined || a[i] === null) {
|
33
|
+
} else c[i] = a[i];
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
for (i in b) {
|
38
|
+
if (isPlainObject(b[i])) {
|
39
|
+
// can't be added into above condition, or empty objects will overwrite and not merge
|
40
|
+
if (Object.keys(b[i]).length > 0) c[i] = hashMerge(c[i], b[i], arg);
|
41
|
+
// make sure empty objects are only created, when no key exists yet
|
42
|
+
else if (!(i in c)) c[i] = {};
|
43
|
+
} else {
|
44
|
+
if (typeof b[i] === "function") {
|
45
|
+
response = b[i](arg);
|
46
|
+
if (isPlainObject(response)) {
|
47
|
+
c[i] = hashMerge(c[i], response, arg);
|
48
|
+
} else {
|
49
|
+
c[i] = response;
|
50
|
+
}
|
51
|
+
} else {
|
52
|
+
// ignore second term if it is undefined
|
53
|
+
if (b[i] === undefined) {
|
54
|
+
} else if (b[i] == null && i in c)
|
55
|
+
// delete second term/key if value is null and it already exists
|
56
|
+
delete c[i];
|
57
|
+
// normal assignments for everything else
|
58
|
+
else c[i] = b[i];
|
59
|
+
}
|
60
|
+
}
|
61
|
+
}
|
62
|
+
return c;
|
63
|
+
}
|
@@ -0,0 +1,45 @@
|
|
1
|
+
/**
|
2
|
+
* Is the JS Object passed in truly just an object?
|
3
|
+
*/
|
4
|
+
export function isPlainObject(o: any) {
|
5
|
+
const safeTypes = [
|
6
|
+
Boolean,
|
7
|
+
Number,
|
8
|
+
String,
|
9
|
+
Function,
|
10
|
+
Array,
|
11
|
+
Date,
|
12
|
+
RegExp,
|
13
|
+
Buffer,
|
14
|
+
];
|
15
|
+
const safeInstances = ["boolean", "number", "string", "function"];
|
16
|
+
const expandPreventMatchKey = "_toExpand"; // set `_toExpand = false` within an object if you don't want to expand it
|
17
|
+
let i;
|
18
|
+
|
19
|
+
if (!o) {
|
20
|
+
return false;
|
21
|
+
}
|
22
|
+
if (o instanceof Object === false) {
|
23
|
+
return false;
|
24
|
+
}
|
25
|
+
for (i in safeTypes) {
|
26
|
+
if (o instanceof safeTypes[i]) {
|
27
|
+
return false;
|
28
|
+
}
|
29
|
+
}
|
30
|
+
for (i in safeInstances) {
|
31
|
+
if (typeof o === safeInstances[i]) {
|
32
|
+
return false;
|
33
|
+
}
|
34
|
+
}
|
35
|
+
if (o[expandPreventMatchKey] === false) {
|
36
|
+
return false;
|
37
|
+
}
|
38
|
+
|
39
|
+
try {
|
40
|
+
// sometimes objects cannot be successfully stringified (https://github.com/node-formidable/formidable/pull/796)
|
41
|
+
return o.toString() === "[object Object]";
|
42
|
+
} catch (error) {
|
43
|
+
return true;
|
44
|
+
}
|
45
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
/**
|
2
|
+
* Transform the cookie headers of a node HTTP `req` Object into a hash.
|
3
|
+
*/
|
4
|
+
export function parseCookies(req: {
|
5
|
+
headers: { [key: string]: string | string[] };
|
6
|
+
}): object {
|
7
|
+
const cookies: Record<string, string> = {};
|
8
|
+
if (req.headers.cookie) {
|
9
|
+
(Array.isArray(req.headers.cookie)
|
10
|
+
? req.headers.cookie.join("")
|
11
|
+
: req.headers.cookie
|
12
|
+
)
|
13
|
+
.split(";")
|
14
|
+
.forEach((cookie) => {
|
15
|
+
const parts = cookie.split("=");
|
16
|
+
cookies[parts[0].trim()] = (parts[1] || "").trim();
|
17
|
+
});
|
18
|
+
}
|
19
|
+
return cookies;
|
20
|
+
}
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import { parseIPv6URI } from "./parseIPv6URI";
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Return ip and port information if defined in the header
|
5
|
+
*/
|
6
|
+
export function parseHeadersForClientAddress(
|
7
|
+
headers: Record<string, string | string[]>,
|
8
|
+
) {
|
9
|
+
let ip: string;
|
10
|
+
let port: number | string;
|
11
|
+
|
12
|
+
if (headers["x-forwarded-for"]) {
|
13
|
+
let parts;
|
14
|
+
let forwardedIp = Array.isArray(headers["x-forwarded-for"])
|
15
|
+
? headers["x-forwarded-for"][0].split(",")[0]
|
16
|
+
: headers["x-forwarded-for"].split(",")[0];
|
17
|
+
if (
|
18
|
+
forwardedIp.indexOf(".") >= 0 ||
|
19
|
+
(forwardedIp.indexOf(".") < 0 && forwardedIp.indexOf(":") < 0)
|
20
|
+
) {
|
21
|
+
// IPv4
|
22
|
+
forwardedIp = forwardedIp.replace("::ffff:", ""); // remove any IPv6 information, ie: '::ffff:127.0.0.1'
|
23
|
+
parts = forwardedIp.split(":");
|
24
|
+
if (parts[0]) {
|
25
|
+
ip = parts[0];
|
26
|
+
}
|
27
|
+
if (parts[1]) {
|
28
|
+
port = parts[1];
|
29
|
+
}
|
30
|
+
} else {
|
31
|
+
// IPv6
|
32
|
+
parts = parseIPv6URI(forwardedIp);
|
33
|
+
if (parts.host) {
|
34
|
+
ip = parts.host;
|
35
|
+
}
|
36
|
+
if (parts.port) {
|
37
|
+
port = parts.port;
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
if (headers["x-forwarded-port"]) {
|
42
|
+
port = Array.isArray(headers["x-forwarded-port"])
|
43
|
+
? headers["x-forwarded-port"][0]
|
44
|
+
: headers["x-forwarded-port"];
|
45
|
+
}
|
46
|
+
if (headers["x-real-ip"]) {
|
47
|
+
// https://distinctplace.com/2014/04/23/story-behind-x-forwarded-for-and-x-real-ip-headers/
|
48
|
+
ip = Array.isArray(headers["x-real-ip"])
|
49
|
+
? headers["x-real-ip"][0]
|
50
|
+
: headers["x-real-ip"];
|
51
|
+
}
|
52
|
+
return { ip, port };
|
53
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
/**
|
2
|
+
* Parse an IPv6 address, returning both host and port.
|
3
|
+
* see https://github.com/actionhero/actionhero/issues/275
|
4
|
+
*/
|
5
|
+
export function parseIPv6URI(addr: string): {
|
6
|
+
host: string;
|
7
|
+
port: number;
|
8
|
+
} {
|
9
|
+
let host = "::1";
|
10
|
+
let port = "80";
|
11
|
+
const regexp = new RegExp(/\[([0-9a-f:]+(?:%.+)?)]:([0-9]{1,5})/);
|
12
|
+
// if we have brackets parse them and find a port
|
13
|
+
if (addr.indexOf("[") > -1 && addr.indexOf("]") > -1) {
|
14
|
+
const res = regexp.exec(addr);
|
15
|
+
if (res === null) {
|
16
|
+
throw new Error("failed to parse address");
|
17
|
+
}
|
18
|
+
host = res[1];
|
19
|
+
port = res[2];
|
20
|
+
} else {
|
21
|
+
host = addr;
|
22
|
+
}
|
23
|
+
return { host: host, port: parseInt(port, 10) };
|
24
|
+
}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { config } from "../config";
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Used by generator functions running from your `dist`, it replaces the path with your `src`
|
5
|
+
* Relies on api.config.general.paths
|
6
|
+
*/
|
7
|
+
export function replaceDistWithSrc(f: string) {
|
8
|
+
return f.replace(config.general.paths.dist, config.general.paths.src);
|
9
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
/**
|
2
|
+
* Sorts an Array of Objects with a priority key
|
3
|
+
*/
|
4
|
+
export function sortGlobalMiddleware(
|
5
|
+
globalMiddlewareList: Array<any>,
|
6
|
+
middleware: {
|
7
|
+
[key: string]: any;
|
8
|
+
},
|
9
|
+
) {
|
10
|
+
globalMiddlewareList.sort((a, b) => {
|
11
|
+
if (middleware[a].priority > middleware[b].priority) {
|
12
|
+
return 1;
|
13
|
+
} else {
|
14
|
+
return -1;
|
15
|
+
}
|
16
|
+
});
|
17
|
+
}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import * as fs from "fs";
|
2
|
+
import * as path from "path";
|
3
|
+
|
4
|
+
export function sourceRelativeLinkPath(
|
5
|
+
linkFile: string,
|
6
|
+
pluginPaths: Array<string>,
|
7
|
+
): string | boolean {
|
8
|
+
const type = fs.readFileSync(linkFile).toString();
|
9
|
+
const pathParts = linkFile.split(path.sep);
|
10
|
+
const name = pathParts[pathParts.length - 1].split(".")[0];
|
11
|
+
const pathsToTry = pluginPaths.slice(0);
|
12
|
+
let pluginRoot: string;
|
13
|
+
|
14
|
+
pathsToTry.forEach((pluginPath) => {
|
15
|
+
const pluginPathAttempt = path.normalize(pluginPath + path.sep + name);
|
16
|
+
try {
|
17
|
+
const stats = fs.lstatSync(pluginPathAttempt);
|
18
|
+
if (!pluginRoot && (stats.isDirectory() || stats.isSymbolicLink())) {
|
19
|
+
pluginRoot = pluginPathAttempt;
|
20
|
+
}
|
21
|
+
} catch (e) {}
|
22
|
+
});
|
23
|
+
|
24
|
+
if (!pluginRoot) {
|
25
|
+
return false;
|
26
|
+
}
|
27
|
+
const pluginSection = path.normalize(pluginRoot + path.sep + type);
|
28
|
+
return pluginSection;
|
29
|
+
}
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import { argv } from "./utils/argv";
|
2
|
+
import { arrayStartingMatch } from "./utils/arrayStartingMatch";
|
3
|
+
import { arrayUnique } from "./utils/arrayUnique";
|
4
|
+
import { collapseObjectToArray } from "./utils/collapseObjectToArray";
|
5
|
+
import { deepCopy } from "./utils/deepCopy";
|
6
|
+
import { ensureNoTsHeaderOrSpecFiles } from "./utils/ensureNoTsHeaderOrSpecFiles";
|
7
|
+
import { eventLoopDelay } from "./utils/eventLoopDelay";
|
8
|
+
import { filterObjectForLogging } from "./utils/filterObjectForLogging";
|
9
|
+
import { filterResponseForLogging } from "./utils/filterResponseForLogging";
|
10
|
+
import { getExternalIPAddress } from "./utils/getExternalIPAddress";
|
11
|
+
import { hashMerge } from "./utils/hashMerge";
|
12
|
+
import { isPlainObject } from "./utils/isPlainObject";
|
13
|
+
import { parseHeadersForClientAddress } from "./utils/parseHeadersForClientAddress";
|
14
|
+
import { parseCookies } from "./utils/parseCookies";
|
15
|
+
import { parseIPv6URI } from "./utils/parseIPv6URI";
|
16
|
+
import { replaceDistWithSrc } from "./utils/replaceDistWithSrc";
|
17
|
+
import { sleep } from "./utils/sleep";
|
18
|
+
import { sortGlobalMiddleware } from "./utils/sortGlobalMiddleware";
|
19
|
+
import { sourceRelativeLinkPath } from "./utils/sourceRelativeLinkPath";
|
20
|
+
import {
|
21
|
+
dirExists,
|
22
|
+
fileExists,
|
23
|
+
createDirSafely,
|
24
|
+
createFileSafely,
|
25
|
+
createLinkfileSafely,
|
26
|
+
removeLinkfileSafely,
|
27
|
+
createSymlinkSafely,
|
28
|
+
} from "./utils/fileUtils";
|
29
|
+
import { isRunning } from "./utils/isRunning";
|
30
|
+
import { safeGlobSync } from "./utils/safeGlob";
|
31
|
+
|
32
|
+
/**
|
33
|
+
* Utility functions for Actionhero
|
34
|
+
*/
|
35
|
+
export const utils = {
|
36
|
+
argv,
|
37
|
+
arrayStartingMatch,
|
38
|
+
arrayUnique,
|
39
|
+
collapseObjectToArray,
|
40
|
+
deepCopy,
|
41
|
+
ensureNoTsHeaderOrSpecFiles,
|
42
|
+
eventLoopDelay,
|
43
|
+
filterObjectForLogging,
|
44
|
+
filterResponseForLogging,
|
45
|
+
getExternalIPAddress,
|
46
|
+
hashMerge,
|
47
|
+
isPlainObject,
|
48
|
+
parseHeadersForClientAddress,
|
49
|
+
parseCookies,
|
50
|
+
parseIPv6URI,
|
51
|
+
replaceDistWithSrc,
|
52
|
+
sleep,
|
53
|
+
sortGlobalMiddleware,
|
54
|
+
sourceRelativeLinkPath,
|
55
|
+
fileUtils: {
|
56
|
+
dirExists,
|
57
|
+
fileExists,
|
58
|
+
createDirSafely,
|
59
|
+
createFileSafely,
|
60
|
+
createLinkfileSafely,
|
61
|
+
removeLinkfileSafely,
|
62
|
+
createSymlinkSafely,
|
63
|
+
},
|
64
|
+
safeGlobSync,
|
65
|
+
isRunning,
|
66
|
+
};
|
package/src/server.ts
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
// load any custom code, configure the env, as needed
|
4
|
+
|
5
|
+
async function main() {
|
6
|
+
// create a new actionhero process
|
7
|
+
const { Process } = await import("./index");
|
8
|
+
const app = new Process();
|
9
|
+
|
10
|
+
// handle unix signals and uncaught exceptions & rejections
|
11
|
+
app.registerProcessSignals((exitCode) => {
|
12
|
+
process.exit(exitCode);
|
13
|
+
});
|
14
|
+
|
15
|
+
// start the app!
|
16
|
+
// you can pass custom configuration to the process as needed
|
17
|
+
await app.start();
|
18
|
+
}
|
19
|
+
|
20
|
+
main();
|