primate 0.17.0 → 0.19.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/package.json +4 -3
- package/src/Logger.js +68 -60
- package/src/app.js +91 -41
- package/src/commands/exports.js +2 -5
- package/src/defaults/primate.config.js +5 -0
- package/src/dispatch.js +24 -0
- package/src/errors.js +141 -0
- package/src/handlers/error.js +8 -7
- package/src/handlers/html.js +27 -5
- package/src/handlers/redirect.js +3 -1
- package/src/handlers/view.js +4 -4
- package/src/hooks/bundle.js +1 -1
- package/src/hooks/compile.js +1 -1
- package/src/hooks/exports.js +2 -0
- package/src/hooks/handle/exports.js +0 -1
- package/src/hooks/handle.js +20 -104
- package/src/hooks/parse.js +59 -0
- package/src/hooks/publish.js +1 -1
- package/src/hooks/register.js +1 -1
- package/src/hooks/route.js +61 -42
- package/src/hooks/serve.js +8 -0
- package/src/http-statuses.js +4 -0
- package/src/run.js +23 -15
- package/src/start.js +24 -9
- package/src/commands/build.js +0 -1
- package/src/commands/create.js +0 -42
- package/src/commands/help.js +0 -3
- package/src/fromNull.js +0 -1
- package/src/hooks/handle/http-statuses.js +0 -5
- package/src/hooks/handle/mime.spec.js +0 -13
- package/src/hooks/handle/respond.spec.js +0 -12
- package/src/hooks/handle.spec.js +0 -88
- package/src/hooks/route.spec.js +0 -143
package/src/handlers/redirect.js
CHANGED
package/src/handlers/view.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import errors from "../errors.js";
|
|
2
|
+
|
|
1
3
|
export default (name, props, options) => async (app, headers) => {
|
|
2
4
|
const ending = name.slice(name.lastIndexOf(".") + 1);
|
|
3
5
|
const handler = app.handlers[ending];
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
return handler(name, {load: true, ...props}, options)(app, headers);
|
|
6
|
+
return handler?.(name, {load: true, ...props}, options)(app, headers)
|
|
7
|
+
?? errors.NoHandlerForExtension.throw({name, ending});
|
|
8
8
|
};
|
package/src/hooks/bundle.js
CHANGED
|
@@ -22,7 +22,7 @@ const pre = async app => {
|
|
|
22
22
|
export default async (app, bundle) => {
|
|
23
23
|
await pre(app);
|
|
24
24
|
if (bundle) {
|
|
25
|
-
app.log.info("running bundle hooks");
|
|
25
|
+
app.log.info("running bundle hooks", {module: "primate"});
|
|
26
26
|
await [...filter("bundle", app.modules), _ => _]
|
|
27
27
|
.reduceRight((acc, handler) => input => handler(input, acc))(app);
|
|
28
28
|
}
|
package/src/hooks/compile.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const filter = (key, array) => array?.flatMap(m => m[key] ?? []) ?? [];
|
|
2
2
|
|
|
3
3
|
export default async app => {
|
|
4
|
-
app.log.info("running compile hooks");
|
|
4
|
+
app.log.info("running compile hooks", {module: "primate"});
|
|
5
5
|
await [...filter("compile", app.modules), _ => _]
|
|
6
6
|
.reduceRight((acc, handler) => input => handler(input, acc))(app);
|
|
7
7
|
};
|
package/src/hooks/exports.js
CHANGED
|
@@ -4,3 +4,5 @@ export {default as publish} from "./publish.js";
|
|
|
4
4
|
export {default as bundle} from "./bundle.js";
|
|
5
5
|
export {default as route} from "./route.js";
|
|
6
6
|
export {default as handle} from "./handle.js";
|
|
7
|
+
export {default as parse} from "./parse.js";
|
|
8
|
+
export {default as serve} from "./serve.js";
|
package/src/hooks/handle.js
CHANGED
|
@@ -1,58 +1,36 @@
|
|
|
1
|
-
import {Response
|
|
1
|
+
import {Response} from "runtime-compat/http";
|
|
2
2
|
import {error as clientError} from "../handlers/exports.js";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
3
|
+
import {mime, isResponse, respond} from "./handle/exports.js";
|
|
4
|
+
import {invalid} from "./route.js";
|
|
5
|
+
import errors from "../errors.js";
|
|
6
|
+
import {OK} from "../http-statuses.js";
|
|
5
7
|
|
|
6
8
|
const filter = (key, array) => array?.flatMap(m => m[key] ?? []) ?? [];
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
"application/x-www-form-urlencoded": body =>
|
|
10
|
-
fromNull(Object.fromEntries(body.split("&").map(part => part.split("=")
|
|
11
|
-
.map(subpart => decodeURIComponent(subpart).replaceAll("+", " "))))),
|
|
12
|
-
"application/json": body => JSON.parse(body),
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export default async app => {
|
|
10
|
+
export default app => {
|
|
16
11
|
const {http} = app.config;
|
|
17
12
|
|
|
18
|
-
const _respond = async request => {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// remove inline resources
|
|
25
|
-
for (let i = app.resources.length - 1; i >= 0; i--) {
|
|
26
|
-
const resource = app.resources[i];
|
|
27
|
-
if (resource.inline) {
|
|
28
|
-
app.resources.splice(i, 1);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
13
|
+
const _respond = async (request, headers) => {
|
|
14
|
+
const {pathname} = request.url;
|
|
15
|
+
return invalid(pathname)
|
|
16
|
+
? errors.NoFileForPath.throw({pathname, config: app.config})
|
|
17
|
+
: (await respond(await app.route(request)))(app, headers);
|
|
18
|
+
};
|
|
31
19
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"Referrer-Policy": "same-origin",
|
|
35
|
-
};
|
|
20
|
+
const route = async request => {
|
|
21
|
+
const headers = app.generateHeaders();
|
|
36
22
|
|
|
37
23
|
try {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
const handlers = [...modules, app.route].reduceRight((acc, handler) =>
|
|
41
|
-
input => handler(input, acc));
|
|
42
|
-
return await respond(await handlers(request))(app, headers);
|
|
24
|
+
const response = await _respond(request, headers);
|
|
25
|
+
return isResponse(response) ? response : new Response(...response);
|
|
43
26
|
} catch (error) {
|
|
44
27
|
app.log.auto(error);
|
|
45
|
-
return clientError()(app,
|
|
28
|
+
return new Response(...await clientError()(app, {}));
|
|
46
29
|
}
|
|
47
30
|
};
|
|
48
31
|
|
|
49
|
-
const route = async request => {
|
|
50
|
-
const response = await _respond(request);
|
|
51
|
-
return isResponse(response) ? response : new Response(...response);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
32
|
const staticResource = async file => new Response(file.readable, {
|
|
55
|
-
status:
|
|
33
|
+
status: OK,
|
|
56
34
|
headers: {
|
|
57
35
|
"Content-Type": mime(file.name),
|
|
58
36
|
Etag: await file.modified,
|
|
@@ -64,7 +42,7 @@ export default async app => {
|
|
|
64
42
|
!inline && src === request.url.pathname);
|
|
65
43
|
if (published !== undefined) {
|
|
66
44
|
return new Response(published.code, {
|
|
67
|
-
status:
|
|
45
|
+
status: OK,
|
|
68
46
|
headers: {
|
|
69
47
|
"Content-Type": mime(published.src),
|
|
70
48
|
Etag: published.integrity,
|
|
@@ -87,68 +65,6 @@ export default async app => {
|
|
|
87
65
|
return route(request);
|
|
88
66
|
};
|
|
89
67
|
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
return await resource(request);
|
|
93
|
-
} catch (error) {
|
|
94
|
-
app.log.auto(error);
|
|
95
|
-
return new Response(null, {status: statuses.InternalServerError});
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const parseContentType = (contentType, body) => {
|
|
100
|
-
const type = contents[contentType];
|
|
101
|
-
return type === undefined ? body : type(body);
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const parseContent = (request, body) => {
|
|
105
|
-
try {
|
|
106
|
-
return parseContentType(request.headers.get("content-type"), body);
|
|
107
|
-
} catch (error) {
|
|
108
|
-
app.log.warn(error);
|
|
109
|
-
return body;
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const decoder = new TextDecoder();
|
|
114
|
-
|
|
115
|
-
const parseBody = async request => {
|
|
116
|
-
if (request.body === null) {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
const reader = request.body.getReader();
|
|
120
|
-
const chunks = [];
|
|
121
|
-
let result;
|
|
122
|
-
do {
|
|
123
|
-
result = await reader.read();
|
|
124
|
-
if (result.value !== undefined) {
|
|
125
|
-
chunks.push(decoder.decode(result.value));
|
|
126
|
-
}
|
|
127
|
-
} while (!result.done);
|
|
128
|
-
|
|
129
|
-
return parseContent(request, chunks.join());
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const parseRequest = async request => {
|
|
133
|
-
const cookies = request.headers.get("cookie");
|
|
134
|
-
const _url = request.url;
|
|
135
|
-
const url = new URL(_url.endsWith("/") ? _url.slice(0, -1) : _url);
|
|
136
|
-
|
|
137
|
-
return {
|
|
138
|
-
original: request,
|
|
139
|
-
url,
|
|
140
|
-
body: await parseBody(request),
|
|
141
|
-
cookies: fromNull(cookies === null
|
|
142
|
-
? {}
|
|
143
|
-
: Object.fromEntries(cookies.split(";").map(c => c.trim().split("=")))),
|
|
144
|
-
headers: fromNull(Object.fromEntries(request.headers)),
|
|
145
|
-
query: fromNull(Object.fromEntries(url.searchParams)),
|
|
146
|
-
};
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
// handle is the last module to be executed
|
|
150
|
-
const handlers = [...filter("handle", app.modules), handle]
|
|
68
|
+
return [...filter("handle", app.modules), resource]
|
|
151
69
|
.reduceRight((acc, handler) => input => handler(input, acc));
|
|
152
|
-
|
|
153
|
-
return async request => handlers(await parseRequest(request));
|
|
154
70
|
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {URL} from "runtime-compat/http";
|
|
2
|
+
import errors from "../errors.js";
|
|
3
|
+
|
|
4
|
+
const contents = {
|
|
5
|
+
"application/x-www-form-urlencoded": body =>
|
|
6
|
+
Object.fromEntries(body.split("&").map(part => part.split("=")
|
|
7
|
+
.map(subpart => decodeURIComponent(subpart).replaceAll("+", " ")))),
|
|
8
|
+
"application/json": body => JSON.parse(body),
|
|
9
|
+
};
|
|
10
|
+
const decoder = new TextDecoder();
|
|
11
|
+
|
|
12
|
+
export default dispatch => async request => {
|
|
13
|
+
const parseContentType = (contentType, body) => {
|
|
14
|
+
const type = contents[contentType];
|
|
15
|
+
return type === undefined ? body : type(body);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const parseContent = async (request, body) => {
|
|
19
|
+
const contentType = request.headers.get("content-type");
|
|
20
|
+
try {
|
|
21
|
+
return parseContentType(contentType, body);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
return errors.CannotParseBody.throw({body, contentType});
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const parseBody = async request => {
|
|
28
|
+
if (request.body === null) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const reader = request.body.getReader();
|
|
32
|
+
const chunks = [];
|
|
33
|
+
let result;
|
|
34
|
+
do {
|
|
35
|
+
result = await reader.read();
|
|
36
|
+
if (result.value !== undefined) {
|
|
37
|
+
chunks.push(decoder.decode(result.value));
|
|
38
|
+
}
|
|
39
|
+
} while (!result.done);
|
|
40
|
+
|
|
41
|
+
return parseContent(request, chunks.join());
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const cookies = request.headers.get("cookie");
|
|
45
|
+
const _url = request.url;
|
|
46
|
+
const url = new URL(_url.endsWith("/") ? _url.slice(0, -1) : _url);
|
|
47
|
+
|
|
48
|
+
const body = await parseBody(request);
|
|
49
|
+
return {
|
|
50
|
+
original: request,
|
|
51
|
+
url,
|
|
52
|
+
body: dispatch(body),
|
|
53
|
+
cookies: dispatch(cookies === null
|
|
54
|
+
? {}
|
|
55
|
+
: Object.fromEntries(cookies.split(";").map(c => c.trim().split("=")))),
|
|
56
|
+
headers: dispatch(Object.fromEntries(request.headers)),
|
|
57
|
+
query: dispatch(Object.fromEntries(url.searchParams)),
|
|
58
|
+
};
|
|
59
|
+
};
|
package/src/hooks/publish.js
CHANGED
|
@@ -29,7 +29,7 @@ const post = async app => {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
export default async app => {
|
|
32
|
-
app.log.info("running publish hooks");
|
|
32
|
+
app.log.info("running publish hooks", {module: "primate"});
|
|
33
33
|
await [...filter("publish", app.modules), _ => _]
|
|
34
34
|
.reduceRight((acc, handler) => input => handler(input, acc))(app);
|
|
35
35
|
await post(app);
|
package/src/hooks/register.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const filter = (key, array) => array?.flatMap(m => m[key] ?? []) ?? [];
|
|
2
2
|
|
|
3
3
|
export default async app => {
|
|
4
|
-
app.log.info("running register hooks");
|
|
4
|
+
app.log.info("running register hooks", {module: "primate"});
|
|
5
5
|
await [...filter("register", app.modules), _ => _]
|
|
6
6
|
.reduceRight((acc, handler) => input => handler(input, acc))(app);
|
|
7
7
|
};
|
package/src/hooks/route.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import
|
|
1
|
+
import errors from "../errors.js";
|
|
2
|
+
|
|
3
|
+
const filter = (key, array) => array?.flatMap(m => m[key] ?? []) ?? [];
|
|
2
4
|
|
|
3
5
|
// insensitive-case equal
|
|
4
6
|
const ieq = (left, right) => left.toLowerCase() === right.toLowerCase();
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
|
|
8
|
+
/* routes may not contain dots */
|
|
9
|
+
export const invalid = route => /\./u.test(route);
|
|
10
|
+
const toRoute = path => {
|
|
11
|
+
const double = path.split("/")
|
|
12
|
+
.filter(part => part.startsWith("{") && part.endsWith("}"))
|
|
13
|
+
.map(part => part.slice(1, part.indexOf(":")))
|
|
14
|
+
.find((part, i, array) =>
|
|
15
|
+
array.filter((_, j) => i !== j).includes(part));
|
|
16
|
+
double && errors.DoublePathParameter.throw({path, double});
|
|
17
|
+
|
|
18
|
+
const route = path
|
|
15
19
|
// transform /index -> ""
|
|
16
20
|
.replace("/index", "")
|
|
17
21
|
// transform index -> ""
|
|
@@ -23,66 +27,81 @@ const toRoute = file => {
|
|
|
23
27
|
const param = type === undefined ? name : `${name}$${type.slice(1)}`;
|
|
24
28
|
return `(?<${param}>[^/]{1,}?)`;
|
|
25
29
|
} catch (error) {
|
|
26
|
-
|
|
30
|
+
return errors.InvalidPathParameter.throw({named, path});
|
|
27
31
|
}
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
abort("same parameter twice");
|
|
34
|
-
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
invalid(route) && errors.InvalidRouteName.throw({path});
|
|
35
|
+
|
|
36
|
+
return new RegExp(`^/${route}$`, "u");
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
const reentry = (object, mapper) =>
|
|
38
40
|
Object.fromEntries(mapper(Object.entries(object ?? {})));
|
|
39
41
|
|
|
40
42
|
export default app => {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
const double = app.routes
|
|
44
|
+
.map(([route]) => route
|
|
45
|
+
.replaceAll("/index", "")
|
|
46
|
+
.replaceAll(/\{(?<name>\w*)(?<_>:\w+)?\}?/gu, (_, name) => `{${name}}`))
|
|
47
|
+
.find((part, i, array) =>
|
|
48
|
+
array.filter((_, j) => i !== j).includes(part));
|
|
49
|
+
|
|
50
|
+
double && errors.DoubleRoute.throw({double});
|
|
51
|
+
|
|
44
52
|
const routes = app.routes
|
|
45
53
|
.map(([route, imported]) => {
|
|
46
|
-
if (imported === undefined) {
|
|
47
|
-
|
|
54
|
+
if (imported === undefined || Object.keys(imported).length === 0) {
|
|
55
|
+
errors.EmptyRouteFile.warn(app.log, {config: app.config, route});
|
|
48
56
|
return [];
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
const path = toRoute(route);
|
|
52
59
|
return Object.entries(imported)
|
|
53
|
-
.
|
|
54
|
-
.map(([method, handler]) => ({method, handler, path}));
|
|
60
|
+
.map(([method, handler]) => ({method, handler, path: toRoute(route)}));
|
|
55
61
|
}).flat();
|
|
56
|
-
const paths = routes.map(({method, path}) => `${method}${path}`);
|
|
57
|
-
if (new Set(paths).size !== paths.length) {
|
|
58
|
-
abort("same route twice");
|
|
59
|
-
}
|
|
60
62
|
|
|
61
|
-
const
|
|
63
|
+
const {types = {}} = app;
|
|
64
|
+
Object.entries(types).some(([name]) => /^(?:[^\W_]*)$/u.test(name) ||
|
|
65
|
+
errors.InvalidTypeName.throw({name}));
|
|
66
|
+
const reserved = ["get", "raw"];
|
|
67
|
+
Object.entries(types).some(([name]) => reserved.includes(name) &&
|
|
68
|
+
errors.ReservedTypeName.throw({name}));
|
|
69
|
+
|
|
70
|
+
const {explicit} = app.config.types;
|
|
71
|
+
const isType = (groups, path) => Object
|
|
62
72
|
.entries(groups ?? {})
|
|
63
73
|
.map(([name, value]) =>
|
|
64
|
-
[types[name] === undefined ? name : `${name}$${name}`, value])
|
|
74
|
+
[types[name] === undefined || explicit ? name : `${name}$${name}`, value])
|
|
65
75
|
.filter(([name]) => name.includes("$"))
|
|
66
76
|
.map(([name, value]) => [name.split("$")[1], value])
|
|
67
|
-
.every(([name, value]) =>
|
|
68
|
-
|
|
77
|
+
.every(([name, value]) => {
|
|
78
|
+
try {
|
|
79
|
+
return types?.[name](value) === true;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return errors.MismatchedPath.throw({message: error.message, path});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
69
84
|
const isPath = ({route, path}) => {
|
|
70
85
|
const result = route.path.exec(path);
|
|
71
|
-
return result === null ? false : isType(result.groups);
|
|
86
|
+
return result === null ? false : isType(result.groups, path);
|
|
72
87
|
};
|
|
73
88
|
const isMethod = ({route, method, path}) => ieq(route.method, method)
|
|
74
89
|
&& isPath({route, path});
|
|
75
90
|
const find = (method, path) => routes.find(route =>
|
|
76
91
|
isMethod({route, method, path}));
|
|
92
|
+
const modules = filter("route", app.modules);
|
|
77
93
|
|
|
78
94
|
return request => {
|
|
79
95
|
const {original: {method}, url: {pathname}} = request;
|
|
80
|
-
const verb = find(method, pathname) ??
|
|
81
|
-
throw
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
const verb = find(method, pathname) ??
|
|
97
|
+
errors.NoRouteToPath.throw({method, pathname, config: app.config});
|
|
98
|
+
const path = app.dispatch(reentry(verb.path?.exec(pathname).groups,
|
|
99
|
+
object => object.map(([key, value]) => [key.split("$")[0], value])));
|
|
100
|
+
|
|
101
|
+
// verb.handler is the last module to be executed
|
|
102
|
+
const handlers = [...modules, verb.handler].reduceRight((acc, handler) =>
|
|
103
|
+
input => handler(input, acc));
|
|
85
104
|
|
|
86
|
-
return
|
|
105
|
+
return handlers({...request, path});
|
|
87
106
|
};
|
|
88
107
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const filter = (key, array) => array?.flatMap(m => m[key] ?? []) ?? [];
|
|
2
|
+
|
|
3
|
+
export default async (app, server) => {
|
|
4
|
+
app.log.info("running serve hooks", {module: "primate"});
|
|
5
|
+
await [...filter("serve", app.modules), _ => _]
|
|
6
|
+
.reduceRight((acc, handler) => input =>
|
|
7
|
+
handler(input, acc))({...app, server});
|
|
8
|
+
};
|
package/src/run.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {Path} from "runtime-compat/fs";
|
|
2
2
|
import app from "./app.js";
|
|
3
|
+
import {default as Logger, bye} from "./Logger.js";
|
|
4
|
+
import extend from "./extend.js";
|
|
5
|
+
import errors from "./errors.js";
|
|
3
6
|
import command from "./commands/exports.js";
|
|
4
|
-
import {Abort, colors, print, default as Logger} from "./Logger.js";
|
|
5
7
|
import defaults from "./defaults/primate.config.js";
|
|
6
|
-
import extend from "./extend.js";
|
|
7
8
|
|
|
8
9
|
const getRoot = async () => {
|
|
9
10
|
try {
|
|
@@ -15,19 +16,21 @@ const getRoot = async () => {
|
|
|
15
16
|
}
|
|
16
17
|
};
|
|
17
18
|
|
|
18
|
-
const
|
|
19
|
+
const protologger = new Logger({level: Logger.Warn});
|
|
20
|
+
|
|
19
21
|
const getConfig = async root => {
|
|
20
|
-
const
|
|
22
|
+
const name = "primate.config.js";
|
|
23
|
+
const config = root.join(name);
|
|
21
24
|
if (await config.exists) {
|
|
22
25
|
try {
|
|
23
|
-
const imported = await import(config);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
throw
|
|
26
|
+
const imported = (await import(config)).default;
|
|
27
|
+
|
|
28
|
+
(imported === undefined || Object.keys(imported).length === 0) &&
|
|
29
|
+
errors.EmptyConfigFile.warn(protologger, {config});
|
|
30
|
+
|
|
31
|
+
return extend(defaults, imported);
|
|
32
|
+
} catch ({message}) {
|
|
33
|
+
return errors.ErrorInConfigFile.throw({message, config});
|
|
31
34
|
}
|
|
32
35
|
} else {
|
|
33
36
|
return defaults;
|
|
@@ -36,11 +39,16 @@ const getConfig = async root => {
|
|
|
36
39
|
|
|
37
40
|
export default async name => {
|
|
38
41
|
const root = await getRoot();
|
|
39
|
-
|
|
42
|
+
let logger = protologger;
|
|
40
43
|
try {
|
|
41
|
-
|
|
44
|
+
const config = await getConfig(root);
|
|
45
|
+
logger = new Logger(config.logger);
|
|
46
|
+
await command(name)(await app(config, root, new Logger(config.logger)));
|
|
42
47
|
} catch (error) {
|
|
43
|
-
if (error
|
|
48
|
+
if (error.level === Logger.Error) {
|
|
49
|
+
logger.auto(error);
|
|
50
|
+
bye();
|
|
51
|
+
} else {
|
|
44
52
|
throw error;
|
|
45
53
|
}
|
|
46
54
|
}
|
package/src/start.js
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
|
-
import {serve} from "runtime-compat/http";
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import {serve, Response} from "runtime-compat/http";
|
|
2
|
+
import {InternalServerError} from "./http-statuses.js";
|
|
3
|
+
import * as hooks from "./hooks/exports.js";
|
|
4
|
+
|
|
5
|
+
const filter = (key, array) => array?.flatMap(m => m[key] ?? []) ?? [];
|
|
4
6
|
|
|
5
7
|
export default async (app, operations = {}) => {
|
|
6
8
|
// register handlers
|
|
7
|
-
await register({...app, register(name, handler) {
|
|
9
|
+
await hooks.register({...app, register(name, handler) {
|
|
8
10
|
app.handlers[name] = handler;
|
|
9
11
|
}});
|
|
10
12
|
|
|
11
13
|
// compile server-side code
|
|
12
|
-
await compile(app);
|
|
14
|
+
await hooks.compile(app);
|
|
13
15
|
// publish client-side code
|
|
14
|
-
await publish(app);
|
|
16
|
+
await hooks.publish(app);
|
|
15
17
|
|
|
16
18
|
// bundle client-side code
|
|
17
|
-
await bundle(app, operations?.bundle);
|
|
19
|
+
await hooks.bundle(app, operations?.bundle);
|
|
20
|
+
|
|
21
|
+
const server = await serve(async request => {
|
|
22
|
+
try {
|
|
23
|
+
// parse, handle
|
|
24
|
+
return await hooks.handle(app)(await app.parse(request));
|
|
25
|
+
} catch(error) {
|
|
26
|
+
console.log("TEST2");
|
|
27
|
+
app.log.auto(error);
|
|
28
|
+
return new Response(null, {status: InternalServerError});
|
|
29
|
+
}
|
|
30
|
+
}, app.config.http);
|
|
18
31
|
|
|
19
|
-
|
|
20
|
-
|
|
32
|
+
await [...filter("serve", app.modules), _ => _]
|
|
33
|
+
.reduceRight((acc, handler) => input => handler(input, acc))({
|
|
34
|
+
...app, server,
|
|
35
|
+
});
|
|
21
36
|
};
|
package/src/commands/build.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {default} from "./serve.js";
|
package/src/commands/create.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import {Path} from "runtime-compat/fs";
|
|
2
|
-
|
|
3
|
-
const createModule = async app => {
|
|
4
|
-
const space = 2;
|
|
5
|
-
try {
|
|
6
|
-
// will throw if cannot find a package.json up the filesystem hierarchy
|
|
7
|
-
await Path.root();
|
|
8
|
-
} catch (error) {
|
|
9
|
-
const rootConfig = JSON.stringify({
|
|
10
|
-
name: "primate-app",
|
|
11
|
-
private: true,
|
|
12
|
-
dependencies: {
|
|
13
|
-
primate: `^${app.version}`,
|
|
14
|
-
},
|
|
15
|
-
scripts: {
|
|
16
|
-
start: "npx primate",
|
|
17
|
-
dev: "npx primate dev",
|
|
18
|
-
serve: "npx primate serve",
|
|
19
|
-
},
|
|
20
|
-
type: "module",
|
|
21
|
-
}, null, space);
|
|
22
|
-
await Path.resolve().join("package.json").file.write(rootConfig);
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const createConfig = async app => {
|
|
27
|
-
const name = "primate.config.js";
|
|
28
|
-
const template = "export default {};";
|
|
29
|
-
const root = (await Path.root()).join(name);
|
|
30
|
-
if (await root.exists) {
|
|
31
|
-
app.log.warn(`${root} already exists`);
|
|
32
|
-
} else {
|
|
33
|
-
await root.file.write(template);
|
|
34
|
-
app.log.info(`created config at ${root}`);
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export default async app => {
|
|
39
|
-
await createModule(app);
|
|
40
|
-
await createConfig(app);
|
|
41
|
-
};
|
|
42
|
-
|
package/src/commands/help.js
DELETED
package/src/fromNull.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default object => Object.assign(Object.create(null), object);
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import mime from "./mime.js";
|
|
2
|
-
|
|
3
|
-
export default test => {
|
|
4
|
-
test.case("match", assert => {
|
|
5
|
-
assert(mime("/app.js")).equals("text/javascript");
|
|
6
|
-
});
|
|
7
|
-
test.case("no extension", assert => {
|
|
8
|
-
assert(mime("/app")).equals("application/octet-stream");
|
|
9
|
-
});
|
|
10
|
-
test.case("unknown extension", assert => {
|
|
11
|
-
assert(mime("/app.unknown")).equals("application/octet-stream");
|
|
12
|
-
});
|
|
13
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import respond from "./respond.js";
|
|
2
|
-
|
|
3
|
-
export default test => {
|
|
4
|
-
test.case("guess URL", assert => {
|
|
5
|
-
const url = "https://primatejs.com/";
|
|
6
|
-
const status = 302;
|
|
7
|
-
const [body, options] = respond(new URL(url))();
|
|
8
|
-
assert(body).null();
|
|
9
|
-
assert(options.status).equals(status);
|
|
10
|
-
assert(options.headers.Location).equals(url);
|
|
11
|
-
});
|
|
12
|
-
};
|