primate 0.20.1 → 0.20.3
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 +5 -2
- package/src/Logger.js +2 -3
- package/src/app.js +32 -27
- package/src/defaults/primate.config.js +4 -3
- package/src/dispatch.js +1 -1
- package/src/handlers/html.js +6 -13
- package/src/hooks/compile.js +2 -3
- package/src/hooks/handle.js +6 -7
- package/src/hooks/parse.js +22 -40
- package/src/hooks/publish.js +2 -2
- package/src/hooks/route.js +5 -4
- package/src/hooks/serve.js +4 -4
- package/src/loaders/common.js +0 -2
- package/src/loaders/routes/load.js +4 -3
- package/src/loaders/routes/routes.js +2 -1
- package/src/loaders/routes.js +1 -1
- package/src/loaders/types.js +3 -1
- package/src/run.js +1 -1
- package/src/start.js +1 -1
- package/src/toSorted.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primate",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.3",
|
|
4
4
|
"description": "Expressive, minimal and extensible web framework",
|
|
5
5
|
"homepage": "https://primatejs.com",
|
|
6
6
|
"bugs": "https://github.com/primatejs/primate/issues",
|
|
@@ -18,7 +18,10 @@
|
|
|
18
18
|
"directory": "packages/primate"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"runtime-compat": "^0.
|
|
21
|
+
"runtime-compat": "^0.21.0"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18.16"
|
|
22
25
|
},
|
|
23
26
|
"type": "module",
|
|
24
27
|
"exports": "./src/exports.js"
|
package/src/Logger.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {assert, is} from "runtime-compat/dyndef";
|
|
2
2
|
import {blue, bold, green, red, yellow, dim} from "runtime-compat/colors";
|
|
3
|
-
import {map
|
|
3
|
+
import {map} from "runtime-compat/object";
|
|
4
4
|
|
|
5
5
|
const levels = {
|
|
6
6
|
Error: 0,
|
|
@@ -17,8 +17,7 @@ const reference = "https://primatejs.com/reference/errors";
|
|
|
17
17
|
|
|
18
18
|
const hyphenate = classCased => classCased
|
|
19
19
|
.split("")
|
|
20
|
-
.map(
|
|
21
|
-
.replace(/[A-Z]/u, capital => `-${capital.toLowerCase()}`))
|
|
20
|
+
.map(letter => letter.replace(/[A-Z]/u, upper => `-${upper.toLowerCase()}`))
|
|
22
21
|
.join("")
|
|
23
22
|
.slice(1);
|
|
24
23
|
|
package/src/app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import crypto from "runtime-compat/crypto";
|
|
2
|
-
import {tryreturn} from "runtime-compat/
|
|
2
|
+
import {tryreturn} from "runtime-compat/async";
|
|
3
3
|
import {File, Path} from "runtime-compat/fs";
|
|
4
4
|
import {bold, blue} from "runtime-compat/colors";
|
|
5
5
|
import {transform, valmap} from "runtime-compat/object";
|
|
@@ -8,6 +8,7 @@ import * as hooks from "./hooks/exports.js";
|
|
|
8
8
|
import * as loaders from "./loaders/exports.js";
|
|
9
9
|
import dispatch from "./dispatch.js";
|
|
10
10
|
import {print} from "./Logger.js";
|
|
11
|
+
import toSorted from "./toSorted.js";
|
|
11
12
|
|
|
12
13
|
const base = new Path(import.meta.url).up(1);
|
|
13
14
|
// do not hard-depend on node
|
|
@@ -16,18 +17,18 @@ const library = import.meta.runtime?.library ?? "node_modules";
|
|
|
16
17
|
|
|
17
18
|
// use user-provided file or fall back to default
|
|
18
19
|
const index = (app, name) =>
|
|
19
|
-
tryreturn(
|
|
20
|
-
.orelse(
|
|
20
|
+
tryreturn(_ => File.read(`${app.paths.pages.join(name)}`))
|
|
21
|
+
.orelse(_ => base.join("defaults", app.config.index).text());
|
|
21
22
|
|
|
23
|
+
const encoder = new TextEncoder();
|
|
22
24
|
const hash = async (string, algorithm = "sha-384") => {
|
|
23
|
-
const encoder = new TextEncoder();
|
|
24
25
|
const bytes = await crypto.subtle.digest(algorithm, encoder.encode(string));
|
|
25
26
|
const algo = algorithm.replace("-", _ => "");
|
|
26
27
|
return `${algo}-${btoa(String.fromCharCode(...new Uint8Array(bytes)))}`;
|
|
27
28
|
};
|
|
28
29
|
|
|
29
|
-
const attribute = attributes => Object.keys(attributes).length > 0
|
|
30
|
-
" ".concat(Object.entries(attributes)
|
|
30
|
+
const attribute = attributes => Object.keys(attributes).length > 0
|
|
31
|
+
? " ".concat(Object.entries(attributes)
|
|
31
32
|
.map(([key, value]) => `${key}="${value}"`).join(" "))
|
|
32
33
|
: "";
|
|
33
34
|
const tag = ({name, attributes = {}, code = "", close = true}) =>
|
|
@@ -74,22 +75,25 @@ export default async (config, root, log) => {
|
|
|
74
75
|
},
|
|
75
76
|
headers: _ => {
|
|
76
77
|
const csp = Object.keys(http.csp).reduce((policy_string, key) =>
|
|
77
|
-
`${policy_string}${key} ${http.csp[key]};`, "")
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
`${policy_string}${key} ${http.csp[key]};`, "")
|
|
79
|
+
.replace("script-src 'self'", `script-src 'self' ${
|
|
80
|
+
app.assets
|
|
81
|
+
.filter(({type}) => type !== "style")
|
|
82
|
+
.map(asset => `'${asset.integrity}'`).join(" ")
|
|
83
|
+
} `)
|
|
84
|
+
.replace("style-src 'self'", `style-src 'self' ${
|
|
85
|
+
app.assets
|
|
86
|
+
.filter(({type}) => type === "style")
|
|
87
|
+
.map(asset => `'${asset.integrity}'`).join(" ")
|
|
88
|
+
} `);
|
|
85
89
|
|
|
86
90
|
return {
|
|
87
|
-
"Content-Security-Policy":
|
|
91
|
+
"Content-Security-Policy": csp,
|
|
88
92
|
"Referrer-Policy": "same-origin",
|
|
89
93
|
};
|
|
90
94
|
},
|
|
91
95
|
handlers: {...handlers},
|
|
92
|
-
render: async ({body = "",
|
|
96
|
+
render: async ({body = "", page} = {}) => {
|
|
93
97
|
const html = await index(app, page ?? config.index);
|
|
94
98
|
// inline: <script type integrity>...</script>
|
|
95
99
|
// outline: <script type integrity src></script>
|
|
@@ -101,16 +105,17 @@ export default async (config, root, log) => {
|
|
|
101
105
|
const style = ({inline, code, href, rel = "stylesheet"}) => inline
|
|
102
106
|
? tag({name: "style", code})
|
|
103
107
|
: tag({name: "link", attributes: {rel, href}, close: false});
|
|
104
|
-
const
|
|
105
|
-
|
|
108
|
+
const head = toSorted(app.assets,
|
|
109
|
+
({type}) => -1 * (type === "importmap"))
|
|
106
110
|
.map(({src, code, type, inline, integrity}) =>
|
|
107
111
|
type === "style"
|
|
108
112
|
? style({inline, code, href: src})
|
|
109
113
|
: script({inline, code, type, integrity, src})
|
|
110
114
|
).join("\n");
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
// remove inline assets
|
|
116
|
+
app.assets = app.assets.filter(({inline, type}) => !inline
|
|
117
|
+
|| type === "importmap");
|
|
118
|
+
return html.replace("%body%", _ => body).replace("%head%", _ => head);
|
|
114
119
|
},
|
|
115
120
|
publish: async ({src, code, type = "", inline = false}) => {
|
|
116
121
|
if (!inline) {
|
|
@@ -118,10 +123,10 @@ export default async (config, root, log) => {
|
|
|
118
123
|
await base.directory.file.create();
|
|
119
124
|
await base.file.write(code);
|
|
120
125
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
if (inline || type === "style") {
|
|
127
|
+
app.assets.push({src: new Path(http.static.root).join(src ?? "").path,
|
|
128
|
+
code: inline ? code : "", type, inline, integrity: await hash(code)});
|
|
129
|
+
}
|
|
125
130
|
},
|
|
126
131
|
bootstrap: ({type, code}) => {
|
|
127
132
|
app.entrypoints.push({type, code});
|
|
@@ -134,10 +139,10 @@ export default async (config, root, log) => {
|
|
|
134
139
|
const exports = pkg.exports === undefined
|
|
135
140
|
? {[module]: `/${module}/${pkg.main}`}
|
|
136
141
|
: transform(pkg.exports, entry => entry
|
|
137
|
-
.filter(([, _export]) => _export.import !== undefined)
|
|
142
|
+
.filter(([, _export]) => _export.import !== undefined || _export.default !== undefined)
|
|
138
143
|
.map(([key, value]) => [
|
|
139
144
|
key.replace(".", module),
|
|
140
|
-
value.import.replace(".", `./${module}`),
|
|
145
|
+
value.import?.replace(".", `./${module}`) ?? value.default.replace(".", `./${module}`),
|
|
141
146
|
]));
|
|
142
147
|
await Promise.all(Object.values(exports).map(async name => app.publish({
|
|
143
148
|
code: await Path.resolve().join(library, name).text(),
|
|
@@ -4,6 +4,7 @@ export default {
|
|
|
4
4
|
base: "/",
|
|
5
5
|
logger: {
|
|
6
6
|
level: Logger.Warn,
|
|
7
|
+
trace: false,
|
|
7
8
|
},
|
|
8
9
|
http: {
|
|
9
10
|
host: "localhost",
|
|
@@ -11,6 +12,7 @@ export default {
|
|
|
11
12
|
csp: {
|
|
12
13
|
"default-src": "'self'",
|
|
13
14
|
"style-src": "'self'",
|
|
15
|
+
"script-src": "'self'",
|
|
14
16
|
"object-src": "'none'",
|
|
15
17
|
"frame-ancestors": "'none'",
|
|
16
18
|
"form-action": "'self'",
|
|
@@ -23,12 +25,11 @@ export default {
|
|
|
23
25
|
index: "app.html",
|
|
24
26
|
paths: {
|
|
25
27
|
build: "build",
|
|
26
|
-
static: "static",
|
|
27
28
|
components: "components",
|
|
29
|
+
pages: "pages",
|
|
28
30
|
routes: "routes",
|
|
31
|
+
static: "static",
|
|
29
32
|
types: "types",
|
|
30
|
-
pages: "pages",
|
|
31
|
-
layouts: "layouts",
|
|
32
33
|
},
|
|
33
34
|
build: {
|
|
34
35
|
includes: [],
|
package/src/dispatch.js
CHANGED
package/src/handlers/html.js
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
const script = /(?<=<script)>(?<code>.*?)(?=<\/script>)/gus;
|
|
2
2
|
const style = /(?<=<style)>(?<code>.*?)(?=<\/style>)/gus;
|
|
3
3
|
|
|
4
|
-
const integrate = async (html, publish
|
|
5
|
-
|
|
4
|
+
const integrate = async (html, publish) => {
|
|
5
|
+
await Promise.all([...html.matchAll(script)]
|
|
6
6
|
.map(({groups: {code}}) => publish({code, inline: true})));
|
|
7
|
-
|
|
8
|
-
headers["Content-Security-Policy"] = headers["Content-Security-Policy"]
|
|
9
|
-
.replace("script-src 'self' ", `script-src 'self' '${integrity}' `);
|
|
10
|
-
}
|
|
11
|
-
const styles = await Promise.all([...html.matchAll(style)]
|
|
7
|
+
await Promise.all([...html.matchAll(style)]
|
|
12
8
|
.map(({groups: {code}}) => publish({code, type: "style", inline: true})));
|
|
13
|
-
for (const integrity of styles) {
|
|
14
|
-
headers["Content-Security-Policy"] = headers["Content-Security-Policy"]
|
|
15
|
-
.replace("style-src 'self'", `style-src 'self' '${integrity}' `);
|
|
16
|
-
}
|
|
17
9
|
return html.replaceAll(/<(?<tag>script|style)>.*?<\/\k<tag>>/gus, _ => "");
|
|
18
10
|
};
|
|
19
11
|
|
|
@@ -21,10 +13,11 @@ export default (component, options = {}) => {
|
|
|
21
13
|
const {status = 200, partial = false, load = false} = options;
|
|
22
14
|
|
|
23
15
|
return async app => {
|
|
24
|
-
const headers = app.headers();
|
|
25
16
|
const body = await integrate(await load ?
|
|
26
17
|
await app.paths.components.join(component).text() : component,
|
|
27
|
-
app.publish
|
|
18
|
+
app.publish);
|
|
19
|
+
// needs to happen before app.render()
|
|
20
|
+
const headers = app.headers();
|
|
28
21
|
|
|
29
22
|
return [partial ? body : await app.render({body}), {
|
|
30
23
|
status,
|
package/src/hooks/compile.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import copy_includes from "./copy_includes.js"
|
|
1
|
+
import copy_includes from "./copy_includes.js";
|
|
2
2
|
|
|
3
3
|
const pre = async app => {
|
|
4
|
-
const {paths, config} = app;
|
|
5
|
-
const build = config.build;
|
|
4
|
+
const {paths, config: {build}} = app;
|
|
6
5
|
|
|
7
6
|
// remove build directory in case exists
|
|
8
7
|
if (await paths.build.exists) {
|
package/src/hooks/handle.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {Response, Status} from "runtime-compat/http";
|
|
2
|
-
import {tryreturn} from "runtime-compat/
|
|
2
|
+
import {tryreturn} from "runtime-compat/async";
|
|
3
3
|
import {mime, isResponse, respond} from "./respond/exports.js";
|
|
4
4
|
import {invalid} from "./route.js";
|
|
5
5
|
import {error as clientError} from "../handlers/exports.js";
|
|
@@ -8,7 +8,7 @@ import errors from "../errors.js";
|
|
|
8
8
|
const guardError = Symbol("guardError");
|
|
9
9
|
|
|
10
10
|
export default app => {
|
|
11
|
-
const {config: {http, build}, paths} = app;
|
|
11
|
+
const {config: {http: {static: {root}}, build}, paths} = app;
|
|
12
12
|
|
|
13
13
|
const run = async request => {
|
|
14
14
|
const {pathname} = request.url;
|
|
@@ -18,10 +18,10 @@ export default app => {
|
|
|
18
18
|
|
|
19
19
|
// handle guards
|
|
20
20
|
try {
|
|
21
|
-
guards.
|
|
21
|
+
guards.every(guard => {
|
|
22
22
|
const result = guard(request);
|
|
23
23
|
if (result === true) {
|
|
24
|
-
return
|
|
24
|
+
return true;
|
|
25
25
|
}
|
|
26
26
|
const error = new Error();
|
|
27
27
|
error.result = result;
|
|
@@ -38,7 +38,7 @@ export default app => {
|
|
|
38
38
|
|
|
39
39
|
// handle request
|
|
40
40
|
const handlers = [...app.modules.route, handler]
|
|
41
|
-
.reduceRight((
|
|
41
|
+
.reduceRight((next, last) => input => last(input, next));
|
|
42
42
|
|
|
43
43
|
return (await respond(await handlers({...request, path})))(app, {
|
|
44
44
|
layouts: await Promise.all(layouts.map(layout => layout(request))),
|
|
@@ -64,7 +64,6 @@ export default app => {
|
|
|
64
64
|
|
|
65
65
|
const handle = async request => {
|
|
66
66
|
const {pathname} = request.url;
|
|
67
|
-
const {root} = http.static;
|
|
68
67
|
if (pathname.startsWith(root)) {
|
|
69
68
|
const debased = pathname.replace(root, _ => "");
|
|
70
69
|
// try static first
|
|
@@ -79,5 +78,5 @@ export default app => {
|
|
|
79
78
|
};
|
|
80
79
|
|
|
81
80
|
return [...app.modules.handle, handle]
|
|
82
|
-
.reduceRight((
|
|
81
|
+
.reduceRight((next, last) => input => last(input, next));
|
|
83
82
|
};
|
package/src/hooks/parse.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {URL} from "runtime-compat/http";
|
|
2
|
-
import {tryreturn} from "runtime-compat/
|
|
2
|
+
import {tryreturn} from "runtime-compat/sync";
|
|
3
|
+
import {stringify} from "runtime-compat/streams";
|
|
3
4
|
import errors from "../errors.js";
|
|
4
5
|
|
|
5
6
|
const {fromEntries: from} = Object;
|
|
@@ -10,47 +11,28 @@ const contents = {
|
|
|
10
11
|
.map(subpart => decodeURIComponent(subpart).replaceAll("+", " ")))),
|
|
11
12
|
"application/json": body => JSON.parse(body),
|
|
12
13
|
};
|
|
13
|
-
const decoder = new TextDecoder();
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
const reader = request.body.getReader();
|
|
30
|
-
const chunks = [];
|
|
31
|
-
let result;
|
|
32
|
-
do {
|
|
33
|
-
result = await reader.read();
|
|
34
|
-
if (result.value !== undefined) {
|
|
35
|
-
chunks.push(decoder.decode(result.value));
|
|
36
|
-
}
|
|
37
|
-
} while (!result.done);
|
|
38
|
-
|
|
39
|
-
return parseContent(request.headers.get("content-type"), chunks.join());
|
|
40
|
-
};
|
|
15
|
+
const parse = {
|
|
16
|
+
content(content_type, body) {
|
|
17
|
+
return tryreturn(_ => {
|
|
18
|
+
const type = contents[content_type];
|
|
19
|
+
return type === undefined ? body : type(body);
|
|
20
|
+
}).orelse(_ => errors.CannotParseBody.throw(body, content_type));
|
|
21
|
+
},
|
|
22
|
+
async body({body, headers}) {
|
|
23
|
+
return body === null
|
|
24
|
+
? null
|
|
25
|
+
: this.content(headers.get("content-type"), await stringify(body));
|
|
26
|
+
},
|
|
27
|
+
};
|
|
41
28
|
|
|
42
|
-
|
|
29
|
+
export default dispatch => async request => {
|
|
30
|
+
const body = dispatch(await parse.body(request));
|
|
31
|
+
const cookies = dispatch(from(request.headers.get("cookie")?.split(";")
|
|
32
|
+
.map(cookie => cookie.trim().split("=")) ?? []));
|
|
33
|
+
const headers = dispatch(from(request.headers));
|
|
43
34
|
const url = new URL(request.url);
|
|
35
|
+
const query = dispatch(from(url.searchParams));
|
|
44
36
|
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
original: request,
|
|
48
|
-
url,
|
|
49
|
-
body: dispatch(body),
|
|
50
|
-
cookies: dispatch(cookies === null
|
|
51
|
-
? {}
|
|
52
|
-
: from(cookies.split(";").map(c => c.trim().split("=")))),
|
|
53
|
-
headers: dispatch(from(request.headers)),
|
|
54
|
-
query: dispatch(from(url.searchParams)),
|
|
55
|
-
};
|
|
37
|
+
return {original: request, url, body, cookies, headers, query};
|
|
56
38
|
};
|
package/src/hooks/publish.js
CHANGED
|
@@ -22,6 +22,7 @@ const post = async app => {
|
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// copy JavaScript and CSS files from `app.paths.static`
|
|
25
26
|
const imports = await Path.collect(app.paths.static, /\.(?:js|css)$/u);
|
|
26
27
|
await Promise.all(imports.map(async file => {
|
|
27
28
|
const code = await file.text();
|
|
@@ -40,9 +41,8 @@ const post = async app => {
|
|
|
40
41
|
// copy additional subdirectories to build/client
|
|
41
42
|
await copy_includes(app, "client", async to =>
|
|
42
43
|
Promise.all((await to.collect(/\.js$/u)).map(async script => {
|
|
43
|
-
const code = await script.text();
|
|
44
44
|
const src = new Path(root, script.path.replace(source, () => ""));
|
|
45
|
-
await app.publish({src, code, type: "module"});
|
|
45
|
+
await app.publish({src, code: await script.text(), type: "module"});
|
|
46
46
|
}))
|
|
47
47
|
);
|
|
48
48
|
};
|
package/src/hooks/route.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {keymap} from "runtime-compat/object";
|
|
2
|
-
import {tryreturn} from "runtime-compat/
|
|
2
|
+
import {tryreturn} from "runtime-compat/sync";
|
|
3
3
|
import errors from "../errors.js";
|
|
4
4
|
|
|
5
5
|
// insensitive-case equal
|
|
@@ -18,8 +18,9 @@ export default app => {
|
|
|
18
18
|
.filter(([name]) => name.includes("$"))
|
|
19
19
|
.map(([name, value]) => [name.split("$")[1], value])
|
|
20
20
|
.every(([name, value]) =>
|
|
21
|
-
tryreturn(_ => types?.[name](value) === true)
|
|
22
|
-
.orelse(({message}) => errors.MismatchedPath.throw(pathname, message))
|
|
21
|
+
tryreturn(_ => types?.[name].type(value) === true)
|
|
22
|
+
.orelse(({message}) => errors.MismatchedPath.throw(pathname, message))
|
|
23
|
+
);
|
|
23
24
|
const isPath = ({route, pathname}) => {
|
|
24
25
|
const result = route.pathname.exec(pathname);
|
|
25
26
|
return result === null ? false : isType(result.groups, pathname);
|
|
@@ -29,7 +30,7 @@ export default app => {
|
|
|
29
30
|
const find = (method, pathname) => routes.find(route =>
|
|
30
31
|
isMethod({route, method, pathname}));
|
|
31
32
|
|
|
32
|
-
const index = path => `${paths.routes}${path === "" ? "index" : path}`;
|
|
33
|
+
const index = path => `${paths.routes}${path === "/" ? "/index" : path}`;
|
|
33
34
|
const deroot = pathname => pathname.endsWith("/") && pathname !== "/"
|
|
34
35
|
? pathname.slice(0, -1) : pathname;
|
|
35
36
|
|
package/src/hooks/serve.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import {identity} from "runtime-compat/function";
|
|
2
2
|
|
|
3
3
|
export default async (app, server) => {
|
|
4
4
|
app.log.info("running serve hooks", {module: "primate"});
|
|
5
|
-
await [...
|
|
6
|
-
.reduceRight((
|
|
7
|
-
|
|
5
|
+
await [...app.modules.serve, identity]
|
|
6
|
+
.reduceRight((next, previous) =>
|
|
7
|
+
input => previous(input, next))({...app, server});
|
|
8
8
|
};
|
package/src/loaders/common.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {Path} from "runtime-compat/fs";
|
|
2
2
|
import errors from "../../errors.js";
|
|
3
|
+
import toSorted from "../../toSorted.js";
|
|
3
4
|
|
|
4
5
|
export default type => async (log, directory, load) => {
|
|
5
6
|
const filter = path => new RegExp(`^\\+${type}.js$`, "u").test(path.name);
|
|
6
7
|
|
|
7
8
|
const replace = new RegExp(`\\+${type}`, "u");
|
|
8
|
-
const objects = (await load({log, directory, filter, warn: false}))
|
|
9
|
-
.map(([name, object]) => [name.replace(replace, () => ""), object])
|
|
10
|
-
|
|
9
|
+
const objects = toSorted((await load({log, directory, filter, warn: false}))
|
|
10
|
+
.map(([name, object]) => [name.replace(replace, () => ""), object]),
|
|
11
|
+
([a], [b]) => a.length - b.length);
|
|
11
12
|
|
|
12
13
|
const resolve = name => new Path(directory, name, `+${type}.js`);
|
|
13
14
|
objects.some(([name, value]) => typeof value !== "function"
|
|
@@ -8,7 +8,8 @@ const normalize = route => {
|
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
// index -> ""
|
|
11
|
-
const deindex = path => path.endsWith("index") ?
|
|
11
|
+
const deindex = path => path.endsWith("index") ?
|
|
12
|
+
path.replace("index", "") : path;
|
|
12
13
|
|
|
13
14
|
export default async (log, directory, load) => {
|
|
14
15
|
const filter = path => /^[^+].*.js$/u.test(path.name);
|
package/src/loaders/routes.js
CHANGED
package/src/loaders/types.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {Path} from "runtime-compat/fs";
|
|
2
2
|
import errors from "../errors.js";
|
|
3
|
-
import
|
|
3
|
+
import fs from "./common.js";
|
|
4
|
+
|
|
5
|
+
const filter = path => /^[a-z]/u.test(path.name);
|
|
4
6
|
|
|
5
7
|
export default async (log, directory, load = fs) => {
|
|
6
8
|
const types = await load({log, directory, name: "types", filter});
|
package/src/run.js
CHANGED
package/src/start.js
CHANGED
package/src/toSorted.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default (array, compareFn) => [...array].sort(compareFn);
|