primate 0.25.0 → 0.26.1
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 +2 -2
- package/src/Logger.js +5 -5
- package/src/app.js +45 -39
- package/src/bin.js +1 -1
- package/src/commands/dev.js +1 -1
- package/src/commands/serve.js +1 -1
- package/src/cwd.js +1 -1
- package/src/defaults/primate.config.js +1 -1
- package/src/dispatch.js +12 -9
- package/src/errors.js +1 -1
- package/src/errors.json +5 -0
- package/src/exports.js +1 -1
- package/src/handlers/error.js +1 -1
- package/src/handlers/html.js +1 -1
- package/src/handlers/json.js +1 -1
- package/src/handlers/redirect.js +1 -1
- package/src/handlers/stream.js +1 -1
- package/src/handlers/text.js +1 -1
- package/src/hooks/bundle.js +1 -1
- package/src/hooks/copy_includes.js +5 -5
- package/src/hooks/exports.js +1 -1
- package/src/hooks/handle.js +7 -7
- package/src/hooks/init.js +1 -1
- package/src/hooks/parse.js +4 -4
- package/src/hooks/publish.js +4 -38
- package/src/hooks/register.js +73 -2
- package/src/hooks/respond/duck.js +1 -1
- package/src/hooks/respond/respond.js +2 -2
- package/src/hooks/route.js +2 -2
- package/src/hooks/serve.js +1 -1
- package/src/hooks/stage.js +48 -0
- package/src/loaders/common.js +2 -2
- package/src/loaders/modules.js +1 -1
- package/src/loaders/routes/exports.js +5 -3
- package/src/loaders/routes/load.js +1 -1
- package/src/loaders/routes.js +14 -13
- package/src/loaders/types.js +11 -4
- package/src/run.js +4 -4
- package/src/start.js +17 -8
- package/src/validate.js +1 -1
- package/src/hooks/compile.js +0 -45
- package/src/loaders/routes/errors.js +0 -3
- package/src/loaders/routes/guards.js +0 -3
- package/src/loaders/routes/layouts.js +0 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.1",
|
|
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,7 @@
|
|
|
18
18
|
"directory": "packages/primate"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"
|
|
21
|
+
"rcompat": "^0.3.3"
|
|
22
22
|
},
|
|
23
23
|
"engines": {
|
|
24
24
|
"node": ">=18"
|
package/src/Logger.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { assert, is } from "
|
|
2
|
-
import { blue, bold, green, red, yellow, dim } from "
|
|
3
|
-
import { map } from "
|
|
4
|
-
import console from "
|
|
5
|
-
import { stdout } from "
|
|
1
|
+
import { assert, is } from "rcompat/invariant";
|
|
2
|
+
import { blue, bold, green, red, yellow, dim } from "rcompat/colors";
|
|
3
|
+
import { map } from "rcompat/object";
|
|
4
|
+
import console from "rcompat/console";
|
|
5
|
+
import { stdout } from "rcompat/stdio";
|
|
6
6
|
|
|
7
7
|
const levels = {
|
|
8
8
|
Error: 0,
|
package/src/app.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import crypto from "
|
|
2
|
-
import { tryreturn } from "
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import * as runtime from "runtime-compat/meta";
|
|
1
|
+
import crypto from "rcompat/crypto";
|
|
2
|
+
import { tryreturn } from "rcompat/async";
|
|
3
|
+
import { Path } from "rcompat/fs";
|
|
4
|
+
import { is } from "rcompat/invariant";
|
|
5
|
+
import { transform, valmap } from "rcompat/object";
|
|
6
|
+
import { globify } from "rcompat/string";
|
|
7
|
+
import * as runtime from "rcompat/meta";
|
|
9
8
|
|
|
10
9
|
import errors from "./errors.js";
|
|
11
|
-
import { print } from "./Logger.js";
|
|
12
|
-
import dispatch from "./dispatch.js";
|
|
13
10
|
import to_sorted from "./to_sorted.js";
|
|
14
11
|
import * as handlers from "./handlers/exports.js";
|
|
15
12
|
import * as loaders from "./loaders/exports.js";
|
|
@@ -18,8 +15,8 @@ const { DoubleFileExtension } = errors;
|
|
|
18
15
|
|
|
19
16
|
// use user-provided file or fall back to default
|
|
20
17
|
const index = (base, page, fallback) =>
|
|
21
|
-
tryreturn(_ =>
|
|
22
|
-
.orelse(_ =>
|
|
18
|
+
tryreturn(_ => Path.read(`${base.join(page)}`))
|
|
19
|
+
.orelse(_ => Path.read(`${base.join(fallback)}`));
|
|
23
20
|
|
|
24
21
|
const encoder = new TextEncoder();
|
|
25
22
|
|
|
@@ -54,18 +51,13 @@ export default async (log, root, config) => {
|
|
|
54
51
|
const secure = http?.ssl !== undefined;
|
|
55
52
|
const path = valmap(config.location, value => root.join(value));
|
|
56
53
|
|
|
57
|
-
const at = `at http${secure ? "s" : ""}://${http.host}:${http.port}\n`;
|
|
58
|
-
print(blue(bold(name)), blue(version), at);
|
|
59
|
-
|
|
60
54
|
// if ssl activated, resolve key and cert early
|
|
61
55
|
if (secure) {
|
|
62
56
|
http.ssl.key = root.join(http.ssl.key);
|
|
63
57
|
http.ssl.cert = root.join(http.ssl.cert);
|
|
64
58
|
}
|
|
65
59
|
|
|
66
|
-
const types = await loaders.types(log, path.types);
|
|
67
60
|
const error = await path.routes.join("+error.js");
|
|
68
|
-
const routes = await loaders.routes(log, path.routes);
|
|
69
61
|
|
|
70
62
|
return {
|
|
71
63
|
config,
|
|
@@ -79,15 +71,10 @@ export default async (log, root, config) => {
|
|
|
79
71
|
root,
|
|
80
72
|
log,
|
|
81
73
|
error: {
|
|
82
|
-
default: await error.exists ? (await import(error)).default : undefined,
|
|
74
|
+
default: await error.exists() ? (await import(error)).default : undefined,
|
|
83
75
|
},
|
|
84
76
|
handlers: { ...handlers },
|
|
85
|
-
|
|
86
|
-
routes,
|
|
87
|
-
layout: {
|
|
88
|
-
depth: Math.max(...routes.map(({ layouts }) => layouts.length)) + 1,
|
|
89
|
-
},
|
|
90
|
-
dispatch: dispatch(types),
|
|
77
|
+
extensions: {},
|
|
91
78
|
modules: await loaders.modules(log, root, config),
|
|
92
79
|
...runtime,
|
|
93
80
|
// copy files to build folder, potentially transforming them
|
|
@@ -97,21 +84,39 @@ export default async (log, root, config) => {
|
|
|
97
84
|
is(mapper).function();
|
|
98
85
|
|
|
99
86
|
const regexs = paths.map(file => globify(file));
|
|
100
|
-
const
|
|
87
|
+
const target_base = this.runpath(directory);
|
|
101
88
|
|
|
102
89
|
await Promise.all((await source.collect(filter)).map(async path => {
|
|
103
90
|
const debased = path.debase(this.root).path.slice(1);
|
|
104
91
|
const filename = new Path(directory).join(path.debase(source));
|
|
105
|
-
const
|
|
106
|
-
await
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
} else {
|
|
111
|
-
await path.file.copy(to);
|
|
112
|
-
}
|
|
92
|
+
const target = await target_base.join(filename.debase(directory));
|
|
93
|
+
await target.directory.create();
|
|
94
|
+
await (regexs.some(regex => regex.test(debased))
|
|
95
|
+
? target.write(mapper(await path.text()))
|
|
96
|
+
: path.copy(target));
|
|
113
97
|
}));
|
|
114
98
|
},
|
|
99
|
+
async compile(component) {
|
|
100
|
+
const { location: { server, client, components } } = this.config;
|
|
101
|
+
|
|
102
|
+
const source = this.path.components;
|
|
103
|
+
const compile = this.extensions[component.extension]?.compile;
|
|
104
|
+
if (compile === undefined) {
|
|
105
|
+
const debased = `${component.path}`.replace(source, "");
|
|
106
|
+
|
|
107
|
+
const server_target = this.runpath(server, components);
|
|
108
|
+
await component.copy(server_target.join(debased));
|
|
109
|
+
|
|
110
|
+
const client_target = this.runpath(client, components);
|
|
111
|
+
await component.copy(client_target.join(debased));
|
|
112
|
+
} else {
|
|
113
|
+
// compile server components
|
|
114
|
+
await compile.server(component);
|
|
115
|
+
|
|
116
|
+
// compile client components
|
|
117
|
+
await compile.client(component);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
115
120
|
headers({ script = "", style = "" } = {}) {
|
|
116
121
|
const csp = Object.keys(http.csp).reduce((policy, key) =>
|
|
117
122
|
`${policy}${key} ${http.csp[key]};`, "")
|
|
@@ -154,8 +159,8 @@ export default async (log, root, config) => {
|
|
|
154
159
|
async publish({ src, code, type = "", inline = false, copy = true }) {
|
|
155
160
|
if (!inline && copy) {
|
|
156
161
|
const base = this.runpath(this.config.location.client).join(src);
|
|
157
|
-
await base.directory.
|
|
158
|
-
await base.
|
|
162
|
+
await base.directory.create();
|
|
163
|
+
await base.write(code);
|
|
159
164
|
}
|
|
160
165
|
if (inline || type === "style") {
|
|
161
166
|
this.assets.push({
|
|
@@ -170,9 +175,10 @@ export default async (log, root, config) => {
|
|
|
170
175
|
export({ type, code }) {
|
|
171
176
|
this.exports.push({ type, code });
|
|
172
177
|
},
|
|
173
|
-
register(extension,
|
|
178
|
+
register(extension, operations) {
|
|
174
179
|
is(this.handlers[extension]).undefined(DoubleFileExtension.new(extension));
|
|
175
|
-
this.handlers[extension] =
|
|
180
|
+
this.handlers[extension] = operations.handle;
|
|
181
|
+
this.extensions[extension] = operations;
|
|
176
182
|
},
|
|
177
183
|
async hash(data, algorithm = "sha-384") {
|
|
178
184
|
const bytes = await crypto.subtle.digest(algorithm, encoder.encode(data));
|
|
@@ -202,8 +208,8 @@ export default async (log, root, config) => {
|
|
|
202
208
|
?? value.import?.replace(".", `./${module}`),
|
|
203
209
|
]));
|
|
204
210
|
const dependency = Path.resolve().join(...path);
|
|
205
|
-
const
|
|
206
|
-
await dependency.
|
|
211
|
+
const target = new Path(this.runpath(client), this.library, ...parts);
|
|
212
|
+
await dependency.copy(target);
|
|
207
213
|
this.importmaps = {
|
|
208
214
|
...valmap(exports, value => new Path(root, this.library, value).path),
|
|
209
215
|
...this.importmaps };
|
package/src/bin.js
CHANGED
package/src/commands/dev.js
CHANGED
package/src/commands/serve.js
CHANGED
package/src/cwd.js
CHANGED
package/src/dispatch.js
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
|
-
import { is
|
|
2
|
-
import { tryreturn } from "
|
|
3
|
-
import { map } from "
|
|
4
|
-
import { camelcased } from "
|
|
1
|
+
import { is } from "rcompat/invariant";
|
|
2
|
+
import { tryreturn } from "rcompat/sync";
|
|
3
|
+
import { map } from "rcompat/object";
|
|
4
|
+
import { camelcased } from "rcompat/string";
|
|
5
5
|
import errors from "./errors.js";
|
|
6
6
|
import validate from "./validate.js";
|
|
7
7
|
|
|
8
|
-
export default (patches = {}) => (
|
|
8
|
+
export default (patches = {}) => (object, raw, cased = true) => {
|
|
9
9
|
return Object.assign(Object.create(null), {
|
|
10
10
|
...map(patches, ([name, patch]) => [`get${camelcased(name)}`, property => {
|
|
11
11
|
is(property).defined(`\`${name}\` called without property`);
|
|
12
|
-
return tryreturn(_ => validate(patch,
|
|
12
|
+
return tryreturn(_ => validate(patch, object[property], property))
|
|
13
13
|
.orelse(({ message }) => errors.MismatchedType.throw(message));
|
|
14
14
|
}]),
|
|
15
15
|
get(property) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
is(property).string();
|
|
17
|
+
|
|
18
|
+
return object[cased ? property : property.toLowerCase()];
|
|
19
|
+
},
|
|
20
|
+
getAll() {
|
|
21
|
+
return object;
|
|
19
22
|
},
|
|
20
23
|
raw,
|
|
21
24
|
});
|
package/src/errors.js
CHANGED
package/src/errors.json
CHANGED
|
@@ -56,6 +56,11 @@
|
|
|
56
56
|
"fix": "do not use dots in route names",
|
|
57
57
|
"level": "Error"
|
|
58
58
|
},
|
|
59
|
+
"InvalidTypeExport": {
|
|
60
|
+
"message": "invalid type export at {0}",
|
|
61
|
+
"fix": "export object with a `base` string and a `validate` function",
|
|
62
|
+
"level": "Error"
|
|
63
|
+
},
|
|
59
64
|
"InvalidTypeName": {
|
|
60
65
|
"message": "invalid type name {0}",
|
|
61
66
|
"fix": "use lowercase-first latin letters and decimals in type names",
|
package/src/exports.js
CHANGED
|
@@ -4,6 +4,6 @@ export * from "./handlers/exports.js";
|
|
|
4
4
|
|
|
5
5
|
export { default as Logger } from "./Logger.js";
|
|
6
6
|
|
|
7
|
-
export { URL, Response, Status, MediaType } from "
|
|
7
|
+
export { URL, Response, Status, MediaType } from "rcompat/http";
|
|
8
8
|
|
|
9
9
|
export default command => run(command);
|
package/src/handlers/error.js
CHANGED
package/src/handlers/html.js
CHANGED
package/src/handlers/json.js
CHANGED
package/src/handlers/redirect.js
CHANGED
package/src/handlers/stream.js
CHANGED
package/src/handlers/text.js
CHANGED
package/src/hooks/bundle.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Path } from "
|
|
1
|
+
import { Path } from "rcompat/fs";
|
|
2
2
|
|
|
3
3
|
export default async (app, type, post = () => undefined) => {
|
|
4
4
|
const { config } = app;
|
|
@@ -13,10 +13,10 @@ export default async (app, type, post = () => undefined) => {
|
|
|
13
13
|
.filter(include => /^[^/]*$/u.test(include))
|
|
14
14
|
.map(async include => {
|
|
15
15
|
const path = app.root.join(include);
|
|
16
|
-
if (await path.exists) {
|
|
17
|
-
const
|
|
18
|
-
await app.stage(path,
|
|
19
|
-
await post(
|
|
16
|
+
if (await path.exists()) {
|
|
17
|
+
const target = Path.join(type, include);
|
|
18
|
+
await app.stage(path, target);
|
|
19
|
+
await post(target);
|
|
20
20
|
}
|
|
21
21
|
}));
|
|
22
22
|
}
|
package/src/hooks/exports.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { default as init } from "./init.js";
|
|
2
|
+
export { default as stage } from "./stage.js";
|
|
2
3
|
export { default as register } from "./register.js";
|
|
3
|
-
export { default as compile } from "./compile.js";
|
|
4
4
|
export { default as publish } from "./publish.js";
|
|
5
5
|
export { default as bundle } from "./bundle.js";
|
|
6
6
|
export { default as route } from "./route.js";
|
package/src/hooks/handle.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Response, Status, MediaType } from "
|
|
2
|
-
import { cascade, tryreturn } from "
|
|
1
|
+
import { Response, Status, MediaType } from "rcompat/http";
|
|
2
|
+
import { cascade, tryreturn } from "rcompat/async";
|
|
3
3
|
import { respond } from "./respond/exports.js";
|
|
4
4
|
import { invalid } from "./route.js";
|
|
5
5
|
import { error as clientError } from "../handlers/exports.js";
|
|
@@ -63,11 +63,11 @@ export default app => {
|
|
|
63
63
|
});
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
const as_asset = async
|
|
66
|
+
const as_asset = async path => new Response(path.stream(), {
|
|
67
67
|
status: Status.OK,
|
|
68
68
|
headers: {
|
|
69
|
-
"Content-Type": MediaType.resolve(
|
|
70
|
-
Etag: await
|
|
69
|
+
"Content-Type": MediaType.resolve(path.name),
|
|
70
|
+
Etag: await path.modified(),
|
|
71
71
|
},
|
|
72
72
|
});
|
|
73
73
|
|
|
@@ -79,11 +79,11 @@ export default app => {
|
|
|
79
79
|
// try static first
|
|
80
80
|
const asset = client.join(location.static, debased);
|
|
81
81
|
if (await asset.isFile) {
|
|
82
|
-
return as_asset(asset
|
|
82
|
+
return as_asset(asset);
|
|
83
83
|
}
|
|
84
84
|
const path = client.join(debased);
|
|
85
85
|
if (await path.isFile) {
|
|
86
|
-
return as_asset(path
|
|
86
|
+
return as_asset(path);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
return as_route(request);
|
package/src/hooks/init.js
CHANGED
package/src/hooks/parse.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { URL, MediaType } from "
|
|
2
|
-
import { tryreturn } from "
|
|
3
|
-
import { stringify } from "
|
|
4
|
-
import { from, valmap } from "
|
|
1
|
+
import { URL, MediaType } from "rcompat/http";
|
|
2
|
+
import { tryreturn } from "rcompat/sync";
|
|
3
|
+
import { stringify } from "rcompat/streams";
|
|
4
|
+
import { from, valmap } from "rcompat/object";
|
|
5
5
|
import errors from "../errors.js";
|
|
6
6
|
|
|
7
7
|
const { APPLICATION_FORM_URLENCODED, APPLICATION_JSON } = MediaType;
|
package/src/hooks/publish.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { Path } from "
|
|
2
|
-
import { cascade } from "
|
|
3
|
-
import { stringify } from "
|
|
4
|
-
import copy_includes from "./copy_includes.js";
|
|
1
|
+
import { Path } from "rcompat/fs";
|
|
2
|
+
import { cascade } from "rcompat/async";
|
|
3
|
+
import { stringify } from "rcompat/object";
|
|
5
4
|
|
|
6
5
|
const post = async app => {
|
|
7
|
-
const { config: {
|
|
6
|
+
const { config: { http: { static: { root } } } } = app;
|
|
8
7
|
|
|
9
8
|
{
|
|
10
9
|
// after hook, publish a zero assumptions app.js (no css imports)
|
|
@@ -17,44 +16,11 @@ const post = async app => {
|
|
|
17
16
|
type: "module",
|
|
18
17
|
});
|
|
19
18
|
|
|
20
|
-
if (await path.components.exists) {
|
|
21
|
-
// copy .js files from components to build/client/components, since
|
|
22
|
-
// frontend frameworks handle non-js files
|
|
23
|
-
const to = Path.join(location.client, location.components);
|
|
24
|
-
await app.stage(path.components, to, /^.*.js$/u);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
19
|
const imports = { ...app.importmaps, app: src.path };
|
|
28
20
|
const type = "importmap";
|
|
29
21
|
await app.publish({ inline: true, code: stringify({ imports }), type });
|
|
30
22
|
}
|
|
31
23
|
|
|
32
|
-
if (await path.static.exists) {
|
|
33
|
-
// copy static files to build/client/static
|
|
34
|
-
await app.stage(path.static, new Path(location.client, location.static));
|
|
35
|
-
|
|
36
|
-
// publish JavaScript and CSS files
|
|
37
|
-
const imports = await Path.collect(path.static, /\.(?:js|css)$/u);
|
|
38
|
-
await Promise.all(imports.map(async file => {
|
|
39
|
-
const code = await file.text();
|
|
40
|
-
const src = file.debase(path.static);
|
|
41
|
-
const type = file.extension === ".css" ? "style" : "module";
|
|
42
|
-
// already copied in `app.stage`
|
|
43
|
-
await app.publish({ src, code, type, copy: false });
|
|
44
|
-
type === "style" && app.export({ type,
|
|
45
|
-
code: `import "./${location.static}${src}";` });
|
|
46
|
-
}));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// copy additional subdirectories to build/client
|
|
50
|
-
const client = app.runpath(location.client);
|
|
51
|
-
await copy_includes(app, location.client, async to =>
|
|
52
|
-
Promise.all((await to.collect(/\.js$/u)).map(async script => {
|
|
53
|
-
const src = new Path(root, script.path.replace(client, _ => ""));
|
|
54
|
-
await app.publish({ src, code: await script.text(), type: "module" });
|
|
55
|
-
})),
|
|
56
|
-
);
|
|
57
|
-
|
|
58
24
|
return app;
|
|
59
25
|
};
|
|
60
26
|
|
package/src/hooks/register.js
CHANGED
|
@@ -1,3 +1,74 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Path } from "rcompat/fs";
|
|
2
|
+
import { cascade } from "rcompat/async";
|
|
3
|
+
import cwd from "../cwd.js";
|
|
4
|
+
import copy_includes from "./copy_includes.js";
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
const html = /^.*.html$/u;
|
|
7
|
+
const defaults = cwd(import.meta, 2).join("defaults");
|
|
8
|
+
|
|
9
|
+
const pre = async app => {
|
|
10
|
+
const { config: { location: { pages, client, components } }, path } = app;
|
|
11
|
+
|
|
12
|
+
await Promise.all(["server", "client", "pages"]
|
|
13
|
+
.map(directory => app.runpath(directory).create()));
|
|
14
|
+
|
|
15
|
+
// copy framework pages
|
|
16
|
+
await app.stage(defaults, pages, html);
|
|
17
|
+
// overwrite transformed pages to build
|
|
18
|
+
await path.pages.exists() && await app.stage(path.pages, pages, html);
|
|
19
|
+
|
|
20
|
+
if (await path.components.exists()) {
|
|
21
|
+
// copy .js files from components to build/client/components, since
|
|
22
|
+
// frontend frameworks handle non-js files
|
|
23
|
+
const target = Path.join(client, components);
|
|
24
|
+
await app.stage(path.components, target, /^.*.js$/u);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return app;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const post = async app => {
|
|
31
|
+
const { config: { location, http: { static: { root } } }, path } = app;
|
|
32
|
+
|
|
33
|
+
if (await path.static.exists()) {
|
|
34
|
+
// copy static files to build/server/static
|
|
35
|
+
await app.stage(path.static, new Path(location.server, location.static));
|
|
36
|
+
|
|
37
|
+
// copy static files to build/client/static
|
|
38
|
+
await app.stage(path.static, new Path(location.client, location.static));
|
|
39
|
+
|
|
40
|
+
// publish JavaScript and CSS files
|
|
41
|
+
const imports = await Path.collect(path.static, /\.(?:js|css)$/u);
|
|
42
|
+
await Promise.all(imports.map(async file => {
|
|
43
|
+
const code = await file.text();
|
|
44
|
+
const src = file.debase(path.static);
|
|
45
|
+
const type = file.extension === "css" ? "style" : "module";
|
|
46
|
+
// already copied in `app.stage`
|
|
47
|
+
await app.publish({ src, code, type, copy: false });
|
|
48
|
+
type === "style" && app.export({ type,
|
|
49
|
+
code: `import "./${location.static}${src}";` });
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// copy additional subdirectories to build/server
|
|
54
|
+
await copy_includes(app, location.server);
|
|
55
|
+
|
|
56
|
+
// copy additional subdirectories to build/client
|
|
57
|
+
const client = app.runpath(location.client);
|
|
58
|
+
await copy_includes(app, location.client, async to =>
|
|
59
|
+
Promise.all((await to.collect(/\.js$/u)).map(async script => {
|
|
60
|
+
const src = new Path(root, script.path.replace(client, _ => ""));
|
|
61
|
+
await app.publish({ src, code: await script.text(), type: "module" });
|
|
62
|
+
})),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const components = await app.path.components.collect();
|
|
66
|
+
|
|
67
|
+
// from the build directory, compile to server and client
|
|
68
|
+
await Promise.all(components.map(component => app.compile(component)));
|
|
69
|
+
|
|
70
|
+
return app;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export default async app =>
|
|
74
|
+
post(await (await cascade(app.modules.register))(await pre(app)));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Blob } from "
|
|
2
|
-
import { URL } from "
|
|
1
|
+
import { Blob } from "rcompat/fs";
|
|
2
|
+
import { URL } from "rcompat/http";
|
|
3
3
|
import { text, json, stream, redirect } from "primate";
|
|
4
4
|
import { isResponse as isResponseDuck } from "./duck.js";
|
|
5
5
|
|
package/src/hooks/route.js
CHANGED
package/src/hooks/serve.js
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { cascade } from "rcompat/async";
|
|
2
|
+
import dispatch from "../dispatch.js";
|
|
3
|
+
import * as loaders from "../loaders/exports.js";
|
|
4
|
+
import { doubled } from "../loaders/common.js";
|
|
5
|
+
import errors from "../errors.js";
|
|
6
|
+
|
|
7
|
+
const pre = async app => {
|
|
8
|
+
// remove build directory in case exists
|
|
9
|
+
await app.path.build.remove();
|
|
10
|
+
|
|
11
|
+
return { ...app };
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const post = async app => {
|
|
15
|
+
const { config: { location } } = app;
|
|
16
|
+
|
|
17
|
+
// stage routes
|
|
18
|
+
await app.runpath(location.routes).create();
|
|
19
|
+
const double = doubled((await app.path.routes.collect())
|
|
20
|
+
.map(path => path.debase(app.path.routes))
|
|
21
|
+
.map(path => `${path}`.slice(1, -path.extension.length - 1)));
|
|
22
|
+
double && errors.DoubleRoute.throw(double);
|
|
23
|
+
|
|
24
|
+
await app.stage(app.path.routes, location.routes);
|
|
25
|
+
if (await app.path.types.exists()) {
|
|
26
|
+
await app.stage(app.path.types, location.types);
|
|
27
|
+
}
|
|
28
|
+
const types = await loaders.types(app.log, app.runpath(location.types));
|
|
29
|
+
const staged = app.runpath(location.routes);
|
|
30
|
+
for (const path of await staged.collect()) {
|
|
31
|
+
await app.extensions[path.extension]
|
|
32
|
+
?.route(staged, path.debase(`${staged}/`), types);
|
|
33
|
+
}
|
|
34
|
+
const routes = await loaders.routes(app);
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
...app,
|
|
38
|
+
types,
|
|
39
|
+
routes,
|
|
40
|
+
dispatch: dispatch(app.types),
|
|
41
|
+
layout: {
|
|
42
|
+
depth: Math.max(...routes.map(({ layouts }) => layouts.length)) + 1,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default async app =>
|
|
48
|
+
post(await (await cascade(app.modules.stage))(await pre(app)));
|
package/src/loaders/common.js
CHANGED
package/src/loaders/modules.js
CHANGED
|
@@ -12,7 +12,7 @@ export default async (log, root, config) => {
|
|
|
12
12
|
|
|
13
13
|
Array.isArray(modules) || errors.ModulesMustBeArray.throw("modules");
|
|
14
14
|
|
|
15
|
-
modules.some((
|
|
15
|
+
modules.some(({ name }, n) => name === undefined &&
|
|
16
16
|
errors.ModuleHasNoName.throw(n));
|
|
17
17
|
|
|
18
18
|
const names = modules.map(({ name }) => name);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import load from "./load.js";
|
|
2
|
+
|
|
1
3
|
export { default as routes } from "./routes.js";
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
4
|
+
export const guards = await load("guard");
|
|
5
|
+
export const errors = await load("error");
|
|
6
|
+
export const layouts = await load("layout");
|
package/src/loaders/routes.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { tryreturn } from "
|
|
2
|
-
import { from } from "
|
|
3
|
-
import errors from "../errors.js";
|
|
4
|
-
import { invalid } from "../hooks/route.js";
|
|
1
|
+
import { tryreturn } from "rcompat/sync";
|
|
2
|
+
import { from } from "rcompat/object";
|
|
5
3
|
import { default as fs, doubled } from "./common.js";
|
|
6
4
|
import * as get from "./routes/exports.js";
|
|
5
|
+
import errors from "../errors.js";
|
|
6
|
+
import { invalid } from "../hooks/route.js";
|
|
7
7
|
|
|
8
8
|
const make = path => {
|
|
9
9
|
const double = doubled(path.split("/")
|
|
@@ -23,26 +23,27 @@ const make = path => {
|
|
|
23
23
|
return new RegExp(`^/${route}$`, "u");
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
const routes$ = from(await Promise.all(specials.map(async extra =>
|
|
30
|
-
[extra, await get[extra](log, directory, load)])));
|
|
26
|
+
export default async (app, load = fs) => {
|
|
27
|
+
const { log } = app;
|
|
28
|
+
const directory = app.runpath(app.config.location.routes);
|
|
31
29
|
const filter = path => ([name]) => path.includes(name);
|
|
30
|
+
const routes = from(await Promise.all(["guards", "errors", "layouts"]
|
|
31
|
+
.map(async extra => [extra, await get[extra](log, directory, load)])));
|
|
32
32
|
|
|
33
|
-
return routes.map(([path, imported]) => {
|
|
33
|
+
return (await get.routes(log, directory, load)).map(([path, imported]) => {
|
|
34
34
|
if (imported === undefined || Object.keys(imported).length === 0) {
|
|
35
35
|
errors.EmptyRouteFile.warn(log, directory.join(`${path}.js`).path);
|
|
36
36
|
return [];
|
|
37
37
|
}
|
|
38
|
+
const filtered = filter(path);
|
|
38
39
|
|
|
39
40
|
return Object.entries(imported).map(([method, handler]) => ({
|
|
40
41
|
method,
|
|
41
42
|
handler,
|
|
42
43
|
pathname: make(path.endsWith("/") ? path.slice(0, -1) : path),
|
|
43
|
-
guards: routes
|
|
44
|
-
errors: routes
|
|
45
|
-
layouts: routes
|
|
44
|
+
guards: routes.guards.filter(filtered).map(([, guard]) => guard),
|
|
45
|
+
errors: routes.errors.filter(filtered).map(([, error]) => error),
|
|
46
|
+
layouts: routes.layouts.filter(filtered).map(([, layout]) => layout),
|
|
46
47
|
}));
|
|
47
48
|
}).flat();
|
|
48
49
|
};
|
package/src/loaders/types.js
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
import { Path } from "
|
|
1
|
+
import { Path } from "rcompat/fs";
|
|
2
|
+
import { is } from "rcompat/invariant";
|
|
3
|
+
import { tryreturn } from "rcompat/sync";
|
|
2
4
|
import errors from "../errors.js";
|
|
3
5
|
import fs from "./common.js";
|
|
4
6
|
|
|
5
7
|
const filter = path => /^[a-z]/u.test(path.name);
|
|
6
8
|
|
|
7
9
|
export default async (log, directory, load = fs) => {
|
|
8
|
-
const types = await
|
|
10
|
+
const types = await fs({ log, directory, name: "types", filter });
|
|
9
11
|
|
|
10
12
|
const resolve = name => new Path(directory, name);
|
|
11
|
-
types.
|
|
12
|
-
|
|
13
|
+
types.every(([name, type]) => tryreturn(_ => {
|
|
14
|
+
is(type).object();
|
|
15
|
+
is(type.base).string();
|
|
16
|
+
is(type.validate).function();
|
|
17
|
+
return true;
|
|
18
|
+
}).orelse(_ => errors.InvalidTypeExport.throw(resolve(`${name}.js`))),
|
|
19
|
+
);
|
|
13
20
|
|
|
14
21
|
types.every(([name]) =>
|
|
15
22
|
/^(?:[a-z][^\W_]*)$/u.test(name) || errors.InvalidTypeName.throw(name));
|
package/src/run.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { tryreturn } from "
|
|
2
|
-
import { Path } from "
|
|
3
|
-
import { extend } from "
|
|
1
|
+
import { tryreturn } from "rcompat/async";
|
|
2
|
+
import { Path } from "rcompat/fs";
|
|
3
|
+
import { extend } from "rcompat/object";
|
|
4
4
|
import app from "./app.js";
|
|
5
5
|
import { default as Logger, bye } from "./Logger.js";
|
|
6
6
|
import errors from "./errors.js";
|
|
@@ -13,7 +13,7 @@ const { runtime = "node" } = import.meta;
|
|
|
13
13
|
const get_config = async root => {
|
|
14
14
|
const name = "primate.config.js";
|
|
15
15
|
const config = root.join(name);
|
|
16
|
-
return await config.exists
|
|
16
|
+
return await config.exists()
|
|
17
17
|
? tryreturn(async _ => {
|
|
18
18
|
const imported = (await import(config)).default;
|
|
19
19
|
|
package/src/start.js
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
import { serve, Response, Status } from "
|
|
2
|
-
import { cascade, tryreturn } from "
|
|
1
|
+
import { serve, Response, Status } from "rcompat/http";
|
|
2
|
+
import { cascade, tryreturn } from "rcompat/async";
|
|
3
|
+
import { bold, blue } from "rcompat/colors";
|
|
3
4
|
import * as hooks from "./hooks/exports.js";
|
|
5
|
+
import { print } from "./Logger.js";
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
const base_hooks = ["init", "stage", "register", "publish", "bundle"];
|
|
8
|
+
|
|
9
|
+
export default async (app$, mode = "development") => {
|
|
10
|
+
app$.mode = mode;
|
|
6
11
|
// run one-time hooks
|
|
7
12
|
let app = app$;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
|
|
14
|
+
const { http } = app.config;
|
|
15
|
+
const address = `http${app.secure ? "s" : ""}://${http.host}:${http.port}`;
|
|
16
|
+
print(blue(bold(app.name)), blue(app.version), `at ${address}\n`);
|
|
17
|
+
|
|
18
|
+
app.log.info(`in ${bold(mode)} mode`, { module: "primate" });
|
|
19
|
+
|
|
20
|
+
for (const hook of base_hooks) {
|
|
21
|
+
app.log.info(`running ${bold(hook)} hooks`, { module: "primate" });
|
|
13
22
|
app = await hooks[hook](app);
|
|
14
23
|
}
|
|
15
24
|
|
package/src/validate.js
CHANGED
package/src/hooks/compile.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { Path } from "runtime-compat/fs";
|
|
2
|
-
import { cascade } from "runtime-compat/async";
|
|
3
|
-
import copy_includes from "./copy_includes.js";
|
|
4
|
-
import cwd from "../cwd.js";
|
|
5
|
-
|
|
6
|
-
const html = /^.*.html$/u;
|
|
7
|
-
const defaults = cwd(import.meta, 2).join("defaults");
|
|
8
|
-
|
|
9
|
-
const pre = async app => {
|
|
10
|
-
const { config: { location }, path } = app;
|
|
11
|
-
|
|
12
|
-
// remove build directory in case exists
|
|
13
|
-
if (await path.build.exists) {
|
|
14
|
-
await path.build.file.remove();
|
|
15
|
-
}
|
|
16
|
-
await Promise.all(["server", "client", "components", "pages"]
|
|
17
|
-
.map(directory => app.runpath(directory).file.create()));
|
|
18
|
-
|
|
19
|
-
// copy framework pages
|
|
20
|
-
await app.stage(defaults, location.pages, html);
|
|
21
|
-
// overwrite transformed pages to build
|
|
22
|
-
await path.pages.exists && await app.stage(path.pages, location.pages, html);
|
|
23
|
-
|
|
24
|
-
if (await path.components.exists) {
|
|
25
|
-
// copy all files to build/components
|
|
26
|
-
await app.stage(path.components, location.components);
|
|
27
|
-
// copy .js files from components to build/server, since frontend
|
|
28
|
-
// frameworks handle non-js files
|
|
29
|
-
const to = Path.join(location.server, location.components);
|
|
30
|
-
await app.stage(path.components, to, /^.*.js$/u);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (await path.static.exists) {
|
|
34
|
-
// copy static files to build/server/static
|
|
35
|
-
await app.stage(path.static, new Path(location.server, location.static));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// copy additional subdirectories to build/server
|
|
39
|
-
await copy_includes(app, location.server);
|
|
40
|
-
|
|
41
|
-
return app;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export default async app =>
|
|
45
|
-
(await cascade(app.modules.compile))(await pre(app));
|