xpine 0.0.44 → 0.0.46

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/README.md CHANGED
@@ -1,64 +1,367 @@
1
1
  # XPine - Alpine.js + JSX + Tailwind framework
2
2
 
3
- Combines JSX with Alpine.js for a simpler, easier development experience.
3
+ Combines JSX with Alpine.js for a simpler, easier development experience. Includes a static site generator.
4
4
 
5
- ## Get started
5
+ ### Install
6
6
 
7
- Add an xpine.config.mjs file to your root directory
7
+ `npm install xpine`
8
+
9
+ ### Routing, page setup, and using Alpine.js
10
+
11
+ XPine uses page based routing. Render an HTML page using JSX components, for example the path `/src/page/about.tsx` will route to `/about` and `/src/page/index.tsx` will route to `/`
8
12
 
9
13
  ```
10
- export default {}
14
+ import { WrapperProps } from 'xpine/dist/types';
15
+ import Base from '../components/Base';
16
+
17
+ export const config = {
18
+ data() {
19
+ return {
20
+ title: 'Home page',
21
+ description: 'The description',
22
+ }
23
+ },
24
+ wrapper({ req, children, data, routePath }: WrapperProps) {
25
+ return (
26
+ <Base
27
+ title={data?.title || 'My awesome website'}
28
+ description={data?.description}
29
+ req={req}
30
+ >
31
+ <h1>Home page wrapper</h1>
32
+ {children}
33
+ </Base>
34
+ )
35
+ },
36
+ }
37
+
38
+ export default function Home() {
39
+ return (
40
+ <div x-data="HomePageData" x-on:click="logMessage">
41
+ Hello world
42
+ </div>
43
+ );
44
+ }
45
+
46
+ <script />
47
+
48
+ export function HomePageData() {
49
+ return {
50
+ logMessage() {
51
+ console.log('Hello world');
52
+ }
53
+ };
54
+ }
11
55
  ```
12
56
 
13
- ## API
57
+ - src/components/Base.tsx
14
58
 
15
- ### Build
59
+ ```
60
+ import { JsxElement } from 'typescript';
61
+ import { ServerRequest } from 'xpine/dist/types';
16
62
 
17
- `xpine-build`
63
+ type BaseProps = {
64
+ head?: JsxElement;
65
+ title: string;
66
+ description?: string;
67
+ req?: ServerRequest;
68
+ children?: JsxElement;
69
+ }
18
70
 
19
- ## Auth
71
+ export default async function Base({
72
+ head,
73
+ title,
74
+ description,
75
+ children,
76
+ }: BaseProps) {
77
+ return (
78
+ <html lang="en">
79
+ <head>
80
+ <meta charset="UTF-8" />
81
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
82
+ <meta name="robots" content="index,follow" />
83
+ <meta name="description" content={description || `${title} page`} />
84
+ <title>{title || 'My Website'}</title>
85
+ <link rel="stylesheet" href="/styles/global.css" />
86
+ <script defer src="/scripts/app.js"></script>
87
+ {head}
88
+ </head>
89
+ <body>
90
+ <div id="xpine-root">
91
+ {children}
92
+ </div>
93
+ </body>
94
+ </html>
95
+ );
96
+ }
97
+ ```
20
98
 
21
- `import { signUser, verifyUser } from 'xpine';`
99
+ ### Client side Javascript and CSS files
22
100
 
23
- ## Dev server
101
+ In the above code, we have a `/styles/global.css` file and a `/scripts/app.js` file import.
24
102
 
25
- `xpine-dev`
103
+ #### CSS
26
104
 
27
- ## Config
105
+ Create a file called `/src/public/styles/global.css` and import Tailwind:
28
106
 
29
- `import { config } from 'xpine';`
107
+ ```
108
+ @import "tailwindcss";
109
+ ```
30
110
 
31
- ## Env
111
+ Note you must install tailwindcss yourself: `npm install tailwindcss`
112
+
113
+ #### Javascript
114
+
115
+ Create a file called `/src/public/scripts/index.ts`:
32
116
 
33
117
  ```
34
- import { setupEnv } from 'xpine';
118
+ import Alpine from 'alpinejs';
35
119
 
36
- await setupEnv();
120
+ Alpine.start();
121
+ ```
122
+
123
+ Note you must install alpinejs yourself: `npm install alpinejs`
124
+
125
+ ### Dynamic routing
126
+
127
+ Create dynamic routes with paths similar to this `/src/pages/[pathA]/[pathB]`
37
128
 
129
+ ### Express API endpoints
130
+
131
+ Create regular Express routes by using .ts file extensions in the `/src/pages` folder. Specify the HTTP method by naming the file something like `/src/pages/api/my-endpoint.POST.ts`
38
132
  ```
133
+ import { PageProps } from 'xpine/dist/types';
39
134
 
40
- setupEnv also supports AWS secrets manager. Simply add SECRETS_NAME=your_aws_secret_name to your .env.{stage} file
135
+ export default async function myEndpoint({ res }: PageProps) {
136
+ res.status(200).json({
137
+ message: 'Hello World!',
138
+ });
139
+ }
140
+ ```
41
141
 
42
- ## Router
142
+ ### Catch all routes
143
+ You can create catch all routes by naming the file \_all\_.(jsx|tsx|js|ts). You can make static catch all route pages by using the param `0` in the staticPaths config function:
144
+ ```
145
+ export const config = {
146
+ staticPaths() {
147
+ return [
148
+ {
149
+ 0: 'hello/world',
150
+ }
151
+ ]
152
+ },
153
+ }
154
+ ```
43
155
 
44
- `import { createXPineRouter } from 'xpine';`
156
+ You can get the route param in your function with req.params[0], such as how express handles catch all routes.
45
157
 
46
- - Catch all routes
47
- - You can create catch all routes by naming the file _all_.(jsx|tsx|js|ts)
48
- - You can make static catch all route pages by using the param `0` in the staticPaths config function:
49
- ```
50
- export const config = {
51
- staticPaths() {
52
- return [
53
- {
54
- 0: 'hello/world',
55
- }
56
- ]
57
- },
158
+ ### Route specific middleware
159
+
160
+ If you need route specific middleware, e.g. for file uploads, you can specify a `routeMiddleware` function in a config variable in the endpoint file:
161
+
162
+ ```
163
+ export const config = {
164
+ routeMiddleware(req, res, next) {
165
+ console.log('route middleware');
166
+ next();
167
+ }
168
+ }
169
+ ```
170
+
171
+ ### Static Site Generation
172
+
173
+ Generate path specific static pages by specifying in the config of either the page's file, such as `/src/pages/about.tsx` with a config export:
174
+ ```
175
+ export const config = {
176
+ staticPaths: true,
177
+ data() {
178
+ return {
179
+ title: 'My title'
180
+ }
181
+ }
182
+ }
183
+ ```
184
+
185
+ or a `+config.ts` file.
186
+
187
+ ### Configs
188
+
189
+ Configs can be nested. Create a +config.ts file in a directory and all subfolders will inherit that config unless overridden by their own +config.ts files. Want to apply static paths to an entire folder except for a single folder? In that folder you can create a `+config.ts` file like this:
190
+ ```
191
+ export default {
192
+ staticPaths: false,
193
+ }
194
+ ```
195
+
196
+ ### Dynamic Static Pages
197
+
198
+ You can also create dynamic static pages by using a function in the staticPaths folder. For example, a directory named `/src/[pathA]/[pathB]/[pathC]/[pathD].tsx` might have a configuration file like this:
199
+ ```
200
+ import { ServerRequest } from 'xpine/dist/types';
201
+ import axios from 'axios';
202
+
203
+ export const config = {
204
+ staticPaths() {
205
+ return [
206
+ {
207
+ pathA: 'my-path-a2',
208
+ pathB: 'my-path-b2',
209
+ pathC: 'my-path-c2',
210
+ pathD: '2'
58
211
  }
59
- ```
60
- - You can get the route param in your function with req.params[0], such as how express handles catch all routes
212
+ ]
213
+ },
214
+ async data(req: ServerRequest) {
215
+ const url = `https://jsonplaceholder.typicode.com/posts/${req.params.pathD}`;
216
+ try {
217
+ const { data } = await axios.get(url);
218
+ return {
219
+ ...data,
220
+ ...req.params,
221
+ };
222
+ } catch (err) {
223
+ console.error('could not fetch', url);
224
+ return {
225
+ ...req.params,
226
+ data: {},
227
+ }
228
+ }
229
+ }
230
+ }
231
+ ```
232
+
233
+ ### Context
234
+
235
+ Create app context, useful for things like Navbars. In `/src/context.tsx`:
236
+ ```
237
+ import { createContext } from 'xpine';
238
+
239
+ export function NavbarContext() {
240
+ const navbar = createContext([]);
241
+ return {
242
+ navbar,
243
+ }
244
+ }
245
+ ```
246
+
247
+ then in a page, say `/src/pages/about.tsx`, you can add to the NavbarContext like this:
248
+ ```
249
+ export function xpineOnLoad() {
250
+ context.addToArray('navbar', 'My awesome context 1', 2);
251
+ context.addToArray('navbar', 'My awesome context 2', new Date('January 11, 2024'));
252
+ context.addToArray('navbar', 'My awesome context 3', new Date('January 10, 2024'));
253
+ context.addToArray('navbar', 'My awesome context 4', new Date('January 30, 2024'));
254
+ }
255
+ ```
256
+
257
+ Context is sorted by array position then by date. You can then use context in your component like this:
258
+ ```
259
+ import { context } from 'xpine';
260
+
261
+ export default function Navbar() {
262
+ const navbar = context.get('navbar');
263
+ return (
264
+ <div>
265
+ {navbar.map(item => {
266
+ return <div>{item}</div>
267
+ })}
268
+ </div>
269
+ );
270
+ }
271
+ ```
61
272
 
273
+ ### Server set up
274
+
275
+ - src/server/app.ts
276
+
277
+ ```
278
+ import express from 'express';
279
+ import cookieParser from 'cookie-parser';
280
+ import logger from 'morgan';
281
+ import http from 'http';
282
+ import { createXPineRouter, setupEnv } from 'xpine';
283
+
284
+ await setupEnv();
285
+
286
+ export default async function startServer() {
287
+ const port = process.env.PORT || 8080;
288
+ const app = express();
289
+ app.set('view engine', 'html');
290
+
291
+ app.enable('trust proxy');
292
+ app.use(logger('dev'));
293
+ app.use(express.json());
294
+ app.use(express.urlencoded({ extended: true, }));
295
+ app.use(cookieParser());
296
+
297
+ await createXPineRouter(app);
298
+
299
+ app.set('port', port);
300
+ const server = http.createServer(app);
301
+ server.listen(port, () => {
302
+ console.info(`Server listening on port ${port}`);
303
+ });
304
+ return {
305
+ app,
306
+ server,
307
+ };
308
+ }
309
+ ```
310
+
311
+ Add a directory in `/src/server` called `run`, and create two files: `/src/server/dev.ts` and `/src/server/prod.ts`.
312
+
313
+ `dev.ts` should look like this:
314
+
315
+ ```
316
+ import { runDevServer } from 'xpine';
317
+
318
+ runDevServer();
319
+ ```
320
+
321
+ and can be run with an npm command like this in your package.json scripts: `"dev": "PORT=8888 LOCALHOST=1 xpine-dev"`.
322
+
323
+ The `prod.ts` should look like this:
324
+
325
+ ```
326
+ import startServer from '../app';
327
+
328
+ await startServer();
329
+ ```
330
+
331
+ and can be run with an npm command in your package.json scripts like this, after the app has been built with `xpine-build`:
332
+
333
+ `"start": "PORT=8888 node ./dist/server/run/prod.js"`
334
+
335
+ ### xpine.config.mjs file
336
+
337
+ Add an xpine.config.mjs file to your root directory. This is used primarily for configuring/changing file paths. Configs can be imported with `import { config } from "xpine"`.
338
+
339
+ ```
340
+ export default {}
341
+ ```
342
+
343
+ These are the following default paths:
344
+
345
+ ```
346
+ rootDir: process.cwd
347
+ srcDir: rootDir + ./src
348
+ distDir: rootDir + ./dist
349
+ packageJsonPath: rootDir + ./package.json
350
+ distPublicDir: distDir + ./public
351
+ distPublicScriptsDir: distPublicDir + ./scripts
352
+ distTempFolder: distDir + ./temp
353
+ clientJSBundlePath: distPublicScriptsDir + ./app.js
354
+ alpineDataPath: distTempFolder + ./alpine-data.ts
355
+ serverDistDir: distDir + ./server
356
+ serverDistAppPath: serverDistDir + ./app.js
357
+ pagesDir: srcDir + ./pages
358
+ distPagesDir: distDir + ./pages
359
+ publicDir: srcDir + ./public
360
+ serverDir: srcDir + ./server
361
+ runDir: serverDir + ./run
362
+ serverAppPath: serverDir + ./app.ts
363
+ globalCSSFile: publicDir + ./styles/global.css
364
+ ```
62
365
 
63
366
  ### SPA interactivity
64
367
 
@@ -69,29 +372,58 @@ setupEnv also supports AWS secrets manager. Simply add SECRETS_NAME=your_aws_sec
69
372
  - data-spa-crossorigin="true"
70
373
  - enables cross-origin spa navigation requests (untested)
71
374
 
72
- #### Custom events
73
- - spa-update-page-content
74
- - sent when the page content has update
75
- - spa-update-page-url
76
- - sent when the page URL has update
77
- - spa-link-click
78
- - sent when link initially gets clicked and when link is done updating content
79
- - the "state" value in the event detail will be "start" or "end"
80
- - breakpoint-change
81
- - sent when a breakpoint is changed via your Tailwind css file's breakpoints in the @theme directive, e.g.
82
- ```
83
- @theme {
84
- --breakpoint-xl: 1184px;
85
- --breakpoint-sm: 640px;
86
- --breakpoint-md: 768px;
87
- --breakpoint-lg: 1024px;
88
- }
89
- ```
90
- - this will send a custom event with the detail being the breakpoint, such as `{ breakpoint: 'xl' }`
91
- - note this will only send an event based on the values of the breakpoints in your @theme configuration
375
+ ## API
376
+
377
+ ### Build command
378
+
379
+ `xpine-build`
380
+
381
+ ### Dev server command
382
+
383
+ `xpine-dev`
384
+
385
+ ### Auth
386
+
387
+ `import { signUser, verifyUser } from 'xpine';`
388
+
389
+ ### Config
390
+
391
+ `import { config } from 'xpine';`
392
+
393
+ ### Env
394
+
395
+ ```
396
+ import { setupEnv } from 'xpine';
397
+
398
+ await setupEnv();
399
+
400
+ ```
401
+
402
+ setupEnv also supports AWS secrets manager. Simply add SECRETS_NAME=your_aws_secret_name to your .env.{stage} file
403
+
404
+ ### Custom events
405
+ - spa-update-page-content
406
+ - sent when the page content has update
407
+ - spa-update-page-url
408
+ - sent when the page URL has update
409
+ - spa-link-click
410
+ - sent when link initially gets clicked and when link is done updating content
411
+ - the "state" value in the event detail will be "start" or "end"
412
+ - breakpoint-change
413
+ - sent when a breakpoint is changed via your Tailwind css file's breakpoints in the @theme directive, e.g.
414
+ ```
415
+ @theme {
416
+ --breakpoint-xl: 1184px;
417
+ --breakpoint-sm: 640px;
418
+ --breakpoint-md: 768px;
419
+ --breakpoint-lg: 1024px;
420
+ }
421
+ ```
422
+ - this will send a custom event with the detail being the breakpoint, such as `{ breakpoint: 'xl' }`
423
+ - note this will only send an event based on the values of the breakpoints in your @theme configuration
92
424
 
93
- #### Custom scripts for certain pages
425
+ ### Custom scripts for certain pages
94
426
 
95
427
  1. Add script to src/public/scripts/pages/your_script.ts
96
- 2. Import script into page HTML (e.g. <script src="/scripts/pages/your_script.ts">)
428
+ 2. Import script into page HTML (e.g. `<script src="/scripts/pages/your_script.ts">`)
97
429
  3. To unload event listeners, use `window.addEventListener('spa-update-page-url', () => { remove event listeners here})` in the code
package/TODO CHANGED
@@ -0,0 +1,2 @@
1
+ [ ] folder specific client side JS builds
2
+ [ ] TypeError: Cannot read properties of undefined (reading 'find')
package/dist/index.js CHANGED
@@ -512,7 +512,7 @@ function getAllBuiltRoutes() {
512
512
  async function createRouteFunction(route, configFiles) {
513
513
  const isJSX = route.originalRoute.endsWith(".tsx") || route.originalRoute.endsWith(".jsx");
514
514
  const slugRoute = route.route.replace(/[ ]/g, "");
515
- const foundMethod = methods.find((method) => slugRoute.endsWith(`.${method}`));
515
+ const foundMethod = methods.find((method) => slugRoute.toUpperCase().endsWith(`.${method.toUpperCase()}`));
516
516
  const isDynamicRoute = slugRoute.match(regex_default.isDynamicRoute);
517
517
  let formattedRouteItem = slugRoute;
518
518
  if (foundMethod) formattedRouteItem = formattedRouteItem.split(".").shift();
@@ -531,14 +531,12 @@ async function createRouteFunction(route, configFiles) {
531
531
  }
532
532
  let config2 = {};
533
533
  const configFilePaths = getConfigFiles(route.originalRoute, configFiles);
534
- if (!isDev) {
535
- config2 = configFilePaths && await getCompleteConfig(configFilePaths, Date.now());
536
- if (componentImport?.config) {
537
- config2 = {
538
- ...config2,
539
- ...componentImport.config
540
- };
541
- }
534
+ config2 = configFilePaths && await getCompleteConfig(configFilePaths, Date.now());
535
+ if (componentImport?.config) {
536
+ config2 = {
537
+ ...config2,
538
+ ...componentImport.config
539
+ };
542
540
  }
543
541
  async function routeFn(req, res) {
544
542
  const urlPath = req?.route?.path || "/";
@@ -588,6 +586,7 @@ async function createRouteFunction(route, configFiles) {
588
586
  formattedRouteItem,
589
587
  foundMethod,
590
588
  route,
589
+ config: config2,
591
590
  routeFn
592
591
  };
593
592
  }
@@ -599,8 +598,12 @@ async function createRouter() {
599
598
  await triggerXPineOnLoad();
600
599
  for (const route of routeMap) {
601
600
  try {
602
- const { formattedRouteItem, foundMethod, routeFn } = await createRouteFunction(route, configFiles);
603
- router[foundMethod || "get"](formattedRouteItem, routeFn);
601
+ const { formattedRouteItem, foundMethod, config: config2, routeFn } = await createRouteFunction(route, configFiles);
602
+ if (config2?.routeMiddleware) {
603
+ router[foundMethod || "get"](formattedRouteItem, config2.routeMiddleware, routeFn);
604
+ } else {
605
+ router[foundMethod || "get"](formattedRouteItem, routeFn);
606
+ }
604
607
  routeResults.push({
605
608
  formattedRouteItem,
606
609
  foundMethod,
@@ -787,7 +790,7 @@ async function buildAppFiles(files, isDev2) {
787
790
  const fileName = file.split("/").at(-1).split(".").shift();
788
791
  return fileName === "+config";
789
792
  });
790
- const backendFiles = files.filter((file) => extensions.find((ext) => file.endsWith(ext))).filter((file) => !file.startsWith(config.publicDir));
793
+ const backendFiles = files.filter((file) => extensions?.find((ext) => file.endsWith(ext))).filter((file) => !file.startsWith(config.publicDir));
791
794
  fs7.ensureDirSync(config.distDir);
792
795
  await build({
793
796
  entryPoints: backendFiles,
@@ -918,7 +921,7 @@ async function buildPublicFolderSymlinks() {
918
921
  async function logSize(pathName, type, validExtensions = [".js", ".css"]) {
919
922
  const files = globSync3(pathName + "/**/*" + (type === "css" ? ".css" : ""));
920
923
  const fileSizes = files.map((file) => {
921
- if (!validExtensions.find((ext) => file.endsWith(ext))) return false;
924
+ if (!validExtensions?.find((ext) => file.endsWith(ext))) return false;
922
925
  return {
923
926
  file,
924
927
  size: fs7.statSync(file).size / (1024 * 1e3)
@@ -1108,7 +1111,9 @@ async function runDevServer() {
1108
1111
  ignored: (pathName) => pathName.endsWith(".map") || pathName.startsWith(path8.join(config.serverDir, "./prisma"))
1109
1112
  });
1110
1113
  watcher.on("all", async (event, path9) => {
1111
- const shouldReloadServer = path9.startsWith(config.serverDir) && !path9.startsWith(config.runDir) || ["add", "unlink"].includes(event) && path9.startsWith(config.pagesDir);
1114
+ const isRegularExpressRoute = path9.startsWith(config.pagesDir) && (path9.endsWith(".ts") || path9.endsWith(".js"));
1115
+ const isServerDir = path9.startsWith(config.serverDir) && !path9.startsWith(config.runDir);
1116
+ const shouldReloadServer = isServerDir || isRegularExpressRoute || ["add", "unlink"].includes(event) && path9.startsWith(config.pagesDir);
1112
1117
  if (shouldReloadServer) {
1113
1118
  await asyncServerClose(appServer.server);
1114
1119
  rebuildEmitter.emit("rebuild-server");
@@ -1 +1 @@
1
- {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/express.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAgB,OAAO,EAAqB,MAAM,SAAS,CAAC;AAuJ5E,wBAAsB,YAAY;;;GA8BjC;AAiBD,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,iBAyB1F;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,KAAK,CAanG;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,OAAc,UAQzE"}
1
+ {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/express.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAgB,OAAO,EAAqB,MAAM,SAAS,CAAC;AAqJ5E,wBAAsB,YAAY;;;GAkCjC;AAiBD,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,iBAyB1F;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,KAAK,CAanG;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,OAAc,UAQzE"}
@@ -1 +1 @@
1
- {"version":3,"file":"runDevServer.d.ts","sourceRoot":"","sources":["../../src/runDevServer.ts"],"names":[],"mappings":"AAYA,wBAAsB,YAAY,kBA6DjC"}
1
+ {"version":3,"file":"runDevServer.d.ts","sourceRoot":"","sources":["../../src/runDevServer.ts"],"names":[],"mappings":"AAYA,wBAAsB,YAAY,kBA+DjC"}
@@ -583,7 +583,7 @@ async function buildAppFiles(files, isDev3) {
583
583
  const fileName = file.split("/").at(-1).split(".").shift();
584
584
  return fileName === "+config";
585
585
  });
586
- const backendFiles = files.filter((file) => extensions.find((ext) => file.endsWith(ext))).filter((file) => !file.startsWith(config.publicDir));
586
+ const backendFiles = files.filter((file) => extensions?.find((ext) => file.endsWith(ext))).filter((file) => !file.startsWith(config.publicDir));
587
587
  fs7.ensureDirSync(config.distDir);
588
588
  await build({
589
589
  entryPoints: backendFiles,
@@ -714,7 +714,7 @@ async function buildPublicFolderSymlinks() {
714
714
  async function logSize(pathName, type, validExtensions = [".js", ".css"]) {
715
715
  const files = globSync3(pathName + "/**/*" + (type === "css" ? ".css" : ""));
716
716
  const fileSizes = files.map((file) => {
717
- if (!validExtensions.find((ext) => file.endsWith(ext))) return false;
717
+ if (!validExtensions?.find((ext) => file.endsWith(ext))) return false;
718
718
  return {
719
719
  file,
720
720
  size: fs7.statSync(file).size / (1024 * 1e3)
@@ -590,7 +590,7 @@ async function buildAppFiles(files, isDev2) {
590
590
  const fileName = file.split("/").at(-1).split(".").shift();
591
591
  return fileName === "+config";
592
592
  });
593
- const backendFiles = files.filter((file) => extensions.find((ext) => file.endsWith(ext))).filter((file) => !file.startsWith(config.publicDir));
593
+ const backendFiles = files.filter((file) => extensions?.find((ext) => file.endsWith(ext))).filter((file) => !file.startsWith(config.publicDir));
594
594
  fs7.ensureDirSync(config.distDir);
595
595
  await build({
596
596
  entryPoints: backendFiles,
@@ -721,7 +721,7 @@ async function buildPublicFolderSymlinks() {
721
721
  async function logSize(pathName, type, validExtensions = [".js", ".css"]) {
722
722
  const files = globSync3(pathName + "/**/*" + (type === "css" ? ".css" : ""));
723
723
  const fileSizes = files.map((file) => {
724
- if (!validExtensions.find((ext) => file.endsWith(ext))) return false;
724
+ if (!validExtensions?.find((ext) => file.endsWith(ext))) return false;
725
725
  return {
726
726
  file,
727
727
  size: fs7.statSync(file).size / (1024 * 1e3)
@@ -911,7 +911,9 @@ async function runDevServer() {
911
911
  ignored: (pathName) => pathName.endsWith(".map") || pathName.startsWith(path7.join(config.serverDir, "./prisma"))
912
912
  });
913
913
  watcher.on("all", async (event, path8) => {
914
- const shouldReloadServer = path8.startsWith(config.serverDir) && !path8.startsWith(config.runDir) || ["add", "unlink"].includes(event) && path8.startsWith(config.pagesDir);
914
+ const isRegularExpressRoute = path8.startsWith(config.pagesDir) && (path8.endsWith(".ts") || path8.endsWith(".js"));
915
+ const isServerDir = path8.startsWith(config.serverDir) && !path8.startsWith(config.runDir);
916
+ const shouldReloadServer = isServerDir || isRegularExpressRoute || ["add", "unlink"].includes(event) && path8.startsWith(config.pagesDir);
915
917
  if (shouldReloadServer) {
916
918
  await asyncServerClose(appServer.server);
917
919
  rebuildEmitter.emit("rebuild-server");
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Request, Response } from 'express';
1
+ import { NextFunction, Request, Response } from 'express';
2
2
  import ts from 'typescript';
3
3
  export type XPineConfig = {
4
4
  [key: string]: any;
@@ -24,6 +24,7 @@ export type ConfigFile = {
24
24
  }[]>);
25
25
  wrapper?: (props: WrapperProps) => Promise<any>;
26
26
  data?: (req: ServerRequest) => Promise<any>;
27
+ routeMiddleware?: (req: ServerRequest, res: Response, next: NextFunction) => void;
27
28
  };
28
29
  export type PageProps = {
29
30
  req: ServerRequest;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,MAAM,MAAM,WAAW,GAAG;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG;IACpC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,aAAa,CAAC;IACnB,QAAQ,EAAE,GAAG,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,WAAW,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC,CAAC;IACrE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IAChD,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CAC7C,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,GAAG,EAAE,aAAa,CAAC;IACnB,GAAG,EAAE,QAAQ,CAAC;IACd,IAAI,EAAE,GAAG,CAAC;IACV,SAAS,EAAE,MAAM,CAAC;CACnB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC;CACvB,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,MAAM,MAAM,WAAW,GAAG;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG;IACpC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,aAAa,CAAC;IACnB,QAAQ,EAAE,GAAG,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,WAAW,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC,CAAC;IACrE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IAChD,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IAC5C,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;CACnF,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,GAAG,EAAE,aAAa,CAAC;IACnB,GAAG,EAAE,QAAQ,CAAC;IACd,IAAI,EAAE,GAAG,CAAC;IACV,SAAS,EAAE,MAAM,CAAC;CACnB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC;CACvB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xpine",
3
- "version": "0.0.44",
3
+ "version": "0.0.46",
4
4
  "main": "dist/index.js",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-secrets-manager": "^3.758.0",
package/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Request, Response } from 'express';
1
+ import { NextFunction, Request, Response } from 'express';
2
2
  import ts from 'typescript';
3
3
 
4
4
  export type XPineConfig = {
@@ -27,6 +27,7 @@ export type ConfigFile = {
27
27
  staticPaths?: boolean | (() => Promise<{ [key: string]: string }[]>);
28
28
  wrapper?: (props: WrapperProps) => Promise<any>;
29
29
  data?: (req: ServerRequest) => Promise<any>;
30
+ routeMiddleware?: (req: ServerRequest, res: Response, next: NextFunction) => void;
30
31
  }
31
32
 
32
33
  export type PageProps = {