@varlabs/create-solidstep 0.1.7 → 0.2.0
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/generate/app/instrumentation.ts +33 -0
- package/generate/app/layout.tsx +37 -39
- package/generate/app/middleware.ts +66 -60
- package/generate/app/page.tsx +37 -40
- package/generate/app.config.ts +18 -3
- package/generate/package.json +1 -1
- package/generate/sqlite-stub.mjs +9 -0
- package/generate/tsconfig.json +3 -2
- package/package.json +1 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineInstrumentation } from 'solidstep/utils/instrumentation';
|
|
2
|
+
|
|
3
|
+
export default defineInstrumentation({
|
|
4
|
+
async register() {
|
|
5
|
+
// Called once at server startup.
|
|
6
|
+
// Initialize your telemetry SDK here (e.g., OpenTelemetry, Sentry).
|
|
7
|
+
console.log('[instrumentation] Server starting...');
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
async onRequest(request, context) {
|
|
11
|
+
// Called before each request is processed.
|
|
12
|
+
// context.metadata is a mutable object you can use to pass data between hooks.
|
|
13
|
+
context.metadata.requestId = crypto.randomUUID();
|
|
14
|
+
console.log(
|
|
15
|
+
`[instrumentation] ${request.method} ${context.pathname} (${context.routeType})`,
|
|
16
|
+
);
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
async onResponseEnd(request, context) {
|
|
20
|
+
// Called after the response stream is complete.
|
|
21
|
+
console.log(
|
|
22
|
+
`[instrumentation] ${context.statusCode} ${context.pathname} ${context.duration.toFixed(1)}ms`,
|
|
23
|
+
);
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
async onRequestError(error, request, context) {
|
|
27
|
+
// Called when an unhandled error occurs during request processing.
|
|
28
|
+
console.error(
|
|
29
|
+
`[instrumentation] Error in ${context.pathname}:`,
|
|
30
|
+
error.message,
|
|
31
|
+
);
|
|
32
|
+
},
|
|
33
|
+
});
|
package/generate/app/layout.tsx
CHANGED
|
@@ -1,39 +1,37 @@
|
|
|
1
|
-
import type { Component, JSX } from 'solid-js';
|
|
2
|
-
import './globals.css';
|
|
3
|
-
|
|
4
|
-
export const generateMeta = (
|
|
5
|
-
'title': {
|
|
6
|
-
type: 'title',
|
|
7
|
-
attributes: {},
|
|
8
|
-
content: 'SolidStep App'
|
|
9
|
-
},
|
|
10
|
-
'description': {
|
|
11
|
-
type: 'meta',
|
|
12
|
-
attributes: {
|
|
13
|
-
name: 'description',
|
|
14
|
-
content: 'This is simple SolidStep application.'
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
'favicon': {
|
|
18
|
-
type: 'link',
|
|
19
|
-
attributes: {
|
|
20
|
-
rel: 'icon',
|
|
21
|
-
href: '/favicon-32x32.png',
|
|
22
|
-
type: 'image/png'
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const Layout: Component<{
|
|
28
|
-
children: JSX.Element;
|
|
29
|
-
}> = ({
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
export default Layout;
|
|
1
|
+
import type { Component, JSX } from 'solid-js';
|
|
2
|
+
import './globals.css';
|
|
3
|
+
|
|
4
|
+
export const generateMeta = () => ({
|
|
5
|
+
'title': {
|
|
6
|
+
type: 'title',
|
|
7
|
+
attributes: {},
|
|
8
|
+
content: 'SolidStep App'
|
|
9
|
+
},
|
|
10
|
+
'description': {
|
|
11
|
+
type: 'meta',
|
|
12
|
+
attributes: {
|
|
13
|
+
name: 'description',
|
|
14
|
+
content: 'This is a simple SolidStep application.'
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
'favicon': {
|
|
18
|
+
type: 'link',
|
|
19
|
+
attributes: {
|
|
20
|
+
rel: 'icon',
|
|
21
|
+
href: '/favicon-32x32.png',
|
|
22
|
+
type: 'image/png'
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const Layout: Component<{
|
|
28
|
+
children: () => JSX.Element;
|
|
29
|
+
}> = (props) => {
|
|
30
|
+
return (
|
|
31
|
+
<body>
|
|
32
|
+
{props.children()}
|
|
33
|
+
</body>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default Layout;
|
|
@@ -1,60 +1,66 @@
|
|
|
1
|
-
import { defineMiddleware } from '
|
|
2
|
-
import { createBasePolicy, serializePolicy, withNonce } from 'solidstep/utils/csp';
|
|
3
|
-
import { cors } from 'solidstep/utils/cors';
|
|
4
|
-
import { csrf } from 'solidstep/utils/csrf';
|
|
5
|
-
import { randomBytes } from 'node:crypto';
|
|
6
|
-
|
|
7
|
-
const trustedOrigins = ['https://example.com', 'https://another-example.com'];
|
|
8
|
-
|
|
9
|
-
const corsMiddleware = cors(trustedOrigins);
|
|
10
|
-
const csrfMiddleware = csrf(trustedOrigins);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
onRequest:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
event.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
event.node.req.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
event.node.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
1
|
+
import { defineMiddleware, type Middleware } from 'solidstep/utils/middleware';
|
|
2
|
+
import { createBasePolicy, serializePolicy, withNonce } from 'solidstep/utils/csp';
|
|
3
|
+
import { cors } from 'solidstep/utils/cors';
|
|
4
|
+
import { csrf } from 'solidstep/utils/csrf';
|
|
5
|
+
import { randomBytes } from 'node:crypto';
|
|
6
|
+
|
|
7
|
+
const trustedOrigins = ['https://example.com', 'https://another-example.com'];
|
|
8
|
+
|
|
9
|
+
const corsMiddleware = cors(trustedOrigins);
|
|
10
|
+
const csrfMiddleware = csrf(trustedOrigins);
|
|
11
|
+
|
|
12
|
+
// Logs every request. A lightweight composable unit.
|
|
13
|
+
const logger: Middleware = {
|
|
14
|
+
onRequest: (event) => {
|
|
15
|
+
console.log(`[req] ${event.node.req.method} ${event.path}`);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Sets a per-request CSP nonce, enforces CSRF on unsafe methods, and applies
|
|
20
|
+
// CORS for trusted origins. Short-circuits the chain by returning a Response.
|
|
21
|
+
const security: Middleware = {
|
|
22
|
+
onRequest: (event) => {
|
|
23
|
+
const nonce = randomBytes(16).toString('base64');
|
|
24
|
+
(event as any).locals = { cspNonce: nonce };
|
|
25
|
+
|
|
26
|
+
let policy = createBasePolicy();
|
|
27
|
+
policy = withNonce(policy, nonce);
|
|
28
|
+
event.node.res.setHeader('Content-Security-Policy', serializePolicy(policy));
|
|
29
|
+
event.node.res.setHeader('Vary', 'Origin, Access-Control-Request-Method');
|
|
30
|
+
|
|
31
|
+
const origin = (event.node.req.headers.origin as string) || '';
|
|
32
|
+
const protocol = origin.startsWith('https') ? 'https' : 'http';
|
|
33
|
+
const requestUrl = new URL(
|
|
34
|
+
event.node.req.url || '/',
|
|
35
|
+
`${protocol}://${event.node.req.headers.host || 'localhost'}`,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const csrfResult = csrfMiddleware(
|
|
39
|
+
event.node.req.method || 'GET',
|
|
40
|
+
requestUrl,
|
|
41
|
+
origin,
|
|
42
|
+
event.node.req.headers.referer,
|
|
43
|
+
);
|
|
44
|
+
if (!csrfResult.success) {
|
|
45
|
+
return new Response(csrfResult.message, { status: 403 });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (origin) {
|
|
49
|
+
const corsHeaders = corsMiddleware(
|
|
50
|
+
origin,
|
|
51
|
+
event.node.req.method === 'OPTIONS',
|
|
52
|
+
);
|
|
53
|
+
for (const [key, value] of Object.entries(corsHeaders)) {
|
|
54
|
+
event.node.res.setHeader(key, value);
|
|
55
|
+
}
|
|
56
|
+
if (
|
|
57
|
+
event.node.req.method === 'OPTIONS' &&
|
|
58
|
+
event.node.req.headers['access-control-request-method']
|
|
59
|
+
) {
|
|
60
|
+
return new Response(null, { status: 204 });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default defineMiddleware([logger, security]);
|
package/generate/app/page.tsx
CHANGED
|
@@ -1,40 +1,37 @@
|
|
|
1
|
-
import { defineLoader, type LoaderDataFromFunction } from 'solidstep/utils/loader';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export default Page;
|
|
1
|
+
import { defineLoader, type LoaderDataFromFunction } from 'solidstep/utils/loader';
|
|
2
|
+
|
|
3
|
+
export const loader = defineLoader(async () => {
|
|
4
|
+
const response = await fetch('https://jsonplaceholder.typicode.com/todos/2');
|
|
5
|
+
if (!response.ok) {
|
|
6
|
+
throw new Error('Failed to fetch data');
|
|
7
|
+
}
|
|
8
|
+
const data = (await response.json()) as {
|
|
9
|
+
userId: number;
|
|
10
|
+
id: number;
|
|
11
|
+
title: string;
|
|
12
|
+
completed: boolean;
|
|
13
|
+
};
|
|
14
|
+
return data;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const generateMeta = () => ({
|
|
18
|
+
'title': {
|
|
19
|
+
type: 'title',
|
|
20
|
+
attributes: {},
|
|
21
|
+
content: 'SolidStep Main Page'
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
type LoaderData = LoaderDataFromFunction<typeof loader>;
|
|
26
|
+
|
|
27
|
+
const Page = (props: { loaderData: LoaderData }) => {
|
|
28
|
+
return (
|
|
29
|
+
<main>
|
|
30
|
+
<h1>Welcome to SolidStep</h1>
|
|
31
|
+
<p>Edit <code>app/page.tsx</code> to get started.</p>
|
|
32
|
+
<p>Loaded todo #{props.loaderData.id}: {props.loaderData.title}</p>
|
|
33
|
+
</main>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default Page;
|
package/generate/app.config.ts
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
-
import { defineConfig } from 'solidstep';
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { defineConfig } from 'solidstep';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
|
|
4
|
+
// Nitro's default Node database connector bundles an unused `import 'node:sqlite'`,
|
|
5
|
+
// a builtin that only exists in Node 22.5+. This starter uses no database, so we both
|
|
6
|
+
// pass an empty `database` config and alias the builtin to an empty stub, keeping the
|
|
7
|
+
// production server runnable on Node 20/21.
|
|
8
|
+
const sqliteStub = fileURLToPath(new URL('./sqlite-stub.mjs', import.meta.url));
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
server: {
|
|
12
|
+
preset: 'node-server',
|
|
13
|
+
database: {},
|
|
14
|
+
alias: {
|
|
15
|
+
'node:sqlite': sqliteStub,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|
package/generate/package.json
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Stub for `node:sqlite`.
|
|
2
|
+
//
|
|
3
|
+
// Nitro's default Node database connector bundles an (unused) `import 'node:sqlite'`
|
|
4
|
+
// into the server output. That builtin only exists in Node 22.5+, so on Node 20/21
|
|
5
|
+
// the server crashes at startup even though no database is ever used. This app has
|
|
6
|
+
// no database, so app.config.ts aliases `node:sqlite` to this empty stub.
|
|
7
|
+
export class DatabaseSync {}
|
|
8
|
+
export class StatementSync {}
|
|
9
|
+
export default { DatabaseSync, StatementSync };
|
package/generate/tsconfig.json
CHANGED