@cloudnux/cli 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.
- package/.deploy/aws/http/main.tf +35 -0
- package/.deploy/aws/http/variables.tf +22 -0
- package/.deploy/aws/scheduler/main.tf +47 -0
- package/.deploy/aws/scheduler/variables.tf +21 -0
- package/.deploy/aws/sqs/main.tf +38 -0
- package/.deploy/aws/sqs/variables.tf +27 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1130 -0
- package/dist/schema/entrypoint.schema.json +131 -0
- package/dist/schema/epf.schema.json +134 -0
- package/dist/schema/schema.json +134 -0
- package/dist/templates/cloud/WIP.ts.ejs +564 -0
- package/dist/templates/cloud/entrypoint-build.ts.ejs +53 -0
- package/dist/templates/cloud/entrypoint-triggers.tf.ejs +67 -0
- package/dist/templates/cloude/entrypoint-build.ts.ejs +50 -0
- package/dist/templates/cloude/entrypoint-triggers.tf.ejs +67 -0
- package/dist/templates/develop.ts.ejs +63 -0
- package/dist/templates/entrypoint-build.ts.ejs +50 -0
- package/dist/templates/entrypoint-develop.ts.ejs +120 -0
- package/dist/templates/entrypoint-triggers.tf.ejs +67 -0
- package/dist/templates/local/dev-server.ts.ejs +106 -0
- package/dist/templates/local/module.ts.ejs +77 -0
- package/dist/templates/local/triggers/http.ts.ejs +33 -0
- package/dist/templates/local/triggers/schedule.ts.ejs +15 -0
- package/package.json +58 -0
- package/readme.md +137 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Context, S3Event,
|
|
2
|
+
APIGatewayProxyEventV2WithJWTAuthorizer,
|
|
3
|
+
ScheduledEvent
|
|
4
|
+
} from "aws-lambda";
|
|
5
|
+
|
|
6
|
+
import { createRouter, httpMatcher, scheduleMatcher } from "@anubis/cloud-provider";
|
|
7
|
+
import { functions } from "@anubis/aws-cloud-provider";
|
|
8
|
+
import { initialize } from "@anubis/datastore";
|
|
9
|
+
import { logger } from "@anubis/utils";
|
|
10
|
+
|
|
11
|
+
import * as src from "../../packages/components/<%=component%>/src";
|
|
12
|
+
|
|
13
|
+
type AWSEventType = S3Event | APIGatewayProxyEventV2WithJWTAuthorizer | ScheduledEvent;
|
|
14
|
+
|
|
15
|
+
const router = createRouter();
|
|
16
|
+
<%_ for(var [key,entry] of Object.entries(entries)) { _%>
|
|
17
|
+
<%_ if(entry.trigger.type.toLowerCase() === "http") { _%>
|
|
18
|
+
router.add(httpMatcher("<%=entry.trigger.options.method%>", "<%=entry.trigger.options.route%>"), src["<%= entry.handler %>"])
|
|
19
|
+
<%_ } _%>
|
|
20
|
+
<%_ if(entry.trigger.type.toLowerCase() === "schedule") { _%>
|
|
21
|
+
router.add(scheduleMatcher("<%= key %>"), src["<%= entry.handler %>"])
|
|
22
|
+
<%_ } _%>
|
|
23
|
+
<%_ } _%>
|
|
24
|
+
|
|
25
|
+
export async function handler(event: AWSEventType, context: Context) {
|
|
26
|
+
logger.initialize("locations", context.awsRequestId);
|
|
27
|
+
logger.info("request started");
|
|
28
|
+
logger.debug({ event }, "Received event");
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await initialize();
|
|
32
|
+
let output = null;
|
|
33
|
+
if ("rawPath" in event) {
|
|
34
|
+
output = await router.run("Http", functions.httpAdapter, event, context);
|
|
35
|
+
}
|
|
36
|
+
else if ("source" in event) {
|
|
37
|
+
output = await router.run("Schedule", functions.scheduleAdapter, event, context);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
throw new Error("invalid handler");
|
|
41
|
+
}
|
|
42
|
+
logger.debug({ output }, "Response Sent");
|
|
43
|
+
logger.info("request completed");
|
|
44
|
+
return output;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
logger.error({ error }, "Error occurred");
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
terraform {
|
|
2
|
+
backend "s3" {
|
|
3
|
+
key = "<%= component %>/terraform.state"
|
|
4
|
+
workspace_key_prefix = "state/<%= component %>"
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
required_providers {
|
|
8
|
+
aws = {
|
|
9
|
+
source = "hashicorp/aws"
|
|
10
|
+
version = ">= 4.0"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
required_version = ">= 0.13.1"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
data "aws_lambda_function" "this" {
|
|
18
|
+
function_name = "<%= component %>"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
data "aws_apigatewayv2_apis" "this" {
|
|
22
|
+
tags = {
|
|
23
|
+
application = "anubis"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
<% const httpEntries = Object.entries(entries).filter(([k,e]) => e.trigger.type.toLowerCase() === "http") %>
|
|
28
|
+
<% if(httpEntries.length > 0) { %>
|
|
29
|
+
module "http_trigger_for_<%= component %>" {
|
|
30
|
+
source = "../../.deploy/aws/http"
|
|
31
|
+
api_gateway_id = sort(data.aws_apigatewayv2_apis.this.ids)[0]
|
|
32
|
+
lambda_arn = data.aws_lambda_function.this.arn
|
|
33
|
+
lambda_name = "<%= component %>"
|
|
34
|
+
entrypoints = {
|
|
35
|
+
<% for(const [key,entry] of httpEntries){ %>
|
|
36
|
+
"<%= key %>" = {
|
|
37
|
+
description = "<% entry.trigger.options.description %>"
|
|
38
|
+
method = "<%= entry.trigger.options.method %>"
|
|
39
|
+
path = "<%= entry.trigger.options.route %>"
|
|
40
|
+
}
|
|
41
|
+
<% } %>
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
<% } %>
|
|
46
|
+
|
|
47
|
+
<% const scheduleEntries = Object.entries(entries).filter(([k,e]) => e.trigger.type.toLowerCase() === "schedule") %>
|
|
48
|
+
<% if(scheduleEntries.length > 0) { %>
|
|
49
|
+
module "schedule_trigger_for_<%= component %>" {
|
|
50
|
+
source = "../../.deploy/aws/scheduler"
|
|
51
|
+
component = "<%= component %>"
|
|
52
|
+
target_arn = data.aws_lambda_function.this.arn
|
|
53
|
+
schedules = {
|
|
54
|
+
<% for(const [key,entry] of scheduleEntries) { %>
|
|
55
|
+
"<%= key %>" = {
|
|
56
|
+
name = "<%= key %>"
|
|
57
|
+
pattern = "<%= entry.trigger.options.pattern %>"
|
|
58
|
+
timezone = "UTC"
|
|
59
|
+
state = "DISABLED"
|
|
60
|
+
retry_policy = {
|
|
61
|
+
maximum_retry_attempts = 3
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
<% } %>
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
<% } %>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import Fastify, { FastifyInstance } from "fastify";
|
|
4
|
+
import { initialize } from "@anubis/datastore";
|
|
5
|
+
import { logger } from "@anubis/utils";
|
|
6
|
+
|
|
7
|
+
<% for(const component of components){ %>
|
|
8
|
+
import <%=component%>Entries from "./<%=component%>/entrypoint.ts" <% }
|
|
9
|
+
%>
|
|
10
|
+
|
|
11
|
+
async function docs(app: FastifyInstance) {
|
|
12
|
+
const docsBuildPath = path.resolve("../../../.dist/docs");
|
|
13
|
+
|
|
14
|
+
app.get("/index.js", function (request, reply) {
|
|
15
|
+
const stream = fs.createReadStream(path.resolve(docsBuildPath, "index.js"));
|
|
16
|
+
reply.type("text/javascript").send(stream);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
app.get("/index.js.map", function (request, reply) {
|
|
20
|
+
const stream = fs.createReadStream(path.resolve(docsBuildPath, "index.js.map"));
|
|
21
|
+
reply.type("text/javascript").send(stream);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
app.get("/index.css", function (request, reply) {
|
|
25
|
+
const stream = fs.createReadStream(path.resolve(docsBuildPath, "index.css"));
|
|
26
|
+
reply.type("text/javascript").send(stream);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
app.get("*", function (request, reply) {
|
|
30
|
+
const filePath = path.resolve(docsBuildPath, "index.html");
|
|
31
|
+
const stream = fs.createReadStream(filePath);
|
|
32
|
+
reply.type("text/html").send(stream);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const fastify = Fastify({
|
|
37
|
+
maxParamLength : 1000,
|
|
38
|
+
logger: {
|
|
39
|
+
transport: {
|
|
40
|
+
target: 'pino-pretty',
|
|
41
|
+
options: {
|
|
42
|
+
colorize: true,
|
|
43
|
+
translateTime: 'HH:MM:ss Z',
|
|
44
|
+
ignore: 'pid,hostname',
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Register the fastify-raw-body plugin
|
|
51
|
+
fastify.register(require('fastify-raw-body'), {
|
|
52
|
+
field: 'rawBody',
|
|
53
|
+
encoding: 'utf8',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
fastify.register(docs, { prefix: "docs" });
|
|
57
|
+
<% for(const component of components){ %>
|
|
58
|
+
fastify.register(<%=component%>Entries, { prefix: "api" });
|
|
59
|
+
<%}%>
|
|
60
|
+
fastify.listen({ port: 3000, host : "::" }).then((address) => {
|
|
61
|
+
logger.setLogger(fastify.log as any);
|
|
62
|
+
initialize();
|
|
63
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Context, S3Event,
|
|
2
|
+
APIGatewayProxyEventV2WithJWTAuthorizer,
|
|
3
|
+
ScheduledEvent
|
|
4
|
+
} from "aws-lambda";
|
|
5
|
+
|
|
6
|
+
import { createRouter, httpMatcher, scheduleMatcher } from "@anubis/cloud-provider";
|
|
7
|
+
import { functions } from "@anubis/aws-cloud-provider";
|
|
8
|
+
import { initialize } from "@anubis/datastore";
|
|
9
|
+
import { logger } from "@anubis/utils";
|
|
10
|
+
|
|
11
|
+
import * as src from "../../packages/components/<%=component%>/src";
|
|
12
|
+
|
|
13
|
+
type AWSEventType = S3Event | APIGatewayProxyEventV2WithJWTAuthorizer | ScheduledEvent;
|
|
14
|
+
|
|
15
|
+
const router = createRouter();
|
|
16
|
+
<%_ for(var [key,entry] of Object.entries(entries)) { _%>
|
|
17
|
+
<%_ if(entry.trigger.type.toLowerCase() === "http") { _%>
|
|
18
|
+
router.add(httpMatcher("<%=entry.trigger.options.method%>", "<%=entry.trigger.options.route%>"), src["<%= entry.handler %>"])
|
|
19
|
+
<%_ } _%>
|
|
20
|
+
<%_ if(entry.trigger.type.toLowerCase() === "schedule") { _%>
|
|
21
|
+
router.add(scheduleMatcher("<%= key %>"), src["<%= entry.handler %>"])
|
|
22
|
+
<%_ } _%>
|
|
23
|
+
<%_ } _%>
|
|
24
|
+
|
|
25
|
+
export async function handler(event: AWSEventType, context: Context) {
|
|
26
|
+
logger.initialize("locations", context.awsRequestId);
|
|
27
|
+
logger.info("request started");
|
|
28
|
+
logger.debug({ event }, "Received event");
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await initialize();
|
|
32
|
+
let output = null;
|
|
33
|
+
if ("rawPath" in event) {
|
|
34
|
+
output = await router.run("Http", functions.httpAdapter, event, context);
|
|
35
|
+
}
|
|
36
|
+
else if ("source" in event) {
|
|
37
|
+
output = await router.run("Schedule", functions.scheduleAdapter, event, context);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
throw new Error("invalid handler");
|
|
41
|
+
}
|
|
42
|
+
logger.debug({ output }, "Response Sent");
|
|
43
|
+
logger.info("request completed");
|
|
44
|
+
return output;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
logger.error({ error }, "Error occurred");
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { FastifyInstance } from "fastify";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import consola from "consola";
|
|
4
|
+
import logSymbols from "log-symbols";
|
|
5
|
+
import { AddressInfo } from "net";
|
|
6
|
+
const querystring = require('querystring');
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
HTTPAuth,
|
|
10
|
+
HttpMethod,
|
|
11
|
+
createHttpContext,
|
|
12
|
+
createScheduleContext,
|
|
13
|
+
} from "@anubis/cloud-provider";
|
|
14
|
+
import { env } from "@anubis/utils";
|
|
15
|
+
|
|
16
|
+
import * as src from "../../packages/components/<%=component%>/src";
|
|
17
|
+
|
|
18
|
+
<%_
|
|
19
|
+
const convertRouteParamstoFastifyRouteTemplate = (route) => {
|
|
20
|
+
return route.replace(/{(.*?)}/g, (sub) => ":" + sub.slice(1, -1))
|
|
21
|
+
}
|
|
22
|
+
_%>
|
|
23
|
+
function logEntryURL(method: string, routePath: string){
|
|
24
|
+
const bgcolors = {
|
|
25
|
+
PUT : chalk.bold.bgYellow.white,
|
|
26
|
+
POST : chalk.bold.bgGreen.white,
|
|
27
|
+
DELETE : chalk.bold.bgRed.white,
|
|
28
|
+
GET : chalk.bold.bgBlue.white,
|
|
29
|
+
PATCH : chalk.bold.bgGreenBright.white
|
|
30
|
+
};
|
|
31
|
+
const colors = {
|
|
32
|
+
PUT : chalk.bold.yellow,
|
|
33
|
+
POST : chalk.bold.green,
|
|
34
|
+
DELETE : chalk.bold.red,
|
|
35
|
+
GET : chalk.bold.blue,
|
|
36
|
+
PATCH : chalk.bold.greenBright
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const prefix = new Array(Math.floor((6 - method.length) / 2)).fill(" ").join("");
|
|
40
|
+
const postfix = new Array(6 - method.length - prefix.length).fill(" ").join("");
|
|
41
|
+
|
|
42
|
+
consola.log(
|
|
43
|
+
logSymbols.info + bgcolors[method](`[${prefix}${method}${postfix}]`) + colors[method](` http://localhost:3000/api${routePath}`)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function entries(app: FastifyInstance) {
|
|
48
|
+
consola.log(chalk.underline(`\n\rUrls you have in`, chalk.bold(`<%=component%>`), `component:`));
|
|
49
|
+
let routePath;
|
|
50
|
+
<%_ for (const [name, entry] of Object.entries(entries)) { _%>
|
|
51
|
+
<%_ if (entry.trigger.type === "http") { _%>
|
|
52
|
+
|
|
53
|
+
routePath = `/http<%= convertRouteParamstoFastifyRouteTemplate(entry.trigger.options.route) %>`;
|
|
54
|
+
logEntryURL('<%= entry.trigger.options.method %>',routePath);
|
|
55
|
+
app.route({
|
|
56
|
+
method: "<%= entry.trigger.options.method %>" as any,
|
|
57
|
+
url: routePath,
|
|
58
|
+
handler: async function (request, reply) {
|
|
59
|
+
|
|
60
|
+
const rawBody = request.rawBody as string;
|
|
61
|
+
const baseAddress = app.server.address() as AddressInfo;
|
|
62
|
+
const isAuth = env("DEV_IDENTITY")
|
|
63
|
+
const ctx = createHttpContext({
|
|
64
|
+
body: rawBody,
|
|
65
|
+
headers: request.headers,
|
|
66
|
+
method: request.method as HttpMethod,
|
|
67
|
+
url: `http://localhost:${baseAddress!.port}/api${routePath}`,
|
|
68
|
+
matchingKey: "<%= entry.trigger.options.route %>",
|
|
69
|
+
params: request.params as Record<string, string>,
|
|
70
|
+
rawQueryString : querystring.stringify(request.query),
|
|
71
|
+
host: request.hostname
|
|
72
|
+
}, isAuth ? {
|
|
73
|
+
appId: env("DEV_APP_ID"),
|
|
74
|
+
memberId: env("DEV_MEMBER_ID"),
|
|
75
|
+
customerId: env("DEV_CUSTOMER_ID"),
|
|
76
|
+
identity: env("DEV_IDENTITY") as HTTPAuth["identity"],
|
|
77
|
+
token: "",
|
|
78
|
+
claims: {
|
|
79
|
+
app_id: env("DEV_APP_ID"),
|
|
80
|
+
member_id: env("DEV_MEMBER_ID"),
|
|
81
|
+
customer_id: env("DEV_CUSTOMER_ID"),
|
|
82
|
+
identity: env("DEV_IDENTITY")
|
|
83
|
+
}
|
|
84
|
+
} : undefined);
|
|
85
|
+
try {
|
|
86
|
+
await src["<%= entry.handler %>"](ctx);
|
|
87
|
+
reply
|
|
88
|
+
.headers(ctx.response.headers || {})
|
|
89
|
+
.status(ctx.response.status)
|
|
90
|
+
.send(ctx.response.body);
|
|
91
|
+
}
|
|
92
|
+
catch(e){
|
|
93
|
+
reply.send(e);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
<%_ } _%>
|
|
99
|
+
<%_ if (entry.trigger.type === "schedule") { _%>
|
|
100
|
+
logEntryURL('GET','/schedule/<%= name %>');
|
|
101
|
+
app.route({
|
|
102
|
+
method: "GET",
|
|
103
|
+
url: `/schedule/<%= name %>`,
|
|
104
|
+
handler: async function (request, reply) {
|
|
105
|
+
const ctx = createScheduleContext("<%= name %>");
|
|
106
|
+
try {
|
|
107
|
+
await src["<%= entry.handler %>"](ctx);
|
|
108
|
+
reply.status(200).send("success");
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
reply.send(e);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
<%_ } _%>
|
|
117
|
+
<%_ } _%>
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export default entries;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
terraform {
|
|
2
|
+
backend "s3" {
|
|
3
|
+
key = "<%= component %>/terraform.state"
|
|
4
|
+
workspace_key_prefix = "state/<%= component %>"
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
required_providers {
|
|
8
|
+
aws = {
|
|
9
|
+
source = "hashicorp/aws"
|
|
10
|
+
version = ">= 4.0"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
required_version = ">= 0.13.1"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
data "aws_lambda_function" "this" {
|
|
18
|
+
function_name = "<%= component %>"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
data "aws_apigatewayv2_apis" "this" {
|
|
22
|
+
tags = {
|
|
23
|
+
application = "anubis"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
<% const httpEntries = Object.entries(entries).filter(([k,e]) => e.trigger.type.toLowerCase() === "http") %>
|
|
28
|
+
<% if(httpEntries.length > 0) { %>
|
|
29
|
+
module "http_trigger_for_<%= component %>" {
|
|
30
|
+
source = "../../.deploy/aws/http"
|
|
31
|
+
api_gateway_id = sort(data.aws_apigatewayv2_apis.this.ids)[0]
|
|
32
|
+
lambda_arn = data.aws_lambda_function.this.arn
|
|
33
|
+
lambda_name = "<%= component %>"
|
|
34
|
+
entrypoints = {
|
|
35
|
+
<% for(const [key,entry] of httpEntries){ %>
|
|
36
|
+
"<%= key %>" = {
|
|
37
|
+
description = "<% entry.trigger.options.description %>"
|
|
38
|
+
method = "<%= entry.trigger.options.method %>"
|
|
39
|
+
path = "<%= entry.trigger.options.route %>"
|
|
40
|
+
}
|
|
41
|
+
<% } %>
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
<% } %>
|
|
46
|
+
|
|
47
|
+
<% const scheduleEntries = Object.entries(entries).filter(([k,e]) => e.trigger.type.toLowerCase() === "schedule") %>
|
|
48
|
+
<% if(scheduleEntries.length > 0) { %>
|
|
49
|
+
module "schedule_trigger_for_<%= component %>" {
|
|
50
|
+
source = "../../.deploy/aws/scheduler"
|
|
51
|
+
component = "<%= component %>"
|
|
52
|
+
target_arn = data.aws_lambda_function.this.arn
|
|
53
|
+
schedules = {
|
|
54
|
+
<% for(const [key,entry] of scheduleEntries) { %>
|
|
55
|
+
"<%= key %>" = {
|
|
56
|
+
name = "<%= key %>"
|
|
57
|
+
pattern = "<%= entry.trigger.options.pattern %>"
|
|
58
|
+
timezone = "UTC"
|
|
59
|
+
state = "DISABLED"
|
|
60
|
+
retry_policy = {
|
|
61
|
+
maximum_retry_attempts = 3
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
<% } %>
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
<% } %>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import Fastify from "fastify";
|
|
2
|
+
import pino from "pino";
|
|
3
|
+
|
|
4
|
+
<%_ for(const module of moduleNames){ _%>
|
|
5
|
+
import <%=module%>Entries from "./<%=module%>/index.ts"
|
|
6
|
+
<%_ } _%>
|
|
7
|
+
|
|
8
|
+
// Create a custom logger
|
|
9
|
+
const customLogger = pino({
|
|
10
|
+
level: 'info', // Set the log level (e.g., 'info', 'error', 'debug')
|
|
11
|
+
base: null, // Remove default metadata like pid and hostname
|
|
12
|
+
hooks: {
|
|
13
|
+
logMethod(args, method) {
|
|
14
|
+
// Intercept log messages and send them to the parent process
|
|
15
|
+
const [message, ...rest] = args;
|
|
16
|
+
const safePayload = JSON.stringify(message, getCircularReplacer());
|
|
17
|
+
|
|
18
|
+
sendMessage('LOG', { level: method.name, message:safePayload, additionalData: rest });
|
|
19
|
+
return method.apply(this, args);
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Function to send logs to the parent process
|
|
25
|
+
function sendMessage(type: string, payload: any) {
|
|
26
|
+
|
|
27
|
+
if (process && process.send) {
|
|
28
|
+
process.send({ type, payload: payload });
|
|
29
|
+
} else {
|
|
30
|
+
console.log(`[${type}]`, payload); // Fallback for non-parent process
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getCircularReplacer() {
|
|
35
|
+
const seen = new WeakSet();
|
|
36
|
+
return (key: string, value: any) => {
|
|
37
|
+
if (typeof value === 'object' && value !== null) {
|
|
38
|
+
if (seen.has(value)) {
|
|
39
|
+
return '[Circular]';
|
|
40
|
+
}
|
|
41
|
+
seen.add(value);
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const fastify = Fastify({
|
|
48
|
+
maxParamLength : 1000,
|
|
49
|
+
loggerInstance:customLogger,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Add fastify hooks
|
|
53
|
+
fastify.addHook('onRegister', async (instance, opts) => {
|
|
54
|
+
sendMessage("APP_REGISTERED", { prefix: instance.prefix, opts });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
fastify.addHook('onRoute', (routeOptions) => {
|
|
58
|
+
sendMessage('ROUTE_REGISTERED', {
|
|
59
|
+
method: routeOptions.method,
|
|
60
|
+
url: routeOptions.url,
|
|
61
|
+
handler: routeOptions.handler.name,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
fastify.addHook('onListen', function (done) {
|
|
66
|
+
try{
|
|
67
|
+
sendMessage('LISTENING', {
|
|
68
|
+
port: 3000,
|
|
69
|
+
host:"::"
|
|
70
|
+
});
|
|
71
|
+
}catch(err){
|
|
72
|
+
sendMessage('ERROR', {err});
|
|
73
|
+
}finally{
|
|
74
|
+
const err = null; // to be updated later to handle crash errors
|
|
75
|
+
done(err)
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
fastify.addHook('preHandler', (request, reply, done) => {
|
|
80
|
+
sendMessage('REQUEST', { id: request.id, params: request.params, query: request.query, body: request.body, headers: request.headers, url: request.url, method:request.method, time: new Date().toLocaleTimeString() });
|
|
81
|
+
done();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
fastify.addHook('onResponse', (request, reply, done) => {
|
|
85
|
+
sendMessage('RESPONSE', { id: request.id, status: reply.statusCode, body: reply.raw, time: new Date().toLocaleTimeString() });
|
|
86
|
+
done();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
fastify.addHook('onError', (request, reply, error, done) => {
|
|
90
|
+
sendMessage('ERROR', {error, request, reply});
|
|
91
|
+
done();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Register the fastify-raw-body plugin
|
|
95
|
+
fastify.register(require('fastify-raw-body'), {
|
|
96
|
+
field: 'rawBody',
|
|
97
|
+
encoding: 'utf8',
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
<%_ for(const module of moduleNames){ _%>
|
|
101
|
+
fastify.register(<%=module%>Entries, { prefix: "<%=module%>/" });
|
|
102
|
+
<%_ } _%>
|
|
103
|
+
|
|
104
|
+
fastify.listen({ port: 3000, host : "::" }).then((address) => {
|
|
105
|
+
// init in entrypoint
|
|
106
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<%# Template generated from a single entrypoint
|
|
2
|
+
input:
|
|
3
|
+
- source : the main export of the package
|
|
4
|
+
- config: the full entrypoint object
|
|
5
|
+
%>
|
|
6
|
+
import { AddressInfo } from "node:net";
|
|
7
|
+
import querystring from "node:querystring";
|
|
8
|
+
import jwt from "jsonwebtoken";
|
|
9
|
+
|
|
10
|
+
import { FastifyInstance } from "fastify";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
HTTPAuth,
|
|
14
|
+
HttpMethod,
|
|
15
|
+
createHttpContext,
|
|
16
|
+
createScheduleContext,
|
|
17
|
+
} from "@@cloudcore";
|
|
18
|
+
|
|
19
|
+
import * as src from "<%= source %>";
|
|
20
|
+
|
|
21
|
+
const prefix = "DEV_IDENTITY_";
|
|
22
|
+
function buildAuth(headers: { authorization?: any; }) {
|
|
23
|
+
try {
|
|
24
|
+
// Case 1: DEV_IDENTITY_TOKEN
|
|
25
|
+
const devToken = process.env[`${prefix}TOKEN`];
|
|
26
|
+
if (devToken) {
|
|
27
|
+
const claims = jwt.decode(devToken) as Record<string, unknown>;
|
|
28
|
+
return { token: devToken, ...claims };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Case 2: DEV_IDENTITY_ variables
|
|
32
|
+
const devIdentityVars = Object.entries(process.env)
|
|
33
|
+
.filter(([key]) => key.startsWith(prefix) && key !== `${prefix}TOKEN`)
|
|
34
|
+
.reduce((acc, [key, value]) => {
|
|
35
|
+
const claimKey = key.replace(prefix, '').toLowerCase();
|
|
36
|
+
acc[claimKey] = value;
|
|
37
|
+
return acc;
|
|
38
|
+
}, {} as Record<string, unknown>);
|
|
39
|
+
|
|
40
|
+
if (Object.keys(devIdentityVars).length > 0) {
|
|
41
|
+
return { token: '', ...devIdentityVars };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Case 3: Authorization header
|
|
45
|
+
const authHeader = headers.authorization;
|
|
46
|
+
if (authHeader?.startsWith('Bearer ')) {
|
|
47
|
+
const token = authHeader.substring(7);
|
|
48
|
+
const claims = jwt.decode(token) as Record<string, unknown>;
|
|
49
|
+
return { token, ...claims };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Case 4: Nothing exists
|
|
53
|
+
return undefined;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('Error processing identity token:', error);
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function entries(app: FastifyInstance) {
|
|
61
|
+
<%_ for (const [name, entry] of Object.entries(entries)) { _%>
|
|
62
|
+
<%_ if (entry.trigger.type === "http") { _%>
|
|
63
|
+
<%- include("triggers/http.ts.ejs", {
|
|
64
|
+
name,
|
|
65
|
+
entry
|
|
66
|
+
}) %>
|
|
67
|
+
<%_ } _%>
|
|
68
|
+
<%_ if (entry.trigger.type === "schedule") { _%>
|
|
69
|
+
<%- include("triggers/schedule.ts.ejs", {
|
|
70
|
+
name,
|
|
71
|
+
entry
|
|
72
|
+
}) %>
|
|
73
|
+
<%_ } _%>
|
|
74
|
+
<%_ } _%>
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default entries;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<%# Template generated a single endpoint entry
|
|
2
|
+
input:
|
|
3
|
+
- entry : entry to be rendered
|
|
4
|
+
%>
|
|
5
|
+
app.route({
|
|
6
|
+
method: "<%= entry.trigger.options.method %>" as any,
|
|
7
|
+
url: `/http<%= $$convertRouteParamstoFastifyRouteTemplate(entry.trigger.options.route) %>`,
|
|
8
|
+
handler: async function (request, reply) {
|
|
9
|
+
const baseAddress = app.server.address() as AddressInfo;
|
|
10
|
+
const routePath = `/http<%= $$convertRouteParamstoFastifyRouteTemplate(entry.trigger.options.route) %>`;
|
|
11
|
+
const ctx = createHttpContext({
|
|
12
|
+
body: request.rawBody as string,
|
|
13
|
+
headers: request.headers,
|
|
14
|
+
method: request.method as HttpMethod,
|
|
15
|
+
url: `http://${request.hostname}:${baseAddress!.port}/api${routePath}`,
|
|
16
|
+
matchingKey: "<%= entry.trigger.options.route %>",
|
|
17
|
+
params: request.params as Record<string, string>,
|
|
18
|
+
rawQueryString : querystring.stringify(request.query),
|
|
19
|
+
host: request.hostname
|
|
20
|
+
}, buildAuth(request.headers));
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
await src["<%= entry.handler %>"](ctx);
|
|
24
|
+
reply
|
|
25
|
+
.headers(ctx.response.headers || {})
|
|
26
|
+
.status(ctx.response.status)
|
|
27
|
+
.send(ctx.response.body);
|
|
28
|
+
}
|
|
29
|
+
catch(e){
|
|
30
|
+
reply.send(e);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
logEntryURL('GET','/schedule/<%= name %>');
|
|
2
|
+
app.route({
|
|
3
|
+
method: "GET",
|
|
4
|
+
url: `/schedule/<%= name %>`,
|
|
5
|
+
handler: async function (request, reply) {
|
|
6
|
+
const ctx = createScheduleContext("<%= name %>");
|
|
7
|
+
try {
|
|
8
|
+
await src["<%= entry.handler %>"](ctx);
|
|
9
|
+
reply.status(200).send("success");
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
reply.send(e);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
});
|