@primate/core 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/LICENSE +19 -0
- package/package.json +53 -0
- package/src/build/app.js +105 -0
- package/src/build/defaults/app.html +9 -0
- package/src/build/defaults/error.html +14 -0
- package/src/build/hook/build.js +179 -0
- package/src/build/hook/copy_includes.js +20 -0
- package/src/build/hook/exports.js +2 -0
- package/src/build/hook/init.js +3 -0
- package/src/build/hook/router.js +32 -0
- package/src/build/index.js +41 -0
- package/src/build/module_loader.js +30 -0
- package/src/build/targets/exports.js +1 -0
- package/src/build/targets/web.js +81 -0
- package/src/dispatch/index.js +1 -0
- package/src/log/index.js +1 -0
- package/src/private/bye.js +5 -0
- package/src/private/config-filename.js +1 -0
- package/src/private/config.js +48 -0
- package/src/private/depend.js +20 -0
- package/src/private/dispatch.js +18 -0
- package/src/private/error/bad-body.js +6 -0
- package/src/private/error/bad-default-export.js +6 -0
- package/src/private/error/bad-path.js +6 -0
- package/src/private/error/bad-type-export.js +6 -0
- package/src/private/error/bad-type-name.js +6 -0
- package/src/private/error/double-extension.js +6 -0
- package/src/private/error/double-module.js +6 -0
- package/src/private/error/double-path-parameter.js +6 -0
- package/src/private/error/double-route.js +6 -0
- package/src/private/error/empty-config-file.js +6 -0
- package/src/private/error/empty-directory.js +6 -0
- package/src/private/error/empty-path-parameter.js +6 -0
- package/src/private/error/empty-route-file.js +6 -0
- package/src/private/error/error-in-config-file.js +6 -0
- package/src/private/error/mismatched-body.js +6 -0
- package/src/private/error/mismatched-path.js +6 -0
- package/src/private/error/mismatched-type.js +6 -0
- package/src/private/error/module-no-name.js +6 -0
- package/src/private/error/modules-array.js +6 -0
- package/src/private/error/no-handler.js +6 -0
- package/src/private/error/no-route-to-path.js +6 -0
- package/src/private/error/optional-route.js +6 -0
- package/src/private/error/reserved-type-name.js +6 -0
- package/src/private/error/rest-route.js +6 -0
- package/src/private/error.js +15 -0
- package/src/private/log.js +69 -0
- package/src/private/loglevel.js +9 -0
- package/src/private/mark.js +5 -0
- package/src/private/validate.js +11 -0
- package/src/serve/app.js +191 -0
- package/src/serve/handler/error.js +10 -0
- package/src/serve/handler/json.js +10 -0
- package/src/serve/handler/redirect.js +11 -0
- package/src/serve/handler/shared/base.js +4 -0
- package/src/serve/handler/sse.js +24 -0
- package/src/serve/handler/stream.js +4 -0
- package/src/serve/handler/text.js +10 -0
- package/src/serve/handler/view.js +15 -0
- package/src/serve/handler/ws.js +2 -0
- package/src/serve/hook/exports.js +5 -0
- package/src/serve/hook/handle.js +120 -0
- package/src/serve/hook/init.js +3 -0
- package/src/serve/hook/parse.js +17 -0
- package/src/serve/hook/respond.js +28 -0
- package/src/serve/hook/route.js +50 -0
- package/src/serve/hook/serve.js +53 -0
- package/src/serve/index.js +9 -0
- package/src/serve/module_loader.js +16 -0
- package/src/serve/to_sorted.js +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) Terrablue <terrablue@proton.me> and contributors.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
16
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@primate/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Primate core",
|
|
5
|
+
"homepage": "https://primatejs.com",
|
|
6
|
+
"bugs": "https://github.com/primatejs/primate/issues",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"files": [
|
|
9
|
+
"src/**/*.js",
|
|
10
|
+
"!src/**/*.spec.js",
|
|
11
|
+
"src/build/defaults/*.html"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/primatejs/primate",
|
|
16
|
+
"directory": "packages/core"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@rcompat/array": "^0.3.0",
|
|
20
|
+
"@rcompat/async": "^0.3.0",
|
|
21
|
+
"@rcompat/build": "^0.4.0",
|
|
22
|
+
"@rcompat/cli": "^0.5.1",
|
|
23
|
+
"@rcompat/crypto": "^0.5.0",
|
|
24
|
+
"@rcompat/fs": "^0.4.0",
|
|
25
|
+
"@rcompat/function": "^0.4.0",
|
|
26
|
+
"@rcompat/http": "^0.5.1",
|
|
27
|
+
"@rcompat/invariant": "^0.5.0",
|
|
28
|
+
"@rcompat/object": "^0.5.0",
|
|
29
|
+
"@rcompat/package": "^0.7.0",
|
|
30
|
+
"@rcompat/platform": "^0.3.0",
|
|
31
|
+
"@rcompat/stdio": "^0.4.0",
|
|
32
|
+
"@rcompat/sync": "^0.3.0"
|
|
33
|
+
},
|
|
34
|
+
"type": "module",
|
|
35
|
+
"imports": {
|
|
36
|
+
"#*": {
|
|
37
|
+
"@primate/lt": "./src/private/*.js",
|
|
38
|
+
"default": "./src/private/*.js"
|
|
39
|
+
},
|
|
40
|
+
"#error/*": {
|
|
41
|
+
"@primate/lt": "./src/private/error/*.js",
|
|
42
|
+
"default": "./src/private/error/*.js"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"exports": {
|
|
46
|
+
"./serve": "./src/serve/index.js",
|
|
47
|
+
"./build": "./src/build/index.js",
|
|
48
|
+
"./error": "./src/error/index.js",
|
|
49
|
+
"./log": "./src/log/index.js",
|
|
50
|
+
"./dispatch": "./src/dispatch/index.js",
|
|
51
|
+
"./handler/*": "./src/serve/handler/*.js"
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/build/app.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import join from "@rcompat/fs/join";
|
|
2
|
+
import get from "@rcompat/object/get";
|
|
3
|
+
import valmap from "@rcompat/object/valmap";
|
|
4
|
+
import module_loader from "./module_loader.js";
|
|
5
|
+
import { web } from "./targets/exports.js";
|
|
6
|
+
|
|
7
|
+
export default async (root, config) => {
|
|
8
|
+
const path = valmap(config.location, value => root.join(value));
|
|
9
|
+
const error = await path.routes.join("+error.js");
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
postbuild: [],
|
|
13
|
+
bindings: {},
|
|
14
|
+
roots: [],
|
|
15
|
+
targets: { web: { target: web } },
|
|
16
|
+
importmaps: {},
|
|
17
|
+
assets: [],
|
|
18
|
+
path,
|
|
19
|
+
root,
|
|
20
|
+
// pseudostatic thus arrowbound
|
|
21
|
+
get: (config_key, fallback) => get(config, config_key) ?? fallback,
|
|
22
|
+
set: (key, value) => {
|
|
23
|
+
config[key] = value;
|
|
24
|
+
},
|
|
25
|
+
error: {
|
|
26
|
+
default: await error.exists() ? await error.import("default") : undefined,
|
|
27
|
+
},
|
|
28
|
+
extensions: {},
|
|
29
|
+
modules: await module_loader(root, config.modules ?? []),
|
|
30
|
+
fonts: [],
|
|
31
|
+
// copy files to build folder, potentially transforming them
|
|
32
|
+
async stage(source, directory, filter) {
|
|
33
|
+
const { define = {} } = this.get("build", {});
|
|
34
|
+
|
|
35
|
+
if (!await source.exists()) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const target_base = this.runpath(directory);
|
|
40
|
+
const defines = Object.entries(define);
|
|
41
|
+
|
|
42
|
+
// first, copy everything
|
|
43
|
+
await source.copy(target_base);
|
|
44
|
+
|
|
45
|
+
const location = this.get("location");
|
|
46
|
+
const client_location = join(location.client, location.static).path;
|
|
47
|
+
const mapper = text =>
|
|
48
|
+
defines.reduce((replaced, [key, substitution]) =>
|
|
49
|
+
replaced.replaceAll(key, substitution), text);
|
|
50
|
+
|
|
51
|
+
// then, copy and transform whitelisted paths using mapper
|
|
52
|
+
await Promise.all((await source.collect(filter)).map(async abs_path => {
|
|
53
|
+
const rel_path = join(directory, abs_path.debase(source));
|
|
54
|
+
if (directory.path === client_location && rel_path.path.endsWith(".css")) {
|
|
55
|
+
const contents = await abs_path.text();
|
|
56
|
+
const font_regex = /@font-face\s*\{.+?url\("(.+?\.woff2)"\).+?\}/gus;
|
|
57
|
+
this.fonts.push(...[...contents.matchAll(font_regex)].map(match => match[1]));
|
|
58
|
+
}
|
|
59
|
+
const target = await target_base.join(rel_path.debase(directory));
|
|
60
|
+
await target.directory.create();
|
|
61
|
+
|
|
62
|
+
defines.length > 0 && await target.write(mapper(await abs_path.text()));
|
|
63
|
+
}));
|
|
64
|
+
},
|
|
65
|
+
async compile(component) {
|
|
66
|
+
const { server, client, components } = this.get("location");
|
|
67
|
+
|
|
68
|
+
const compile = this.extensions[component.fullExtension]
|
|
69
|
+
?? this.extensions[component.extension];
|
|
70
|
+
if (compile === undefined) {
|
|
71
|
+
const source = this.path.build.join(components);
|
|
72
|
+
const debased = `${component.path}`.replace(source, "");
|
|
73
|
+
|
|
74
|
+
const server_target = this.runpath(server, components, debased);
|
|
75
|
+
await server_target.directory.create();
|
|
76
|
+
await component.copy(server_target);
|
|
77
|
+
|
|
78
|
+
const client_target = this.runpath(client, components, debased);
|
|
79
|
+
await client_target.directory.create();
|
|
80
|
+
await component.copy(client_target);
|
|
81
|
+
} else {
|
|
82
|
+
// compile server components
|
|
83
|
+
await compile.server(component, this);
|
|
84
|
+
|
|
85
|
+
// compile client components
|
|
86
|
+
await compile.client(component, this);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
register(extension, compile) {
|
|
90
|
+
this.extensions[extension] = compile;
|
|
91
|
+
},
|
|
92
|
+
runpath(...directories) {
|
|
93
|
+
return this.path.build.join(...directories);
|
|
94
|
+
},
|
|
95
|
+
target(name, handler) {
|
|
96
|
+
this.targets[name] = handler;
|
|
97
|
+
},
|
|
98
|
+
bind(extension, handler) {
|
|
99
|
+
this.bindings[extension] = handler;
|
|
100
|
+
},
|
|
101
|
+
done(fn) {
|
|
102
|
+
this.postbuild.push(fn);
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import config_filename from "#config-filename";
|
|
2
|
+
import log from "@primate/core/log";
|
|
3
|
+
import cascade from "@rcompat/async/cascade";
|
|
4
|
+
import Build from "@rcompat/build";
|
|
5
|
+
import dim from "@rcompat/cli/color/dim";
|
|
6
|
+
import collect from "@rcompat/fs/collect";
|
|
7
|
+
import join from "@rcompat/fs/join";
|
|
8
|
+
import exclude from "@rcompat/object/exclude";
|
|
9
|
+
import stringify from "@rcompat/object/stringify";
|
|
10
|
+
import manifest from "@rcompat/package/manifest";
|
|
11
|
+
import copy_includes from "./copy_includes.js";
|
|
12
|
+
import $router from "./router.js";
|
|
13
|
+
|
|
14
|
+
const html = /^.*.html$/u;
|
|
15
|
+
|
|
16
|
+
const pre = async (app, mode, target) => {
|
|
17
|
+
let target$ = target;
|
|
18
|
+
if (app.targets[target$] === undefined) {
|
|
19
|
+
throw new Error(`target ${dim(target)} does not exist`);
|
|
20
|
+
}
|
|
21
|
+
if (app.targets[target$].forward) {
|
|
22
|
+
target$ = app.targets[target$].forward;
|
|
23
|
+
}
|
|
24
|
+
app.build_target = target$;
|
|
25
|
+
log.system(`starting ${dim(target$)} build in ${dim(mode)} mode`);
|
|
26
|
+
|
|
27
|
+
app.build = new Build({
|
|
28
|
+
...exclude(app.get("build"), ["includes", "index"]),
|
|
29
|
+
outdir: app.runpath(app.get("location.client")).path,
|
|
30
|
+
stdin: {
|
|
31
|
+
resolveDir: app.root.path,
|
|
32
|
+
},
|
|
33
|
+
}, mode);
|
|
34
|
+
|
|
35
|
+
app.server_build = ["routes", "types"];
|
|
36
|
+
|
|
37
|
+
// remove build directory in case exists
|
|
38
|
+
await app.path.build.remove();
|
|
39
|
+
await app.path.build.create();
|
|
40
|
+
|
|
41
|
+
await Promise.all(["server", "client", "pages", "components"]
|
|
42
|
+
.map(directory => app.runpath(directory).create()));
|
|
43
|
+
|
|
44
|
+
const router = await $router(app.path.routes);
|
|
45
|
+
const layout = { depth: router.depth("layout") };
|
|
46
|
+
app.set("layout", layout);
|
|
47
|
+
|
|
48
|
+
return { ...app, layout };
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const js_re = /^.*.js$/u;
|
|
52
|
+
const write_directories = async (build_directory, app) => {
|
|
53
|
+
for (const name of app.server_build) {
|
|
54
|
+
const d = app.runpath(name);
|
|
55
|
+
const e = await Promise.all((await collect(d, js_re, { recursive: true }))
|
|
56
|
+
.map(async file => `${file}`.replace(d, _ => "")));
|
|
57
|
+
const files_js = `
|
|
58
|
+
const ${name} = [];
|
|
59
|
+
${e.map((file , i) =>
|
|
60
|
+
`import * as ${name}${i} from "../${name}${file}";
|
|
61
|
+
${name}.push(["${file.slice(1, -".js".length)}", ${name}${i}]);`,
|
|
62
|
+
).join("\n")}
|
|
63
|
+
export default ${name};`;
|
|
64
|
+
await build_directory.join(`${name}.js`).write(files_js);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const write_components = async (build_directory, app) => {
|
|
69
|
+
const location = app.get("location");
|
|
70
|
+
const d2 = app.runpath(location.server, location.components);
|
|
71
|
+
const e = await Promise.all((await collect(d2, js_re, { recursive: true }))
|
|
72
|
+
.map(async file => `${file}`.replace(d2, _ => "")));
|
|
73
|
+
const components_js = `
|
|
74
|
+
const components = [];
|
|
75
|
+
${e.map((component, i) =>
|
|
76
|
+
`import * as component${i} from "../server/components${component}";
|
|
77
|
+
components.push(["${component.slice(1, -".js".length)}", component${i}]);`,
|
|
78
|
+
).join("\n")}
|
|
79
|
+
|
|
80
|
+
${app.roots.map((root, i) => `
|
|
81
|
+
import * as root${i} from "${root}";
|
|
82
|
+
components.push(["${root.name}", root${i}]);
|
|
83
|
+
`).join("\n")}
|
|
84
|
+
|
|
85
|
+
export default components;`;
|
|
86
|
+
await build_directory.join("components.js").write(components_js);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const write_bootstrap = async (build_number, app, mode) => {
|
|
90
|
+
const build_start_script = `
|
|
91
|
+
import file from "@rcompat/fs/file";
|
|
92
|
+
import serve from "@primate/core/serve";
|
|
93
|
+
import config from "./${config_filename}";
|
|
94
|
+
const files = {};
|
|
95
|
+
${app.server_build.map(name =>
|
|
96
|
+
`import ${name} from "./${build_number}/${name}.js";
|
|
97
|
+
files.${name} = ${name};`,
|
|
98
|
+
).join("\n")}
|
|
99
|
+
import components from "./${build_number}/components.js";
|
|
100
|
+
import * as target from "./target.js";
|
|
101
|
+
|
|
102
|
+
await serve(file(import.meta.url).directory, {
|
|
103
|
+
...target,
|
|
104
|
+
config,
|
|
105
|
+
files,
|
|
106
|
+
components,
|
|
107
|
+
mode: "${mode}",
|
|
108
|
+
});`;
|
|
109
|
+
await app.path.build.join("serve.js").write(build_start_script);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const post = async (app, mode, target) => {
|
|
113
|
+
const location = app.get("location");
|
|
114
|
+
const defaults = join(import.meta.dirname, "../defaults");
|
|
115
|
+
|
|
116
|
+
await Promise.all(["routes", "types", "components"].map(directory =>
|
|
117
|
+
app.stage(app.path[directory], location[directory])));
|
|
118
|
+
|
|
119
|
+
const directory = app.runpath(location.routes);
|
|
120
|
+
for (const path of await directory.collect()) {
|
|
121
|
+
await app.bindings[path.extension]
|
|
122
|
+
?.(directory, path.debase(`${directory}/`));
|
|
123
|
+
}
|
|
124
|
+
// copy framework pages
|
|
125
|
+
await app.stage(defaults, location.pages, html);
|
|
126
|
+
// overwrite transformed pages to build
|
|
127
|
+
await app.stage(app.path.pages, location.pages, html);
|
|
128
|
+
|
|
129
|
+
// copy static files to build/server/static
|
|
130
|
+
await app.stage(app.path.static, join(location.server, location.static));
|
|
131
|
+
|
|
132
|
+
// copy static files to build/client/static
|
|
133
|
+
await app.stage(app.path.static, join(location.client, location.static));
|
|
134
|
+
|
|
135
|
+
// publish JavaScript and CSS files
|
|
136
|
+
const imports = await collect(app.path.static, /\.(?:css)$/u);
|
|
137
|
+
await Promise.all(imports.map(async file => {
|
|
138
|
+
const src = file.debase(app.path.static);
|
|
139
|
+
app.build.export(`import "./${location.static}${src}";`);
|
|
140
|
+
}));
|
|
141
|
+
|
|
142
|
+
// copy additional subdirectories to build/server
|
|
143
|
+
await copy_includes(app, location.server);
|
|
144
|
+
|
|
145
|
+
const components = await app.runpath(location.components).collect();
|
|
146
|
+
|
|
147
|
+
// from the build directory, compile to server and client
|
|
148
|
+
await Promise.all(components.map(component => app.compile(component)));
|
|
149
|
+
|
|
150
|
+
// start the build
|
|
151
|
+
await app.build.start();
|
|
152
|
+
|
|
153
|
+
// a target needs to create an `assets.js` that exports assets
|
|
154
|
+
await app.targets[app.build_target].target(app);
|
|
155
|
+
|
|
156
|
+
const build_number = crypto.randomUUID().slice(0, 8);
|
|
157
|
+
const build_directory = app.path.build.join(build_number);
|
|
158
|
+
// TODO: remove after rcompat automatically creates directories
|
|
159
|
+
await build_directory.create();
|
|
160
|
+
|
|
161
|
+
await write_components(build_directory, app);
|
|
162
|
+
await write_directories(build_directory, app);
|
|
163
|
+
await write_bootstrap(build_number, app, mode);
|
|
164
|
+
|
|
165
|
+
// copy config file
|
|
166
|
+
await app.root.join(config_filename).copy(app.path.build.join(config_filename));
|
|
167
|
+
|
|
168
|
+
const manifest_data = await manifest();
|
|
169
|
+
// create package.json
|
|
170
|
+
const package_json = "package.json";
|
|
171
|
+
await app.path.build.join(package_json).write(stringify(manifest_data));
|
|
172
|
+
|
|
173
|
+
log.system(`build written to ${dim(app.path.build)}`);
|
|
174
|
+
|
|
175
|
+
app.postbuild.forEach(fn => fn());
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export default async (app, mode = "development", target = "web") =>
|
|
179
|
+
post(await (await cascade(app.modules.build))(await pre(app, mode, target)), mode, target);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import join from "@rcompat/fs/join";
|
|
2
|
+
|
|
3
|
+
export default async (app, type, post = () => undefined) => {
|
|
4
|
+
const includes = app.get("build.includes");
|
|
5
|
+
const reserved = Object.values(app.get("location"));
|
|
6
|
+
|
|
7
|
+
if (Array.isArray(includes)) {
|
|
8
|
+
await Promise.all(includes
|
|
9
|
+
.filter(include => !reserved.includes(include))
|
|
10
|
+
.filter(include => /^[^/]*$/u.test(include))
|
|
11
|
+
.map(async include => {
|
|
12
|
+
const path = app.root.join(include);
|
|
13
|
+
if (await path.exists()) {
|
|
14
|
+
const target = join(type, include);
|
|
15
|
+
await app.stage(path, target);
|
|
16
|
+
await post(target);
|
|
17
|
+
}
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import double_route from "#error/double-route";
|
|
2
|
+
import optional_route from "#error/optional-route";
|
|
3
|
+
import rest_route from "#error/rest-route";
|
|
4
|
+
import Router from "@rcompat/fs/router";
|
|
5
|
+
|
|
6
|
+
const error_entries = Object.entries({
|
|
7
|
+
DoubleRoute: double_route,
|
|
8
|
+
OptionalRoute: optional_route,
|
|
9
|
+
RestRoute: rest_route,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export default async directory => {
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
return await Router.load({
|
|
16
|
+
directory,
|
|
17
|
+
specials: {
|
|
18
|
+
guard: { recursive: true },
|
|
19
|
+
error: { recursive: false },
|
|
20
|
+
layout: { recursive: true },
|
|
21
|
+
},
|
|
22
|
+
predicate(route, request) {
|
|
23
|
+
return route.default[request.method.toLowerCase()] !== undefined;
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
} catch (error) {
|
|
27
|
+
error_entries.forEach(([key, value]) =>
|
|
28
|
+
error instanceof Router.Error[key] && value(error.route));
|
|
29
|
+
// rethrow original error
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import bye from "#bye";
|
|
2
|
+
import config from "#config";
|
|
3
|
+
import config_filename from "#config-filename";
|
|
4
|
+
import empty_config_file from "#error/empty-config-file";
|
|
5
|
+
import error_in_config_file from "#error/error-in-config-file";
|
|
6
|
+
import log from "@primate/core/log";
|
|
7
|
+
import tryreturn from "@rcompat/async/tryreturn";
|
|
8
|
+
import empty from "@rcompat/object/empty";
|
|
9
|
+
import override from "@rcompat/object/override";
|
|
10
|
+
import root from "@rcompat/package/root";
|
|
11
|
+
import platform from "@rcompat/platform";
|
|
12
|
+
import app from "./app.js";
|
|
13
|
+
import { build, init } from "./hook/exports.js";
|
|
14
|
+
|
|
15
|
+
const get_config = async project_root => {
|
|
16
|
+
const local_config = project_root.join(config_filename);
|
|
17
|
+
return await local_config.exists()
|
|
18
|
+
? tryreturn(async _ => {
|
|
19
|
+
const imported = await local_config.import("default");
|
|
20
|
+
|
|
21
|
+
empty(imported) && empty_config_file(local_config);
|
|
22
|
+
|
|
23
|
+
return imported;
|
|
24
|
+
}).orelse(({ message }) =>
|
|
25
|
+
error_in_config_file(message, `${platform} ${local_config}`))
|
|
26
|
+
: config;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default async (mode, target) => tryreturn(async _ => {
|
|
30
|
+
const package_root = await root();
|
|
31
|
+
const app_config = override(config, await get_config(package_root));
|
|
32
|
+
const $app = await app(package_root, app_config);
|
|
33
|
+
await build(await init($app), mode, target);
|
|
34
|
+
return true;
|
|
35
|
+
}).orelse(error => {
|
|
36
|
+
if (error.level === "error") {
|
|
37
|
+
bye();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
throw error;
|
|
41
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import config_filename from "#config-filename";
|
|
2
|
+
import double_module from "#error/double-module";
|
|
3
|
+
import module_no_name from "#error/module-no-name";
|
|
4
|
+
import modules_array from "#error/modules-array";
|
|
5
|
+
import * as hooks from "./hook/exports.js";
|
|
6
|
+
|
|
7
|
+
const doubled = set => set.find((part, i, array) =>
|
|
8
|
+
array.filter((_, j) => i !== j).includes(part));
|
|
9
|
+
const filter = (key, array) => array?.flatMap(m => m[key] ?? []) ?? [];
|
|
10
|
+
const load = (modules = []) => modules.map(module =>
|
|
11
|
+
[module, load(module.load?.() ?? [])]).flat();
|
|
12
|
+
|
|
13
|
+
export default async (root, modules) => {
|
|
14
|
+
!Array.isArray(modules) && modules_array("modules");
|
|
15
|
+
|
|
16
|
+
modules.some(({ name }, n) => name === undefined && module_no_name(n));
|
|
17
|
+
|
|
18
|
+
const names = modules.map(({ name }) => name);
|
|
19
|
+
new Set(names).size !== modules.length && double_module(
|
|
20
|
+
doubled(names), root.join(config_filename));
|
|
21
|
+
|
|
22
|
+
// collect modules
|
|
23
|
+
const loaded = load(modules).flat(2);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
names: loaded.map(module => module.name),
|
|
27
|
+
...Object.fromEntries([...Object.keys(hooks), "context"]
|
|
28
|
+
.map(hook => [hook, filter(hook, loaded)])),
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as web } from "./web.js";
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import collect from "@rcompat/fs/collect";
|
|
2
|
+
|
|
3
|
+
const html = /^.*.html$/u;
|
|
4
|
+
|
|
5
|
+
export default async app => {
|
|
6
|
+
const location = app.get("location");
|
|
7
|
+
const http = app.get("http");
|
|
8
|
+
const client = app.runpath(location.client);
|
|
9
|
+
const re = /app..*(?:js|css)$/u;
|
|
10
|
+
const $imports = (await client.collect(re, { recursive: false })).map((file, i) => {
|
|
11
|
+
const type = file.extension === ".css" ? "style" : "js";
|
|
12
|
+
const src = `${http.static.root}${file.debase(client).name}`;
|
|
13
|
+
const path = `./${file.debase(`${app.path.build}/`)}`;
|
|
14
|
+
return {
|
|
15
|
+
src,
|
|
16
|
+
path,
|
|
17
|
+
code: `await file(asset${i}).text()`,
|
|
18
|
+
type,
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
const d = app.runpath(location.pages);
|
|
22
|
+
const pages = await Promise.all((await collect(d, html, { recursive: true }))
|
|
23
|
+
.map(async file => `${file}`.replace(`${d}/`, _ => "")));
|
|
24
|
+
const pages_str = pages.map(page =>
|
|
25
|
+
`"${page}": await file("./${location.pages}/${page}").text(),`).join("\n");
|
|
26
|
+
|
|
27
|
+
const assets_scripts = `
|
|
28
|
+
import file from "@rcompat/fs/file";
|
|
29
|
+
import join from "@rcompat/fs/join";
|
|
30
|
+
import stringify from "@rcompat/object/stringify";
|
|
31
|
+
import crypto from "@rcompat/crypto";
|
|
32
|
+
|
|
33
|
+
const encoder = new TextEncoder();
|
|
34
|
+
const hash = async (data, algorithm = "sha-384") => {
|
|
35
|
+
const bytes = await crypto.subtle.digest(algorithm, encoder.encode(data));
|
|
36
|
+
const prefix = algorithm.replace("-", _ => "");
|
|
37
|
+
return \`\${prefix}-\${btoa(String.fromCharCode(...new Uint8Array(bytes)))}\`;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
${$imports.map(({ path }, i) =>
|
|
41
|
+
`const asset${i} = await join(import.meta.dirname, "${path}").text();`)
|
|
42
|
+
.join("\n ")}
|
|
43
|
+
const assets = [${$imports.map(($import, i) => `{
|
|
44
|
+
src: "${$import.src}",
|
|
45
|
+
code: asset${i},
|
|
46
|
+
type: "${$import.type}",
|
|
47
|
+
inline: false,
|
|
48
|
+
integrity: await hash(asset${i}),
|
|
49
|
+
}`).join(",\n ")}];
|
|
50
|
+
|
|
51
|
+
const imports = {
|
|
52
|
+
app: join("${http.static.root}", "${$imports.find($import =>
|
|
53
|
+
$import.src.includes("app") && $import.src.endsWith(".js")).src}").webpath(),
|
|
54
|
+
};
|
|
55
|
+
// importmap
|
|
56
|
+
assets.push({
|
|
57
|
+
inline: true,
|
|
58
|
+
code: stringify({ imports }),
|
|
59
|
+
type: "importmap",
|
|
60
|
+
integrity: await hash(stringify({ imports })),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const pages = {
|
|
64
|
+
${pages_str}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const loader = {
|
|
68
|
+
page(name) {
|
|
69
|
+
return pages[name] ?? pages["${app.get("pages.app")}"];
|
|
70
|
+
},
|
|
71
|
+
asset(pathname) {
|
|
72
|
+
return assets.find(asset => asset.src === pathname);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
const target = "web";
|
|
76
|
+
|
|
77
|
+
export { assets, loader, target };
|
|
78
|
+
`;
|
|
79
|
+
await app.path.build.join("target.js").write(assets_scripts);
|
|
80
|
+
|
|
81
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "#dispatch";
|
package/src/log/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "#log";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default "primate.config.js";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
base: "/",
|
|
3
|
+
modules: [],
|
|
4
|
+
pages: {
|
|
5
|
+
app: "app.html",
|
|
6
|
+
error: "error.html",
|
|
7
|
+
},
|
|
8
|
+
log: {
|
|
9
|
+
level: "warn",
|
|
10
|
+
trace: false,
|
|
11
|
+
},
|
|
12
|
+
http: {
|
|
13
|
+
host: "localhost",
|
|
14
|
+
port: 6161,
|
|
15
|
+
csp: {},
|
|
16
|
+
static: {
|
|
17
|
+
root: "/",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
request: {
|
|
21
|
+
body: {
|
|
22
|
+
parse: true,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
location: {
|
|
26
|
+
// renderable components
|
|
27
|
+
components: "components",
|
|
28
|
+
// HTML pages
|
|
29
|
+
pages: "pages",
|
|
30
|
+
// hierarchical routes
|
|
31
|
+
routes: "routes",
|
|
32
|
+
// static assets
|
|
33
|
+
static: "static",
|
|
34
|
+
// runtime types
|
|
35
|
+
types: "types",
|
|
36
|
+
// build environment
|
|
37
|
+
build: "build",
|
|
38
|
+
// client build
|
|
39
|
+
client: "client",
|
|
40
|
+
// server build
|
|
41
|
+
server: "server",
|
|
42
|
+
},
|
|
43
|
+
build: {
|
|
44
|
+
name: "app",
|
|
45
|
+
includes: [],
|
|
46
|
+
excludes: [],
|
|
47
|
+
},
|
|
48
|
+
};
|