Skip to content

Commit

Permalink
add 'config' hook, upgrade runtime-compat, show http vs https
Browse files Browse the repository at this point in the history
  • Loading branch information
terrablue committed Apr 11, 2023
1 parent 8f3b285 commit cc27422
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 114 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"lint": "npx eslint ."
},
"dependencies": {
"runtime-compat": "^0.15.0"
"runtime-compat": "^0.15.1"
},
"devDependencies": {
"maximin": "^0.1.2"
Expand Down
114 changes: 4 additions & 110 deletions src/config.js
Original file line number Diff line number Diff line change
@@ -1,111 +1,5 @@
import crypto from "runtime-compat/crypto";
import {is} from "runtime-compat/dyndef";
import {File, Path} from "runtime-compat/fs";
import cache from "./cache.js";
import extend from "./extend.js";
import defaults from "./primate.config.js";
import {colors, print, default as Logger} from "./Logger.js";
import * as handlers from "./handlers/exports.js";
const filter = (key, array) => array?.flatMap(m => m[key] ?? []) ?? [];

const qualify = (root, paths) =>
Object.keys(paths).reduce((sofar, key) => {
const value = paths[key];
sofar[key] = typeof value === "string"
? new Path(root, value)
: qualify(`${root}/${key}`, value);
return sofar;
}, {});

const getConfig = async (root, filename) => {
try {
return extend(defaults, (await import(root.join(filename))).default);
} catch (error) {
return defaults;
}
};

const getRoot = async () => {
try {
// use module root if possible
return await Path.root();
} catch (error) {
// fall back to current directory
return Path.resolve();
}
};

const index = async env => {
const name = "index.html";
try {
// user-provided file
return await File.read(`${env.paths.static.join(name)}`);
} catch (error) {
// fallback
return new Path(import.meta.url).directory.join(name).file.read();
}
};

const hash = async (string, algorithm = "sha-384") => {
const encoder = new TextEncoder();
const bytes = await crypto.subtle.digest(algorithm, encoder.encode(string));
const algo = algorithm.replace("-", () => "");
return `${algo}-${btoa(String.fromCharCode(...new Uint8Array(bytes)))}`;
};

export default async (filename = "primate.config.js") => {
is(filename).string();
const root = await getRoot();
const config = await getConfig(root, filename);

const {name, version} = JSON.parse(await new Path(import.meta.url)
.directory.directory.join("package.json").file.read());

const env = {
...config,
name, version,
resources: [],
entrypoints: [],
paths: qualify(root, config.paths),
root,
log: new Logger(config.logger),
register: (name, handler) => {
env.handlers[name] = handler;
},
handlers: {...handlers},
render: async ({body = "", head = ""} = {}) => {
const html = await index(env);
const heads = env.resources.map(({src, code, type, inline, integrity}) => {
const tag = type === "style" ? "link" : "script";
const pre = type === "style"
? `<${tag} rel="stylesheet" integrity="${integrity}"`
: `<${tag} type="${type}" integrity="${integrity}"`;
const middle = type === "style"
? ` href="${src}">`
: ` src="${src}">`;
const post = type === "style" ? "" : `</${tag}>`;
return inline ? `${pre}>${code}${post}` : `${pre}${middle}${post}`;
}).join("\n");
return html
.replace("%body%", () => body)
.replace("%head%", () => `${head}${heads}`);
},
publish: async ({src, code, type = "", inline = false}) => {
const integrity = await hash(code);
env.resources.push({src, code, type, inline, integrity});
return integrity;
},
bootstrap: ({type, code}) => {
env.entrypoints.push({type, code});
},
};
print(colors.blue(colors.bold(name)), colors.blue(version), "");
print(colors.gray(`at http://${config.http.host}:${config.http.port}`), "\n");
const {modules} = config;
// modules may load other modules
const loads = await Promise.all(modules
.filter(module => module.load !== undefined)
.map(module => module.load()));

return cache("config", filename, () => ({...env,
modules: modules.concat(loads.flat())}));
};
export default async env =>
[...filter("config", env.modules), _ => _].reduceRight((acc, handler) =>
input => handler(input, acc))(env);
120 changes: 120 additions & 0 deletions src/env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import crypto from "runtime-compat/crypto";
import {is} from "runtime-compat/dyndef";
import {File, Path} from "runtime-compat/fs";
import cache from "./cache.js";
import extend from "./extend.js";
import defaults from "./primate.config.js";
import {colors, print, default as Logger} from "./Logger.js";
import * as handlers from "./handlers/exports.js";

const qualify = (root, paths) =>
Object.keys(paths).reduce((sofar, key) => {
const value = paths[key];
sofar[key] = typeof value === "string"
? new Path(root, value)
: qualify(`${root}/${key}`, value);
return sofar;
}, {});

const getConfig = async (root, filename) => {
try {
return extend(defaults, (await import(root.join(filename))).default);
} catch (error) {
return defaults;
}
};

const getRoot = async () => {
try {
// use module root if possible
return await Path.root();
} catch (error) {
// fall back to current directory
return Path.resolve();
}
};

const index = async env => {
const name = "index.html";
try {
// user-provided file
return await File.read(`${env.paths.static.join(name)}`);
} catch (error) {
// fallback
return new Path(import.meta.url).directory.join(name).file.read();
}
};

const hash = async (string, algorithm = "sha-384") => {
const encoder = new TextEncoder();
const bytes = await crypto.subtle.digest(algorithm, encoder.encode(string));
const algo = algorithm.replace("-", () => "");
return `${algo}-${btoa(String.fromCharCode(...new Uint8Array(bytes)))}`;
};

export default async (filename = "primate.config.js") => {
is(filename).string();
const root = await getRoot();
const config = await getConfig(root, filename);

const {name, version} = JSON.parse(await new Path(import.meta.url)
.directory.directory.join("package.json").file.read());

// if ssl activated, resolve key and cert early
if (config.http.ssl) {
config.http.ssl.key = root.join(config.http.ssl.key);
config.http.ssl.cert = root.join(config.http.ssl.cert);
config.secure = true;
}

const env = {
...config,
name, version,
resources: [],
entrypoints: [],
paths: qualify(root, config.paths),
root,
log: new Logger(config.logger),
register: (name, handler) => {
env.handlers[name] = handler;
},
handlers: {...handlers},
render: async ({body = "", head = ""} = {}) => {
const html = await index(env);
const heads = env.resources.map(({src, code, type, inline, integrity}) => {
const tag = type === "style" ? "link" : "script";
const pre = type === "style"
? `<${tag} rel="stylesheet" integrity="${integrity}"`
: `<${tag} type="${type}" integrity="${integrity}"`;
const middle = type === "style"
? ` href="${src}">`
: ` src="${src}">`;
const post = type === "style" ? "" : `</${tag}>`;
return inline ? `${pre}>${code}${post}` : `${pre}${middle}${post}`;
}).join("\n");
return html
.replace("%body%", () => body)
.replace("%head%", () => `${head}${heads}`);
},
publish: async ({src, code, type = "", inline = false}) => {
const integrity = await hash(code);
env.resources.push({src, code, type, inline, integrity});
return integrity;
},
bootstrap: ({type, code}) => {
env.entrypoints.push({type, code});
},
};
print(colors.blue(colors.bold(name)), colors.blue(version), "");
const type = env.secure ? "https" : "http";
const address = `${type}://${config.http.host}:${config.http.port}`;
print(colors.gray(`at ${address}`), "\n");
const {modules} = config;
// modules may load other modules
const loads = await Promise.all(modules
.filter(module => module.load !== undefined)
.map(module => module.load()));

return cache("config", filename, () => ({...env,
modules: modules.concat(loads.flat())}));
};
4 changes: 2 additions & 2 deletions src/run.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import config from "./config.js";
import env from "./env.js";
import command from "./commands/exports.js";

export default async name => command(name)(await config());
export default async name => command(name)(await env());
1 change: 0 additions & 1 deletion src/serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import mimes from "./mimes.js";
import {http404} from "./handlers/http.js";
import {isResponse} from "./duck.js";
import respond from "./respond.js";
import {colors, print} from "./Logger.js";

const regex = /\.([a-z1-9]*)$/u;
const mime = filename => mimes[filename.match(regex)[1]] ?? mimes.binary;
Expand Down
3 changes: 3 additions & 0 deletions src/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import publish from "./publish.js";
import bundle from "./bundle.js";
import route from "./route.js";
import serve from "./serve.js";
import config from "./config.js";

export default async (env, operations = {}) => {
// read/write configuration
await config(env);
// register handlers
await register(env);
// compile server-side code
Expand Down

0 comments on commit cc27422

Please sign in to comment.