@korajs/cli 0.3.2 → 0.3.4
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 +120 -67
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +3 -3
- package/dist/{chunk-KTSRAPSE.js → chunk-HNVCNPER.js} +105 -65
- package/dist/chunk-HNVCNPER.js.map +1 -0
- package/dist/{chunk-E7OCVRYL.js → chunk-MIMLCXSX.js} +2 -2
- package/dist/{chunk-ZGYRDYXS.js → chunk-ULTO5NFA.js} +22 -4
- package/dist/chunk-ULTO5NFA.js.map +1 -0
- package/dist/create.cjs +119 -66
- package/dist/create.cjs.map +1 -1
- package/dist/create.js +2 -2
- package/dist/index.cjs +105 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/templates/tauri-react/.env.example +7 -0
- package/templates/tauri-react/.github/workflows/release-desktop.yml +107 -0
- package/templates/tauri-react/README.md.hbs +148 -0
- package/templates/tauri-react/dev.ts +37 -0
- package/templates/tauri-react/index.html.hbs +16 -0
- package/templates/tauri-react/kora.config.ts +17 -0
- package/templates/tauri-react/package.json.hbs +44 -0
- package/templates/tauri-react/server.ts +23 -0
- package/templates/tauri-react/src/App.tsx +578 -0
- package/templates/tauri-react/src/AppShell.tsx +116 -0
- package/templates/tauri-react/src/SetupScreen.tsx +239 -0
- package/templates/tauri-react/src/main.tsx +9 -0
- package/templates/tauri-react/src/schema.ts +15 -0
- package/templates/tauri-react/src/sync-config.ts +103 -0
- package/templates/tauri-react/src/updater.ts +38 -0
- package/templates/tauri-react/src-tauri/Cargo.toml +19 -0
- package/templates/tauri-react/src-tauri/build.rs +3 -0
- package/templates/tauri-react/src-tauri/capabilities/default.json +12 -0
- package/templates/tauri-react/src-tauri/icons/128x128.png +0 -0
- package/templates/tauri-react/src-tauri/icons/128x128@2x.png +0 -0
- package/templates/tauri-react/src-tauri/icons/32x32.png +0 -0
- package/templates/tauri-react/src-tauri/icons/icon.icns +0 -0
- package/templates/tauri-react/src-tauri/icons/icon.ico +0 -0
- package/templates/tauri-react/src-tauri/src/lib.rs +8 -0
- package/templates/tauri-react/src-tauri/src/main.rs +6 -0
- package/templates/tauri-react/src-tauri/tauri.conf.json +44 -0
- package/templates/tauri-react/tsconfig.json +15 -0
- package/templates/tauri-react/vite.config.ts +15 -0
- package/dist/chunk-KTSRAPSE.js.map +0 -1
- package/dist/chunk-ZGYRDYXS.js.map +0 -1
- /package/dist/{chunk-E7OCVRYL.js.map → chunk-MIMLCXSX.js.map} +0 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { Monitor, Wifi, ArrowRight, Loader2, AlertCircle } from 'lucide-react'
|
|
3
|
+
import { testConnection } from './sync-config'
|
|
4
|
+
|
|
5
|
+
interface SetupScreenProps {
|
|
6
|
+
onConnect: (syncUrl: string) => void
|
|
7
|
+
onSkip: () => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* First-launch setup screen.
|
|
12
|
+
*
|
|
13
|
+
* Shown when no sync server URL is configured. The user enters their
|
|
14
|
+
* organization's sync server URL, or skips to use the app in local-only mode.
|
|
15
|
+
*
|
|
16
|
+
* The connection test is advisory — if the server is unreachable (e.g.,
|
|
17
|
+
* during maintenance), the user can still save the URL and the app will
|
|
18
|
+
* connect when the server comes back.
|
|
19
|
+
*/
|
|
20
|
+
export function SetupScreen({ onConnect, onSkip }: SetupScreenProps) {
|
|
21
|
+
const [url, setUrl] = useState('')
|
|
22
|
+
const [testing, setTesting] = useState(false)
|
|
23
|
+
const [error, setError] = useState<string | null>(null)
|
|
24
|
+
const [unreachable, setUnreachable] = useState(false)
|
|
25
|
+
|
|
26
|
+
const validateUrl = (value: string): string | null => {
|
|
27
|
+
if (!value.startsWith('ws://') && !value.startsWith('wss://')) {
|
|
28
|
+
return 'URL must start with ws:// or wss://'
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
new URL(value.replace(/^ws/, 'http'))
|
|
32
|
+
} catch {
|
|
33
|
+
return 'Invalid URL format'
|
|
34
|
+
}
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
39
|
+
e.preventDefault()
|
|
40
|
+
const syncUrl = url.trim()
|
|
41
|
+
if (!syncUrl) return
|
|
42
|
+
|
|
43
|
+
const validationError = validateUrl(syncUrl)
|
|
44
|
+
if (validationError) {
|
|
45
|
+
setError(validationError)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
setTesting(true)
|
|
50
|
+
setError(null)
|
|
51
|
+
setUnreachable(false)
|
|
52
|
+
|
|
53
|
+
const reachable = await testConnection(syncUrl)
|
|
54
|
+
setTesting(false)
|
|
55
|
+
|
|
56
|
+
if (reachable) {
|
|
57
|
+
onConnect(syncUrl)
|
|
58
|
+
} else {
|
|
59
|
+
// Server unreachable — let the user save anyway
|
|
60
|
+
setUnreachable(true)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const handleSaveAnyway = () => {
|
|
65
|
+
onConnect(url.trim())
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div style={{ minHeight: '100vh', background: '#0a0a0a', color: '#f3f4f6', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
70
|
+
<div style={{ maxWidth: '480px', width: '100%', padding: '0 24px' }}>
|
|
71
|
+
{/* Logo */}
|
|
72
|
+
<div style={{ textAlign: 'center', marginBottom: '48px' }}>
|
|
73
|
+
<Monitor style={{ width: '48px', height: '48px', color: '#818cf8', margin: '0 auto 16px' }} />
|
|
74
|
+
<h1 style={{ fontSize: '28px', fontWeight: 'bold', marginBottom: '8px' }}>Welcome</h1>
|
|
75
|
+
<p style={{ color: '#6b7280', fontSize: '15px' }}>
|
|
76
|
+
Connect to your organization's sync server to sync data across devices.
|
|
77
|
+
</p>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
{/* Connection form */}
|
|
81
|
+
<form onSubmit={handleSubmit}>
|
|
82
|
+
<div style={{ marginBottom: '16px' }}>
|
|
83
|
+
<label style={{ display: 'block', fontSize: '14px', fontWeight: '500', marginBottom: '8px', color: '#d1d5db' }}>
|
|
84
|
+
Sync server URL
|
|
85
|
+
</label>
|
|
86
|
+
<div style={{ position: 'relative' }}>
|
|
87
|
+
<Wifi style={{ position: 'absolute', left: '12px', top: '50%', transform: 'translateY(-50%)', width: '18px', height: '18px', color: '#6b7280' }} />
|
|
88
|
+
<input
|
|
89
|
+
type="text"
|
|
90
|
+
value={url}
|
|
91
|
+
onChange={(e) => { setUrl(e.target.value); setError(null); setUnreachable(false) }}
|
|
92
|
+
placeholder="wss://your-server.example.com/kora-sync"
|
|
93
|
+
autoFocus
|
|
94
|
+
style={{
|
|
95
|
+
width: '100%',
|
|
96
|
+
borderRadius: '8px',
|
|
97
|
+
border: `1px solid ${error ? '#ef4444' : '#374151'}`,
|
|
98
|
+
background: '#111827',
|
|
99
|
+
padding: '12px 16px 12px 40px',
|
|
100
|
+
color: '#f3f4f6',
|
|
101
|
+
outline: 'none',
|
|
102
|
+
fontSize: '14px',
|
|
103
|
+
boxSizing: 'border-box',
|
|
104
|
+
}}
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
{error && (
|
|
108
|
+
<p style={{ color: '#ef4444', fontSize: '13px', marginTop: '8px' }}>{error}</p>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
{/* Server unreachable — offer to save anyway */}
|
|
113
|
+
{unreachable && (
|
|
114
|
+
<div style={{
|
|
115
|
+
marginBottom: '16px',
|
|
116
|
+
padding: '12px 16px',
|
|
117
|
+
borderRadius: '8px',
|
|
118
|
+
border: '1px solid #92400e',
|
|
119
|
+
background: '#451a03',
|
|
120
|
+
}}>
|
|
121
|
+
<div style={{ display: 'flex', gap: '8px', alignItems: 'flex-start' }}>
|
|
122
|
+
<AlertCircle style={{ width: '16px', height: '16px', color: '#fbbf24', flexShrink: 0, marginTop: '2px' }} />
|
|
123
|
+
<div>
|
|
124
|
+
<p style={{ fontSize: '13px', color: '#fde68a', marginBottom: '8px' }}>
|
|
125
|
+
Server is not reachable right now. This could be temporary (maintenance, network issues).
|
|
126
|
+
</p>
|
|
127
|
+
<div style={{ display: 'flex', gap: '8px' }}>
|
|
128
|
+
<button
|
|
129
|
+
type="button"
|
|
130
|
+
onClick={handleSaveAnyway}
|
|
131
|
+
style={{
|
|
132
|
+
fontSize: '13px',
|
|
133
|
+
color: '#fbbf24',
|
|
134
|
+
background: 'none',
|
|
135
|
+
border: '1px solid #92400e',
|
|
136
|
+
borderRadius: '6px',
|
|
137
|
+
padding: '4px 12px',
|
|
138
|
+
cursor: 'pointer',
|
|
139
|
+
}}
|
|
140
|
+
>
|
|
141
|
+
Save anyway — connect later
|
|
142
|
+
</button>
|
|
143
|
+
<button
|
|
144
|
+
type="submit"
|
|
145
|
+
style={{
|
|
146
|
+
fontSize: '13px',
|
|
147
|
+
color: '#9ca3af',
|
|
148
|
+
background: 'none',
|
|
149
|
+
border: '1px solid #374151',
|
|
150
|
+
borderRadius: '6px',
|
|
151
|
+
padding: '4px 12px',
|
|
152
|
+
cursor: 'pointer',
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
Retry
|
|
156
|
+
</button>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
|
|
163
|
+
{!unreachable && (
|
|
164
|
+
<button
|
|
165
|
+
type="submit"
|
|
166
|
+
disabled={testing || !url.trim()}
|
|
167
|
+
style={{
|
|
168
|
+
width: '100%',
|
|
169
|
+
display: 'flex',
|
|
170
|
+
alignItems: 'center',
|
|
171
|
+
justifyContent: 'center',
|
|
172
|
+
gap: '8px',
|
|
173
|
+
borderRadius: '8px',
|
|
174
|
+
background: '#4f46e5',
|
|
175
|
+
padding: '12px 20px',
|
|
176
|
+
fontWeight: '500',
|
|
177
|
+
color: 'white',
|
|
178
|
+
border: 'none',
|
|
179
|
+
cursor: testing || !url.trim() ? 'default' : 'pointer',
|
|
180
|
+
opacity: testing || !url.trim() ? 0.5 : 1,
|
|
181
|
+
fontSize: '14px',
|
|
182
|
+
marginBottom: '12px',
|
|
183
|
+
}}
|
|
184
|
+
>
|
|
185
|
+
{testing ? (
|
|
186
|
+
<>
|
|
187
|
+
<Loader2 style={{ width: '16px', height: '16px', animation: 'spin 1s linear infinite' }} />
|
|
188
|
+
Testing connection...
|
|
189
|
+
</>
|
|
190
|
+
) : (
|
|
191
|
+
<>
|
|
192
|
+
Connect
|
|
193
|
+
<ArrowRight style={{ width: '16px', height: '16px' }} />
|
|
194
|
+
</>
|
|
195
|
+
)}
|
|
196
|
+
</button>
|
|
197
|
+
)}
|
|
198
|
+
</form>
|
|
199
|
+
|
|
200
|
+
{/* Skip option */}
|
|
201
|
+
<div style={{ textAlign: 'center', marginTop: '24px' }}>
|
|
202
|
+
<button
|
|
203
|
+
onClick={onSkip}
|
|
204
|
+
style={{
|
|
205
|
+
background: 'none',
|
|
206
|
+
border: 'none',
|
|
207
|
+
color: '#6b7280',
|
|
208
|
+
cursor: 'pointer',
|
|
209
|
+
fontSize: '14px',
|
|
210
|
+
textDecoration: 'underline',
|
|
211
|
+
textUnderlineOffset: '3px',
|
|
212
|
+
}}
|
|
213
|
+
>
|
|
214
|
+
Skip — use offline only
|
|
215
|
+
</button>
|
|
216
|
+
<p style={{ color: '#374151', fontSize: '12px', marginTop: '8px' }}>
|
|
217
|
+
You can connect to a sync server later from settings.
|
|
218
|
+
</p>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
{/* Help text */}
|
|
222
|
+
<div style={{
|
|
223
|
+
marginTop: '48px',
|
|
224
|
+
padding: '16px',
|
|
225
|
+
borderRadius: '8px',
|
|
226
|
+
border: '1px solid #1f2937',
|
|
227
|
+
background: '#111827',
|
|
228
|
+
}}>
|
|
229
|
+
<p style={{ fontSize: '13px', color: '#9ca3af', lineHeight: '1.6' }}>
|
|
230
|
+
<strong style={{ color: '#d1d5db' }}>Don't have a server URL?</strong>
|
|
231
|
+
<br />
|
|
232
|
+
Ask your administrator for the sync server address. For local development,
|
|
233
|
+
use <code style={{ color: '#818cf8' }}>ws://localhost:3001/kora-sync</code>.
|
|
234
|
+
</p>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
)
|
|
239
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineSchema, t } from '@korajs/core'
|
|
2
|
+
|
|
3
|
+
export default defineSchema({
|
|
4
|
+
version: 1,
|
|
5
|
+
collections: {
|
|
6
|
+
todos: {
|
|
7
|
+
fields: {
|
|
8
|
+
title: t.string(),
|
|
9
|
+
completed: t.boolean().default(false),
|
|
10
|
+
createdAt: t.timestamp().auto(),
|
|
11
|
+
},
|
|
12
|
+
indexes: ['completed'],
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
})
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime sync server configuration.
|
|
3
|
+
*
|
|
4
|
+
* On first launch, the user enters their sync server URL (or organization URL).
|
|
5
|
+
* This is stored in localStorage and used for all subsequent launches.
|
|
6
|
+
*
|
|
7
|
+
* This allows a single binary to be distributed to multiple organizations,
|
|
8
|
+
* each pointing to their own sync server.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const SYNC_URL_KEY = 'kora-sync-url'
|
|
12
|
+
const SYNC_CONFIGURED_KEY = 'kora-sync-configured'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get the configured sync server URL.
|
|
16
|
+
* Priority: localStorage → VITE_SYNC_URL env var → null (needs setup).
|
|
17
|
+
*/
|
|
18
|
+
export function getSyncUrl(): string | null {
|
|
19
|
+
const stored = localStorage.getItem(SYNC_URL_KEY)
|
|
20
|
+
if (stored) return stored
|
|
21
|
+
|
|
22
|
+
// Compile-time default (set via VITE_SYNC_URL when building)
|
|
23
|
+
const envUrl = import.meta.env.VITE_SYNC_URL
|
|
24
|
+
if (envUrl) return envUrl
|
|
25
|
+
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Save a sync server URL to persistent local config. */
|
|
30
|
+
export function setSyncUrl(url: string): void {
|
|
31
|
+
localStorage.setItem(SYNC_URL_KEY, url)
|
|
32
|
+
localStorage.setItem(SYNC_CONFIGURED_KEY, 'true')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Clear the stored sync server URL (returns to setup screen). */
|
|
36
|
+
export function clearSyncUrl(): void {
|
|
37
|
+
localStorage.removeItem(SYNC_URL_KEY)
|
|
38
|
+
localStorage.removeItem(SYNC_CONFIGURED_KEY)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Whether the user has completed initial setup (connected or explicitly skipped).
|
|
43
|
+
* This is separate from getSyncUrl() — a user who skipped still completed setup.
|
|
44
|
+
*/
|
|
45
|
+
export function hasCompletedSetup(): boolean {
|
|
46
|
+
// If there's a compile-time URL, setup is not needed
|
|
47
|
+
if (import.meta.env.VITE_SYNC_URL) return true
|
|
48
|
+
return localStorage.getItem(SYNC_CONFIGURED_KEY) === 'true'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Mark setup as completed (used when user skips sync). */
|
|
52
|
+
export function markSetupComplete(): void {
|
|
53
|
+
localStorage.setItem(SYNC_CONFIGURED_KEY, 'true')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Full factory reset: clears sync config and all local data.
|
|
58
|
+
* The app reloads with a fresh state, showing the setup screen.
|
|
59
|
+
*/
|
|
60
|
+
export function factoryReset(): void {
|
|
61
|
+
// Clear sync config
|
|
62
|
+
clearSyncUrl()
|
|
63
|
+
localStorage.removeItem(SYNC_CONFIGURED_KEY)
|
|
64
|
+
|
|
65
|
+
// Clear all Kora local data by removing IndexedDB databases
|
|
66
|
+
// and any other localStorage keys the app may have set.
|
|
67
|
+
const keysToRemove: string[] = []
|
|
68
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
69
|
+
const key = localStorage.key(i)
|
|
70
|
+
if (key && key.startsWith('kora-')) {
|
|
71
|
+
keysToRemove.push(key)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const key of keysToRemove) {
|
|
75
|
+
localStorage.removeItem(key)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Reload to reinitialize everything from scratch
|
|
79
|
+
window.location.reload()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Test if a WebSocket URL is reachable by attempting a brief connection. */
|
|
83
|
+
export function testConnection(url: string, timeoutMs = 5000): Promise<boolean> {
|
|
84
|
+
return new Promise((resolve) => {
|
|
85
|
+
const timeout = setTimeout(() => {
|
|
86
|
+
ws.close()
|
|
87
|
+
resolve(false)
|
|
88
|
+
}, timeoutMs)
|
|
89
|
+
|
|
90
|
+
const ws = new WebSocket(url)
|
|
91
|
+
|
|
92
|
+
ws.onopen = () => {
|
|
93
|
+
clearTimeout(timeout)
|
|
94
|
+
ws.close()
|
|
95
|
+
resolve(true)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
ws.onerror = () => {
|
|
99
|
+
clearTimeout(timeout)
|
|
100
|
+
resolve(false)
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-updater integration via tauri-plugin-updater.
|
|
3
|
+
*
|
|
4
|
+
* Checks for updates on app launch and periodically. When an update is
|
|
5
|
+
* available, the user is notified and can install it with a restart.
|
|
6
|
+
*
|
|
7
|
+
* Configure the update endpoint in src-tauri/tauri.conf.json under
|
|
8
|
+
* plugins.updater.endpoints. For GitHub Releases, use:
|
|
9
|
+
* "endpoints": ["https://github.com/YOUR_ORG/YOUR_REPO/releases/latest/download/latest.json"]
|
|
10
|
+
*
|
|
11
|
+
* To generate signing keys: `pnpm tauri signer generate -w ~/.tauri/myapp.key`
|
|
12
|
+
* Set the pubkey in tauri.conf.json and TAURI_SIGNING_PRIVATE_KEY in CI.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
let updateChecked = false
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check for updates once on app startup.
|
|
19
|
+
* Silently skips if the updater is not configured (empty endpoints/pubkey).
|
|
20
|
+
*/
|
|
21
|
+
export async function checkForUpdates(): Promise<void> {
|
|
22
|
+
if (updateChecked) return
|
|
23
|
+
updateChecked = true
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const { check } = await import('@tauri-apps/plugin-updater')
|
|
27
|
+
const update = await check()
|
|
28
|
+
if (update) {
|
|
29
|
+
console.log(`Update available: v${update.version}`)
|
|
30
|
+
// Download and install — the app will restart automatically
|
|
31
|
+
await update.downloadAndInstall()
|
|
32
|
+
}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
// Updater not configured or network unavailable — this is fine.
|
|
35
|
+
// The app works fully offline; updates are best-effort.
|
|
36
|
+
console.debug('Update check skipped:', err)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "app"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
|
|
6
|
+
[lib]
|
|
7
|
+
name = "app_lib"
|
|
8
|
+
crate-type = ["staticlib", "cdylib", "rlib"]
|
|
9
|
+
|
|
10
|
+
[build-dependencies]
|
|
11
|
+
tauri-build = { version = "2", features = [] }
|
|
12
|
+
|
|
13
|
+
[dependencies]
|
|
14
|
+
tauri = { version = "2", features = [] }
|
|
15
|
+
tauri-plugin-opener = "2"
|
|
16
|
+
tauri-plugin-updater = "2"
|
|
17
|
+
tauri-plugin-kora-sqlite = { version = "0.1", path = "../node_modules/@korajs/tauri/plugin" }
|
|
18
|
+
serde = { version = "1", features = ["derive"] }
|
|
19
|
+
serde_json = "1"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../gen/schemas/desktop-schema.json",
|
|
3
|
+
"identifier": "default",
|
|
4
|
+
"description": "Capability for the main window",
|
|
5
|
+
"windows": ["main"],
|
|
6
|
+
"permissions": [
|
|
7
|
+
"core:default",
|
|
8
|
+
"opener:default",
|
|
9
|
+
"kora-sqlite:default",
|
|
10
|
+
"updater:default"
|
|
11
|
+
]
|
|
12
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pub fn run() {
|
|
2
|
+
tauri::Builder::default()
|
|
3
|
+
.plugin(tauri_plugin_opener::init())
|
|
4
|
+
.plugin(tauri_plugin_updater::Builder::new().build())
|
|
5
|
+
.plugin(tauri_plugin_kora_sqlite::init())
|
|
6
|
+
.run(tauri::generate_context!())
|
|
7
|
+
.expect("error while running tauri application");
|
|
8
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/nicholasgriffintn/tauri/refs/heads/dev/crates/tauri-config-schema/schema.json",
|
|
3
|
+
"productName": "kora-app",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"identifier": "com.kora.app",
|
|
6
|
+
"build": {
|
|
7
|
+
"frontendDist": "../dist",
|
|
8
|
+
"devUrl": "http://localhost:5173",
|
|
9
|
+
"beforeDevCommand": "vite",
|
|
10
|
+
"beforeBuildCommand": "vite build"
|
|
11
|
+
},
|
|
12
|
+
"app": {
|
|
13
|
+
"windows": [
|
|
14
|
+
{
|
|
15
|
+
"title": "Kora Desktop App",
|
|
16
|
+
"width": 900,
|
|
17
|
+
"height": 700,
|
|
18
|
+
"resizable": true,
|
|
19
|
+
"fullscreen": false
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"security": {
|
|
23
|
+
"csp": null
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"bundle": {
|
|
27
|
+
"active": true,
|
|
28
|
+
"targets": "all",
|
|
29
|
+
"icon": [
|
|
30
|
+
"icons/32x32.png",
|
|
31
|
+
"icons/128x128.png",
|
|
32
|
+
"icons/128x128@2x.png",
|
|
33
|
+
"icons/icon.icns",
|
|
34
|
+
"icons/icon.ico"
|
|
35
|
+
],
|
|
36
|
+
"createUpdaterArtifacts": "v2Compatible"
|
|
37
|
+
},
|
|
38
|
+
"plugins": {
|
|
39
|
+
"updater": {
|
|
40
|
+
"pubkey": "",
|
|
41
|
+
"endpoints": []
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noUncheckedIndexedAccess": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"outDir": "dist",
|
|
12
|
+
"types": ["vite/client"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import react from '@vitejs/plugin-react'
|
|
2
|
+
import { defineConfig } from 'vite'
|
|
3
|
+
|
|
4
|
+
// Tauri apps use native SQLite — no WASM, no OPFS, no cross-origin isolation needed.
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react()],
|
|
7
|
+
// Prevent Vite from obscuring Rust compilation errors
|
|
8
|
+
clearScreen: false,
|
|
9
|
+
server: {
|
|
10
|
+
// Tauri expects a fixed port; fail if it's not available
|
|
11
|
+
strictPort: true,
|
|
12
|
+
},
|
|
13
|
+
// Environment variables with this prefix are exposed to the app
|
|
14
|
+
envPrefix: ['VITE_', 'TAURI_'],
|
|
15
|
+
})
|