@korajs/cli 1.0.0-beta.6 → 1.0.0-beta.9

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.
Files changed (58) hide show
  1. package/dist/bin.cjs +123 -41
  2. package/dist/bin.cjs.map +1 -1
  3. package/dist/bin.js +108 -31
  4. package/dist/bin.js.map +1 -1
  5. package/dist/{chunk-ZTVVAYHJ.js → chunk-Z7GCXMGW.js} +8 -4
  6. package/dist/chunk-Z7GCXMGW.js.map +1 -0
  7. package/dist/create.cjs +7 -3
  8. package/dist/create.cjs.map +1 -1
  9. package/dist/create.js +1 -1
  10. package/package.json +8 -8
  11. package/templates/react-basic/README.md.hbs +1 -1
  12. package/templates/react-basic/src/main.tsx +1 -1
  13. package/templates/react-basic/src/vite-env.d.ts +1 -0
  14. package/templates/react-sync/README.md.hbs +3 -3
  15. package/templates/react-sync/server.ts +44 -1
  16. package/templates/react-sync/src/main.tsx +1 -1
  17. package/templates/react-sync/src/vite-env.d.ts +1 -0
  18. package/templates/react-sync/vite.config.ts +1 -1
  19. package/templates/react-tailwind/README.md.hbs +1 -1
  20. package/templates/react-tailwind/src/main.tsx +1 -1
  21. package/templates/react-tailwind/src/vite-env.d.ts +1 -0
  22. package/templates/react-tailwind-sync/README.md.hbs +3 -3
  23. package/templates/react-tailwind-sync/server.ts +44 -1
  24. package/templates/react-tailwind-sync/src/main.tsx +1 -1
  25. package/templates/react-tailwind-sync/src/vite-env.d.ts +1 -0
  26. package/templates/react-tailwind-sync/vite.config.ts +1 -1
  27. package/templates/svelte-basic/README.md.hbs +1 -1
  28. package/templates/svelte-basic/src/main.ts +1 -1
  29. package/templates/svelte-sync/README.md.hbs +3 -3
  30. package/templates/svelte-sync/server.ts +44 -1
  31. package/templates/svelte-sync/src/main.ts +1 -1
  32. package/templates/svelte-sync/src/vite-env.d.ts +1 -0
  33. package/templates/svelte-sync/vite.config.ts +1 -1
  34. package/templates/svelte-tailwind/README.md.hbs +1 -1
  35. package/templates/svelte-tailwind/src/main.ts +1 -1
  36. package/templates/svelte-tailwind-sync/README.md.hbs +3 -3
  37. package/templates/svelte-tailwind-sync/server.ts +44 -1
  38. package/templates/svelte-tailwind-sync/src/main.ts +1 -1
  39. package/templates/svelte-tailwind-sync/src/vite-env.d.ts +1 -0
  40. package/templates/svelte-tailwind-sync/vite.config.ts +1 -1
  41. package/templates/tauri-react/server.ts +44 -1
  42. package/templates/tauri-react/src/AppShell.tsx +1 -1
  43. package/templates/tauri-react/src/vite-env.d.ts +1 -0
  44. package/templates/vue-basic/README.md.hbs +1 -1
  45. package/templates/vue-basic/src/main.ts +1 -1
  46. package/templates/vue-sync/README.md.hbs +3 -3
  47. package/templates/vue-sync/server.ts +44 -1
  48. package/templates/vue-sync/src/main.ts +1 -1
  49. package/templates/vue-sync/src/vite-env.d.ts +1 -0
  50. package/templates/vue-sync/vite.config.ts +1 -1
  51. package/templates/vue-tailwind/README.md.hbs +1 -1
  52. package/templates/vue-tailwind/src/main.ts +1 -1
  53. package/templates/vue-tailwind-sync/README.md.hbs +3 -3
  54. package/templates/vue-tailwind-sync/server.ts +44 -1
  55. package/templates/vue-tailwind-sync/src/main.ts +1 -1
  56. package/templates/vue-tailwind-sync/src/vite-env.d.ts +1 -0
  57. package/templates/vue-tailwind-sync/vite.config.ts +1 -1
  58. package/dist/chunk-ZTVVAYHJ.js.map +0 -1
@@ -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({
@@ -36,7 +76,10 @@ async function start() {
36
76
  staticDir: './dist',
37
77
  syncPath,
38
78
  httpRoutes: auth ? [{ path: '/auth', handle: auth.handleRequest }] : undefined,
39
- syncOptions: auth ? { auth: auth.auth } : undefined,
79
+ syncOptions: {
80
+ schemaVersion: schema.version,
81
+ ...(auth ? { auth: auth.auth } : {}),
82
+ },
40
83
  operationalAuth: {
41
84
  adminToken: process.env.KORA_ADMIN_TOKEN,
42
85
  metricsToken: process.env.KORA_METRICS_TOKEN,
@@ -26,7 +26,7 @@ const app = createApp({
26
26
  store: {
27
27
  workerUrl: koraWorkerUrl,
28
28
  },
29
- devtools: true,
29
+ devtools: import.meta.env.DEV,
30
30
  })
31
31
 
32
32
  // Connect to sync server once the app is ready
@@ -1,4 +1,5 @@
1
1
  interface ImportMetaEnv {
2
+ readonly DEV: boolean
2
3
  readonly VITE_SYNC_URL?: string
3
4
  }
4
5
 
@@ -85,7 +85,7 @@ export default defineConfig({
85
85
  server: {
86
86
  allowedHosts: true,
87
87
  watch: {
88
- ignored: ['**/.kora/**', '**/*.db', '**/*.db-shm', '**/*.db-wal'],
88
+ ignored: ['**/.kora/**', '**/kora/generated/**', '**/*.db', '**/*.db-shm', '**/*.db-wal'],
89
89
  },
90
90
  proxy: {
91
91
  '/kora-sync': {
@@ -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
  })
@@ -12,7 +12,7 @@ const app = createApp({
12
12
  store: {
13
13
  workerUrl: koraWorkerUrl,
14
14
  },
15
- devtools: true,
15
+ devtools: import.meta.env.DEV,
16
16
  })
17
17
 
18
18
  createRoot(document.getElementById('root')!).render(
@@ -1,4 +1,5 @@
1
1
  interface ImportMetaEnv {
2
+ readonly DEV: boolean
2
3
  readonly VITE_SYNC_URL?: string
3
4
  }
4
5
 
@@ -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,7 +40,7 @@ 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 server |
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
46
  | `KORA_SERVER_DB` | `./.kora/kora-server.db` | SQLite file used by the sync server when `DATABASE_URL` is empty |
@@ -95,7 +95,7 @@ The client and sync server must be deployed separately:
95
95
  Set `VITE_SYNC_URL` to your deployed sync server URL when building:
96
96
 
97
97
  ```bash
98
- 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
99
99
  ```
100
100
 
101
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({
@@ -36,7 +76,10 @@ async function start() {
36
76
  staticDir: './dist',
37
77
  syncPath,
38
78
  httpRoutes: auth ? [{ path: '/auth', handle: auth.handleRequest }] : undefined,
39
- syncOptions: auth ? { auth: auth.auth } : undefined,
79
+ syncOptions: {
80
+ schemaVersion: schema.version,
81
+ ...(auth ? { auth: auth.auth } : {}),
82
+ },
40
83
  operationalAuth: {
41
84
  adminToken: process.env.KORA_ADMIN_TOKEN,
42
85
  metricsToken: process.env.KORA_METRICS_TOKEN,
@@ -26,7 +26,7 @@ const app = createApp({
26
26
  store: {
27
27
  workerUrl: koraWorkerUrl,
28
28
  },
29
- devtools: true,
29
+ devtools: import.meta.env.DEV,
30
30
  })
31
31
 
32
32
  // Connect to sync server once the app is ready
@@ -1,4 +1,5 @@
1
1
  interface ImportMetaEnv {
2
+ readonly DEV: boolean
2
3
  readonly VITE_SYNC_URL?: string
3
4
  }
4
5
 
@@ -86,7 +86,7 @@ export default defineConfig({
86
86
  server: {
87
87
  allowedHosts: true,
88
88
  watch: {
89
- ignored: ['**/.kora/**', '**/*.db', '**/*.db-shm', '**/*.db-wal'],
89
+ ignored: ['**/.kora/**', '**/kora/generated/**', '**/*.db', '**/*.db-shm', '**/*.db-wal'],
90
90
  },
91
91
  proxy: {
92
92
  '/kora-sync': {
@@ -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
  })
@@ -10,7 +10,7 @@ const kora = createKoraApp({
10
10
  store: {
11
11
  workerUrl: koraWorkerUrl,
12
12
  },
13
- devtools: true,
13
+ devtools: import.meta.env.DEV,
14
14
  })
15
15
 
16
16
  mount(Root, { target: document.getElementById('app')!, props: { kora } })
@@ -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,7 +40,7 @@ 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 server |
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
46
  | `KORA_SERVER_DB` | `./.kora/kora-server.db` | SQLite file used by the sync server when `DATABASE_URL` is empty |
@@ -95,7 +95,7 @@ The client and sync server must be deployed separately:
95
95
  Set `VITE_SYNC_URL` to your deployed sync server URL when building:
96
96
 
97
97
  ```bash
98
- 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
99
99
  ```
100
100
 
101
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({
@@ -36,7 +76,10 @@ async function start() {
36
76
  staticDir: './dist',
37
77
  syncPath,
38
78
  httpRoutes: auth ? [{ path: '/auth', handle: auth.handleRequest }] : undefined,
39
- syncOptions: auth ? { auth: auth.auth } : undefined,
79
+ syncOptions: {
80
+ schemaVersion: schema.version,
81
+ ...(auth ? { auth: auth.auth } : {}),
82
+ },
40
83
  operationalAuth: {
41
84
  adminToken: process.env.KORA_ADMIN_TOKEN,
42
85
  metricsToken: process.env.KORA_METRICS_TOKEN,
@@ -20,7 +20,7 @@ const kora = createKoraApp({
20
20
  store: {
21
21
  workerUrl: koraWorkerUrl,
22
22
  },
23
- devtools: true,
23
+ devtools: import.meta.env.DEV,
24
24
  })
25
25
 
26
26
  kora.ready.then(() => kora.sync?.connect())
@@ -2,6 +2,7 @@
2
2
  /// <reference types="vite/client" />
3
3
 
4
4
  interface ImportMetaEnv {
5
+ readonly DEV: boolean
5
6
  readonly VITE_SYNC_URL?: string
6
7
  readonly VITE_AUTH_URL?: string
7
8
  }
@@ -71,7 +71,7 @@ export default defineConfig({
71
71
  server: {
72
72
  allowedHosts: true,
73
73
  watch: {
74
- ignored: ['**/.kora/**', '**/*.db', '**/*.db-shm', '**/*.db-wal'],
74
+ ignored: ['**/.kora/**', '**/kora/generated/**', '**/*.db', '**/*.db-shm', '**/*.db-wal'],
75
75
  },
76
76
  proxy: {
77
77
  '/kora-sync': {
@@ -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
  })
@@ -10,7 +10,7 @@ const kora = createKoraApp({
10
10
  store: {
11
11
  workerUrl: koraWorkerUrl,
12
12
  },
13
- devtools: true,
13
+ devtools: import.meta.env.DEV,
14
14
  })
15
15
 
16
16
  mount(Root, { target: document.getElementById('app')!, props: { kora } })
@@ -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,7 +40,7 @@ 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 server |
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
46
  | `KORA_SERVER_DB` | `./.kora/kora-server.db` | SQLite file used by the sync server when `DATABASE_URL` is empty |
@@ -95,7 +95,7 @@ The client and sync server must be deployed separately:
95
95
  Set `VITE_SYNC_URL` to your deployed sync server URL when building:
96
96
 
97
97
  ```bash
98
- 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
99
99
  ```
100
100
 
101
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({
@@ -36,7 +76,10 @@ async function start() {
36
76
  staticDir: './dist',
37
77
  syncPath,
38
78
  httpRoutes: auth ? [{ path: '/auth', handle: auth.handleRequest }] : undefined,
39
- syncOptions: auth ? { auth: auth.auth } : undefined,
79
+ syncOptions: {
80
+ schemaVersion: schema.version,
81
+ ...(auth ? { auth: auth.auth } : {}),
82
+ },
40
83
  operationalAuth: {
41
84
  adminToken: process.env.KORA_ADMIN_TOKEN,
42
85
  metricsToken: process.env.KORA_METRICS_TOKEN,
@@ -20,7 +20,7 @@ const kora = createKoraApp({
20
20
  store: {
21
21
  workerUrl: koraWorkerUrl,
22
22
  },
23
- devtools: true,
23
+ devtools: import.meta.env.DEV,
24
24
  })
25
25
 
26
26
  kora.ready.then(() => kora.sync?.connect())
@@ -2,6 +2,7 @@
2
2
  /// <reference types="vite/client" />
3
3
 
4
4
  interface ImportMetaEnv {
5
+ readonly DEV: boolean
5
6
  readonly VITE_SYNC_URL?: string
6
7
  readonly VITE_AUTH_URL?: string
7
8
  }
@@ -72,7 +72,7 @@ export default defineConfig({
72
72
  server: {
73
73
  allowedHosts: true,
74
74
  watch: {
75
- ignored: ['**/.kora/**', '**/*.db', '**/*.db-shm', '**/*.db-wal'],
75
+ ignored: ['**/.kora/**', '**/kora/generated/**', '**/*.db', '**/*.db-shm', '**/*.db-wal'],
76
76
  },
77
77
  proxy: {
78
78
  '/kora-sync': {
@@ -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({
@@ -43,7 +83,10 @@ async function start() {
43
83
  port: Number(process.env.PORT) || 3001,
44
84
  syncPath,
45
85
  httpRoutes: auth ? [{ path: '/auth', handle: auth.handleRequest }] : undefined,
46
- syncOptions: auth ? { auth: auth.auth } : undefined,
86
+ syncOptions: {
87
+ schemaVersion: schema.version,
88
+ ...(auth ? { auth: auth.auth } : {}),
89
+ },
47
90
  operationalAuth: {
48
91
  adminToken: process.env.KORA_ADMIN_TOKEN,
49
92
  metricsToken: process.env.KORA_METRICS_TOKEN,
@@ -101,7 +101,7 @@ function ConnectedApp({ syncUrl, onChangeServer, onFactoryReset }: ConnectedAppP
101
101
  },
102
102
  }
103
103
  : {}),
104
- devtools: true,
104
+ devtools: import.meta.env.DEV,
105
105
  }),
106
106
  )
107
107
 
@@ -1,4 +1,5 @@
1
1
  interface ImportMetaEnv {
2
+ readonly DEV: boolean
2
3
  readonly VITE_SYNC_URL?: string
3
4
  }
4
5
 
@@ -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
  })
@@ -11,7 +11,7 @@ const kora = createKoraApp({
11
11
  store: {
12
12
  workerUrl: koraWorkerUrl,
13
13
  },
14
- devtools: true,
14
+ devtools: import.meta.env.DEV,
15
15
  })
16
16
 
17
17
  createApp({
@@ -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,7 +40,7 @@ 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 server |
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
46
  | `KORA_SERVER_DB` | `./.kora/kora-server.db` | SQLite file used by the sync server when `DATABASE_URL` is empty |
@@ -95,7 +95,7 @@ The client and sync server must be deployed separately:
95
95
  Set `VITE_SYNC_URL` to your deployed sync server URL when building:
96
96
 
97
97
  ```bash
98
- 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
99
99
  ```
100
100
 
101
101
  ## Learn More