mirror of
https://github.com/sbrl/powahroot.git
synced 2024-11-21 22:22:59 +00:00
Import from Nibriboard-Panel
This commit is contained in:
commit
2fe1f30fad
9 changed files with 592 additions and 0 deletions
91
.gitignore
vendored
Normal file
91
.gitignore
vendored
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
|
||||||
|
# Created by https://www.gitignore.io/api/node
|
||||||
|
# Edit at https://www.gitignore.io/?templates=node
|
||||||
|
|
||||||
|
### Node ###
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/node
|
29
.tern-project
Normal file
29
.tern-project
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"ecmaVersion": 8,
|
||||||
|
"libs": [],
|
||||||
|
"loadEagerly": [
|
||||||
|
"**.mjs"
|
||||||
|
],
|
||||||
|
"dontLoad": [],
|
||||||
|
"plugins": {
|
||||||
|
"doc_comment": true,
|
||||||
|
"node": {
|
||||||
|
"dontLoad": "",
|
||||||
|
"load": "",
|
||||||
|
"modules": ""
|
||||||
|
},
|
||||||
|
"node_resolve": {},
|
||||||
|
"modules": {
|
||||||
|
"dontLoad": "",
|
||||||
|
"load": "",
|
||||||
|
"modules": ""
|
||||||
|
},
|
||||||
|
"es_modules": {},
|
||||||
|
"requirejs": {
|
||||||
|
"baseURL": "",
|
||||||
|
"paths": "",
|
||||||
|
"override": ""
|
||||||
|
},
|
||||||
|
"commonjs": {}
|
||||||
|
}
|
||||||
|
}
|
105
Client/Router.mjs
Normal file
105
Client/Router.mjs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
import EventEmitter from 'event-emitter-es6';
|
||||||
|
|
||||||
|
class ClientRouter extends EventEmitter {
|
||||||
|
constructor(options) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
/***** Settings *****/
|
||||||
|
this.listen_pushstate = true;
|
||||||
|
this.verbose = false;
|
||||||
|
|
||||||
|
/********************/
|
||||||
|
|
||||||
|
for(let key in options) {
|
||||||
|
this[key] = options[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
/********************/
|
||||||
|
|
||||||
|
/** Whether we should handle popstate events or not. @type {Boolean} */
|
||||||
|
this.handle_popstates = true;
|
||||||
|
this.routes = [];
|
||||||
|
|
||||||
|
window.addEventListener("popstate", ((event) => {
|
||||||
|
if(!this.handle_popstates) return;
|
||||||
|
|
||||||
|
console.info(`[ClientRouter] Handling popstate - path: ${window.location.hash.substr(1)} (state`, event.state, `)`);
|
||||||
|
this.navigate(window.location.hash.substr(1));
|
||||||
|
}).bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
add_404(callback) {
|
||||||
|
this.callback_404 = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_page(routespec, callback) {
|
||||||
|
this.routes.push({
|
||||||
|
spec: routespec,
|
||||||
|
match: this.pathspec_to_regex(routespec),
|
||||||
|
callback
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate(path) {
|
||||||
|
for(let route_info of this.routes) {
|
||||||
|
const matches = path.match(route_info.match.regex);
|
||||||
|
if(matches) {
|
||||||
|
if(this.verbose) console.log(`%c[Router] Matched against ${route_info.match.regex}!`, "color: hsl(331, 76%, 40%); font-weight: bold;");
|
||||||
|
|
||||||
|
// Build the parameters object
|
||||||
|
let params = {};
|
||||||
|
for(let i = 1; i < matches.length; i++) { // Skip the top-level group match
|
||||||
|
params[route_info.match.tokens[i-1]] = matches[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't handle any popstates potentially generated by changing the hash
|
||||||
|
this.handle_popstates = false;
|
||||||
|
window.location.hash = `#${path}`;
|
||||||
|
this.handle_popstates = true;
|
||||||
|
|
||||||
|
route_info.callback(params);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.verbose) console.debug(`%c[Router] No match against ${route_info.match.regex} - moving on.`, "color: hsl(331, 76%, 40%)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.verbose) console.warn(`Couldn't find a match for '${path}'.`);
|
||||||
|
if(typeof this.callback_404 == "function")
|
||||||
|
this.callback_404(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate_current_hash() {
|
||||||
|
this.navigate(window.location.hash.substr(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a path specification into a regular expression.
|
||||||
|
* From the server-side sibling of this (client-side) router.
|
||||||
|
* @param {string} pathspec The path specification to convert.
|
||||||
|
* @return {RegExp} The resulting regular expression
|
||||||
|
*/
|
||||||
|
pathspec_to_regex(pathspec) {
|
||||||
|
if(pathspec == "*") // Support wildcards
|
||||||
|
return { regex: /^/, tokens: [] };
|
||||||
|
|
||||||
|
let tokens = [];
|
||||||
|
let regex = new RegExp("^" + pathspec.replace(/::?([a-zA-Z0-9\-_]+)/g, (substr/*, index, template (not used)*/) => {
|
||||||
|
tokens.push(substr.replace(/:/g, ""));
|
||||||
|
|
||||||
|
// FUTURE: We could add optional param support here too
|
||||||
|
if(substr.startsWith("::"))
|
||||||
|
return `(.+)`;
|
||||||
|
else
|
||||||
|
return `([^\/]+)`;
|
||||||
|
}) + "$", "i");
|
||||||
|
|
||||||
|
if(this.verbose) console.info("[router/verbose] Created regex", regex);
|
||||||
|
|
||||||
|
return { regex, tokens };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ClientRouter;
|
169
Server/Router.mjs
Normal file
169
Server/Router.mjs
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
import RouterContext from './RouterContext.mjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A standalone HTTP router that's based on the principle of middleware.
|
||||||
|
* Based on rill (see the npm package bearing the name), but stripped down and
|
||||||
|
* simplified.
|
||||||
|
*/
|
||||||
|
class Router
|
||||||
|
{
|
||||||
|
constructor(verbose = false) {
|
||||||
|
/** The actions to run in turn. */
|
||||||
|
this.actions = [];
|
||||||
|
/** Whether to activate versbose mode. Useful for debugging the router. */
|
||||||
|
this.verbose = verbose;
|
||||||
|
|
||||||
|
this.default_action = async (ctx) => {
|
||||||
|
let message = `No route was found for '${ctx.request.url}'.`;
|
||||||
|
if(this.verbose) {
|
||||||
|
message += `\n\nRegistered Actions:\n`;
|
||||||
|
for(let action of this.actions) {
|
||||||
|
message += ` - ${action.toString()}\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.send.plain(404, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Shortcut function for attaching an action to any request method. Full function: on */
|
||||||
|
any(pathspec, action) { this.on("*", pathspec, action); }
|
||||||
|
/** Shortcut function for attaching an action to head requests. Full function: on */
|
||||||
|
head(pathspec, action) { this.on(["head"], pathspec, action); }
|
||||||
|
/** Shortcut function for attaching an action to get requests. Full function: on */
|
||||||
|
get(pathspec, action) { this.on(["get"], pathspec, action); }
|
||||||
|
/** Shortcut function for attaching an action to post requests. Full function: on */
|
||||||
|
post(pathspec, action) { this.on(["post"], pathspec, action); }
|
||||||
|
/** Shortcut function for attaching an action to put requests. Full function: on */
|
||||||
|
put(pathspec, action) { this.on(["put"], pathspec, action); }
|
||||||
|
/** Shortcut function for attaching an action to delete requests. Full function: on */
|
||||||
|
delete(pathspec, action) { this.on(["delete"], pathspec, action); }
|
||||||
|
/** Shortcut function for attaching an action to options requests. Full function: on */
|
||||||
|
options(pathspec, action) { this.on(["options"], pathspec, action); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the specified action for requests matching the given parameters.
|
||||||
|
* TODO: Consider merging with on_all and refactoring to take an object literal which we cna destructure instead
|
||||||
|
* @param {array} methods The HTTP methods to run the action for. Include '*' to specify all methods.
|
||||||
|
* @param {string|regex} pathspec The specification of the paths that this action should match against. May be a regular expression.
|
||||||
|
* @param {Function} action The action to execute. Will be passed the parameters `context` (Object) and `next` (Function).
|
||||||
|
*/
|
||||||
|
on(methods, pathspec, action) {
|
||||||
|
let regex_info = pathspec instanceof RegExp ? {regex: pathspec, tokens: [] } : this.pathspec_to_regex(pathspec);
|
||||||
|
|
||||||
|
// next must be a generator that returns each action in turn
|
||||||
|
this.actions.push(async (context, next) => {
|
||||||
|
const matches = context.url.pathname.match(regex_info.regex);
|
||||||
|
|
||||||
|
if(this.verbose) console.error(`[router/verbose] [${methods.join(", ")} -> ${pathspec} ] Matches: `, matches);
|
||||||
|
|
||||||
|
if((methods.indexOf(context.request.method.toLowerCase()) > -1 || methods.indexOf("*") > -1) && matches) {
|
||||||
|
if(this.verbose) console.error(`[router/verbose] Match found! Executing action.`);
|
||||||
|
for(let i = 1; i < matches.length; i++) { // Skip the top-level group
|
||||||
|
context.params[regex_info.tokens[i-1]] = matches[i];
|
||||||
|
}
|
||||||
|
await action(context, next);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(this.verbose) console.error(`[router/verbose] Nope, didn't match. Moving on`);
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
on_all(action) {
|
||||||
|
this.actions.push(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the specified action for requests if the provided testing function
|
||||||
|
* returns true.
|
||||||
|
* @param {Function} test The testing function. Will be passed the context as it's only parameter.
|
||||||
|
* @param {Function} action The action to run if the test returns true.
|
||||||
|
*/
|
||||||
|
onif(test, action) {
|
||||||
|
this.actions.push(async (context, next) => {
|
||||||
|
let test_result = test(context);
|
||||||
|
if(this.verbose) console.error("[router/verbose] Test action result: ", test_result);
|
||||||
|
if(test_result)
|
||||||
|
await action(context, next);
|
||||||
|
else
|
||||||
|
await next(context);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a path specification into a regular expression.
|
||||||
|
* @param {string} pathspec The path specification to convert.
|
||||||
|
* @return {RegExp} The resulting regular expression
|
||||||
|
*/
|
||||||
|
pathspec_to_regex(pathspec) {
|
||||||
|
if(pathspec == "*") // Support wildcards
|
||||||
|
return { regex: /^/, tokens: [] };
|
||||||
|
|
||||||
|
let tokens = [];
|
||||||
|
let regex = new RegExp("^" + pathspec.replace(/::?([a-zA-Z0-9\-_]+)/g, (substr/*, index, template (not actually used)*/) => {
|
||||||
|
tokens.push(substr.replace(/:/g, ""));
|
||||||
|
|
||||||
|
// FUTURE: We could add optional param support here too
|
||||||
|
if(substr.startsWith("::"))
|
||||||
|
return `(.+)`;
|
||||||
|
else
|
||||||
|
return `([^\/]+)`;
|
||||||
|
}) + "$", "i");
|
||||||
|
|
||||||
|
/*if(this.verbose)*/ console.error("[router/verbose] Created regex", regex);
|
||||||
|
|
||||||
|
return { regex, tokens };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the specified request.
|
||||||
|
* @param {http.ClientRequest} request The request to handle.
|
||||||
|
* @param {http.ServerResponse} response The response object to use to send the response.
|
||||||
|
* @return {[type]} [description]
|
||||||
|
*/
|
||||||
|
async handle(request, response) {
|
||||||
|
let context = new RouterContext(request, response),
|
||||||
|
iterator = this.iterate();
|
||||||
|
|
||||||
|
// Begin the middleware execution
|
||||||
|
this.gen_next(iterator, context)();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an anonymous function that, when called, will execute the next
|
||||||
|
* item of middleware.
|
||||||
|
* It achieves this via a combination of a generator, anonymous function
|
||||||
|
* scope abuse, being recursive, and magic.
|
||||||
|
* @param {Generator} iterator The generator that emits the middleware.
|
||||||
|
* @param {Object} context The context of the request.
|
||||||
|
* @return {Function} A magic next function.
|
||||||
|
*/
|
||||||
|
gen_next(iterator, context) {
|
||||||
|
let next_raw = iterator.next();
|
||||||
|
// Return the default action if we've reached the end of the line
|
||||||
|
if(next_raw.done)
|
||||||
|
return async () => {
|
||||||
|
this.default_action(context);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (async () => {
|
||||||
|
if(this.verbose) console.error(`[router/verbose] Executing ${next_raw.value}`);
|
||||||
|
await next_raw.value(context, this.gen_next(iterator, context));
|
||||||
|
}).bind(this); // Don't forget to bind each successive function to this context
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterates over all the generated middleware.
|
||||||
|
* @return {Generator} A generator that returns each successive piece of middleware in turn.
|
||||||
|
*/
|
||||||
|
*iterate() {
|
||||||
|
for(let action of this.actions) {
|
||||||
|
yield action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Router;
|
73
Server/RouterContext.mjs
Normal file
73
Server/RouterContext.mjs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// core
|
||||||
|
import url from 'url';
|
||||||
|
// npm
|
||||||
|
import cookie from 'cookie';
|
||||||
|
// files
|
||||||
|
import Sender from './Sender.mjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains context information about a single request / response pair.
|
||||||
|
*/
|
||||||
|
class RouterContext {
|
||||||
|
constructor(in_request, in_response) {
|
||||||
|
/**
|
||||||
|
* The Node.JS request object
|
||||||
|
* @type {http.ClientRequest}
|
||||||
|
*/
|
||||||
|
this.request = in_request;
|
||||||
|
/**
|
||||||
|
* The Node.JS response object
|
||||||
|
* @type {http.ServerResponse}
|
||||||
|
*/
|
||||||
|
this.response = in_response;
|
||||||
|
/**
|
||||||
|
* The parsed request URL
|
||||||
|
* @type {URL}
|
||||||
|
*/
|
||||||
|
this.url = url.parse(this.request.url, true);
|
||||||
|
/**
|
||||||
|
* The url parameters parsed out by the router
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
this.params = {};
|
||||||
|
/**
|
||||||
|
* An object containing some utitlity methods for quickly sending responses
|
||||||
|
* @type {Sender}
|
||||||
|
*/
|
||||||
|
this.send = new Sender(this.response);
|
||||||
|
|
||||||
|
// FUTURE: Refactor the default population of this object elsewhere
|
||||||
|
/**
|
||||||
|
* The environment object.
|
||||||
|
* State variables that need to be attached to a specific request can
|
||||||
|
* go in here.
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
this.env = {
|
||||||
|
/**
|
||||||
|
* Whether the user is logged in or not.
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
logged_in: false,
|
||||||
|
/**
|
||||||
|
* The user's name. Guaranteed to be specified - if only as "anonymous".
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
username: "anonymous",
|
||||||
|
/**
|
||||||
|
* The parsed cookie object
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
cookie: cookie.parse(this.request.headers["cookie"] || ""),
|
||||||
|
/**
|
||||||
|
* The parsed post data as an object, if applicable.
|
||||||
|
* @type {Object|null}
|
||||||
|
*/
|
||||||
|
post_data: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RouterContext;
|
56
Server/Sender.mjs
Normal file
56
Server/Sender.mjs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
import { NightInkFile } from 'nightink';
|
||||||
|
|
||||||
|
class Sender {
|
||||||
|
constructor(response) {
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a HTML response, rendering a NightInk template.
|
||||||
|
* Don't forget to await this!
|
||||||
|
* @param {number} status_code The status code to return.
|
||||||
|
* @param {string} template_filename The path to the filename containing the template to render.
|
||||||
|
* @param {Object} data The data to use whilst rendering the template.
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
async html(status_code, template_filename, data) {
|
||||||
|
let response_html = await NightInkFile(template_filename, data);
|
||||||
|
this.response.writeHead(status_code, {
|
||||||
|
"content-type": "text/html",
|
||||||
|
"content-length": Buffer.byteLength(response_html, "utf8"),
|
||||||
|
});
|
||||||
|
this.response.end(response_html);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a plain text response.
|
||||||
|
* @param {number} status_code The HTTP status code to return.
|
||||||
|
* @param {string} data The data to send.
|
||||||
|
*/
|
||||||
|
plain(status_code, data) {
|
||||||
|
this.response.writeHead(status_code, {
|
||||||
|
"content-type": "text/plain",
|
||||||
|
"content-length": Buffer.byteLength(data, "utf8"),
|
||||||
|
});
|
||||||
|
this.response.end(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a redirect.
|
||||||
|
* @param {number} status_code The HTTP status code to send.
|
||||||
|
* @param {string} new_path The (possibly relative) uri to redirect the client to.
|
||||||
|
* @param {string} message The informational plain-text message to return, just in case.
|
||||||
|
*/
|
||||||
|
redirect(status_code, new_path, message) {
|
||||||
|
this.response.writeHead(status_code, {
|
||||||
|
"location": new_path,
|
||||||
|
"content-type": "text/plain",
|
||||||
|
"content-length": message.length
|
||||||
|
});
|
||||||
|
this.response.end(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Sender;
|
7
index.mjs
Normal file
7
index.mjs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
import Router as ServerRouter from './Server/Router.mjs';
|
||||||
|
|
||||||
|
export {
|
||||||
|
ServerRouter
|
||||||
|
};
|
33
package-lock.json
generated
Normal file
33
package-lock.json
generated
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "powerroot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"await-fs": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/await-fs/-/await-fs-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-QAnTAIYz/WYlqgCfCm8aujY38wE="
|
||||||
|
},
|
||||||
|
"event-emitter-es6": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/event-emitter-es6/-/event-emitter-es6-1.1.5.tgz",
|
||||||
|
"integrity": "sha1-75UxGy4Xqjm+djsDHOSvfunLeEk=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"html-entities": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz",
|
||||||
|
"integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8="
|
||||||
|
},
|
||||||
|
"nightink": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/nightink/-/nightink-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-nAyyf1EvghaFtmwD4ox2rFiY0eWqAsW6rulIPaPC5IMv3YaNKYdQ4F7zUZ6E7eONEVQGtiCGTv6YksY9wHHs1g==",
|
||||||
|
"requires": {
|
||||||
|
"await-fs": "^1.0.0",
|
||||||
|
"html-entities": "^1.2.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
package.json
Normal file
29
package.json
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"name": "powerroot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Client and server-side routing micro frameworks",
|
||||||
|
"main": "index.mjs",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/sbrl/powerroot.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"routing",
|
||||||
|
"micro-framework"
|
||||||
|
],
|
||||||
|
"author": "Starbeamrainbowlabs",
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/sbrl/powerroot/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/sbrl/powerroot#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"nightink": "^0.1.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"event-emitter-es6": "^1.1.5"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue