@varlabs/create-solidstep 0.1.2 → 0.1.3

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.
@@ -0,0 +1,106 @@
1
+ type CacheValue<T = any> = {
2
+ key: string
3
+ value: T
4
+ expiresAt: number | null
5
+ prev?: CacheValue<T>
6
+ next?: CacheValue<T>
7
+ };
8
+
9
+ const MAX_CACHE_ENTRIES = 1000;
10
+
11
+ const cacheMap = new Map<string, CacheValue>();
12
+ let head: CacheValue | undefined;
13
+ let tail: CacheValue | undefined;
14
+
15
+ const moveToFront = <T>(node: CacheValue<T>) => {
16
+ if (node === head) return;
17
+
18
+ // Detach
19
+ if (node.prev) node.prev.next = node.next;
20
+ if (node.next) node.next.prev = node.prev;
21
+
22
+ if (node === tail) tail = node.prev;
23
+
24
+ // Insert at head
25
+ node.prev = undefined;
26
+ node.next = head;
27
+ if (head) head.prev = node;
28
+ head = node;
29
+
30
+ if (!tail) tail = node;
31
+ };
32
+
33
+ const removeTail = <T>() => {
34
+ if (!tail) return;
35
+ cacheMap.delete(tail.key);
36
+
37
+ if (tail.prev) {
38
+ tail.prev.next = undefined;
39
+ tail = tail.prev;
40
+ } else {
41
+ // Only one node
42
+ head = tail = undefined;
43
+ }
44
+ };
45
+
46
+ export const getCache = <T>(key: string): T | null => {
47
+ const entry = cacheMap.get(key);
48
+ if (!entry) return null;
49
+
50
+ if (entry.expiresAt && entry.expiresAt < Date.now()) {
51
+ cacheMap.delete(key);
52
+ if (entry.prev) entry.prev.next = entry.next;
53
+ if (entry.next) entry.next.prev = entry.prev;
54
+ if (entry === head) head = entry.next;
55
+ if (entry === tail) tail = entry.prev;
56
+ return null;
57
+ }
58
+
59
+ moveToFront(entry);
60
+ return entry.value;
61
+ };
62
+
63
+ export const setCache = <T>(key: string, value: T, ttlMs?: number) => {
64
+ if (cacheMap.has(key)) {
65
+ const node = cacheMap.get(key)!;
66
+ node.value = value;
67
+ node.expiresAt = ttlMs ? Date.now() + ttlMs : null;
68
+ moveToFront(node);
69
+ return;
70
+ }
71
+
72
+ const newNode: CacheValue<T> = {
73
+ key,
74
+ value,
75
+ expiresAt: ttlMs ? Date.now() + ttlMs : null
76
+ };
77
+
78
+ newNode.next = head;
79
+ if (head) head.prev = newNode;
80
+ head = newNode;
81
+
82
+ if (!tail) tail = newNode;
83
+
84
+ cacheMap.set(key, newNode);
85
+
86
+ if (cacheMap.size > MAX_CACHE_ENTRIES) {
87
+ removeTail();
88
+ }
89
+ };
90
+
91
+ export const invalidateCache = (key: string) => {
92
+ const node = cacheMap.get(key);
93
+ if (!node) return;
94
+
95
+ if (node.prev) node.prev.next = node.next;
96
+ if (node.next) node.next.prev = node.prev;
97
+ if (node === head) head = node.next;
98
+ if (node === tail) tail = node.prev;
99
+
100
+ cacheMap.delete(key);
101
+ };
102
+
103
+ export const clearAllCache = () => {
104
+ cacheMap.clear();
105
+ head = tail = undefined;
106
+ };
@@ -0,0 +1,25 @@
1
+ import {
2
+ setCookie as baseSetCookie,
3
+ getCookie as baseGetCookie,
4
+ deleteCookie as baseDeleteCookie,
5
+ getEvent
6
+ } from 'vinxi/http';
7
+
8
+ export const getCookie = (key: string): string | undefined => {
9
+ const event = getEvent();
10
+ return baseGetCookie(event, key);
11
+ };
12
+
13
+ export const setCookie = (
14
+ key: string,
15
+ value: string,
16
+ options?: Parameters<typeof baseSetCookie>[2]
17
+ ) => {
18
+ const event = getEvent();
19
+ return baseSetCookie(event, key, value, options);
20
+ }
21
+
22
+ export const deleteCookie = (key: string) => {
23
+ const event = getEvent();
24
+ return baseDeleteCookie(event, key);
25
+ };
@@ -0,0 +1,16 @@
1
+
2
+ export const cors = (trustedOrigins: string[]) => (origin: string, isPreflight: boolean) => {
3
+ if (trustedOrigins.includes(origin)) {
4
+ if (isPreflight) {
5
+ return {
6
+ 'Access-Control-Allow-Origin': origin,
7
+ 'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
8
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
9
+ };
10
+ }
11
+ return {
12
+ 'Access-Control-Allow-Origin': origin,
13
+ };
14
+ }
15
+ return {};
16
+ }
@@ -0,0 +1,27 @@
1
+ export const cspNonce = (nonce: string) => `
2
+ default-src 'self';
3
+ font-src 'self' https://fonts.gstatic.com;
4
+ object-src 'none';
5
+ base-uri 'none';
6
+ frame-ancestors 'none';
7
+ form-action 'self';
8
+ style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
9
+ style-src-elem 'self' 'unsafe-inline' https://fonts.googleapis.com;
10
+ script-src 'nonce-${nonce}' 'strict-dynamic' 'unsafe-eval';
11
+ connect-src 'self' ws:;
12
+ img-src 'self' data:;
13
+ `.replace(/\s+/g, ' ');
14
+
15
+ export const csp = `
16
+ default-src 'self';
17
+ font-src 'self' https://fonts.gstatic.com;
18
+ object-src 'none';
19
+ base-uri 'none';
20
+ frame-ancestors 'none';
21
+ form-action 'self';
22
+ style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
23
+ style-src-elem 'self' 'unsafe-inline' https://fonts.googleapis.com;
24
+ script-src 'self' 'unsafe-inline' 'unsafe-eval';
25
+ connect-src 'self' ws:;
26
+ img-src 'self' data:;
27
+ `.replace(/\s+/g, ' ');
@@ -0,0 +1,62 @@
1
+ const SAFE_METHODS = ['GET', 'OPTIONS', 'HEAD', 'TRACE'];
2
+
3
+ export const csrf = (trustedOrigins: string[]) =>
4
+ (
5
+ requestMethod: string,
6
+ requestUrl: URL,
7
+ origin?: string,
8
+ referer?: string
9
+ ) => {
10
+ // Check if the request method is safe
11
+ if (!SAFE_METHODS.includes(requestMethod)) {
12
+ // If we have an Origin header, check it against our allowlist.
13
+ if (origin) {
14
+ const parsedOrigin = new URL(origin);
15
+ if (
16
+ parsedOrigin.origin !== requestUrl.origin &&
17
+ !trustedOrigins.includes(parsedOrigin.host)
18
+ ) {
19
+ return {
20
+ success: false,
21
+ message: 'Invalid origin',
22
+ };
23
+ }
24
+ }
25
+
26
+ // If we are serving via TLS and have no Origin header, prevent against
27
+ // CSRF via HTTP man-in-the-middle attacks by enforcing strict Referer
28
+ // origin checks.
29
+ if (!origin && requestUrl.protocol === 'https:') {
30
+ if (!referer) {
31
+ return {
32
+ success: false,
33
+ message: 'referer not supplied',
34
+ };
35
+ }
36
+
37
+ const parsedReferer = new URL(referer);
38
+
39
+ if (parsedReferer.protocol !== 'https:') {
40
+ return {
41
+ success: false,
42
+ message: 'Invalid referer',
43
+ };
44
+ }
45
+
46
+ if (
47
+ parsedReferer.host !== requestUrl.host &&
48
+ !trustedOrigins.includes(parsedReferer.host)
49
+ ) {
50
+ return {
51
+ success: false,
52
+ message: 'Invalid referer',
53
+ };
54
+ }
55
+ }
56
+ }
57
+
58
+ return {
59
+ success: true,
60
+ message: 'CSRF check passed',
61
+ };
62
+ };
@@ -0,0 +1,16 @@
1
+ import { isServer } from 'solid-js/web';
2
+
3
+ export class RedirectError extends Error {
4
+ constructor(message: string) {
5
+ super(message);
6
+ this.name = 'RedirectError';
7
+ }
8
+ }
9
+
10
+ export const redirect = (url: string) => {
11
+ if (isServer) {
12
+ throw new RedirectError(url);
13
+ } else {
14
+ window.location.href = url;
15
+ }
16
+ };
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url';
4
4
 
5
5
  const __dirname = dirname(fileURLToPath(import.meta.url));
6
6
 
7
- export class Router extends BaseFileSystemRouter {
7
+ export class ServerRouter extends BaseFileSystemRouter {
8
8
  toPath(src: string) {
9
9
  src = src
10
10
  .slice((__dirname + '/app').length);
@@ -39,6 +39,10 @@ export class Router extends BaseFileSystemRouter {
39
39
  type: 'group',
40
40
  parent: parent,
41
41
  path: '/group' + path,
42
+ $handler: {
43
+ src: filePath,
44
+ pick: []
45
+ },
42
46
  $component: {
43
47
  src: filePath,
44
48
  pick: ['default'],
@@ -47,6 +51,10 @@ export class Router extends BaseFileSystemRouter {
47
51
  src: filePath,
48
52
  pick: ['loader'],
49
53
  },
54
+ $options: {
55
+ src: filePath,
56
+ pick: ['options'],
57
+ },
50
58
  };
51
59
  }
52
60
 
@@ -54,6 +62,10 @@ export class Router extends BaseFileSystemRouter {
54
62
  return {
55
63
  type: 'route',
56
64
  path: '/route' + path,
65
+ $handler: {
66
+ src: filePath,
67
+ pick: []
68
+ },
57
69
  $component: {
58
70
  src: filePath,
59
71
  pick: ['default'],
@@ -66,6 +78,10 @@ export class Router extends BaseFileSystemRouter {
66
78
  src: filePath,
67
79
  pick: ['generateMeta'],
68
80
  },
81
+ $options: {
82
+ src: filePath,
83
+ pick: ['options'],
84
+ },
69
85
  };
70
86
  }
71
87
 
@@ -73,6 +89,10 @@ export class Router extends BaseFileSystemRouter {
73
89
  return {
74
90
  type: 'layout',
75
91
  path: '/layout' + path,
92
+ $handler: {
93
+ src: filePath,
94
+ pick: []
95
+ },
76
96
  $component: {
77
97
  src: filePath,
78
98
  pick: ['default'],
@@ -85,6 +105,10 @@ export class Router extends BaseFileSystemRouter {
85
105
  src: filePath,
86
106
  pick: ['generateMeta'],
87
107
  },
108
+ $options: {
109
+ src: filePath,
110
+ pick: ['options'],
111
+ },
88
112
  };
89
113
  }
90
114
 
@@ -92,6 +116,10 @@ export class Router extends BaseFileSystemRouter {
92
116
  return {
93
117
  type: 'error',
94
118
  path: '/error' + path,
119
+ $handler: {
120
+ src: filePath,
121
+ pick: []
122
+ },
95
123
  $component: {
96
124
  src: filePath,
97
125
  pick: ['default'],
@@ -100,6 +128,10 @@ export class Router extends BaseFileSystemRouter {
100
128
  src: filePath,
101
129
  pick: ['generateMeta'],
102
130
  },
131
+ $options: {
132
+ src: filePath,
133
+ pick: ['options'],
134
+ },
103
135
  };
104
136
  }
105
137
 
@@ -107,6 +139,10 @@ export class Router extends BaseFileSystemRouter {
107
139
  return {
108
140
  type: 'loading',
109
141
  path: '/loading' + path,
142
+ $handler: {
143
+ src: filePath,
144
+ pick: []
145
+ },
110
146
  $component: {
111
147
  src: filePath,
112
148
  pick: ['default'],
@@ -115,6 +151,10 @@ export class Router extends BaseFileSystemRouter {
115
151
  src: filePath,
116
152
  pick: ['generateMeta'],
117
153
  },
154
+ $options: {
155
+ src: filePath,
156
+ pick: ['options'],
157
+ },
118
158
  };
119
159
  }
120
160
 
@@ -122,6 +162,10 @@ export class Router extends BaseFileSystemRouter {
122
162
  return {
123
163
  type: 'not-found',
124
164
  path: '/not-found' + path,
165
+ $handler: {
166
+ src: filePath,
167
+ pick: []
168
+ },
125
169
  $component: {
126
170
  src: filePath,
127
171
  pick: ['default'],
@@ -130,6 +174,98 @@ export class Router extends BaseFileSystemRouter {
130
174
  src: filePath,
131
175
  pick: ['generateMeta'],
132
176
  },
177
+ $options: {
178
+ src: filePath,
179
+ pick: ['options'],
180
+ },
181
+ };
182
+ }
183
+ }
184
+ }
185
+
186
+ export class ClientRouter extends BaseFileSystemRouter {
187
+ toPath(src: string) {
188
+ src = src
189
+ .slice((__dirname + '/app').length);
190
+
191
+ const routePath = src
192
+ .replace(new RegExp(`\.(${(this.config.extensions ?? []).join('|')})$`), '')
193
+ .replace(/\/(page|layout|error|not-found|loading)$/, '');
194
+
195
+ return routePath?.length > 0 ? routePath : '/';
196
+ }
197
+
198
+ toRoute(filePath: string) {
199
+ const path = this.toPath(filePath);
200
+
201
+ const scopedPackageMatch = path.match(/@[^]+/g);
202
+ if (scopedPackageMatch) {
203
+ // Remove the scoped package part
204
+ const scopedPackage = scopedPackageMatch[0];
205
+ const parent = path.replace('/' + scopedPackage, '');
206
+ return {
207
+ type: 'group',
208
+ parent: parent,
209
+ path: '/group' + path,
210
+ $component: {
211
+ src: filePath,
212
+ pick: ['default', '$css'],
213
+ },
214
+ };
215
+ }
216
+
217
+ if ((/page\.(jsx|js|tsx|ts)$/).test(filePath)) {
218
+ return {
219
+ type: 'route',
220
+ path: '/route' + path,
221
+ $component: {
222
+ src: filePath,
223
+ pick: ['default', '$css'],
224
+ },
225
+ };
226
+ }
227
+
228
+ if ((/layout\.(jsx|js|tsx|ts)$/).test(filePath)) {
229
+ return {
230
+ type: 'layout',
231
+ path: '/layout' + path,
232
+ $component: {
233
+ src: filePath,
234
+ pick: ['default', '$css'],
235
+ },
236
+ };
237
+ }
238
+
239
+ if ((/error\.(jsx|js|tsx|ts)$/).test(filePath)) {
240
+ return {
241
+ type: 'error',
242
+ path: '/error' + path,
243
+ $component: {
244
+ src: filePath,
245
+ pick: ['default', '$css'],
246
+ },
247
+ };
248
+ }
249
+
250
+ if ((/loading\.(jsx|js|tsx|ts)$/).test(filePath)) {
251
+ return {
252
+ type: 'loading',
253
+ path: '/loading' + path,
254
+ $component: {
255
+ src: filePath,
256
+ pick: ['default', '$css'],
257
+ },
258
+ };
259
+ }
260
+
261
+ if ((/not-found\.(jsx|js|tsx|ts)$/).test(filePath) && path === '/') {
262
+ return {
263
+ type: 'not-found',
264
+ path: '/not-found' + path,
265
+ $component: {
266
+ src: filePath,
267
+ pick: ['default'],
268
+ },
133
269
  };
134
270
  }
135
271
  }
@@ -0,0 +1,5 @@
1
+ import { isServer } from 'solid-js/web';
2
+
3
+ if (!isServer) {
4
+ throw new Error('This module is only available on the server side.');
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@varlabs/create-solidstep",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Next Step SolidJS CLI for building web applications.",
5
5
  "type": "module",
6
6
  "author": "HamzaKV <hamzakv333@gmail.com>",