xpine 0.0.58 → 0.0.60
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/.claude/settings.local.json +2 -1
- package/.claude/worktrees/happy-lalande-c6c39c/.claude/settings.local.json +33 -0
- package/.claude/worktrees/happy-lalande-c6c39c/.d.ts +1 -0
- package/.claude/worktrees/happy-lalande-c6c39c/.gitattributes +2 -0
- package/.claude/worktrees/happy-lalande-c6c39c/README.md +571 -0
- package/.claude/worktrees/happy-lalande-c6c39c/TODO +2 -0
- package/.claude/worktrees/happy-lalande-c6c39c/eslint.config.mjs +27 -0
- package/.claude/worktrees/happy-lalande-c6c39c/jsx-runtime.d.ts +1 -0
- package/.claude/worktrees/happy-lalande-c6c39c/package-lock.json +5862 -0
- package/.claude/worktrees/happy-lalande-c6c39c/package.json +62 -0
- package/.claude/worktrees/happy-lalande-c6c39c/tsconfig.json +43 -0
- package/.claude/worktrees/happy-lalande-c6c39c/types.ts +55 -0
- package/.claude/worktrees/happy-lalande-c6c39c/xpine.config.mjs +1 -0
- package/.claude/worktrees/jolly-hofstadter-2764be/.claude/settings.local.json +33 -0
- package/.claude/worktrees/jolly-hofstadter-2764be/.d.ts +1 -0
- package/.claude/worktrees/jolly-hofstadter-2764be/.gitattributes +2 -0
- package/.claude/worktrees/jolly-hofstadter-2764be/README.md +571 -0
- package/.claude/worktrees/jolly-hofstadter-2764be/TODO +2 -0
- package/.claude/worktrees/jolly-hofstadter-2764be/eslint.config.mjs +27 -0
- package/.claude/worktrees/jolly-hofstadter-2764be/jsx-runtime.d.ts +1 -0
- package/.claude/worktrees/jolly-hofstadter-2764be/package-lock.json +5862 -0
- package/.claude/worktrees/jolly-hofstadter-2764be/package.json +62 -0
- package/.claude/worktrees/jolly-hofstadter-2764be/tsconfig.json +43 -0
- package/.claude/worktrees/jolly-hofstadter-2764be/types.ts +55 -0
- package/.claude/worktrees/jolly-hofstadter-2764be/xpine.config.mjs +1 -0
- package/README.md +104 -0
- package/create-xpine-app/README.md +20 -0
- package/create-xpine-app/index.js +80 -0
- package/create-xpine-app/package.json +30 -0
- package/create-xpine-app/template/README.md +50 -0
- package/create-xpine-app/template/eslint.config.mjs +27 -0
- package/create-xpine-app/template/gitignore +5 -0
- package/create-xpine-app/template/package.json +32 -0
- package/create-xpine-app/template/tsconfig.json +36 -0
- package/create-xpine-app/template/xpine.config.mjs +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +269 -106
- package/dist/src/auth.d.ts.map +1 -1
- package/dist/src/express.d.ts +5 -0
- package/dist/src/express.d.ts.map +1 -1
- package/dist/src/scripts/build.d.ts.map +1 -1
- package/dist/src/scripts/xpine-build.js +6 -2
- package/dist/src/scripts/xpine-dev.js +15 -21
- package/dist/src/util/html.d.ts +9 -1
- package/dist/src/util/html.d.ts.map +1 -1
- package/dist/src/util/regex.d.ts +2 -0
- package/dist/src/util/regex.d.ts.map +1 -1
- package/dist/types.d.ts +4 -1
- package/dist/types.d.ts.map +1 -1
- package/eslint.config.mjs +1 -1
- package/package.json +1 -1
- package/types.ts +5 -1
package/dist/index.js
CHANGED
|
@@ -326,6 +326,9 @@ var regex_default = {
|
|
|
326
326
|
configFile: /\+config\.[tj]sx?/g,
|
|
327
327
|
dynamicRoutes: /(\[)(.*?)(\])/g,
|
|
328
328
|
isDynamicRoute: /\[(.*)\]/g,
|
|
329
|
+
// Multi-segment (slash-containing) dynamic route, e.g. [...slug]
|
|
330
|
+
spreadRoute: /\[\.\.\.(.*?)\]/g,
|
|
331
|
+
isSpreadRoute: /\[\.\.\..*?\]/,
|
|
329
332
|
endsWithTSX: /\.tsx$/,
|
|
330
333
|
endsWithJSX: /\.jsx$/,
|
|
331
334
|
endsWithJs: /\.js$/,
|
|
@@ -467,6 +470,12 @@ import { globSync } from "glob";
|
|
|
467
470
|
// src/auth.ts
|
|
468
471
|
import jsonwebtoken from "jsonwebtoken";
|
|
469
472
|
var { verify, sign } = jsonwebtoken;
|
|
473
|
+
var JWT_ALGORITHM = "HS256";
|
|
474
|
+
function getSecret() {
|
|
475
|
+
const secret = process.env.JWT_PRIVATE_KEY;
|
|
476
|
+
if (!secret) throw new Error("JWT_PRIVATE_KEY is not set");
|
|
477
|
+
return secret;
|
|
478
|
+
}
|
|
470
479
|
async function signUser(email, username) {
|
|
471
480
|
return new Promise((resolve, reject) => {
|
|
472
481
|
sign(
|
|
@@ -476,11 +485,10 @@ async function signUser(email, username) {
|
|
|
476
485
|
username
|
|
477
486
|
}
|
|
478
487
|
},
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
{ expiresIn: "30d" },
|
|
488
|
+
getSecret(),
|
|
489
|
+
{ expiresIn: "30d", algorithm: JWT_ALGORITHM },
|
|
482
490
|
(err, token) => {
|
|
483
|
-
if (err) reject(err);
|
|
491
|
+
if (err) return reject(err);
|
|
484
492
|
resolve(token);
|
|
485
493
|
}
|
|
486
494
|
);
|
|
@@ -488,8 +496,8 @@ async function signUser(email, username) {
|
|
|
488
496
|
}
|
|
489
497
|
async function verifyUser(token) {
|
|
490
498
|
return new Promise((resolve, reject) => {
|
|
491
|
-
verify(token,
|
|
492
|
-
if (err) reject(err);
|
|
499
|
+
verify(token, getSecret(), { algorithms: [JWT_ALGORITHM] }, (err, authorizedData) => {
|
|
500
|
+
if (err) return reject(err);
|
|
493
501
|
resolve(authorizedData);
|
|
494
502
|
});
|
|
495
503
|
});
|
|
@@ -506,8 +514,163 @@ import requestIP from "request-ip";
|
|
|
506
514
|
import fs5 from "fs-extra";
|
|
507
515
|
import path5 from "path";
|
|
508
516
|
import EventEmitter from "events";
|
|
517
|
+
|
|
518
|
+
// src/csrf.ts
|
|
519
|
+
import crypto from "crypto";
|
|
520
|
+
var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
521
|
+
function resolveSecret(options) {
|
|
522
|
+
const secret = options?.secret || process.env.CSRF_SECRET || process.env.JWT_PRIVATE_KEY;
|
|
523
|
+
if (!secret) throw new Error("CSRF secret is not set (set CSRF_SECRET or JWT_PRIVATE_KEY)");
|
|
524
|
+
return secret;
|
|
525
|
+
}
|
|
526
|
+
function hmac(value, secret) {
|
|
527
|
+
return crypto.createHmac("sha256", secret).update(value).digest("base64url");
|
|
528
|
+
}
|
|
529
|
+
function issueToken(secret) {
|
|
530
|
+
const random = crypto.randomBytes(32).toString("base64url");
|
|
531
|
+
return `${random}.${hmac(random, secret)}`;
|
|
532
|
+
}
|
|
533
|
+
function isValidToken(token, secret) {
|
|
534
|
+
if (typeof token !== "string" || !token) return false;
|
|
535
|
+
const idx = token.lastIndexOf(".");
|
|
536
|
+
if (idx <= 0 || idx === token.length - 1) return false;
|
|
537
|
+
const random = token.slice(0, idx);
|
|
538
|
+
const signature = token.slice(idx + 1);
|
|
539
|
+
return timingSafeEqual(signature, hmac(random, secret));
|
|
540
|
+
}
|
|
541
|
+
function timingSafeEqual(a, b) {
|
|
542
|
+
const ab = Buffer.from(String(a));
|
|
543
|
+
const bb = Buffer.from(String(b));
|
|
544
|
+
if (ab.length !== bb.length) return false;
|
|
545
|
+
return crypto.timingSafeEqual(ab, bb);
|
|
546
|
+
}
|
|
547
|
+
function readSubmittedToken(req, headerName, fieldName) {
|
|
548
|
+
const header = req.headers[headerName];
|
|
549
|
+
const fromHeader = Array.isArray(header) ? header[0] : header;
|
|
550
|
+
if (fromHeader) return fromHeader;
|
|
551
|
+
if (req.body && typeof req.body[fieldName] === "string") return req.body[fieldName];
|
|
552
|
+
return "";
|
|
553
|
+
}
|
|
554
|
+
function createCsrfMiddleware(options) {
|
|
555
|
+
const secret = resolveSecret(options);
|
|
556
|
+
const cookieName = options?.cookieName || "csrfToken";
|
|
557
|
+
const headerName = (options?.headerName || "x-csrf-token").toLowerCase();
|
|
558
|
+
const fieldName = options?.fieldName || "_csrf";
|
|
559
|
+
const ignorePaths = options?.ignorePaths || [];
|
|
560
|
+
const cookieOptions = {
|
|
561
|
+
httpOnly: false,
|
|
562
|
+
// must be readable by client JS for the double-submit
|
|
563
|
+
sameSite: options?.cookie?.sameSite ?? "lax",
|
|
564
|
+
secure: options?.cookie?.secure ?? process.env.NODE_ENV !== "development",
|
|
565
|
+
path: options?.cookie?.path ?? "/",
|
|
566
|
+
maxAge: options?.cookie?.maxAge
|
|
567
|
+
};
|
|
568
|
+
return function csrfMiddleware(req, res, next) {
|
|
569
|
+
const existing = req.cookies?.[cookieName] || "";
|
|
570
|
+
let token = existing;
|
|
571
|
+
if (!isValidToken(existing, secret)) {
|
|
572
|
+
token = issueToken(secret);
|
|
573
|
+
res.cookie(cookieName, token, cookieOptions);
|
|
574
|
+
}
|
|
575
|
+
res.locals.csrfToken = token;
|
|
576
|
+
if (SAFE_METHODS.has(req.method)) return next();
|
|
577
|
+
if (ignorePaths.some((prefix) => req.path.startsWith(prefix))) return next();
|
|
578
|
+
const submitted = readSubmittedToken(req, headerName, fieldName);
|
|
579
|
+
if (isValidToken(existing, secret) && timingSafeEqual(submitted, existing)) {
|
|
580
|
+
return next();
|
|
581
|
+
}
|
|
582
|
+
res.status(403).json({ message: "Invalid CSRF token" });
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// src/express.ts
|
|
509
587
|
var methods = ["get", "post", "put", "patch", "delete"];
|
|
510
588
|
var isDev = process.env.NODE_ENV === "development";
|
|
589
|
+
function parseRoutePath(route) {
|
|
590
|
+
const slugRoute = route.replace(/[ ]/g, "");
|
|
591
|
+
const foundMethod = methods.find((method) => slugRoute.toUpperCase().endsWith(`.${method.toUpperCase()}`));
|
|
592
|
+
const templateRoute = foundMethod ? slugRoute.slice(0, -(foundMethod.length + 1)) : slugRoute;
|
|
593
|
+
let formattedRouteItem = templateRoute.replace(regex_default.spreadRoute, (_match, name) => ":" + name);
|
|
594
|
+
if (slugRoute.match(regex_default.isDynamicRoute)) {
|
|
595
|
+
for (const match of formattedRouteItem.matchAll(regex_default.dynamicRoutes)) {
|
|
596
|
+
formattedRouteItem = formattedRouteItem.replace(match[0], ":" + match[2]);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (formattedRouteItem.match(regex_default.catchAllRoute)) {
|
|
600
|
+
formattedRouteItem = formattedRouteItem.replace(regex_default.catchAllRoute, "/*");
|
|
601
|
+
}
|
|
602
|
+
return {
|
|
603
|
+
foundMethod,
|
|
604
|
+
formattedRouteItem,
|
|
605
|
+
templateRoute,
|
|
606
|
+
isSpread: regex_default.isSpreadRoute.test(templateRoute)
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
function spreadConcretePath(templateRoute, entry) {
|
|
610
|
+
return templateRoute.replace(regex_default.spreadRoute, (_match, name) => entry[name] ?? "").replace(regex_default.dynamicRoutes, (_match, _open, name) => entry[name] ?? "").replace(/\/{2,}/g, "/");
|
|
611
|
+
}
|
|
612
|
+
function spreadWildcardPath(templateRoute) {
|
|
613
|
+
return templateRoute.replace(regex_default.spreadRoute, "*").replace(regex_default.dynamicRoutes, (_match, _open, name) => ":" + name);
|
|
614
|
+
}
|
|
615
|
+
function spreadSlugName(templateRoute) {
|
|
616
|
+
const match = templateRoute.match(/\[\.\.\.(.*?)\]/);
|
|
617
|
+
return match ? match[1] : null;
|
|
618
|
+
}
|
|
619
|
+
async function resolveConfig(configFilePaths, componentImport) {
|
|
620
|
+
const baseConfig = configFilePaths ? await getCompleteConfig(configFilePaths, Date.now()) : {};
|
|
621
|
+
return componentImport?.config ? { ...baseConfig, ...componentImport.config } : baseConfig;
|
|
622
|
+
}
|
|
623
|
+
async function renderJSX(componentFn, config2, req, res, urlPath) {
|
|
624
|
+
const data = config2?.data ? await config2.data(req) : null;
|
|
625
|
+
const children = await componentFn({ req, res, data, config: config2, routePath: urlPath });
|
|
626
|
+
const output = config2?.wrapper ? await config2.wrapper({ req, children, config: config2, data, routePath: urlPath }) : children;
|
|
627
|
+
return doctypeHTML + output;
|
|
628
|
+
}
|
|
629
|
+
async function resolveSpreadRoute(templateRoute, config2, originalRoute) {
|
|
630
|
+
let entries = [];
|
|
631
|
+
if (typeof config2?.staticPaths === "function") {
|
|
632
|
+
try {
|
|
633
|
+
entries = await config2.staticPaths() || [];
|
|
634
|
+
} catch (err) {
|
|
635
|
+
console.error(err);
|
|
636
|
+
console.error("Could not resolve staticPaths for spread route", originalRoute);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return {
|
|
640
|
+
templateRoute,
|
|
641
|
+
entries,
|
|
642
|
+
validator: config2?.isValid || null
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function registerSpreadRoutes(register, spread) {
|
|
646
|
+
const slugName = spreadSlugName(spread.templateRoute);
|
|
647
|
+
for (const entry of spread.entries) {
|
|
648
|
+
register(spreadConcretePath(spread.templateRoute, entry), [
|
|
649
|
+
(req, _res, next) => {
|
|
650
|
+
Object.assign(req.params, entry);
|
|
651
|
+
next();
|
|
652
|
+
}
|
|
653
|
+
]);
|
|
654
|
+
}
|
|
655
|
+
const { validator } = spread;
|
|
656
|
+
if (validator) {
|
|
657
|
+
register(spreadWildcardPath(spread.templateRoute), [
|
|
658
|
+
async (req, _res, next) => {
|
|
659
|
+
const slug = req.params[0];
|
|
660
|
+
if (slugName) req.params[slugName] = slug;
|
|
661
|
+
try {
|
|
662
|
+
if (await validator(slug, req)) {
|
|
663
|
+
next();
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
} catch (err) {
|
|
667
|
+
console.error(err);
|
|
668
|
+
}
|
|
669
|
+
next("route");
|
|
670
|
+
}
|
|
671
|
+
]);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
511
674
|
var OnInitEmitter = class extends EventEmitter {
|
|
512
675
|
};
|
|
513
676
|
var onInitEmitter = new OnInitEmitter();
|
|
@@ -532,33 +695,12 @@ function getAllBuiltRoutes() {
|
|
|
532
695
|
}
|
|
533
696
|
async function createRouteFunction(route, configFiles) {
|
|
534
697
|
const isJSX = route.originalRoute.endsWith(".tsx") || route.originalRoute.endsWith(".jsx");
|
|
535
|
-
const
|
|
536
|
-
const foundMethod = methods.find((method) => slugRoute.toUpperCase().endsWith(`.${method.toUpperCase()}`));
|
|
537
|
-
const isDynamicRoute = slugRoute.match(regex_default.isDynamicRoute);
|
|
538
|
-
let formattedRouteItem = slugRoute;
|
|
539
|
-
if (foundMethod) formattedRouteItem = formattedRouteItem.split(".").shift();
|
|
540
|
-
if (isDynamicRoute) {
|
|
541
|
-
const result = [...formattedRouteItem.matchAll(regex_default.dynamicRoutes)];
|
|
542
|
-
for (const match of result) {
|
|
543
|
-
formattedRouteItem = formattedRouteItem.replace(match[0], ":" + match[2]);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
const hasCatchAll = formattedRouteItem.match(regex_default.catchAllRoute);
|
|
547
|
-
if (hasCatchAll) formattedRouteItem = formattedRouteItem.replace(regex_default.catchAllRoute, "/*");
|
|
698
|
+
const { foundMethod, formattedRouteItem, templateRoute, isSpread } = parseRoutePath(route.route);
|
|
548
699
|
const componentImport = await import(route.path);
|
|
549
700
|
const componentFn = isDev ? null : componentImport?.default;
|
|
550
|
-
if (componentImport?.config?.onInit)
|
|
551
|
-
await componentImport.config?.onInit();
|
|
552
|
-
}
|
|
553
|
-
let config2 = {};
|
|
701
|
+
if (componentImport?.config?.onInit) await componentImport.config.onInit();
|
|
554
702
|
const configFilePaths = getConfigFiles(route.originalRoute, configFiles);
|
|
555
|
-
config2 =
|
|
556
|
-
if (componentImport?.config) {
|
|
557
|
-
config2 = {
|
|
558
|
-
...config2,
|
|
559
|
-
...componentImport.config
|
|
560
|
-
};
|
|
561
|
-
}
|
|
703
|
+
const config2 = await resolveConfig(configFilePaths, componentImport);
|
|
562
704
|
async function routeFn(req, res) {
|
|
563
705
|
const urlPath = req?.route?.path || "/";
|
|
564
706
|
try {
|
|
@@ -568,47 +710,37 @@ async function createRouteFunction(route, configFiles) {
|
|
|
568
710
|
return;
|
|
569
711
|
}
|
|
570
712
|
if (componentFn && !isDev) {
|
|
571
|
-
if (isJSX)
|
|
572
|
-
|
|
573
|
-
const originalResult = await componentFn({ req, res, data, routePath: urlPath });
|
|
574
|
-
const output = config2?.wrapper ? await config2.wrapper({ req, children: originalResult, config: config2, data, routePath: urlPath }) : originalResult;
|
|
575
|
-
res.send(doctypeHTML + output);
|
|
576
|
-
} else {
|
|
577
|
-
await componentFn(req, res);
|
|
578
|
-
}
|
|
713
|
+
if (isJSX) res.send(await renderJSX(componentFn, config2, req, res, urlPath));
|
|
714
|
+
else await componentFn(req, res);
|
|
579
715
|
return;
|
|
580
716
|
}
|
|
581
717
|
await triggerXPineOnLoad(true);
|
|
582
|
-
const
|
|
583
|
-
const componentFnDev = componentImportDev.default;
|
|
718
|
+
const devImport = await import(route.path + `?cache=${Date.now()}`);
|
|
584
719
|
onInitEmitter.emit("triggerOnInit", getAllBuiltRoutes());
|
|
585
720
|
if (isJSX) {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
config3 = {
|
|
589
|
-
...config3,
|
|
590
|
-
...componentImportDev.config
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
const data = config3?.data ? await config3.data(req) : null;
|
|
594
|
-
const originalResult = await componentFnDev({ req, res, data, config: config3, routePath: urlPath });
|
|
595
|
-
const output = config3?.wrapper ? await config3.wrapper({ req, children: originalResult, config: config3, data, routePath: urlPath }) : originalResult;
|
|
721
|
+
const devConfig = await resolveConfig(configFilePaths, devImport);
|
|
722
|
+
const html2 = await renderJSX(devImport.default, devConfig, req, res, urlPath);
|
|
596
723
|
context.clear();
|
|
597
|
-
res.send(
|
|
724
|
+
res.send(html2);
|
|
598
725
|
} else {
|
|
599
|
-
await
|
|
726
|
+
await devImport.default(req, res);
|
|
600
727
|
}
|
|
601
728
|
} catch (err) {
|
|
602
729
|
console.error(err);
|
|
603
|
-
|
|
730
|
+
const status = err?.status || 500;
|
|
731
|
+
const message = isDev || err?.status ? err?.message || "Error" : "Internal Server Error";
|
|
732
|
+
res.status(status).send(message);
|
|
604
733
|
}
|
|
605
734
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
735
|
+
const spread = isSpread ? await resolveSpreadRoute(templateRoute, config2, route.originalRoute) : null;
|
|
736
|
+
return { formattedRouteItem, foundMethod, route, config: config2, routeFn, spread };
|
|
737
|
+
}
|
|
738
|
+
function buildRouteRegister(router, method, config2, routeFn) {
|
|
739
|
+
return (routePath, preHandlers = []) => {
|
|
740
|
+
const handlers = [...preHandlers];
|
|
741
|
+
if (config2?.routeMiddleware) handlers.push(config2.routeMiddleware);
|
|
742
|
+
handlers.push(routeFn);
|
|
743
|
+
router[method](routePath, ...handlers);
|
|
612
744
|
};
|
|
613
745
|
}
|
|
614
746
|
async function createRouter() {
|
|
@@ -619,46 +751,50 @@ async function createRouter() {
|
|
|
619
751
|
await triggerXPineOnLoad();
|
|
620
752
|
for (const route of routeMap) {
|
|
621
753
|
try {
|
|
622
|
-
const { formattedRouteItem, foundMethod, config:
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
}
|
|
628
|
-
routeResults.push({
|
|
629
|
-
formattedRouteItem,
|
|
630
|
-
foundMethod,
|
|
631
|
-
route,
|
|
632
|
-
routeFn
|
|
633
|
-
});
|
|
754
|
+
const { formattedRouteItem, foundMethod, config: routeConfig, routeFn, spread } = await createRouteFunction(route, configFiles);
|
|
755
|
+
const register = buildRouteRegister(router, foundMethod || "get", routeConfig, routeFn);
|
|
756
|
+
if (spread) registerSpreadRoutes(register, spread);
|
|
757
|
+
else register(formattedRouteItem);
|
|
758
|
+
routeResults.push({ formattedRouteItem, foundMethod, route, routeFn });
|
|
634
759
|
} catch (err) {
|
|
635
760
|
console.error(err);
|
|
636
761
|
}
|
|
637
762
|
}
|
|
638
|
-
return {
|
|
639
|
-
router,
|
|
640
|
-
routeResults
|
|
641
|
-
};
|
|
763
|
+
return { router, routeResults };
|
|
642
764
|
}
|
|
643
765
|
async function verifyUserMiddleware(req, _res, next) {
|
|
644
|
-
const
|
|
766
|
+
const usertoken = req.cookies?.usertoken;
|
|
645
767
|
if (!usertoken) {
|
|
646
768
|
req.user = null;
|
|
769
|
+
next();
|
|
770
|
+
return;
|
|
647
771
|
}
|
|
648
772
|
try {
|
|
649
773
|
const { user } = await verifyUser(usertoken);
|
|
650
774
|
req.user = user;
|
|
651
|
-
} catch
|
|
775
|
+
} catch {
|
|
652
776
|
req.user = null;
|
|
653
777
|
}
|
|
654
778
|
next();
|
|
655
779
|
}
|
|
656
780
|
async function verifyAuthenticatedBundleMiddleware(req, res, next) {
|
|
657
|
-
|
|
781
|
+
let decodedPath;
|
|
782
|
+
try {
|
|
783
|
+
decodedPath = decodeURIComponent(req?.path || "");
|
|
784
|
+
} catch {
|
|
785
|
+
next();
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
if (!decodedPath.startsWith("/scripts/")) {
|
|
789
|
+
next();
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
const fileName = decodedPath.split("/").pop() || "";
|
|
793
|
+
if (!fileName.endsWith(".js")) {
|
|
658
794
|
next();
|
|
659
795
|
return;
|
|
660
796
|
}
|
|
661
|
-
const bundle =
|
|
797
|
+
const bundle = fileName.slice(0, -".js".length);
|
|
662
798
|
const foundBundle = config?.bundles?.find((bundleItem) => bundleItem?.id === bundle);
|
|
663
799
|
if (!foundBundle) {
|
|
664
800
|
next();
|
|
@@ -677,6 +813,9 @@ async function createXPineRouter(app, beforeErrorRoute) {
|
|
|
677
813
|
app.use(verifyAuthenticatedBundleMiddleware);
|
|
678
814
|
app.use(express.static(config.distPublicDir));
|
|
679
815
|
app.use(requestIP.mw());
|
|
816
|
+
if (config?.csrf) {
|
|
817
|
+
app.use(createCsrfMiddleware(typeof config.csrf === "object" ? config.csrf : void 0));
|
|
818
|
+
}
|
|
680
819
|
const { router, routeResults } = await createRouter();
|
|
681
820
|
app.use(function replaceableRouter(req, res, next) {
|
|
682
821
|
router(req, res, next);
|
|
@@ -696,11 +835,13 @@ function routeHasStaticPath(route, params) {
|
|
|
696
835
|
const paramEntries = Object.entries(params);
|
|
697
836
|
let routeToStaticPath = route;
|
|
698
837
|
for (const [key, value] of paramEntries) {
|
|
699
|
-
routeToStaticPath = routeToStaticPath.replace(`:${key}`, value);
|
|
700
|
-
if (key === "0") routeToStaticPath = routeToStaticPath.replace(/\/\*/g, `/${value}`);
|
|
838
|
+
routeToStaticPath = routeToStaticPath.replace(`:${key}`, () => value);
|
|
839
|
+
if (key === "0") routeToStaticPath = routeToStaticPath.replace(/\/\*/g, () => `/${value}`);
|
|
701
840
|
}
|
|
702
841
|
routeToStaticPath += "/index.html";
|
|
703
|
-
const
|
|
842
|
+
const pagesDir = path5.resolve(config.distPagesDir);
|
|
843
|
+
const outputPath = path5.resolve(pagesDir, "." + path5.posix.normalize("/" + routeToStaticPath));
|
|
844
|
+
if (outputPath !== pagesDir && !outputPath.startsWith(pagesDir + path5.sep)) return false;
|
|
704
845
|
if (fs5.existsSync(outputPath)) return outputPath;
|
|
705
846
|
return false;
|
|
706
847
|
}
|
|
@@ -1118,7 +1259,7 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
|
|
|
1118
1259
|
function determineOutputPathForStaticPath(builtComponentPath, componentDynamicPaths) {
|
|
1119
1260
|
if (componentDynamicPaths?.length) {
|
|
1120
1261
|
return componentDynamicPaths.reduce((total, current) => {
|
|
1121
|
-
return total.replace(`/[${current}]`, "");
|
|
1262
|
+
return total.replace(`/[...${current}]`, "").replace(`/[${current}]`, "");
|
|
1122
1263
|
}, path6.dirname(builtComponentPath));
|
|
1123
1264
|
} else {
|
|
1124
1265
|
if (builtComponentPath?.match(regex_default.endsWithIndex)) {
|
|
@@ -1134,7 +1275,8 @@ function getComponentDynamicPaths(componentPath) {
|
|
|
1134
1275
|
if (!matches?.length) return null;
|
|
1135
1276
|
const output = [];
|
|
1136
1277
|
for (const match of matches) {
|
|
1137
|
-
|
|
1278
|
+
if (!match[2]) continue;
|
|
1279
|
+
output.push(match[2].replace(/^\.\.\./, ""));
|
|
1138
1280
|
}
|
|
1139
1281
|
return output;
|
|
1140
1282
|
}
|
|
@@ -1316,9 +1458,9 @@ async function runDevServer() {
|
|
|
1316
1458
|
});
|
|
1317
1459
|
}
|
|
1318
1460
|
function asyncServerClose(server) {
|
|
1319
|
-
return new Promise((resolve
|
|
1320
|
-
server.close();
|
|
1321
|
-
|
|
1461
|
+
return new Promise((resolve) => {
|
|
1462
|
+
server.close(() => resolve(true));
|
|
1463
|
+
server.closeAllConnections?.();
|
|
1322
1464
|
});
|
|
1323
1465
|
}
|
|
1324
1466
|
function createRebuildEmitter(delay = 500) {
|
|
@@ -1326,39 +1468,51 @@ function createRebuildEmitter(delay = 500) {
|
|
|
1326
1468
|
}
|
|
1327
1469
|
;
|
|
1328
1470
|
const rebuildEmitter = new RebuildEmitter();
|
|
1329
|
-
let
|
|
1330
|
-
let hasEmitted = false;
|
|
1471
|
+
let timeout = null;
|
|
1331
1472
|
rebuildEmitter.on("rebuild-server", () => {
|
|
1332
|
-
|
|
1333
|
-
|
|
1473
|
+
if (timeout) clearTimeout(timeout);
|
|
1474
|
+
timeout = setTimeout(() => {
|
|
1475
|
+
timeout = null;
|
|
1476
|
+
rebuildEmitter.emit("done");
|
|
1477
|
+
}, delay);
|
|
1334
1478
|
});
|
|
1335
|
-
function trigger() {
|
|
1336
|
-
start = Date.now();
|
|
1337
|
-
const interval = setInterval(() => {
|
|
1338
|
-
if (hasEmitted) return;
|
|
1339
|
-
const now = Date.now();
|
|
1340
|
-
if (now - start >= delay) {
|
|
1341
|
-
rebuildEmitter.emit("done");
|
|
1342
|
-
clearInterval(interval);
|
|
1343
|
-
hasEmitted = true;
|
|
1344
|
-
}
|
|
1345
|
-
}, 100);
|
|
1346
|
-
}
|
|
1347
1479
|
return rebuildEmitter;
|
|
1348
1480
|
}
|
|
1349
1481
|
|
|
1350
1482
|
// src/util/html.ts
|
|
1483
|
+
var RawHtml = class {
|
|
1484
|
+
constructor(value) {
|
|
1485
|
+
this.value = value;
|
|
1486
|
+
}
|
|
1487
|
+
toString() {
|
|
1488
|
+
return this.value;
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
var RAW_TEXT_ELEMENTS = /* @__PURE__ */ new Set(["script", "style"]);
|
|
1492
|
+
function escapeHtml(value) {
|
|
1493
|
+
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1494
|
+
}
|
|
1495
|
+
function raw(value) {
|
|
1496
|
+
return new RawHtml(value);
|
|
1497
|
+
}
|
|
1498
|
+
function renderChild(child, rawText) {
|
|
1499
|
+
if (Array.isArray(child)) return child.map((item) => renderChild(item, rawText)).join("");
|
|
1500
|
+
if (child instanceof RawHtml) return child.value;
|
|
1501
|
+
if (!child) return "";
|
|
1502
|
+
return rawText ? String(child) : escapeHtml(child);
|
|
1503
|
+
}
|
|
1351
1504
|
var html = class {
|
|
1505
|
+
static raw = raw;
|
|
1352
1506
|
static attributeObjectToString(props) {
|
|
1353
1507
|
if (!props) return "";
|
|
1354
1508
|
return Object.entries(props).filter(([, value]) => value !== null && value !== void 0).map(([key, value], index) => {
|
|
1355
1509
|
const start = index === 0 ? " " : "";
|
|
1356
|
-
return `${start}${key}="${value}"`;
|
|
1510
|
+
return `${start}${key}="${escapeHtml(value)}"`;
|
|
1357
1511
|
}).join(" ");
|
|
1358
1512
|
}
|
|
1359
1513
|
static async fragment(props) {
|
|
1360
1514
|
const childrenResult = await Promise.all(props.children.flat());
|
|
1361
|
-
return childrenResult.
|
|
1515
|
+
return new RawHtml(childrenResult.map((child) => renderChild(child, false)).join(""));
|
|
1362
1516
|
}
|
|
1363
1517
|
static async createElement(type, props, ...children) {
|
|
1364
1518
|
const childrenResult = await Promise.all(children.flat());
|
|
@@ -1366,7 +1520,9 @@ var html = class {
|
|
|
1366
1520
|
const result = await type({ ...props, children: childrenResult });
|
|
1367
1521
|
return result;
|
|
1368
1522
|
}
|
|
1369
|
-
|
|
1523
|
+
const rawText = RAW_TEXT_ELEMENTS.has(type);
|
|
1524
|
+
const inner = childrenResult.map((child) => renderChild(child, rawText)).join("");
|
|
1525
|
+
return new RawHtml(`<${type}${this.attributeObjectToString(props)}>${inner}</${type}>`);
|
|
1370
1526
|
}
|
|
1371
1527
|
};
|
|
1372
1528
|
function JSXRuntime() {
|
|
@@ -1379,6 +1535,7 @@ function getCurrentBreakpoint() {
|
|
|
1379
1535
|
}
|
|
1380
1536
|
export {
|
|
1381
1537
|
JSXRuntime,
|
|
1538
|
+
RawHtml,
|
|
1382
1539
|
addToContextArray,
|
|
1383
1540
|
buildApp,
|
|
1384
1541
|
buildFilesWithConfigs,
|
|
@@ -1389,9 +1546,11 @@ export {
|
|
|
1389
1546
|
config,
|
|
1390
1547
|
context,
|
|
1391
1548
|
createContext,
|
|
1549
|
+
createCsrfMiddleware,
|
|
1392
1550
|
createRouter,
|
|
1393
1551
|
createXPineRouter,
|
|
1394
1552
|
determineOutputPathForStaticPath,
|
|
1553
|
+
escapeHtml,
|
|
1395
1554
|
filePathToURLPath,
|
|
1396
1555
|
fromRoot,
|
|
1397
1556
|
getComponentDynamicPaths,
|
|
@@ -1399,9 +1558,13 @@ export {
|
|
|
1399
1558
|
getTokenFromRequest,
|
|
1400
1559
|
html,
|
|
1401
1560
|
logSize,
|
|
1561
|
+
raw,
|
|
1402
1562
|
routeHasStaticPath,
|
|
1403
1563
|
runDevServer,
|
|
1404
1564
|
setupEnv,
|
|
1405
1565
|
signUser,
|
|
1566
|
+
spreadConcretePath,
|
|
1567
|
+
spreadSlugName,
|
|
1568
|
+
spreadWildcardPath,
|
|
1406
1569
|
verifyUser
|
|
1407
1570
|
};
|
package/dist/src/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAgBzC,wBAAsB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,oBAgB7D;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAO5D;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,aAAa,UAKrD"}
|
package/dist/src/express.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { Express } from 'express';
|
|
2
|
+
export declare function spreadConcretePath(templateRoute: string, entry: {
|
|
3
|
+
[key: string]: string;
|
|
4
|
+
}): string;
|
|
5
|
+
export declare function spreadWildcardPath(templateRoute: string): string;
|
|
6
|
+
export declare function spreadSlugName(templateRoute: string): string | null;
|
|
2
7
|
export declare function createRouter(): Promise<{
|
|
3
8
|
router: import("express-serve-static-core").Router;
|
|
4
9
|
routeResults: any[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/express.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAgB,OAAO,
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/express.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAgB,OAAO,EAA6C,MAAM,SAAS,CAAC;AA8FpG,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAAG,MAAM,CAKlG;AAGD,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAIhE;AAGD,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGnE;AAqLD,wBAAsB,YAAY;;;GAuBjC;AAuDD,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,iBAgC1F;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,KAAK,CAoBnG;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,OAAc,UAQzE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/scripts/build.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,UAAU,EAA2B,aAAa,EAAE,MAAM,aAAa,CAAC;AAajF,KAAK,YAAY,GAAG;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAA;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,iBAgDhD;AA4JD,wBAAsB,yBAAyB,kBAiC9C;AAED,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,WAAkB,iBAa9F;AAED,wBAAsB,qBAAqB,CAAC,aAAa,EAAE,aAAa,EAAE,iBAezE;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAE,GAAG,EAAE,kBAAkB,EAAE,MAAM,iBA2FpI;AAED,wBAAgB,gCAAgC,CAAC,kBAAkB,EAAE,MAAM,EAAE,qBAAqB,CAAC,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/scripts/build.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,UAAU,EAA2B,aAAa,EAAE,MAAM,aAAa,CAAC;AAajF,KAAK,YAAY,GAAG;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAA;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,iBAgDhD;AA4JD,wBAAsB,yBAAyB,kBAiC9C;AAED,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,WAAkB,iBAa9F;AAED,wBAAsB,qBAAqB,CAAC,aAAa,EAAE,aAAa,EAAE,iBAezE;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAE,GAAG,EAAE,kBAAkB,EAAE,MAAM,iBA2FpI;AAED,wBAAgB,gCAAgC,CAAC,kBAAkB,EAAE,MAAM,EAAE,qBAAqB,CAAC,EAAE,MAAM,EAAE,UAgB5G;AAED,wBAAgB,wBAAwB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,CAUxE;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAA;AAGD,wBAAsB,eAAe,CAAC,aAAa,EAAE,aAAa,EAAE,EAAE,KAAK,CAAC,EAAE,OAAO,iBAoDpF;AAED,wBAAsB,YAAY,kBA4CjC"}
|
|
@@ -321,6 +321,9 @@ var regex_default = {
|
|
|
321
321
|
configFile: /\+config\.[tj]sx?/g,
|
|
322
322
|
dynamicRoutes: /(\[)(.*?)(\])/g,
|
|
323
323
|
isDynamicRoute: /\[(.*)\]/g,
|
|
324
|
+
// Multi-segment (slash-containing) dynamic route, e.g. [...slug]
|
|
325
|
+
spreadRoute: /\[\.\.\.(.*?)\]/g,
|
|
326
|
+
isSpreadRoute: /\[\.\.\..*?\]/,
|
|
324
327
|
endsWithTSX: /\.tsx$/,
|
|
325
328
|
endsWithJSX: /\.jsx$/,
|
|
326
329
|
endsWithJs: /\.js$/,
|
|
@@ -891,7 +894,7 @@ async function buildStaticFiles(config2, component, componentImport, builtCompon
|
|
|
891
894
|
function determineOutputPathForStaticPath(builtComponentPath, componentDynamicPaths) {
|
|
892
895
|
if (componentDynamicPaths?.length) {
|
|
893
896
|
return componentDynamicPaths.reduce((total, current) => {
|
|
894
|
-
return total.replace(`/[${current}]`, "");
|
|
897
|
+
return total.replace(`/[...${current}]`, "").replace(`/[${current}]`, "");
|
|
895
898
|
}, path5.dirname(builtComponentPath));
|
|
896
899
|
} else {
|
|
897
900
|
if (builtComponentPath?.match(regex_default.endsWithIndex)) {
|
|
@@ -907,7 +910,8 @@ function getComponentDynamicPaths(componentPath) {
|
|
|
907
910
|
if (!matches?.length) return null;
|
|
908
911
|
const output = [];
|
|
909
912
|
for (const match of matches) {
|
|
910
|
-
|
|
913
|
+
if (!match[2]) continue;
|
|
914
|
+
output.push(match[2].replace(/^\.\.\./, ""));
|
|
911
915
|
}
|
|
912
916
|
return output;
|
|
913
917
|
}
|