@kuratchi/js 0.0.16 → 0.0.17
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 +168 -11
- package/dist/cli.js +13 -13
- package/dist/compiler/client-module-pipeline.js +5 -5
- package/dist/compiler/compiler-shared.d.ts +18 -0
- package/dist/compiler/component-pipeline.js +4 -9
- package/dist/compiler/config-reading.d.ts +2 -1
- package/dist/compiler/config-reading.js +57 -0
- package/dist/compiler/durable-object-pipeline.js +1 -1
- package/dist/compiler/import-linking.js +2 -1
- package/dist/compiler/index.d.ts +6 -6
- package/dist/compiler/index.js +54 -22
- package/dist/compiler/layout-pipeline.js +6 -6
- package/dist/compiler/parser.js +10 -11
- package/dist/compiler/root-layout-pipeline.js +444 -429
- package/dist/compiler/route-pipeline.js +36 -41
- package/dist/compiler/route-state-pipeline.d.ts +1 -0
- package/dist/compiler/route-state-pipeline.js +3 -3
- package/dist/compiler/routes-module-feature-blocks.js +63 -63
- package/dist/compiler/routes-module-runtime-shell.js +65 -55
- package/dist/compiler/routes-module-types.d.ts +2 -1
- package/dist/compiler/server-module-pipeline.js +1 -1
- package/dist/compiler/template.js +24 -15
- package/dist/compiler/worker-output-pipeline.js +2 -2
- package/dist/runtime/context.d.ts +4 -0
- package/dist/runtime/context.js +40 -2
- package/dist/runtime/do.js +21 -6
- package/dist/runtime/generated-worker.d.ts +22 -0
- package/dist/runtime/generated-worker.js +154 -23
- package/dist/runtime/index.d.ts +3 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/router.d.ts +5 -1
- package/dist/runtime/router.js +116 -31
- package/dist/runtime/security.d.ts +101 -0
- package/dist/runtime/security.js +298 -0
- package/dist/runtime/types.d.ts +21 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,9 +16,9 @@ cd my-app
|
|
|
16
16
|
bun run dev
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
## How it works
|
|
20
|
-
|
|
21
|
-
`kuratchi build` (or `kuratchi watch`) scans `src/routes/` and generates framework output:
|
|
19
|
+
## How it works
|
|
20
|
+
|
|
21
|
+
`kuratchi build` (or `kuratchi watch`) scans `src/routes/` and generates framework output:
|
|
22
22
|
|
|
23
23
|
| File | Purpose |
|
|
24
24
|
|---|---|
|
|
@@ -26,13 +26,13 @@ bun run dev
|
|
|
26
26
|
| `.kuratchi/worker.js` | Stable wrangler entry - re-exports the fetch handler plus all Durable Object and Agent classes |
|
|
27
27
|
| `.kuratchi/do/*.js` | Generated Durable Object RPC proxy modules for `$durable-objects/*` imports |
|
|
28
28
|
|
|
29
|
-
Point wrangler at the entry and you're done. **No `src/index.ts` needed.**
|
|
30
|
-
|
|
31
|
-
For the framework's internal compiler/runtime orchestration and tracked implementation roadmap, see [ARCHITECTURE.md](./ARCHITECTURE.md).
|
|
32
|
-
|
|
33
|
-
```jsonc
|
|
34
|
-
// wrangler.jsonc
|
|
35
|
-
{
|
|
29
|
+
Point wrangler at the entry and you're done. **No `src/index.ts` needed.**
|
|
30
|
+
|
|
31
|
+
For the framework's internal compiler/runtime orchestration and tracked implementation roadmap, see [ARCHITECTURE.md](./ARCHITECTURE.md).
|
|
32
|
+
|
|
33
|
+
```jsonc
|
|
34
|
+
// wrangler.jsonc
|
|
35
|
+
{
|
|
36
36
|
"main": ".kuratchi/worker.js"
|
|
37
37
|
}
|
|
38
38
|
```
|
|
@@ -766,9 +766,166 @@ Kuratchi also exposes a framework build-mode flag:
|
|
|
766
766
|
- `dev` is compile-time framework state, not a generic process env var
|
|
767
767
|
- `@kuratchi/js/environment` is intended for server route code, not client `$:` scripts
|
|
768
768
|
|
|
769
|
+
## Security
|
|
770
|
+
|
|
771
|
+
Kuratchi includes built-in security features that are enabled by default or configurable via `kuratchi.config.ts`.
|
|
772
|
+
|
|
773
|
+
### Default Security Headers
|
|
774
|
+
|
|
775
|
+
All responses include these headers automatically:
|
|
776
|
+
|
|
777
|
+
- `X-Content-Type-Options: nosniff`
|
|
778
|
+
- `X-Frame-Options: DENY`
|
|
779
|
+
- `Referrer-Policy: strict-origin-when-cross-origin`
|
|
780
|
+
|
|
781
|
+
### CSRF Protection
|
|
782
|
+
|
|
783
|
+
CSRF protection is **enabled by default** for all form actions and RPC calls.
|
|
784
|
+
|
|
785
|
+
**How it works:**
|
|
786
|
+
1. A cryptographically random token is generated per session and stored in a cookie
|
|
787
|
+
2. The compiler auto-injects a hidden `_csrf` field into forms with `action={fn}`
|
|
788
|
+
3. The client bridge includes the CSRF token header in fetch action requests
|
|
789
|
+
4. Server validates the token using timing-safe comparison
|
|
790
|
+
|
|
791
|
+
No configuration required — it just works.
|
|
792
|
+
|
|
793
|
+
```html
|
|
794
|
+
<!-- CSRF token is auto-injected -->
|
|
795
|
+
<form action={submitForm}>
|
|
796
|
+
<input type="text" name="email" />
|
|
797
|
+
<button type="submit">Submit</button>
|
|
798
|
+
</form>
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
### Authentication Enforcement
|
|
802
|
+
|
|
803
|
+
Optionally require authentication for all RPC calls or form actions:
|
|
804
|
+
|
|
805
|
+
```ts
|
|
806
|
+
// kuratchi.config.ts
|
|
807
|
+
export default defineConfig({
|
|
808
|
+
security: {
|
|
809
|
+
rpcRequireAuth: true, // Require auth for all RPC calls (default: false)
|
|
810
|
+
actionRequireAuth: true, // Require auth for all form actions (default: false)
|
|
811
|
+
},
|
|
812
|
+
});
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
When enabled, unauthenticated requests return `401 Authentication required`. The check looks for `locals.user` or `locals.session.user`, which is populated by `@kuratchi/auth`.
|
|
816
|
+
|
|
817
|
+
For per-function control, use guards in individual functions instead:
|
|
818
|
+
|
|
819
|
+
```ts
|
|
820
|
+
import { requireAuth } from '@kuratchi/auth';
|
|
821
|
+
|
|
822
|
+
export async function deleteItem(formData: FormData) {
|
|
823
|
+
await requireAuth(); // Throws 401 if not authenticated
|
|
824
|
+
// ... action logic
|
|
825
|
+
}
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
### Configurable Security Headers
|
|
829
|
+
|
|
830
|
+
Add CSP, HSTS, and Permissions-Policy headers:
|
|
831
|
+
|
|
832
|
+
```ts
|
|
833
|
+
// kuratchi.config.ts
|
|
834
|
+
export default defineConfig({
|
|
835
|
+
security: {
|
|
836
|
+
contentSecurityPolicy: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'",
|
|
837
|
+
strictTransportSecurity: "max-age=31536000; includeSubDomains",
|
|
838
|
+
permissionsPolicy: "camera=(), microphone=(), geolocation=()",
|
|
839
|
+
},
|
|
840
|
+
});
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
### HTML Sanitization
|
|
844
|
+
|
|
845
|
+
The `{@html}` directive automatically sanitizes output to prevent XSS:
|
|
846
|
+
|
|
847
|
+
- Removes dangerous elements: `<script>`, `<iframe>`, `<object>`, `<embed>`, `<style>`, `<template>`, etc.
|
|
848
|
+
- Strips all `on*` event handlers
|
|
849
|
+
- Neutralizes `javascript:` and `vbscript:` URLs
|
|
850
|
+
- Removes `data:` URLs from `src` attributes
|
|
851
|
+
|
|
852
|
+
For user-generated HTML, we recommend using DOMPurify on the client side for maximum security.
|
|
853
|
+
|
|
854
|
+
### Fragment Refresh Security
|
|
855
|
+
|
|
856
|
+
Fragment IDs used for `data-poll` are automatically signed to prevent attackers from probing for data:
|
|
857
|
+
|
|
858
|
+
- Fragment IDs are signed at render time with the session's CSRF token
|
|
859
|
+
- Server validates signatures before returning fragment content
|
|
860
|
+
- Invalid or unsigned fragments return 403 when CSRF is enabled
|
|
861
|
+
|
|
862
|
+
This is automatic — no configuration required.
|
|
863
|
+
|
|
864
|
+
### Query Override Protection
|
|
865
|
+
|
|
866
|
+
Query function calls via `x-kuratchi-query-fn` headers are validated against a whitelist:
|
|
867
|
+
|
|
868
|
+
- Only query functions registered for the current route can be called
|
|
869
|
+
- Prevents attackers from invoking arbitrary RPC functions
|
|
870
|
+
- Returns 403 for unauthorized query function calls
|
|
871
|
+
|
|
872
|
+
This is automatic — no configuration required.
|
|
873
|
+
|
|
874
|
+
### Client Bridge Security
|
|
875
|
+
|
|
876
|
+
Client-side handler invocation is protected against injection attacks:
|
|
877
|
+
|
|
878
|
+
- Route and handler IDs are validated against safe patterns
|
|
879
|
+
- Prototype pollution attempts are blocked (`__proto__`, `constructor`, `prototype`)
|
|
880
|
+
- Uses `hasOwnProperty` checks to prevent prototype chain traversal
|
|
881
|
+
|
|
882
|
+
This is automatic — no configuration required.
|
|
883
|
+
|
|
884
|
+
### Error Information Protection
|
|
885
|
+
|
|
886
|
+
Error messages are sanitized to prevent information leakage in production:
|
|
887
|
+
|
|
888
|
+
- Generic errors show full details in dev mode only
|
|
889
|
+
- Production uses safe fallback messages ("Internal Server Error", "Action failed")
|
|
890
|
+
- `ActionError` and `PageError` messages are always shown (developer-controlled)
|
|
891
|
+
|
|
892
|
+
```ts
|
|
893
|
+
// Safe to show - developer-controlled message
|
|
894
|
+
throw new ActionError('Invalid email format');
|
|
895
|
+
|
|
896
|
+
// In production: "Internal Server Error" (details hidden)
|
|
897
|
+
// In dev mode: Full error message for debugging
|
|
898
|
+
throw new Error('Database connection failed at line 42');
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
### Full Security Configuration
|
|
902
|
+
|
|
903
|
+
```ts
|
|
904
|
+
// kuratchi.config.ts
|
|
905
|
+
export default defineConfig({
|
|
906
|
+
security: {
|
|
907
|
+
// CSRF Protection (enabled by default)
|
|
908
|
+
csrfEnabled: true,
|
|
909
|
+
csrfCookieName: '__kuratchi_csrf',
|
|
910
|
+
csrfHeaderName: 'x-kuratchi-csrf',
|
|
911
|
+
|
|
912
|
+
// Authentication Enforcement
|
|
913
|
+
rpcRequireAuth: false, // Require auth for RPC calls
|
|
914
|
+
actionRequireAuth: false, // Require auth for form actions
|
|
915
|
+
|
|
916
|
+
// Security Headers
|
|
917
|
+
contentSecurityPolicy: "default-src 'self'",
|
|
918
|
+
strictTransportSecurity: "max-age=31536000; includeSubDomains",
|
|
919
|
+
permissionsPolicy: "camera=(), microphone=()",
|
|
920
|
+
},
|
|
921
|
+
});
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
For a comprehensive security analysis and roadmap, see [SECURITY.md](./SECURITY.md).
|
|
925
|
+
|
|
769
926
|
## `kuratchi.config.ts`
|
|
770
927
|
|
|
771
|
-
Optional. Required only when using framework integrations (ORM, auth, UI).
|
|
928
|
+
Optional. Required only when using framework integrations (ORM, auth, UI, security).
|
|
772
929
|
|
|
773
930
|
**Durable Objects are auto-discovered** — no config needed unless you need `stubId` for auth integration.
|
|
774
931
|
|
package/dist/cli.js
CHANGED
|
@@ -30,14 +30,14 @@ async function main() {
|
|
|
30
30
|
await runCreate();
|
|
31
31
|
return;
|
|
32
32
|
default:
|
|
33
|
-
console.log(`
|
|
34
|
-
KuratchiJS CLI
|
|
35
|
-
|
|
36
|
-
Usage:
|
|
37
|
-
kuratchi create [name] Scaffold a new KuratchiJS project
|
|
38
|
-
kuratchi build Compile routes once
|
|
39
|
-
kuratchi dev Compile, watch for changes, and start wrangler dev server
|
|
40
|
-
kuratchi watch Compile + watch only (no wrangler — for custom setups)
|
|
33
|
+
console.log(`
|
|
34
|
+
KuratchiJS CLI
|
|
35
|
+
|
|
36
|
+
Usage:
|
|
37
|
+
kuratchi create [name] Scaffold a new KuratchiJS project
|
|
38
|
+
kuratchi build Compile routes once
|
|
39
|
+
kuratchi dev Compile, watch for changes, and start wrangler dev server
|
|
40
|
+
kuratchi watch Compile + watch only (no wrangler — for custom setups)
|
|
41
41
|
`);
|
|
42
42
|
process.exit(1);
|
|
43
43
|
}
|
|
@@ -49,10 +49,10 @@ async function runCreate() {
|
|
|
49
49
|
const positional = remaining.filter(a => !a.startsWith('-'));
|
|
50
50
|
await create(positional[0], flags);
|
|
51
51
|
}
|
|
52
|
-
function runBuild(isDev = false) {
|
|
52
|
+
async function runBuild(isDev = false) {
|
|
53
53
|
console.log('[kuratchi] Compiling...');
|
|
54
54
|
try {
|
|
55
|
-
const outFile = compile({ projectDir, isDev });
|
|
55
|
+
const outFile = await compile({ projectDir, isDev });
|
|
56
56
|
console.log(`[kuratchi] Built → ${path.relative(projectDir, outFile)}`);
|
|
57
57
|
}
|
|
58
58
|
catch (err) {
|
|
@@ -61,7 +61,7 @@ function runBuild(isDev = false) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
async function runWatch(withWrangler = false) {
|
|
64
|
-
runBuild(true);
|
|
64
|
+
await runBuild(true);
|
|
65
65
|
const routesDir = path.join(projectDir, 'src', 'routes');
|
|
66
66
|
const serverDir = path.join(projectDir, 'src', 'server');
|
|
67
67
|
const watchDirs = [routesDir, serverDir].filter(d => fs.existsSync(d));
|
|
@@ -69,10 +69,10 @@ async function runWatch(withWrangler = false) {
|
|
|
69
69
|
const triggerRebuild = () => {
|
|
70
70
|
if (rebuildTimeout)
|
|
71
71
|
clearTimeout(rebuildTimeout);
|
|
72
|
-
rebuildTimeout = setTimeout(() => {
|
|
72
|
+
rebuildTimeout = setTimeout(async () => {
|
|
73
73
|
console.log('[kuratchi] File changed, rebuilding...');
|
|
74
74
|
try {
|
|
75
|
-
compile({ projectDir, isDev: true });
|
|
75
|
+
await compile({ projectDir, isDev: true });
|
|
76
76
|
console.log('[kuratchi] Rebuilt.');
|
|
77
77
|
}
|
|
78
78
|
catch (err) {
|
|
@@ -2,7 +2,6 @@ import * as crypto from 'node:crypto';
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { collectReferencedIdentifiers, parseImportStatement } from './import-linking.js';
|
|
5
|
-
import { transpileTypeScript } from './transpile.js';
|
|
6
5
|
function resolveExistingModuleFile(absBase) {
|
|
7
6
|
const candidates = [
|
|
8
7
|
absBase,
|
|
@@ -179,12 +178,13 @@ class CompilerBackedClientRouteRegistry {
|
|
|
179
178
|
const registrationEntries = Array.from(this.handlerByKey.values()).map((record) => {
|
|
180
179
|
return `${JSON.stringify(record.id)}: (args, event, element) => ${record.calleeExpr}(...args, event, element)`;
|
|
181
180
|
});
|
|
182
|
-
|
|
181
|
+
// TypeScript is preserved — wrangler's esbuild handles transpilation
|
|
182
|
+
const source = [
|
|
183
183
|
...importLines,
|
|
184
184
|
`window.__kuratchiClient?.register(${JSON.stringify(this.routeId)}, {`,
|
|
185
185
|
registrationEntries.map((entry) => ` ${entry},`).join('\n'),
|
|
186
186
|
`});`,
|
|
187
|
-
].join('\n')
|
|
187
|
+
].join('\n');
|
|
188
188
|
const asset = buildAsset(assetName, source);
|
|
189
189
|
this.compiler.registerAsset(asset);
|
|
190
190
|
return { assetName, asset };
|
|
@@ -241,9 +241,9 @@ class CompilerBackedClientModuleCompiler {
|
|
|
241
241
|
if (!fs.existsSync(resolved)) {
|
|
242
242
|
throw new Error(`[kuratchi compiler] Browser module not found: ${resolved}`);
|
|
243
243
|
}
|
|
244
|
+
// TypeScript is preserved — wrangler's esbuild handles transpilation
|
|
244
245
|
const source = fs.readFileSync(resolved, 'utf-8');
|
|
245
|
-
let rewritten =
|
|
246
|
-
rewritten = rewriteImportSpecifiers(rewritten, (spec) => {
|
|
246
|
+
let rewritten = rewriteImportSpecifiers(source, (spec) => {
|
|
247
247
|
const targetAbs = resolveClientImportTarget(this.srcDir, resolved, spec);
|
|
248
248
|
const targetAssetName = this.transformClientModule(targetAbs);
|
|
249
249
|
return toRelativeSpecifier(assetName, targetAssetName);
|
|
@@ -18,6 +18,24 @@ export interface AuthConfigEntry {
|
|
|
18
18
|
hasTurnstile: boolean;
|
|
19
19
|
hasOrganization: boolean;
|
|
20
20
|
}
|
|
21
|
+
export interface SecurityConfigEntry {
|
|
22
|
+
/** Enable CSRF protection for actions and RPC (default: true) */
|
|
23
|
+
csrfEnabled: boolean;
|
|
24
|
+
/** CSRF cookie name (default: '__kuratchi_csrf') */
|
|
25
|
+
csrfCookieName: string;
|
|
26
|
+
/** CSRF header name for fetch requests (default: 'x-kuratchi-csrf') */
|
|
27
|
+
csrfHeaderName: string;
|
|
28
|
+
/** Require authentication for RPC calls (default: false) */
|
|
29
|
+
rpcRequireAuth: boolean;
|
|
30
|
+
/** Require authentication for form actions (default: false) */
|
|
31
|
+
actionRequireAuth: boolean;
|
|
32
|
+
/** Content Security Policy directive string (default: null - no CSP) */
|
|
33
|
+
contentSecurityPolicy: string | null;
|
|
34
|
+
/** Strict-Transport-Security header (default: null - no HSTS) */
|
|
35
|
+
strictTransportSecurity: string | null;
|
|
36
|
+
/** Permissions-Policy header (default: null) */
|
|
37
|
+
permissionsPolicy: string | null;
|
|
38
|
+
}
|
|
21
39
|
export interface DoConfigEntry {
|
|
22
40
|
binding: string;
|
|
23
41
|
className: string;
|
|
@@ -3,7 +3,6 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { parseFile, stripTopLevelImports } from './parser.js';
|
|
5
5
|
import { compileTemplate } from './template.js';
|
|
6
|
-
import { transpileTypeScript } from './transpile.js';
|
|
7
6
|
import { buildDevAliasDeclarations } from './script-transform.js';
|
|
8
7
|
function resolvePackageComponent(projectDir, pkgName, componentFile) {
|
|
9
8
|
const nmPath = path.join(projectDir, 'node_modules', pkgName, 'src', 'lib', componentFile + '.html');
|
|
@@ -62,12 +61,8 @@ export function createComponentCompiler(options) {
|
|
|
62
61
|
const parsed = parseFile(rawSource, { kind: 'component', filePath });
|
|
63
62
|
const propsCode = parsed.script ? stripTopLevelImports(parsed.script) : '';
|
|
64
63
|
const devDecls = buildDevAliasDeclarations(parsed.devAliases, isDev);
|
|
64
|
+
// TypeScript is preserved — wrangler's esbuild handles transpilation
|
|
65
65
|
const effectivePropsCode = [devDecls, propsCode].filter(Boolean).join('\n');
|
|
66
|
-
const transpiledPropsCode = propsCode
|
|
67
|
-
? transpileTypeScript(effectivePropsCode, `component-script:${fileName}.ts`)
|
|
68
|
-
: devDecls
|
|
69
|
-
? transpileTypeScript(devDecls, `component-script:${fileName}.ts`)
|
|
70
|
-
: '';
|
|
71
66
|
let source = parsed.template;
|
|
72
67
|
let styleBlock = '';
|
|
73
68
|
const styleMatch = source.match(/<style[\s>][\s\S]*?<\/style>/i);
|
|
@@ -99,11 +94,11 @@ export function createComponentCompiler(options) {
|
|
|
99
94
|
}
|
|
100
95
|
componentActionCache.set(fileName, actionPropNames);
|
|
101
96
|
const body = compileTemplate(source, subComponentNames, undefined, undefined);
|
|
102
|
-
const scopeOpen = `
|
|
103
|
-
const scopeClose = `
|
|
97
|
+
const scopeOpen = `__parts.push('<div class="${scopeHash}">');`;
|
|
98
|
+
const scopeClose = `__parts.push('</div>');`;
|
|
104
99
|
const bodyLines = body.split('\n');
|
|
105
100
|
const scopedBody = [bodyLines[0], scopeOpen, ...bodyLines.slice(1), scopeClose].join('\n');
|
|
106
|
-
const fnBody =
|
|
101
|
+
const fnBody = effectivePropsCode ? `${effectivePropsCode}\n ${scopedBody}` : scopedBody;
|
|
107
102
|
const compiled = `function ${funcName}(props, __esc) {\n ${fnBody}\n return __html;\n}`;
|
|
108
103
|
compiledComponentCache.set(fileName, compiled);
|
|
109
104
|
return compiled;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AuthConfigEntry, type DoConfigEntry, type OrmDatabaseEntry, type WorkerClassConfigEntry } from './compiler-shared.js';
|
|
1
|
+
import { type AuthConfigEntry, type DoConfigEntry, type OrmDatabaseEntry, type SecurityConfigEntry, type WorkerClassConfigEntry } from './compiler-shared.js';
|
|
2
2
|
export declare function readUiTheme(projectDir: string): string | null;
|
|
3
3
|
export declare function readUiConfigValues(projectDir: string): {
|
|
4
4
|
theme: string;
|
|
@@ -9,3 +9,4 @@ export declare function readAuthConfig(projectDir: string): AuthConfigEntry | nu
|
|
|
9
9
|
export declare function readDoConfig(projectDir: string): DoConfigEntry[];
|
|
10
10
|
export declare function readWorkerClassConfig(projectDir: string, key: 'containers' | 'workflows'): WorkerClassConfigEntry[];
|
|
11
11
|
export declare function readAssetsPrefix(projectDir: string): string;
|
|
12
|
+
export declare function readSecurityConfig(projectDir: string): SecurityConfigEntry;
|
|
@@ -321,3 +321,60 @@ export function readAssetsPrefix(projectDir) {
|
|
|
321
321
|
prefix += '/';
|
|
322
322
|
return prefix;
|
|
323
323
|
}
|
|
324
|
+
export function readSecurityConfig(projectDir) {
|
|
325
|
+
const defaults = {
|
|
326
|
+
csrfEnabled: true,
|
|
327
|
+
csrfCookieName: '__kuratchi_csrf',
|
|
328
|
+
csrfHeaderName: 'x-kuratchi-csrf',
|
|
329
|
+
rpcRequireAuth: false,
|
|
330
|
+
actionRequireAuth: false,
|
|
331
|
+
contentSecurityPolicy: null,
|
|
332
|
+
strictTransportSecurity: null,
|
|
333
|
+
permissionsPolicy: null,
|
|
334
|
+
};
|
|
335
|
+
const configPath = path.join(projectDir, 'kuratchi.config.ts');
|
|
336
|
+
if (!fs.existsSync(configPath))
|
|
337
|
+
return defaults;
|
|
338
|
+
const source = fs.readFileSync(configPath, 'utf-8');
|
|
339
|
+
const securityBlock = readConfigBlock(source, 'security');
|
|
340
|
+
if (!securityBlock)
|
|
341
|
+
return defaults;
|
|
342
|
+
const body = securityBlock.body;
|
|
343
|
+
// Parse CSRF settings
|
|
344
|
+
const csrfEnabledMatch = body.match(/csrfEnabled\s*:\s*(true|false)/);
|
|
345
|
+
if (csrfEnabledMatch) {
|
|
346
|
+
defaults.csrfEnabled = csrfEnabledMatch[1] === 'true';
|
|
347
|
+
}
|
|
348
|
+
const csrfCookieMatch = body.match(/csrfCookieName\s*:\s*['"]([^'"]+)['"]/);
|
|
349
|
+
if (csrfCookieMatch) {
|
|
350
|
+
defaults.csrfCookieName = csrfCookieMatch[1];
|
|
351
|
+
}
|
|
352
|
+
const csrfHeaderMatch = body.match(/csrfHeaderName\s*:\s*['"]([^'"]+)['"]/);
|
|
353
|
+
if (csrfHeaderMatch) {
|
|
354
|
+
defaults.csrfHeaderName = csrfHeaderMatch[1];
|
|
355
|
+
}
|
|
356
|
+
// Parse RPC settings
|
|
357
|
+
const rpcAuthMatch = body.match(/rpcRequireAuth\s*:\s*(true|false)/);
|
|
358
|
+
if (rpcAuthMatch) {
|
|
359
|
+
defaults.rpcRequireAuth = rpcAuthMatch[1] === 'true';
|
|
360
|
+
}
|
|
361
|
+
// Parse action settings
|
|
362
|
+
const actionAuthMatch = body.match(/actionRequireAuth\s*:\s*(true|false)/);
|
|
363
|
+
if (actionAuthMatch) {
|
|
364
|
+
defaults.actionRequireAuth = actionAuthMatch[1] === 'true';
|
|
365
|
+
}
|
|
366
|
+
// Parse security headers
|
|
367
|
+
const cspMatch = body.match(/contentSecurityPolicy\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
368
|
+
if (cspMatch) {
|
|
369
|
+
defaults.contentSecurityPolicy = cspMatch[1];
|
|
370
|
+
}
|
|
371
|
+
const hstsMatch = body.match(/strictTransportSecurity\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
372
|
+
if (hstsMatch) {
|
|
373
|
+
defaults.strictTransportSecurity = hstsMatch[1];
|
|
374
|
+
}
|
|
375
|
+
const permMatch = body.match(/permissionsPolicy\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
376
|
+
if (permMatch) {
|
|
377
|
+
defaults.permissionsPolicy = permMatch[1];
|
|
378
|
+
}
|
|
379
|
+
return defaults;
|
|
380
|
+
}
|
|
@@ -65,7 +65,7 @@ export function discoverDurableObjects(srcDir, configDoEntries, ormDatabases) {
|
|
|
65
65
|
}
|
|
66
66
|
export function generateHandlerProxy(handler, opts) {
|
|
67
67
|
const doDir = path.join(opts.projectDir, '.kuratchi', 'do');
|
|
68
|
-
const origRelPath = path.relative(doDir, handler.absPath).replace(/\\/g, '/')
|
|
68
|
+
const origRelPath = path.relative(doDir, handler.absPath).replace(/\\/g, '/');
|
|
69
69
|
const handlerLocal = `__handler_${toSafeIdentifier(handler.fileName)}`;
|
|
70
70
|
const lifecycle = new Set(['constructor', 'fetch', 'alarm', 'webSocketMessage', 'webSocketClose', 'webSocketError']);
|
|
71
71
|
const rpcFunctions = handler.classMethods
|
|
@@ -93,6 +93,7 @@ export function filterImportsByNeededBindings(imports, neededBindings) {
|
|
|
93
93
|
}
|
|
94
94
|
return selected;
|
|
95
95
|
}
|
|
96
|
+
const RESERVED_RENDER_VARS = new Set(['params', 'breadcrumbs']);
|
|
96
97
|
export function linkRouteServerImports(opts) {
|
|
97
98
|
const fnToModule = {};
|
|
98
99
|
const routeImportDeclMap = new Map();
|
|
@@ -119,7 +120,7 @@ export function linkRouteServerImports(opts) {
|
|
|
119
120
|
continue;
|
|
120
121
|
}
|
|
121
122
|
fnToModule[binding.local] = moduleId;
|
|
122
|
-
if (!routeImportDeclMap.has(binding.local)) {
|
|
123
|
+
if (!routeImportDeclMap.has(binding.local) && !RESERVED_RENDER_VARS.has(binding.local)) {
|
|
123
124
|
const accessExpr = binding.imported === 'default' ? `${moduleId}.default` : `${moduleId}.${binding.imported}`;
|
|
124
125
|
routeImportDeclMap.set(binding.local, `const ${binding.local} = ${accessExpr};`);
|
|
125
126
|
}
|
package/dist/compiler/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { compileTemplate, generateRenderFunction } from './template.js';
|
|
|
7
7
|
export interface CompileOptions {
|
|
8
8
|
/** Absolute path to the project root */
|
|
9
9
|
projectDir: string;
|
|
10
|
-
/** Override path for routes.
|
|
10
|
+
/** Override path for routes.ts (default: .kuratchi/routes.ts). worker.ts is always co-located. */
|
|
11
11
|
outFile?: string;
|
|
12
12
|
/** Whether this is a dev build (sets __kuratchi_DEV__ global) */
|
|
13
13
|
isDev?: boolean;
|
|
@@ -25,12 +25,12 @@ export interface CompiledRoute {
|
|
|
25
25
|
hasRpc: boolean;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* Compile a project's src/routes/ into .kuratchi/routes.
|
|
28
|
+
* Compile a project's src/routes/ into .kuratchi/routes.ts
|
|
29
29
|
*
|
|
30
|
-
* The generated module exports { app }
|
|
30
|
+
* The generated module exports { app } — an object with a fetch() method
|
|
31
31
|
* that handles routing, load functions, form actions, and rendering.
|
|
32
|
-
* Returns the path to .kuratchi/worker.
|
|
33
|
-
* re-exports everything from routes.
|
|
32
|
+
* Returns the path to .kuratchi/worker.ts — the stable wrangler entry point that
|
|
33
|
+
* re-exports everything from routes.ts (default fetch handler + named DO class exports).
|
|
34
34
|
* No src/index.ts is needed in user projects.
|
|
35
35
|
*/
|
|
36
|
-
export declare function compile(options: CompileOptions): string
|
|
36
|
+
export declare function compile(options: CompileOptions): Promise<string>;
|