helixevo 0.2.28 → 0.2.30
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { execSync } from 'child_process'
|
|
3
|
-
import { existsSync, cpSync, readdirSync, rmSync, writeFileSync, mkdirSync } from 'fs'
|
|
3
|
+
import { existsSync, cpSync, readdirSync, rmSync, writeFileSync, readFileSync, mkdirSync } from 'fs'
|
|
4
4
|
import { join } from 'path'
|
|
5
5
|
import { homedir } from 'os'
|
|
6
6
|
|
|
@@ -45,8 +45,15 @@ function copyDashboardFiles(sourceDir: string, newVersion: string): void {
|
|
|
45
45
|
// Write version marker
|
|
46
46
|
writeFileSync(join(HELIX_DASHBOARD_DIR, '.helixevo-version'), newVersion)
|
|
47
47
|
|
|
48
|
-
//
|
|
48
|
+
// Write .env.local — survives copyToHelix overwrites from old CLIs
|
|
49
49
|
writeFileSync(join(HELIX_DASHBOARD_DIR, '.env.local'), `HELIXEVO_VERSION=${newVersion}\n`)
|
|
50
|
+
|
|
51
|
+
// Touch next.config.mjs to trigger Next.js dev mode auto-restart
|
|
52
|
+
const configPath = join(HELIX_DASHBOARD_DIR, 'next.config.mjs')
|
|
53
|
+
if (existsSync(configPath)) {
|
|
54
|
+
const content = readFileSync(configPath, 'utf-8')
|
|
55
|
+
writeFileSync(configPath, content) // rewrite same content = new mtime
|
|
56
|
+
}
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
export async function POST() {
|
|
@@ -38,8 +38,8 @@ export default function EvolutionPage() {
|
|
|
38
38
|
<div style={{ paddingLeft: 20, position: 'relative' }}>
|
|
39
39
|
<div style={{ position: 'absolute', left: 9, top: 0, bottom: 0, width: 2, background: 'var(--border)' }} />
|
|
40
40
|
|
|
41
|
-
{iterations.map(iter => (
|
|
42
|
-
<div key={iter.id} style={{ position: 'relative', marginBottom: 24, paddingLeft: 28 }}>
|
|
41
|
+
{iterations.map((iter, idx) => (
|
|
42
|
+
<div key={`${iter.id}-${idx}`} style={{ position: 'relative', marginBottom: 24, paddingLeft: 28 }}>
|
|
43
43
|
{/* Timeline dot */}
|
|
44
44
|
<div style={{
|
|
45
45
|
position: 'absolute', left: -1, top: 4, width: 14, height: 14,
|
|
@@ -15,7 +15,7 @@ function compareVersions(current: string, latest: string): boolean {
|
|
|
15
15
|
return false
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
type UpdateState = 'idle' | 'updating' | '
|
|
18
|
+
type UpdateState = 'idle' | 'updating' | 'reloading' | 'error'
|
|
19
19
|
|
|
20
20
|
export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
21
21
|
const [latestVersion, setLatestVersion] = useState<string | null>(null)
|
|
@@ -23,7 +23,7 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
23
23
|
const [state, setState] = useState<UpdateState>('idle')
|
|
24
24
|
const [newVersion, setNewVersion] = useState<string | null>(null)
|
|
25
25
|
const [errorMsg, setErrorMsg] = useState<string | null>(null)
|
|
26
|
-
const [
|
|
26
|
+
const [countdown, setCountdown] = useState(0)
|
|
27
27
|
|
|
28
28
|
useEffect(() => {
|
|
29
29
|
let mounted = true
|
|
@@ -53,8 +53,29 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
53
53
|
const data = await res.json()
|
|
54
54
|
|
|
55
55
|
if (data.success) {
|
|
56
|
-
setState('installed')
|
|
57
56
|
setNewVersion(data.version)
|
|
57
|
+
setState('reloading')
|
|
58
|
+
|
|
59
|
+
// Next.js dev mode detects config change and auto-restarts.
|
|
60
|
+
// Wait for recompilation, then poll until ready, then reload.
|
|
61
|
+
let secs = 12
|
|
62
|
+
setCountdown(secs)
|
|
63
|
+
const timer = setInterval(async () => {
|
|
64
|
+
secs--
|
|
65
|
+
setCountdown(secs)
|
|
66
|
+
if (secs <= 0) {
|
|
67
|
+
clearInterval(timer)
|
|
68
|
+
// Try polling a few times before force-reloading
|
|
69
|
+
for (let i = 0; i < 10; i++) {
|
|
70
|
+
try {
|
|
71
|
+
const check = await fetch('/?_v=' + Date.now(), { cache: 'no-store' })
|
|
72
|
+
if (check.ok) break
|
|
73
|
+
} catch {}
|
|
74
|
+
await new Promise(r => setTimeout(r, 1500))
|
|
75
|
+
}
|
|
76
|
+
window.location.href = window.location.pathname + '?v=' + Date.now()
|
|
77
|
+
}
|
|
78
|
+
}, 1000)
|
|
58
79
|
} else {
|
|
59
80
|
setState('error')
|
|
60
81
|
setErrorMsg(data.error ?? 'Update failed')
|
|
@@ -65,65 +86,47 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
65
86
|
}
|
|
66
87
|
}
|
|
67
88
|
|
|
68
|
-
const restartCmd = 'helixevo dashboard'
|
|
69
|
-
const handleCopy = async () => {
|
|
70
|
-
try {
|
|
71
|
-
await navigator.clipboard.writeText(restartCmd)
|
|
72
|
-
setCopied(true)
|
|
73
|
-
setTimeout(() => setCopied(false), 2000)
|
|
74
|
-
} catch {}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
89
|
return (
|
|
78
90
|
<div style={{
|
|
79
91
|
position: 'fixed',
|
|
80
92
|
bottom: 20,
|
|
81
93
|
right: 20,
|
|
82
|
-
width:
|
|
94
|
+
width: 320,
|
|
83
95
|
background: 'var(--bg-card)',
|
|
84
|
-
border: `1px solid ${state === '
|
|
96
|
+
border: `1px solid ${state === 'reloading' ? 'var(--green-border)' : state === 'error' ? 'var(--red-border)' : 'var(--purple-border)'}`,
|
|
85
97
|
borderRadius: 'var(--radius-lg)',
|
|
86
98
|
boxShadow: 'var(--shadow-xl)',
|
|
87
99
|
padding: '16px 18px',
|
|
88
100
|
zIndex: 9999,
|
|
89
101
|
animation: 'updateSlideIn 0.4s ease-out',
|
|
90
102
|
}}>
|
|
91
|
-
{/* Close
|
|
92
|
-
{state
|
|
103
|
+
{/* Close */}
|
|
104
|
+
{(state === 'idle' || state === 'error') && (
|
|
93
105
|
<button
|
|
94
106
|
onClick={() => setDismissed(true)}
|
|
95
107
|
style={{
|
|
96
108
|
position: 'absolute', top: 8, right: 10,
|
|
97
109
|
background: 'none', border: 'none', cursor: 'pointer',
|
|
98
|
-
color: 'var(--text-dim)', fontSize: 18, lineHeight: 1,
|
|
99
|
-
padding: '2px 4px',
|
|
110
|
+
color: 'var(--text-dim)', fontSize: 18, lineHeight: 1, padding: '2px 4px',
|
|
100
111
|
}}
|
|
101
|
-
|
|
102
|
-
>
|
|
103
|
-
×
|
|
104
|
-
</button>
|
|
112
|
+
>×</button>
|
|
105
113
|
)}
|
|
106
114
|
|
|
107
115
|
{/* Header */}
|
|
108
116
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
|
|
109
117
|
<div style={{
|
|
110
118
|
width: 28, height: 28, borderRadius: '50%',
|
|
111
|
-
background: state === '
|
|
112
|
-
|
|
113
|
-
: 'var(--purple-light)',
|
|
114
|
-
display: 'flex',
|
|
115
|
-
alignItems: 'center', justifyContent: 'center',
|
|
119
|
+
background: state === 'reloading' ? 'var(--green-light)' : state === 'error' ? 'var(--red-light)' : 'var(--purple-light)',
|
|
120
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
116
121
|
}}>
|
|
117
|
-
{state === 'updating' ? (
|
|
122
|
+
{(state === 'updating' || state === 'reloading') ? (
|
|
118
123
|
<div style={{
|
|
119
|
-
width: 14, height: 14,
|
|
120
|
-
|
|
124
|
+
width: 14, height: 14,
|
|
125
|
+
border: `2px solid ${state === 'reloading' ? 'var(--green-border)' : 'var(--purple-border)'}`,
|
|
126
|
+
borderTopColor: state === 'reloading' ? 'var(--green)' : 'var(--purple)',
|
|
127
|
+
borderRadius: '50%',
|
|
121
128
|
animation: 'updateSpin 0.8s linear infinite',
|
|
122
129
|
}} />
|
|
123
|
-
) : state === 'installed' ? (
|
|
124
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--green)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
125
|
-
<path d="M20 6L9 17l-5-5" />
|
|
126
|
-
</svg>
|
|
127
130
|
) : state === 'error' ? (
|
|
128
131
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--red)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
129
132
|
<path d="M18 6L6 18M6 6l12 12" />
|
|
@@ -135,41 +138,29 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
135
138
|
)}
|
|
136
139
|
</div>
|
|
137
140
|
<div>
|
|
138
|
-
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)'
|
|
141
|
+
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>
|
|
139
142
|
{state === 'updating' ? 'Installing...'
|
|
140
|
-
: state === '
|
|
143
|
+
: state === 'reloading' ? 'Reloading...'
|
|
141
144
|
: state === 'error' ? 'Update Failed'
|
|
142
145
|
: 'Update Available'}
|
|
143
146
|
</div>
|
|
144
147
|
<div style={{ fontSize: 11, color: 'var(--text-dim)' }}>
|
|
145
|
-
{state === '
|
|
146
|
-
? <>v{newVersion}
|
|
148
|
+
{state === 'reloading'
|
|
149
|
+
? <>v{newVersion} installed — refreshing in {countdown}s</>
|
|
147
150
|
: <>v{currentVersion} → <span style={{ color: 'var(--green)', fontWeight: 600 }}>v{latestVersion}</span></>
|
|
148
151
|
}
|
|
149
152
|
</div>
|
|
150
153
|
</div>
|
|
151
154
|
</div>
|
|
152
155
|
|
|
153
|
-
{/* Idle
|
|
156
|
+
{/* Idle */}
|
|
154
157
|
{state === 'idle' && (
|
|
155
|
-
<button
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
color: '#fff',
|
|
162
|
-
border: 'none',
|
|
163
|
-
borderRadius: 'var(--radius)',
|
|
164
|
-
fontSize: 13,
|
|
165
|
-
fontWeight: 600,
|
|
166
|
-
cursor: 'pointer',
|
|
167
|
-
display: 'flex',
|
|
168
|
-
alignItems: 'center',
|
|
169
|
-
justifyContent: 'center',
|
|
170
|
-
gap: 6,
|
|
171
|
-
}}
|
|
172
|
-
>
|
|
158
|
+
<button onClick={handleUpdate} style={{
|
|
159
|
+
width: '100%', padding: '9px 16px',
|
|
160
|
+
background: 'var(--purple)', color: '#fff', border: 'none',
|
|
161
|
+
borderRadius: 'var(--radius)', fontSize: 13, fontWeight: 600, cursor: 'pointer',
|
|
162
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
|
|
163
|
+
}}>
|
|
173
164
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
174
165
|
<path d="M12 19V5m-7 7l7-7 7 7" />
|
|
175
166
|
</svg>
|
|
@@ -177,62 +168,36 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
177
168
|
</button>
|
|
178
169
|
)}
|
|
179
170
|
|
|
180
|
-
{/* Updating
|
|
171
|
+
{/* Updating */}
|
|
181
172
|
{state === 'updating' && (
|
|
182
173
|
<div style={{
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
borderRadius: 'var(--radius)',
|
|
187
|
-
fontSize: 12,
|
|
188
|
-
color: 'var(--text-secondary)',
|
|
189
|
-
textAlign: 'center',
|
|
174
|
+
padding: '9px 16px', background: 'var(--bg-section)',
|
|
175
|
+
borderRadius: 'var(--radius)', fontSize: 12,
|
|
176
|
+
color: 'var(--text-secondary)', textAlign: 'center',
|
|
190
177
|
}}>
|
|
191
178
|
Installing helixevo@latest...
|
|
192
179
|
</div>
|
|
193
180
|
)}
|
|
194
181
|
|
|
195
|
-
{/*
|
|
196
|
-
{state === '
|
|
182
|
+
{/* Reloading with countdown */}
|
|
183
|
+
{state === 'reloading' && (
|
|
197
184
|
<div>
|
|
198
185
|
<div style={{
|
|
199
|
-
padding: '
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
color: 'var(--green)',
|
|
204
|
-
fontWeight: 600,
|
|
205
|
-
marginBottom: 10,
|
|
206
|
-
textAlign: 'center',
|
|
186
|
+
padding: '9px 16px', background: 'var(--green-light)',
|
|
187
|
+
borderRadius: 'var(--radius)', fontSize: 12,
|
|
188
|
+
color: 'var(--green)', textAlign: 'center', fontWeight: 600,
|
|
189
|
+
marginBottom: 8,
|
|
207
190
|
}}>
|
|
208
|
-
|
|
209
|
-
</div>
|
|
210
|
-
<div style={{ fontSize: 11, color: 'var(--text-dim)', marginBottom: 8, lineHeight: 1.5 }}>
|
|
211
|
-
Restart the dashboard to load the new version:
|
|
191
|
+
Dashboard will refresh automatically
|
|
212
192
|
</div>
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
onClick={handleCopy}
|
|
222
|
-
style={{
|
|
223
|
-
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
|
224
|
-
padding: '7px 12px',
|
|
225
|
-
background: 'var(--bg-section)', border: '1px solid var(--border)',
|
|
226
|
-
borderRadius: 'var(--radius)',
|
|
227
|
-
fontFamily: 'var(--font-mono)', fontSize: 11,
|
|
228
|
-
color: 'var(--text)', cursor: 'pointer',
|
|
229
|
-
}}
|
|
230
|
-
>
|
|
231
|
-
<span>$ {restartCmd}</span>
|
|
232
|
-
<span style={{ fontSize: 10, color: copied ? 'var(--green)' : 'var(--purple)', fontWeight: 600, fontFamily: 'var(--font)' }}>
|
|
233
|
-
{copied ? 'Copied!' : 'Copy'}
|
|
234
|
-
</span>
|
|
235
|
-
</div>
|
|
193
|
+
{/* Progress bar */}
|
|
194
|
+
<div style={{ height: 3, background: 'var(--bg-section)', borderRadius: 2, overflow: 'hidden' }}>
|
|
195
|
+
<div style={{
|
|
196
|
+
height: '100%', background: 'var(--green)',
|
|
197
|
+
borderRadius: 2,
|
|
198
|
+
width: `${((8 - countdown) / 8) * 100}%`,
|
|
199
|
+
transition: 'width 1s linear',
|
|
200
|
+
}} />
|
|
236
201
|
</div>
|
|
237
202
|
</div>
|
|
238
203
|
)}
|
|
@@ -241,32 +206,18 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
241
206
|
{state === 'error' && (
|
|
242
207
|
<>
|
|
243
208
|
<div style={{
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
borderRadius: 'var(--radius)',
|
|
248
|
-
fontSize: 11,
|
|
249
|
-
color: 'var(--red)',
|
|
250
|
-
marginBottom: 8,
|
|
251
|
-
maxHeight: 60,
|
|
252
|
-
overflow: 'auto',
|
|
209
|
+
padding: '8px 12px', background: 'var(--red-light)',
|
|
210
|
+
borderRadius: 'var(--radius)', fontSize: 11, color: 'var(--red)',
|
|
211
|
+
marginBottom: 8, maxHeight: 60, overflow: 'auto',
|
|
253
212
|
}}>
|
|
254
213
|
{errorMsg}
|
|
255
214
|
</div>
|
|
256
|
-
<button
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
color: 'var(--text-secondary)',
|
|
263
|
-
border: '1px solid var(--border)',
|
|
264
|
-
borderRadius: 'var(--radius)',
|
|
265
|
-
fontSize: 12,
|
|
266
|
-
fontWeight: 600,
|
|
267
|
-
cursor: 'pointer',
|
|
268
|
-
}}
|
|
269
|
-
>
|
|
215
|
+
<button onClick={handleUpdate} style={{
|
|
216
|
+
width: '100%', padding: '7px 12px',
|
|
217
|
+
background: 'var(--bg-section)', color: 'var(--text-secondary)',
|
|
218
|
+
border: '1px solid var(--border)', borderRadius: 'var(--radius)',
|
|
219
|
+
fontSize: 12, fontWeight: 600, cursor: 'pointer',
|
|
220
|
+
}}>
|
|
270
221
|
Retry
|
|
271
222
|
</button>
|
|
272
223
|
</>
|
package/package.json
CHANGED