@korajs/cli 1.0.0-beta.5 → 1.0.0-beta.7
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/dist/bin.cjs +49 -10
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +41 -2
- package/dist/bin.js.map +1 -1
- package/package.json +8 -8
- package/templates/react-basic/README.md.hbs +1 -1
- package/templates/react-basic/vite.config.ts +7 -4
- package/templates/react-sync/README.md.hbs +6 -5
- package/templates/react-sync/server.ts +42 -2
- package/templates/react-sync/vite.config.ts +14 -4
- package/templates/react-tailwind/README.md.hbs +1 -1
- package/templates/react-tailwind/vite.config.ts +7 -4
- package/templates/react-tailwind-sync/README.md.hbs +6 -5
- package/templates/react-tailwind-sync/server.ts +42 -2
- package/templates/react-tailwind-sync/vite.config.ts +14 -4
- package/templates/svelte-basic/README.md.hbs +1 -1
- package/templates/svelte-basic/vite.config.ts +4 -2
- package/templates/svelte-sync/README.md.hbs +6 -5
- package/templates/svelte-sync/server.ts +42 -2
- package/templates/svelte-sync/vite.config.ts +11 -2
- package/templates/svelte-tailwind/README.md.hbs +1 -1
- package/templates/svelte-tailwind/vite.config.ts +4 -2
- package/templates/svelte-tailwind-sync/README.md.hbs +6 -5
- package/templates/svelte-tailwind-sync/server.ts +42 -2
- package/templates/svelte-tailwind-sync/vite.config.ts +11 -2
- package/templates/tauri-react/server.ts +40 -0
- package/templates/vue-basic/README.md.hbs +1 -1
- package/templates/vue-basic/vite.config.ts +4 -2
- package/templates/vue-sync/README.md.hbs +6 -5
- package/templates/vue-sync/server.ts +42 -2
- package/templates/vue-sync/vite.config.ts +11 -2
- package/templates/vue-tailwind/README.md.hbs +1 -1
- package/templates/vue-tailwind/vite.config.ts +4 -2
- package/templates/vue-tailwind-sync/README.md.hbs +6 -5
- package/templates/vue-tailwind-sync/server.ts +42 -2
- package/templates/vue-tailwind-sync/vite.config.ts +11 -2
|
@@ -11,7 +11,7 @@ npm run dev
|
|
|
11
11
|
This starts both the Vite dev server and the Kora sync server.
|
|
12
12
|
|
|
13
13
|
- **App**: http://localhost:5173
|
|
14
|
-
- **Sync server**: ws://localhost:3001
|
|
14
|
+
- **Sync server**: ws://localhost:3001/kora-sync
|
|
15
15
|
|
|
16
16
|
## Project Structure
|
|
17
17
|
|
|
@@ -40,11 +40,12 @@ writes, and `useTodos.ts` is the React binding that exposes todo data and action
|
|
|
40
40
|
|
|
41
41
|
| Variable | Default | Description |
|
|
42
42
|
|----------|---------|-------------|
|
|
43
|
-
| `VITE_SYNC_URL` | `ws://localhost:3001` | WebSocket URL for the sync
|
|
43
|
+
| `VITE_SYNC_URL` | `ws://localhost:3001/kora-sync` | WebSocket URL for the sync endpoint |
|
|
44
44
|
| `VITE_AUTH_URL` | current origin | HTTP URL for `/auth/*` routes |
|
|
45
45
|
| `PORT` | `3001` | Sync server port |
|
|
46
|
-
| `KORA_SERVER_DB` |
|
|
47
|
-
| `KORA_AUTH_DB` |
|
|
46
|
+
| `KORA_SERVER_DB` | `./.kora/kora-server.db` | SQLite file used by the sync server when `DATABASE_URL` is empty |
|
|
47
|
+
| `KORA_AUTH_DB` | `./.kora/kora-auth.db` | SQLite file used by OAuth state and linked identities when `DATABASE_URL` is empty |
|
|
48
|
+
| `KORA_COEP_POLICY` | `credentialless` | Dev/preview Cross-Origin-Embedder-Policy. Use `require-corp` only when every embedded resource opts in. |
|
|
48
49
|
| `DATABASE_URL` | (none) | PostgreSQL connection string for production sync server deployments |
|
|
49
50
|
| `KORA_SYNC_PATH` | `/kora-sync` | WebSocket path exposed by the sync server |
|
|
50
51
|
| `KORA_AUTH_SECRET` | (none) | Enables built-in `/auth/*` routes and authenticated sync when set |
|
|
@@ -94,7 +95,7 @@ The client and sync server must be deployed separately:
|
|
|
94
95
|
Set `VITE_SYNC_URL` to your deployed sync server URL when building:
|
|
95
96
|
|
|
96
97
|
```bash
|
|
97
|
-
VITE_SYNC_URL=wss://your-server.example.com npm run build
|
|
98
|
+
VITE_SYNC_URL=wss://your-server.example.com/kora-sync npm run build
|
|
98
99
|
```
|
|
99
100
|
|
|
100
101
|
## Learn More
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
1
2
|
import {
|
|
2
3
|
type CreateKoraAuthServerOptions,
|
|
3
4
|
createKoraAuthServer,
|
|
@@ -12,6 +13,45 @@ import {
|
|
|
12
13
|
} from '@korajs/server'
|
|
13
14
|
import schema from './src/schema'
|
|
14
15
|
|
|
16
|
+
loadLocalEnv()
|
|
17
|
+
|
|
18
|
+
function loadLocalEnv(mode = process.env.NODE_ENV || 'development') {
|
|
19
|
+
const existingKeys = new Set(Object.keys(process.env))
|
|
20
|
+
for (const file of ['.env', '.env.local', `.env.${mode}`, `.env.${mode}.local`]) {
|
|
21
|
+
if (!existsSync(file)) continue
|
|
22
|
+
for (const [key, value] of Object.entries(parseEnvFile(readFileSync(file, 'utf8')))) {
|
|
23
|
+
if (!existingKeys.has(key)) {
|
|
24
|
+
process.env[key] = value
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseEnvFile(contents: string): Record<string, string> {
|
|
31
|
+
const env: Record<string, string> = {}
|
|
32
|
+
for (const rawLine of contents.split(/\r?\n/)) {
|
|
33
|
+
const line = rawLine.trim()
|
|
34
|
+
if (!line || line.startsWith('#')) continue
|
|
35
|
+
const normalized = line.startsWith('export ') ? line.slice('export '.length).trim() : line
|
|
36
|
+
const equalsIndex = normalized.indexOf('=')
|
|
37
|
+
if (equalsIndex <= 0) continue
|
|
38
|
+
const key = normalized.slice(0, equalsIndex).trim()
|
|
39
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue
|
|
40
|
+
env[key] = parseEnvValue(normalized.slice(equalsIndex + 1).trim())
|
|
41
|
+
}
|
|
42
|
+
return env
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseEnvValue(value: string): string {
|
|
46
|
+
const quote = value[0]
|
|
47
|
+
if ((quote === '"' || quote === "'") && value[value.length - 1] === quote) {
|
|
48
|
+
const inner = value.slice(1, -1)
|
|
49
|
+
return quote === '"' ? inner.replace(/\\n/g, '\n').replace(/\\r/g, '\r') : inner
|
|
50
|
+
}
|
|
51
|
+
const commentIndex = value.search(/\s#/)
|
|
52
|
+
return (commentIndex >= 0 ? value.slice(0, commentIndex) : value).trim()
|
|
53
|
+
}
|
|
54
|
+
|
|
15
55
|
async function createStore() {
|
|
16
56
|
if (process.env.DATABASE_URL) {
|
|
17
57
|
return createPostgresServerStore({
|
|
@@ -20,7 +60,7 @@ async function createStore() {
|
|
|
20
60
|
}
|
|
21
61
|
|
|
22
62
|
return createSqliteServerStore({
|
|
23
|
-
filename: process.env.KORA_SERVER_DB || '
|
|
63
|
+
filename: process.env.KORA_SERVER_DB || './.kora/kora-server.db',
|
|
24
64
|
})
|
|
25
65
|
}
|
|
26
66
|
|
|
@@ -72,7 +112,7 @@ async function createOAuthConfig(): Promise<CreateKoraAuthServerOptions['oauth']
|
|
|
72
112
|
const oauthStores = process.env.DATABASE_URL
|
|
73
113
|
? await createPostgresOAuthStores({ connectionString: process.env.DATABASE_URL })
|
|
74
114
|
: await createSqliteOAuthStores({
|
|
75
|
-
filename: process.env.KORA_AUTH_DB || '
|
|
115
|
+
filename: process.env.KORA_AUTH_DB || './.kora/kora-auth.db',
|
|
76
116
|
})
|
|
77
117
|
|
|
78
118
|
return {
|
|
@@ -6,19 +6,21 @@ import type { Plugin } from 'vite'
|
|
|
6
6
|
import { defineConfig } from 'vite'
|
|
7
7
|
|
|
8
8
|
function crossOriginIsolation(): Plugin {
|
|
9
|
+
const embedderPolicy = process.env.KORA_COEP_POLICY || 'credentialless'
|
|
10
|
+
|
|
9
11
|
return {
|
|
10
12
|
name: 'cross-origin-isolation',
|
|
11
13
|
configureServer(server) {
|
|
12
14
|
server.middlewares.use((_req, res, next) => {
|
|
13
15
|
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
|
|
14
|
-
res.setHeader('Cross-Origin-Embedder-Policy',
|
|
16
|
+
res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
|
|
15
17
|
next()
|
|
16
18
|
})
|
|
17
19
|
},
|
|
18
20
|
configurePreviewServer(server) {
|
|
19
21
|
server.middlewares.use((_req, res, next) => {
|
|
20
22
|
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
|
|
21
|
-
res.setHeader('Cross-Origin-Embedder-Policy',
|
|
23
|
+
res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
|
|
22
24
|
next()
|
|
23
25
|
})
|
|
24
26
|
},
|
|
@@ -69,12 +71,19 @@ export default defineConfig({
|
|
|
69
71
|
},
|
|
70
72
|
server: {
|
|
71
73
|
allowedHosts: true,
|
|
74
|
+
watch: {
|
|
75
|
+
ignored: ['**/.kora/**', '**/*.db', '**/*.db-shm', '**/*.db-wal'],
|
|
76
|
+
},
|
|
72
77
|
proxy: {
|
|
73
78
|
'/kora-sync': {
|
|
74
79
|
target: 'ws://localhost:3001',
|
|
75
80
|
ws: true,
|
|
76
81
|
rewriteWsOrigin: true,
|
|
77
82
|
},
|
|
83
|
+
'/auth': {
|
|
84
|
+
target: 'http://localhost:3001',
|
|
85
|
+
changeOrigin: true,
|
|
86
|
+
},
|
|
78
87
|
},
|
|
79
88
|
},
|
|
80
89
|
})
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
1
2
|
import {
|
|
2
3
|
type CreateKoraAuthServerOptions,
|
|
3
4
|
createKoraAuthServer,
|
|
@@ -20,6 +21,45 @@ import schema from './src/schema'
|
|
|
20
21
|
// For production, deploy this server and set VITE_SYNC_URL when building
|
|
21
22
|
// the desktop app: VITE_SYNC_URL=wss://your-server.com/kora-sync pnpm build
|
|
22
23
|
|
|
24
|
+
loadLocalEnv()
|
|
25
|
+
|
|
26
|
+
function loadLocalEnv(mode = process.env.NODE_ENV || 'development') {
|
|
27
|
+
const existingKeys = new Set(Object.keys(process.env))
|
|
28
|
+
for (const file of ['.env', '.env.local', `.env.${mode}`, `.env.${mode}.local`]) {
|
|
29
|
+
if (!existsSync(file)) continue
|
|
30
|
+
for (const [key, value] of Object.entries(parseEnvFile(readFileSync(file, 'utf8')))) {
|
|
31
|
+
if (!existingKeys.has(key)) {
|
|
32
|
+
process.env[key] = value
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseEnvFile(contents: string): Record<string, string> {
|
|
39
|
+
const env: Record<string, string> = {}
|
|
40
|
+
for (const rawLine of contents.split(/\r?\n/)) {
|
|
41
|
+
const line = rawLine.trim()
|
|
42
|
+
if (!line || line.startsWith('#')) continue
|
|
43
|
+
const normalized = line.startsWith('export ') ? line.slice('export '.length).trim() : line
|
|
44
|
+
const equalsIndex = normalized.indexOf('=')
|
|
45
|
+
if (equalsIndex <= 0) continue
|
|
46
|
+
const key = normalized.slice(0, equalsIndex).trim()
|
|
47
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue
|
|
48
|
+
env[key] = parseEnvValue(normalized.slice(equalsIndex + 1).trim())
|
|
49
|
+
}
|
|
50
|
+
return env
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseEnvValue(value: string): string {
|
|
54
|
+
const quote = value[0]
|
|
55
|
+
if ((quote === '"' || quote === "'") && value[value.length - 1] === quote) {
|
|
56
|
+
const inner = value.slice(1, -1)
|
|
57
|
+
return quote === '"' ? inner.replace(/\\n/g, '\n').replace(/\\r/g, '\r') : inner
|
|
58
|
+
}
|
|
59
|
+
const commentIndex = value.search(/\s#/)
|
|
60
|
+
return (commentIndex >= 0 ? value.slice(0, commentIndex) : value).trim()
|
|
61
|
+
}
|
|
62
|
+
|
|
23
63
|
async function createStore() {
|
|
24
64
|
if (process.env.DATABASE_URL) {
|
|
25
65
|
return createPostgresServerStore({
|
|
@@ -47,7 +47,7 @@ To enable real-time sync across devices, update `src/main.tsx`:
|
|
|
47
47
|
const app = createApp({
|
|
48
48
|
schema,
|
|
49
49
|
sync: {
|
|
50
|
-
url: import.meta.env.VITE_SYNC_URL || 'ws://localhost:3001',
|
|
50
|
+
url: import.meta.env.VITE_SYNC_URL || 'ws://localhost:3001/kora-sync',
|
|
51
51
|
},
|
|
52
52
|
// ...
|
|
53
53
|
})
|
|
@@ -5,19 +5,21 @@ import type { Plugin } from 'vite'
|
|
|
5
5
|
import { defineConfig } from 'vite'
|
|
6
6
|
|
|
7
7
|
function crossOriginIsolation(): Plugin {
|
|
8
|
+
const embedderPolicy = process.env.KORA_COEP_POLICY || 'credentialless'
|
|
9
|
+
|
|
8
10
|
return {
|
|
9
11
|
name: 'cross-origin-isolation',
|
|
10
12
|
configureServer(server) {
|
|
11
13
|
server.middlewares.use((_req, res, next) => {
|
|
12
14
|
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
|
|
13
|
-
res.setHeader('Cross-Origin-Embedder-Policy',
|
|
15
|
+
res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
|
|
14
16
|
next()
|
|
15
17
|
})
|
|
16
18
|
},
|
|
17
19
|
configurePreviewServer(server) {
|
|
18
20
|
server.middlewares.use((_req, res, next) => {
|
|
19
21
|
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
|
|
20
|
-
res.setHeader('Cross-Origin-Embedder-Policy',
|
|
22
|
+
res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
|
|
21
23
|
next()
|
|
22
24
|
})
|
|
23
25
|
},
|
|
@@ -11,7 +11,7 @@ npm run dev
|
|
|
11
11
|
This starts both the Vite dev server and the Kora sync server.
|
|
12
12
|
|
|
13
13
|
- **App**: http://localhost:5173
|
|
14
|
-
- **Sync server**: ws://localhost:3001
|
|
14
|
+
- **Sync server**: ws://localhost:3001/kora-sync
|
|
15
15
|
|
|
16
16
|
## Project Structure
|
|
17
17
|
|
|
@@ -40,11 +40,12 @@ writes, and `useTodos.ts` is the React binding that exposes todo data and action
|
|
|
40
40
|
|
|
41
41
|
| Variable | Default | Description |
|
|
42
42
|
|----------|---------|-------------|
|
|
43
|
-
| `VITE_SYNC_URL` | `ws://localhost:3001` | WebSocket URL for the sync
|
|
43
|
+
| `VITE_SYNC_URL` | `ws://localhost:3001/kora-sync` | WebSocket URL for the sync endpoint |
|
|
44
44
|
| `VITE_AUTH_URL` | current origin | HTTP URL for `/auth/*` routes |
|
|
45
45
|
| `PORT` | `3001` | Sync server port |
|
|
46
|
-
| `KORA_SERVER_DB` |
|
|
47
|
-
| `KORA_AUTH_DB` |
|
|
46
|
+
| `KORA_SERVER_DB` | `./.kora/kora-server.db` | SQLite file used by the sync server when `DATABASE_URL` is empty |
|
|
47
|
+
| `KORA_AUTH_DB` | `./.kora/kora-auth.db` | SQLite file used by OAuth state and linked identities when `DATABASE_URL` is empty |
|
|
48
|
+
| `KORA_COEP_POLICY` | `credentialless` | Dev/preview Cross-Origin-Embedder-Policy. Use `require-corp` only when every embedded resource opts in. |
|
|
48
49
|
| `DATABASE_URL` | (none) | PostgreSQL connection string for production sync server deployments |
|
|
49
50
|
| `KORA_SYNC_PATH` | `/kora-sync` | WebSocket path exposed by the sync server |
|
|
50
51
|
| `KORA_AUTH_SECRET` | (none) | Enables built-in `/auth/*` routes and authenticated sync when set |
|
|
@@ -94,7 +95,7 @@ The client and sync server must be deployed separately:
|
|
|
94
95
|
Set `VITE_SYNC_URL` to your deployed sync server URL when building:
|
|
95
96
|
|
|
96
97
|
```bash
|
|
97
|
-
VITE_SYNC_URL=wss://your-server.example.com npm run build
|
|
98
|
+
VITE_SYNC_URL=wss://your-server.example.com/kora-sync npm run build
|
|
98
99
|
```
|
|
99
100
|
|
|
100
101
|
## Learn More
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
1
2
|
import {
|
|
2
3
|
type CreateKoraAuthServerOptions,
|
|
3
4
|
createKoraAuthServer,
|
|
@@ -12,6 +13,45 @@ import {
|
|
|
12
13
|
} from '@korajs/server'
|
|
13
14
|
import schema from './src/schema'
|
|
14
15
|
|
|
16
|
+
loadLocalEnv()
|
|
17
|
+
|
|
18
|
+
function loadLocalEnv(mode = process.env.NODE_ENV || 'development') {
|
|
19
|
+
const existingKeys = new Set(Object.keys(process.env))
|
|
20
|
+
for (const file of ['.env', '.env.local', `.env.${mode}`, `.env.${mode}.local`]) {
|
|
21
|
+
if (!existsSync(file)) continue
|
|
22
|
+
for (const [key, value] of Object.entries(parseEnvFile(readFileSync(file, 'utf8')))) {
|
|
23
|
+
if (!existingKeys.has(key)) {
|
|
24
|
+
process.env[key] = value
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseEnvFile(contents: string): Record<string, string> {
|
|
31
|
+
const env: Record<string, string> = {}
|
|
32
|
+
for (const rawLine of contents.split(/\r?\n/)) {
|
|
33
|
+
const line = rawLine.trim()
|
|
34
|
+
if (!line || line.startsWith('#')) continue
|
|
35
|
+
const normalized = line.startsWith('export ') ? line.slice('export '.length).trim() : line
|
|
36
|
+
const equalsIndex = normalized.indexOf('=')
|
|
37
|
+
if (equalsIndex <= 0) continue
|
|
38
|
+
const key = normalized.slice(0, equalsIndex).trim()
|
|
39
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue
|
|
40
|
+
env[key] = parseEnvValue(normalized.slice(equalsIndex + 1).trim())
|
|
41
|
+
}
|
|
42
|
+
return env
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseEnvValue(value: string): string {
|
|
46
|
+
const quote = value[0]
|
|
47
|
+
if ((quote === '"' || quote === "'") && value[value.length - 1] === quote) {
|
|
48
|
+
const inner = value.slice(1, -1)
|
|
49
|
+
return quote === '"' ? inner.replace(/\\n/g, '\n').replace(/\\r/g, '\r') : inner
|
|
50
|
+
}
|
|
51
|
+
const commentIndex = value.search(/\s#/)
|
|
52
|
+
return (commentIndex >= 0 ? value.slice(0, commentIndex) : value).trim()
|
|
53
|
+
}
|
|
54
|
+
|
|
15
55
|
async function createStore() {
|
|
16
56
|
if (process.env.DATABASE_URL) {
|
|
17
57
|
return createPostgresServerStore({
|
|
@@ -20,7 +60,7 @@ async function createStore() {
|
|
|
20
60
|
}
|
|
21
61
|
|
|
22
62
|
return createSqliteServerStore({
|
|
23
|
-
filename: process.env.KORA_SERVER_DB || '
|
|
63
|
+
filename: process.env.KORA_SERVER_DB || './.kora/kora-server.db',
|
|
24
64
|
})
|
|
25
65
|
}
|
|
26
66
|
|
|
@@ -72,7 +112,7 @@ async function createOAuthConfig(): Promise<CreateKoraAuthServerOptions['oauth']
|
|
|
72
112
|
const oauthStores = process.env.DATABASE_URL
|
|
73
113
|
? await createPostgresOAuthStores({ connectionString: process.env.DATABASE_URL })
|
|
74
114
|
: await createSqliteOAuthStores({
|
|
75
|
-
filename: process.env.KORA_AUTH_DB || '
|
|
115
|
+
filename: process.env.KORA_AUTH_DB || './.kora/kora-auth.db',
|
|
76
116
|
})
|
|
77
117
|
|
|
78
118
|
return {
|
|
@@ -5,19 +5,21 @@ import type { Plugin } from 'vite'
|
|
|
5
5
|
import { defineConfig } from 'vite'
|
|
6
6
|
|
|
7
7
|
function crossOriginIsolation(): Plugin {
|
|
8
|
+
const embedderPolicy = process.env.KORA_COEP_POLICY || 'credentialless'
|
|
9
|
+
|
|
8
10
|
return {
|
|
9
11
|
name: 'cross-origin-isolation',
|
|
10
12
|
configureServer(server) {
|
|
11
13
|
server.middlewares.use((_req, res, next) => {
|
|
12
14
|
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
|
|
13
|
-
res.setHeader('Cross-Origin-Embedder-Policy',
|
|
15
|
+
res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
|
|
14
16
|
next()
|
|
15
17
|
})
|
|
16
18
|
},
|
|
17
19
|
configurePreviewServer(server) {
|
|
18
20
|
server.middlewares.use((_req, res, next) => {
|
|
19
21
|
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
|
|
20
|
-
res.setHeader('Cross-Origin-Embedder-Policy',
|
|
22
|
+
res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
|
|
21
23
|
next()
|
|
22
24
|
})
|
|
23
25
|
},
|
|
@@ -68,12 +70,19 @@ export default defineConfig({
|
|
|
68
70
|
},
|
|
69
71
|
server: {
|
|
70
72
|
allowedHosts: true,
|
|
73
|
+
watch: {
|
|
74
|
+
ignored: ['**/.kora/**', '**/*.db', '**/*.db-shm', '**/*.db-wal'],
|
|
75
|
+
},
|
|
71
76
|
proxy: {
|
|
72
77
|
'/kora-sync': {
|
|
73
78
|
target: 'ws://localhost:3001',
|
|
74
79
|
ws: true,
|
|
75
80
|
rewriteWsOrigin: true,
|
|
76
81
|
},
|
|
82
|
+
'/auth': {
|
|
83
|
+
target: 'http://localhost:3001',
|
|
84
|
+
changeOrigin: true,
|
|
85
|
+
},
|
|
77
86
|
},
|
|
78
87
|
},
|
|
79
88
|
})
|
|
@@ -47,7 +47,7 @@ To enable real-time sync across devices, update `src/main.tsx`:
|
|
|
47
47
|
const app = createApp({
|
|
48
48
|
schema,
|
|
49
49
|
sync: {
|
|
50
|
-
url: import.meta.env.VITE_SYNC_URL || 'ws://localhost:3001',
|
|
50
|
+
url: import.meta.env.VITE_SYNC_URL || 'ws://localhost:3001/kora-sync',
|
|
51
51
|
},
|
|
52
52
|
// ...
|
|
53
53
|
})
|
|
@@ -6,19 +6,21 @@ import type { Plugin } from 'vite'
|
|
|
6
6
|
import { defineConfig } from 'vite'
|
|
7
7
|
|
|
8
8
|
function crossOriginIsolation(): Plugin {
|
|
9
|
+
const embedderPolicy = process.env.KORA_COEP_POLICY || 'credentialless'
|
|
10
|
+
|
|
9
11
|
return {
|
|
10
12
|
name: 'cross-origin-isolation',
|
|
11
13
|
configureServer(server) {
|
|
12
14
|
server.middlewares.use((_req, res, next) => {
|
|
13
15
|
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
|
|
14
|
-
res.setHeader('Cross-Origin-Embedder-Policy',
|
|
16
|
+
res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
|
|
15
17
|
next()
|
|
16
18
|
})
|
|
17
19
|
},
|
|
18
20
|
configurePreviewServer(server) {
|
|
19
21
|
server.middlewares.use((_req, res, next) => {
|
|
20
22
|
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
|
|
21
|
-
res.setHeader('Cross-Origin-Embedder-Policy',
|
|
23
|
+
res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
|
|
22
24
|
next()
|
|
23
25
|
})
|
|
24
26
|
},
|
|
@@ -11,7 +11,7 @@ npm run dev
|
|
|
11
11
|
This starts both the Vite dev server and the Kora sync server.
|
|
12
12
|
|
|
13
13
|
- **App**: http://localhost:5173
|
|
14
|
-
- **Sync server**: ws://localhost:3001
|
|
14
|
+
- **Sync server**: ws://localhost:3001/kora-sync
|
|
15
15
|
|
|
16
16
|
## Project Structure
|
|
17
17
|
|
|
@@ -40,11 +40,12 @@ writes, and `useTodos.ts` is the React binding that exposes todo data and action
|
|
|
40
40
|
|
|
41
41
|
| Variable | Default | Description |
|
|
42
42
|
|----------|---------|-------------|
|
|
43
|
-
| `VITE_SYNC_URL` | `ws://localhost:3001` | WebSocket URL for the sync
|
|
43
|
+
| `VITE_SYNC_URL` | `ws://localhost:3001/kora-sync` | WebSocket URL for the sync endpoint |
|
|
44
44
|
| `VITE_AUTH_URL` | current origin | HTTP URL for `/auth/*` routes |
|
|
45
45
|
| `PORT` | `3001` | Sync server port |
|
|
46
|
-
| `KORA_SERVER_DB` |
|
|
47
|
-
| `KORA_AUTH_DB` |
|
|
46
|
+
| `KORA_SERVER_DB` | `./.kora/kora-server.db` | SQLite file used by the sync server when `DATABASE_URL` is empty |
|
|
47
|
+
| `KORA_AUTH_DB` | `./.kora/kora-auth.db` | SQLite file used by OAuth state and linked identities when `DATABASE_URL` is empty |
|
|
48
|
+
| `KORA_COEP_POLICY` | `credentialless` | Dev/preview Cross-Origin-Embedder-Policy. Use `require-corp` only when every embedded resource opts in. |
|
|
48
49
|
| `DATABASE_URL` | (none) | PostgreSQL connection string for production sync server deployments |
|
|
49
50
|
| `KORA_SYNC_PATH` | `/kora-sync` | WebSocket path exposed by the sync server |
|
|
50
51
|
| `KORA_AUTH_SECRET` | (none) | Enables built-in `/auth/*` routes and authenticated sync when set |
|
|
@@ -94,7 +95,7 @@ The client and sync server must be deployed separately:
|
|
|
94
95
|
Set `VITE_SYNC_URL` to your deployed sync server URL when building:
|
|
95
96
|
|
|
96
97
|
```bash
|
|
97
|
-
VITE_SYNC_URL=wss://your-server.example.com npm run build
|
|
98
|
+
VITE_SYNC_URL=wss://your-server.example.com/kora-sync npm run build
|
|
98
99
|
```
|
|
99
100
|
|
|
100
101
|
## Learn More
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
1
2
|
import {
|
|
2
3
|
type CreateKoraAuthServerOptions,
|
|
3
4
|
createKoraAuthServer,
|
|
@@ -12,6 +13,45 @@ import {
|
|
|
12
13
|
} from '@korajs/server'
|
|
13
14
|
import schema from './src/schema'
|
|
14
15
|
|
|
16
|
+
loadLocalEnv()
|
|
17
|
+
|
|
18
|
+
function loadLocalEnv(mode = process.env.NODE_ENV || 'development') {
|
|
19
|
+
const existingKeys = new Set(Object.keys(process.env))
|
|
20
|
+
for (const file of ['.env', '.env.local', `.env.${mode}`, `.env.${mode}.local`]) {
|
|
21
|
+
if (!existsSync(file)) continue
|
|
22
|
+
for (const [key, value] of Object.entries(parseEnvFile(readFileSync(file, 'utf8')))) {
|
|
23
|
+
if (!existingKeys.has(key)) {
|
|
24
|
+
process.env[key] = value
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseEnvFile(contents: string): Record<string, string> {
|
|
31
|
+
const env: Record<string, string> = {}
|
|
32
|
+
for (const rawLine of contents.split(/\r?\n/)) {
|
|
33
|
+
const line = rawLine.trim()
|
|
34
|
+
if (!line || line.startsWith('#')) continue
|
|
35
|
+
const normalized = line.startsWith('export ') ? line.slice('export '.length).trim() : line
|
|
36
|
+
const equalsIndex = normalized.indexOf('=')
|
|
37
|
+
if (equalsIndex <= 0) continue
|
|
38
|
+
const key = normalized.slice(0, equalsIndex).trim()
|
|
39
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue
|
|
40
|
+
env[key] = parseEnvValue(normalized.slice(equalsIndex + 1).trim())
|
|
41
|
+
}
|
|
42
|
+
return env
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseEnvValue(value: string): string {
|
|
46
|
+
const quote = value[0]
|
|
47
|
+
if ((quote === '"' || quote === "'") && value[value.length - 1] === quote) {
|
|
48
|
+
const inner = value.slice(1, -1)
|
|
49
|
+
return quote === '"' ? inner.replace(/\\n/g, '\n').replace(/\\r/g, '\r') : inner
|
|
50
|
+
}
|
|
51
|
+
const commentIndex = value.search(/\s#/)
|
|
52
|
+
return (commentIndex >= 0 ? value.slice(0, commentIndex) : value).trim()
|
|
53
|
+
}
|
|
54
|
+
|
|
15
55
|
async function createStore() {
|
|
16
56
|
if (process.env.DATABASE_URL) {
|
|
17
57
|
return createPostgresServerStore({
|
|
@@ -20,7 +60,7 @@ async function createStore() {
|
|
|
20
60
|
}
|
|
21
61
|
|
|
22
62
|
return createSqliteServerStore({
|
|
23
|
-
filename: process.env.KORA_SERVER_DB || '
|
|
63
|
+
filename: process.env.KORA_SERVER_DB || './.kora/kora-server.db',
|
|
24
64
|
})
|
|
25
65
|
}
|
|
26
66
|
|
|
@@ -72,7 +112,7 @@ async function createOAuthConfig(): Promise<CreateKoraAuthServerOptions['oauth']
|
|
|
72
112
|
const oauthStores = process.env.DATABASE_URL
|
|
73
113
|
? await createPostgresOAuthStores({ connectionString: process.env.DATABASE_URL })
|
|
74
114
|
: await createSqliteOAuthStores({
|
|
75
|
-
filename: process.env.KORA_AUTH_DB || '
|
|
115
|
+
filename: process.env.KORA_AUTH_DB || './.kora/kora-auth.db',
|
|
76
116
|
})
|
|
77
117
|
|
|
78
118
|
return {
|
|
@@ -6,19 +6,21 @@ import type { Plugin } from 'vite'
|
|
|
6
6
|
import { defineConfig } from 'vite'
|
|
7
7
|
|
|
8
8
|
function crossOriginIsolation(): Plugin {
|
|
9
|
+
const embedderPolicy = process.env.KORA_COEP_POLICY || 'credentialless'
|
|
10
|
+
|
|
9
11
|
return {
|
|
10
12
|
name: 'cross-origin-isolation',
|
|
11
13
|
configureServer(server) {
|
|
12
14
|
server.middlewares.use((_req, res, next) => {
|
|
13
15
|
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
|
|
14
|
-
res.setHeader('Cross-Origin-Embedder-Policy',
|
|
16
|
+
res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
|
|
15
17
|
next()
|
|
16
18
|
})
|
|
17
19
|
},
|
|
18
20
|
configurePreviewServer(server) {
|
|
19
21
|
server.middlewares.use((_req, res, next) => {
|
|
20
22
|
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
|
|
21
|
-
res.setHeader('Cross-Origin-Embedder-Policy',
|
|
23
|
+
res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
|
|
22
24
|
next()
|
|
23
25
|
})
|
|
24
26
|
},
|
|
@@ -69,12 +71,19 @@ export default defineConfig({
|
|
|
69
71
|
},
|
|
70
72
|
server: {
|
|
71
73
|
allowedHosts: true,
|
|
74
|
+
watch: {
|
|
75
|
+
ignored: ['**/.kora/**', '**/*.db', '**/*.db-shm', '**/*.db-wal'],
|
|
76
|
+
},
|
|
72
77
|
proxy: {
|
|
73
78
|
'/kora-sync': {
|
|
74
79
|
target: 'ws://localhost:3001',
|
|
75
80
|
ws: true,
|
|
76
81
|
rewriteWsOrigin: true,
|
|
77
82
|
},
|
|
83
|
+
'/auth': {
|
|
84
|
+
target: 'http://localhost:3001',
|
|
85
|
+
changeOrigin: true,
|
|
86
|
+
},
|
|
78
87
|
},
|
|
79
88
|
},
|
|
80
89
|
})
|