veryfront 0.0.82 → 0.0.84
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 +18 -17
- package/esm/deno.js +1 -1
- package/esm/proxy/cache/index.d.ts +41 -0
- package/esm/proxy/cache/index.d.ts.map +1 -0
- package/esm/proxy/cache/index.js +75 -0
- package/esm/proxy/cache/memory-cache.d.ts +18 -0
- package/esm/proxy/cache/memory-cache.d.ts.map +1 -0
- package/esm/proxy/cache/memory-cache.js +100 -0
- package/esm/proxy/cache/redis-cache.d.ts +27 -0
- package/esm/proxy/cache/redis-cache.d.ts.map +1 -0
- package/esm/proxy/cache/redis-cache.js +183 -0
- package/esm/proxy/cache/resilient-cache.d.ts +44 -0
- package/esm/proxy/cache/resilient-cache.d.ts.map +1 -0
- package/esm/proxy/cache/resilient-cache.js +178 -0
- package/esm/proxy/cache/types.d.ts +65 -0
- package/esm/proxy/cache/types.d.ts.map +1 -0
- package/esm/proxy/cache/types.js +7 -0
- package/esm/proxy/handler.d.ts +81 -0
- package/esm/proxy/handler.d.ts.map +1 -0
- package/esm/proxy/handler.js +417 -0
- package/esm/proxy/logger.d.ts +29 -0
- package/esm/proxy/logger.d.ts.map +1 -0
- package/esm/proxy/logger.js +258 -0
- package/esm/proxy/oauth-client.d.ts +15 -0
- package/esm/proxy/oauth-client.d.ts.map +1 -0
- package/esm/proxy/oauth-client.js +52 -0
- package/esm/proxy/token-manager.d.ts +59 -0
- package/esm/proxy/token-manager.d.ts.map +1 -0
- package/esm/proxy/token-manager.js +125 -0
- package/esm/proxy/tracing.d.ts +39 -0
- package/esm/proxy/tracing.d.ts.map +1 -0
- package/esm/proxy/tracing.js +194 -0
- package/esm/src/cache/backend.d.ts +2 -0
- package/esm/src/cache/backend.d.ts.map +1 -1
- package/esm/src/cache/backend.js +2 -0
- package/esm/src/cache/cache-key-builder.d.ts +0 -4
- package/esm/src/cache/cache-key-builder.d.ts.map +1 -1
- package/esm/src/cache/cache-key-builder.js +0 -6
- package/esm/src/cache/multi-tier.d.ts +0 -29
- package/esm/src/cache/multi-tier.d.ts.map +1 -1
- package/esm/src/cache/multi-tier.js +0 -26
- package/esm/src/cli/app/actions.d.ts +26 -0
- package/esm/src/cli/app/actions.d.ts.map +1 -0
- package/esm/src/cli/app/actions.js +152 -0
- package/esm/src/cli/app/components/inline-input.d.ts +35 -0
- package/esm/src/cli/app/components/inline-input.d.ts.map +1 -0
- package/esm/src/cli/app/components/inline-input.js +220 -0
- package/esm/src/cli/app/components/list-select.d.ts +69 -0
- package/esm/src/cli/app/components/list-select.d.ts.map +1 -0
- package/esm/src/cli/app/components/list-select.js +137 -0
- package/esm/src/cli/app/index.d.ts +45 -0
- package/esm/src/cli/app/index.d.ts.map +1 -0
- package/esm/src/cli/app/index.js +1252 -0
- package/esm/src/cli/app/state.d.ts +122 -0
- package/esm/src/cli/app/state.d.ts.map +1 -0
- package/esm/src/cli/app/state.js +232 -0
- package/esm/src/cli/app/views/dashboard.d.ts +19 -0
- package/esm/src/cli/app/views/dashboard.d.ts.map +1 -0
- package/esm/src/cli/app/views/dashboard.js +178 -0
- package/esm/src/cli/commands/dev.js +2 -2
- package/esm/src/cli/commands/new.js +1 -1
- package/esm/src/cli/index/command-router.d.ts.map +1 -1
- package/esm/src/cli/index/command-router.js +9 -39
- package/esm/src/cli/index/start-handler.d.ts +3 -0
- package/esm/src/cli/index/start-handler.d.ts.map +1 -0
- package/esm/src/cli/index/start-handler.js +145 -0
- package/esm/src/cli/mcp/index.d.ts +11 -0
- package/esm/src/cli/mcp/index.d.ts.map +1 -0
- package/esm/src/cli/mcp/index.js +10 -0
- package/esm/src/cli/ui/tui.js +1 -1
- package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts +2 -0
- package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts.map +1 -1
- package/esm/src/middleware/builtin/security/redis-rate-limit.js +23 -9
- package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.d.ts +10 -0
- package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.js +30 -42
- package/esm/src/modules/react-loader/ssr-module-loader/loader.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/loader.js +34 -13
- package/esm/src/platform/adapters/fs/cache/file-cache.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/cache/file-cache.js +9 -3
- package/esm/src/server/context/cache-invalidation.d.ts.map +1 -1
- package/esm/src/server/context/cache-invalidation.js +4 -0
- package/esm/src/server/handlers/dev/dashboard/api.js +4 -0
- package/esm/src/server/handlers/dev/projects/ui-handler.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/projects/ui-handler.js +6 -0
- package/esm/src/transforms/esm/http-cache.d.ts.map +1 -1
- package/esm/src/transforms/esm/http-cache.js +139 -64
- package/esm/src/utils/index.d.ts +1 -1
- package/esm/src/utils/index.d.ts.map +1 -1
- package/esm/src/utils/index.js +1 -1
- package/package.json +2 -1
- package/src/deno.js +1 -1
- package/src/proxy/cache/index.ts +93 -0
- package/src/proxy/cache/memory-cache.ts +120 -0
- package/src/proxy/cache/redis-cache.ts +203 -0
- package/src/proxy/cache/resilient-cache.ts +205 -0
- package/src/proxy/cache/types.ts +72 -0
- package/src/proxy/handler.ts +593 -0
- package/src/proxy/logger.ts +329 -0
- package/src/proxy/oauth-client.ts +91 -0
- package/src/proxy/token-manager.ts +174 -0
- package/src/proxy/tracing.ts +237 -0
- package/src/src/cache/backend.ts +3 -0
- package/src/src/cache/cache-key-builder.ts +0 -9
- package/src/src/cache/multi-tier.ts +0 -41
- package/src/src/cli/app/actions.ts +190 -0
- package/src/src/cli/app/components/inline-input.ts +255 -0
- package/src/src/cli/app/components/list-select.ts +215 -0
- package/src/src/cli/app/index.ts +1471 -0
- package/src/src/cli/app/state.ts +385 -0
- package/src/src/cli/app/views/dashboard.ts +212 -0
- package/src/src/cli/commands/dev.ts +2 -2
- package/src/src/cli/commands/new.ts +1 -1
- package/src/src/cli/index/command-router.ts +9 -40
- package/src/src/cli/index/start-handler.ts +195 -0
- package/src/src/cli/mcp/index.ts +11 -0
- package/src/src/cli/ui/tui.ts +1 -1
- package/src/src/middleware/builtin/security/redis-rate-limit.ts +24 -11
- package/src/src/modules/react-loader/ssr-module-loader/cache/redis.ts +36 -50
- package/src/src/modules/react-loader/ssr-module-loader/loader.ts +38 -14
- package/src/src/platform/adapters/fs/cache/file-cache.ts +9 -3
- package/src/src/server/context/cache-invalidation.ts +4 -0
- package/src/src/server/handlers/dev/dashboard/api.ts +2 -0
- package/src/src/server/handlers/dev/projects/ui-handler.ts +6 -0
- package/src/src/transforms/esm/http-cache.ts +148 -73
- package/src/src/utils/index.ts +0 -1
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import type { ListItem, ListSelectState } from "./components/list-select.js";
|
|
2
|
+
import { createListState } from "./components/list-select.js";
|
|
3
|
+
import { getRuntimeEnv, type RuntimeEnv } from "../../config/runtime-env.js";
|
|
4
|
+
import { cwd } from "../../platform/compat/process.js";
|
|
5
|
+
|
|
6
|
+
export type AppView =
|
|
7
|
+
| "dashboard"
|
|
8
|
+
| "project-detail"
|
|
9
|
+
| "new-project"
|
|
10
|
+
| "templates"
|
|
11
|
+
| "examples"
|
|
12
|
+
| "auth"
|
|
13
|
+
| "help";
|
|
14
|
+
|
|
15
|
+
export interface ProjectInfo {
|
|
16
|
+
slug: string;
|
|
17
|
+
path: string;
|
|
18
|
+
type: "local" | "example" | "template";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ServerStatus {
|
|
22
|
+
running: boolean;
|
|
23
|
+
url: string;
|
|
24
|
+
port: number;
|
|
25
|
+
errors: number;
|
|
26
|
+
warnings: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface MCPStatus {
|
|
30
|
+
enabled: boolean;
|
|
31
|
+
transport: "stdio" | "http" | null;
|
|
32
|
+
connected: boolean;
|
|
33
|
+
clientName?: string;
|
|
34
|
+
httpPort?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface RemoteState {
|
|
38
|
+
user: { email: string; name?: string } | null;
|
|
39
|
+
projects: Array<{ id: string; name: string; slug: string }>;
|
|
40
|
+
/** Currently focused index in remote projects list */
|
|
41
|
+
focusedIndex: number;
|
|
42
|
+
/** Scroll offset for remote projects list */
|
|
43
|
+
scrollOffset: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface InputState {
|
|
47
|
+
active: boolean;
|
|
48
|
+
prompt: string;
|
|
49
|
+
value: string;
|
|
50
|
+
cursorPos: number;
|
|
51
|
+
onSubmit: ((value: string) => void) | null;
|
|
52
|
+
onCancel: (() => void) | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface LogMeta {
|
|
56
|
+
method?: string;
|
|
57
|
+
path?: string;
|
|
58
|
+
status?: number;
|
|
59
|
+
durationMs?: number;
|
|
60
|
+
project?: string;
|
|
61
|
+
env?: string;
|
|
62
|
+
releaseId?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface LogEntry {
|
|
66
|
+
time: Date;
|
|
67
|
+
level: "info" | "warn" | "error" | "debug";
|
|
68
|
+
message: string;
|
|
69
|
+
meta?: LogMeta;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface AppState {
|
|
73
|
+
view: AppView;
|
|
74
|
+
previousView: AppView | null;
|
|
75
|
+
|
|
76
|
+
server: ServerStatus;
|
|
77
|
+
mcp: MCPStatus;
|
|
78
|
+
remote: RemoteState;
|
|
79
|
+
|
|
80
|
+
projects: ListSelectState<ProjectInfo>;
|
|
81
|
+
examples: ListSelectState<ProjectInfo>;
|
|
82
|
+
templates: ListSelectState<ProjectInfo>;
|
|
83
|
+
|
|
84
|
+
activeList: "projects" | "examples" | "templates" | "remoteProjects";
|
|
85
|
+
selectedProject: ProjectInfo | null;
|
|
86
|
+
|
|
87
|
+
wizard: {
|
|
88
|
+
step: number;
|
|
89
|
+
startType: "scratch" | "template" | "example" | null;
|
|
90
|
+
selectedTemplate: string | null;
|
|
91
|
+
integrations: string[];
|
|
92
|
+
projectName: string;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
input: InputState;
|
|
96
|
+
|
|
97
|
+
logs: LogEntry[];
|
|
98
|
+
maxLogs: number;
|
|
99
|
+
logsExpanded: boolean;
|
|
100
|
+
logScroll: number;
|
|
101
|
+
|
|
102
|
+
/** Auth provider selection index (0=Google, 1=GitHub, 2=Microsoft) */
|
|
103
|
+
authProviderIndex: number;
|
|
104
|
+
/** New project option index (0=template, 1=example, 2=scratch) */
|
|
105
|
+
newProjectIndex: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function createInitialState(): AppState {
|
|
109
|
+
return {
|
|
110
|
+
view: "dashboard",
|
|
111
|
+
previousView: null,
|
|
112
|
+
server: {
|
|
113
|
+
running: false,
|
|
114
|
+
url: "http://veryfront.me:8080",
|
|
115
|
+
port: 8080,
|
|
116
|
+
errors: 0,
|
|
117
|
+
warnings: 0,
|
|
118
|
+
},
|
|
119
|
+
mcp: {
|
|
120
|
+
enabled: false,
|
|
121
|
+
transport: null,
|
|
122
|
+
connected: false,
|
|
123
|
+
},
|
|
124
|
+
remote: {
|
|
125
|
+
user: null,
|
|
126
|
+
projects: [],
|
|
127
|
+
focusedIndex: 0,
|
|
128
|
+
scrollOffset: 0,
|
|
129
|
+
},
|
|
130
|
+
projects: createListState([]),
|
|
131
|
+
examples: createListState([]),
|
|
132
|
+
templates: createListState([]),
|
|
133
|
+
activeList: "projects",
|
|
134
|
+
selectedProject: null,
|
|
135
|
+
wizard: {
|
|
136
|
+
step: 0,
|
|
137
|
+
startType: null,
|
|
138
|
+
selectedTemplate: null,
|
|
139
|
+
integrations: [],
|
|
140
|
+
projectName: "",
|
|
141
|
+
},
|
|
142
|
+
input: {
|
|
143
|
+
active: false,
|
|
144
|
+
prompt: "",
|
|
145
|
+
value: "",
|
|
146
|
+
cursorPos: 0,
|
|
147
|
+
onSubmit: null,
|
|
148
|
+
onCancel: null,
|
|
149
|
+
},
|
|
150
|
+
logs: [],
|
|
151
|
+
maxLogs: 100,
|
|
152
|
+
logsExpanded: false,
|
|
153
|
+
logScroll: 0,
|
|
154
|
+
authProviderIndex: 0,
|
|
155
|
+
newProjectIndex: 0,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export type StateUpdater = (state: AppState) => AppState;
|
|
160
|
+
|
|
161
|
+
export function setProjects(
|
|
162
|
+
projects: Array<{ slug: string; path: string }>,
|
|
163
|
+
): StateUpdater {
|
|
164
|
+
return (state) => ({
|
|
165
|
+
...state,
|
|
166
|
+
projects: createListState(
|
|
167
|
+
projects.map((p) => ({
|
|
168
|
+
id: p.slug,
|
|
169
|
+
label: p.slug,
|
|
170
|
+
meta: shortenPath(p.path),
|
|
171
|
+
data: { slug: p.slug, path: p.path, type: "local" },
|
|
172
|
+
})),
|
|
173
|
+
),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function setExamples(
|
|
178
|
+
examples: Array<{ slug: string; path: string; description?: string }>,
|
|
179
|
+
): StateUpdater {
|
|
180
|
+
return (state) => ({
|
|
181
|
+
...state,
|
|
182
|
+
examples: createListState(
|
|
183
|
+
examples.map((e) => ({
|
|
184
|
+
id: e.slug,
|
|
185
|
+
label: e.slug,
|
|
186
|
+
description: e.description,
|
|
187
|
+
data: { slug: e.slug, path: e.path, type: "example" },
|
|
188
|
+
})),
|
|
189
|
+
),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function setTemplates(
|
|
194
|
+
templates: Array<{ id: string; name: string; description: string }>,
|
|
195
|
+
): StateUpdater {
|
|
196
|
+
return (state) => ({
|
|
197
|
+
...state,
|
|
198
|
+
templates: createListState(
|
|
199
|
+
templates.map((t) => ({
|
|
200
|
+
id: t.id,
|
|
201
|
+
label: t.name,
|
|
202
|
+
description: t.description,
|
|
203
|
+
data: { slug: t.id, path: "", type: "template" },
|
|
204
|
+
})),
|
|
205
|
+
),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function updateServer(update: Partial<ServerStatus>): StateUpdater {
|
|
210
|
+
return (state) => ({ ...state, server: { ...state.server, ...update } });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function updateMCP(update: Partial<MCPStatus>): StateUpdater {
|
|
214
|
+
return (state) => ({ ...state, mcp: { ...state.mcp, ...update } });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function updateRemote(update: Partial<RemoteState>): StateUpdater {
|
|
218
|
+
return (state) => ({ ...state, remote: { ...state.remote, ...update } });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function navigateTo(view: AppView): StateUpdater {
|
|
222
|
+
return (state) => ({ ...state, view, previousView: state.view });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function goBack(): StateUpdater {
|
|
226
|
+
return (state) => ({
|
|
227
|
+
...state,
|
|
228
|
+
view: state.previousView ?? "dashboard",
|
|
229
|
+
previousView: null,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function setActiveList(
|
|
234
|
+
list: "projects" | "examples" | "templates" | "remoteProjects",
|
|
235
|
+
): StateUpdater {
|
|
236
|
+
return (state) => ({ ...state, activeList: list });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function updateActiveList(
|
|
240
|
+
updater: (list: ListSelectState<ProjectInfo>) => ListSelectState<ProjectInfo>,
|
|
241
|
+
): StateUpdater {
|
|
242
|
+
return (state) => {
|
|
243
|
+
const key = state.activeList;
|
|
244
|
+
// remoteProjects is not a ListSelectState, skip update
|
|
245
|
+
if (key === "remoteProjects") return state;
|
|
246
|
+
return { ...state, [key]: updater(state[key]) };
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function selectProject(project: ProjectInfo | null): StateUpdater {
|
|
251
|
+
return (state) => {
|
|
252
|
+
if (!project) return { ...state, selectedProject: null };
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
...state,
|
|
256
|
+
selectedProject: project,
|
|
257
|
+
view: "project-detail",
|
|
258
|
+
previousView: state.view,
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function updateWizard(
|
|
264
|
+
update: Partial<AppState["wizard"]>,
|
|
265
|
+
): StateUpdater {
|
|
266
|
+
return (state) => ({ ...state, wizard: { ...state.wizard, ...update } });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function resetWizard(): StateUpdater {
|
|
270
|
+
return (state) => ({
|
|
271
|
+
...state,
|
|
272
|
+
wizard: {
|
|
273
|
+
step: 0,
|
|
274
|
+
startType: null,
|
|
275
|
+
selectedTemplate: null,
|
|
276
|
+
integrations: [],
|
|
277
|
+
projectName: "",
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function startInput(
|
|
283
|
+
prompt: string,
|
|
284
|
+
onSubmit: (value: string) => void,
|
|
285
|
+
onCancel?: () => void,
|
|
286
|
+
initialValue?: string,
|
|
287
|
+
): StateUpdater {
|
|
288
|
+
return (state) => ({
|
|
289
|
+
...state,
|
|
290
|
+
input: {
|
|
291
|
+
active: true,
|
|
292
|
+
prompt,
|
|
293
|
+
value: initialValue ?? "",
|
|
294
|
+
cursorPos: initialValue?.length ?? 0,
|
|
295
|
+
onSubmit,
|
|
296
|
+
onCancel: onCancel ?? null,
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function updateInputValue(value: string, cursorPos: number): StateUpdater {
|
|
302
|
+
return (state) => ({
|
|
303
|
+
...state,
|
|
304
|
+
input: { ...state.input, value, cursorPos },
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function endInput(): StateUpdater {
|
|
309
|
+
return (state) => ({
|
|
310
|
+
...state,
|
|
311
|
+
input: {
|
|
312
|
+
active: false,
|
|
313
|
+
prompt: "",
|
|
314
|
+
value: "",
|
|
315
|
+
cursorPos: 0,
|
|
316
|
+
onSubmit: null,
|
|
317
|
+
onCancel: null,
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function addLog(level: LogEntry["level"], message: string, meta?: LogMeta): StateUpdater {
|
|
323
|
+
return (state) => {
|
|
324
|
+
const logs = [...state.logs, { time: new Date(), level, message, meta }];
|
|
325
|
+
if (logs.length > state.maxLogs) logs.shift();
|
|
326
|
+
return { ...state, logs };
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function clearLogs(): StateUpdater {
|
|
331
|
+
return (state) => ({ ...state, logs: [], logScroll: 0 });
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function toggleLogsExpanded(): StateUpdater {
|
|
335
|
+
return (state) => ({
|
|
336
|
+
...state,
|
|
337
|
+
logsExpanded: !state.logsExpanded,
|
|
338
|
+
logScroll: 0,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export function scrollLogs(direction: "up" | "down"): StateUpdater {
|
|
343
|
+
return (state) => {
|
|
344
|
+
if (!state.logsExpanded) return state;
|
|
345
|
+
|
|
346
|
+
const maxScroll = Math.max(0, state.logs.length - 5);
|
|
347
|
+
let newScroll = state.logScroll;
|
|
348
|
+
|
|
349
|
+
if (direction === "up") {
|
|
350
|
+
newScroll = Math.min(maxScroll, state.logScroll + 1);
|
|
351
|
+
} else {
|
|
352
|
+
newScroll = Math.max(0, state.logScroll - 1);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return { ...state, logScroll: newScroll };
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function shortenPath(path: string, env: RuntimeEnv = getRuntimeEnv()): string {
|
|
360
|
+
// Prefer relative path to cwd
|
|
361
|
+
const currentDir = cwd();
|
|
362
|
+
if (path.startsWith(currentDir + "/")) {
|
|
363
|
+
return "./" + path.slice(currentDir.length + 1);
|
|
364
|
+
}
|
|
365
|
+
if (path === currentDir) {
|
|
366
|
+
return "./";
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Fall back to ~ for home
|
|
370
|
+
const home = env.homeDir ?? "";
|
|
371
|
+
if (home && path.startsWith(home)) {
|
|
372
|
+
return `~${path.slice(home.length)}`;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return path;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export function getActiveSelection(
|
|
379
|
+
state: AppState,
|
|
380
|
+
): ListItem<ProjectInfo> | undefined {
|
|
381
|
+
// remoteProjects is not a ListSelectState
|
|
382
|
+
if (state.activeList === "remoteProjects") return undefined;
|
|
383
|
+
const list = state[state.activeList];
|
|
384
|
+
return list.items[list.selectedIndex];
|
|
385
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard View
|
|
3
|
+
*
|
|
4
|
+
* Main view showing server status, projects, and quick actions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { box } from "../../ui/box.js";
|
|
8
|
+
import { brand, dim, error, muted, success } from "../../ui/colors.js";
|
|
9
|
+
import { getTerminalWidth } from "../../ui/layout.js";
|
|
10
|
+
import { getAgentFaceWithText } from "../../ui/dot-matrix.js";
|
|
11
|
+
import { renderList } from "../components/list-select.js";
|
|
12
|
+
import type { AppState } from "../state.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Render the dashboard view
|
|
16
|
+
*/
|
|
17
|
+
export function renderDashboard(state: AppState): string {
|
|
18
|
+
const termWidth = Math.min(getTerminalWidth() - 4, 80);
|
|
19
|
+
const maxListWidth = termWidth - 4;
|
|
20
|
+
const lines: string[] = [];
|
|
21
|
+
|
|
22
|
+
lines.push(renderBanner(state), "");
|
|
23
|
+
|
|
24
|
+
const hasProjects = state.projects.items.length > 0;
|
|
25
|
+
const hasExamples = state.examples.items.length > 0;
|
|
26
|
+
const hasRemoteProjects = state.remote.user && state.remote.projects.length > 0;
|
|
27
|
+
|
|
28
|
+
if (hasProjects) {
|
|
29
|
+
const isActive = state.activeList === "projects";
|
|
30
|
+
lines.push(renderSection("Local Projects", state.projects.items.length, isActive));
|
|
31
|
+
lines.push(
|
|
32
|
+
renderList(state.projects, {
|
|
33
|
+
maxWidth: maxListWidth,
|
|
34
|
+
visibleCount: 5,
|
|
35
|
+
showNumbers: true,
|
|
36
|
+
showSelection: isActive,
|
|
37
|
+
}),
|
|
38
|
+
"",
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Remote projects (when logged in)
|
|
43
|
+
if (hasRemoteProjects) {
|
|
44
|
+
const isRemoteActive = state.activeList === "remoteProjects";
|
|
45
|
+
const visibleCount = 5;
|
|
46
|
+
const start = state.remote.scrollOffset;
|
|
47
|
+
const end = Math.min(start + visibleCount, state.remote.projects.length);
|
|
48
|
+
const visibleProjects = state.remote.projects.slice(start, end);
|
|
49
|
+
|
|
50
|
+
lines.push(renderSection("Remote Projects", state.remote.projects.length, isRemoteActive));
|
|
51
|
+
|
|
52
|
+
if (start > 0) {
|
|
53
|
+
lines.push(` ${dim("↑ more above")}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
visibleProjects.forEach((p, i) => {
|
|
57
|
+
const actualIndex = start + i;
|
|
58
|
+
const isFocused = isRemoteActive && actualIndex === state.remote.focusedIndex;
|
|
59
|
+
const cursor = isFocused ? brand("›") : " ";
|
|
60
|
+
// Show number 1-9 or letter a-z for 10+
|
|
61
|
+
const displayNum = actualIndex + 1;
|
|
62
|
+
const shortcut = displayNum <= 9
|
|
63
|
+
? String(displayNum)
|
|
64
|
+
: String.fromCharCode(96 + displayNum - 9); // 10='a', 11='b', etc.
|
|
65
|
+
const num = isFocused ? brand(`[${shortcut}]`) : dim(`[${shortcut}]`);
|
|
66
|
+
const label = isFocused ? p.slug : dim(p.slug);
|
|
67
|
+
lines.push(`${cursor} ${num} ${label}`);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (end < state.remote.projects.length) {
|
|
71
|
+
lines.push(` ${dim("↓ more below")}`);
|
|
72
|
+
}
|
|
73
|
+
lines.push("");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (hasExamples) {
|
|
77
|
+
const isActive = state.activeList === "examples";
|
|
78
|
+
lines.push(renderSection("Examples", state.examples.items.length, isActive));
|
|
79
|
+
lines.push(
|
|
80
|
+
renderList(state.examples, {
|
|
81
|
+
maxWidth: maxListWidth,
|
|
82
|
+
visibleCount: 5,
|
|
83
|
+
showNumbers: true,
|
|
84
|
+
showSelection: isActive,
|
|
85
|
+
}),
|
|
86
|
+
"",
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
lines.push(renderHelpBar(state));
|
|
91
|
+
|
|
92
|
+
return lines.join("\n");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Render the banner with agent face and server info
|
|
97
|
+
*/
|
|
98
|
+
function renderBanner(state: AppState): string {
|
|
99
|
+
const serverDot = state.server.running ? success("●") : error("●");
|
|
100
|
+
const mcpDot = state.mcp.enabled ? success("●") : dim("○");
|
|
101
|
+
|
|
102
|
+
const textLines: string[] = [];
|
|
103
|
+
|
|
104
|
+
textLines.push(`${serverDot} ${dim("Server running")}`);
|
|
105
|
+
textLines.push(` ${brand(state.server.url)}`);
|
|
106
|
+
|
|
107
|
+
if (state.mcp.enabled) {
|
|
108
|
+
textLines.push(`${mcpDot} ${dim("MCP")}`);
|
|
109
|
+
if (state.mcp.transport === "http") {
|
|
110
|
+
const port = state.mcp.httpPort ?? 9999;
|
|
111
|
+
textLines.push(` ${brand(`http://veryfront.me:${port}/mcp`)}`);
|
|
112
|
+
} else {
|
|
113
|
+
textLines.push(` ${dim("stdio")}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const { errors, warnings } = state.server;
|
|
118
|
+
if (errors > 0 || warnings > 0) {
|
|
119
|
+
const parts: string[] = [];
|
|
120
|
+
if (errors > 0) parts.push(error(`${errors} errors`));
|
|
121
|
+
if (warnings > 0) parts.push(muted(`${warnings} warnings`));
|
|
122
|
+
textLines.push(parts.join(" "));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return getAgentFaceWithText(textLines, {
|
|
126
|
+
litColor: "\x1b[38;2;252;143;93m", // Veryfront brand orange
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Render a section header
|
|
132
|
+
*/
|
|
133
|
+
function renderSection(title: string, count: number, isActive = true): string {
|
|
134
|
+
const indicator = isActive ? brand("›") : " ";
|
|
135
|
+
const titleText = isActive ? title : dim(title);
|
|
136
|
+
return ` ${indicator} ${titleText} ${dim(`(${count})`)}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Render the help bar at the bottom
|
|
141
|
+
*/
|
|
142
|
+
function renderHelpBar(state: AppState): string {
|
|
143
|
+
const parts: string[] = [];
|
|
144
|
+
|
|
145
|
+
const hasProjects = state.projects.items.length > 0;
|
|
146
|
+
const hasExamples = state.examples.items.length > 0;
|
|
147
|
+
const hasRemoteProjects = state.remote.user && state.remote.projects.length > 0;
|
|
148
|
+
|
|
149
|
+
// Count sections for tab switching
|
|
150
|
+
const sectionCount = [hasProjects, hasExamples, hasRemoteProjects].filter(Boolean).length;
|
|
151
|
+
|
|
152
|
+
if (sectionCount > 1) {
|
|
153
|
+
parts.push(dim("tab switch"));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
parts.push(dim("↑↓ nav"));
|
|
157
|
+
|
|
158
|
+
if (hasProjects || hasExamples || hasRemoteProjects) {
|
|
159
|
+
parts.push(dim("o open"), dim("s studio"), dim("i ide"));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!state.remote.user) {
|
|
163
|
+
parts.push(dim("a login"));
|
|
164
|
+
} else {
|
|
165
|
+
// Show context-aware actions based on active list
|
|
166
|
+
if (state.activeList === "projects") {
|
|
167
|
+
parts.push(dim("p pull"), dim("u push"));
|
|
168
|
+
} else if (state.activeList === "remoteProjects") {
|
|
169
|
+
parts.push(dim("p pull"));
|
|
170
|
+
}
|
|
171
|
+
parts.push(dim("n new"), dim("x logout"));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
parts.push(dim("? help"), dim("q quit"));
|
|
175
|
+
|
|
176
|
+
return ` ${parts.join(" ")}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Render a boxed dashboard (alternative style)
|
|
181
|
+
*/
|
|
182
|
+
export function renderDashboardBoxed(state: AppState): string {
|
|
183
|
+
const termWidth = Math.min(getTerminalWidth() - 4, 80);
|
|
184
|
+
const content = renderDashboard(state);
|
|
185
|
+
|
|
186
|
+
return box(content, {
|
|
187
|
+
style: "rounded",
|
|
188
|
+
title: "Veryfront Code",
|
|
189
|
+
titleColor: "\x1b[38;2;252;143;93m",
|
|
190
|
+
width: termWidth,
|
|
191
|
+
paddingX: 1,
|
|
192
|
+
paddingY: 0,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Render empty state when no projects found
|
|
198
|
+
*/
|
|
199
|
+
export function renderEmptyState(): string {
|
|
200
|
+
return [
|
|
201
|
+
"",
|
|
202
|
+
` ${dim("No projects found.")}`,
|
|
203
|
+
"",
|
|
204
|
+
` ${dim("Get started:")}`,
|
|
205
|
+
` ${brand("[n]")} Create a new project`,
|
|
206
|
+
` ${brand("[t]")} Browse templates`,
|
|
207
|
+
"",
|
|
208
|
+
` ${dim("Or run with a project directory:")}`,
|
|
209
|
+
` ${muted("deno task start --project ./my-project")}`,
|
|
210
|
+
"",
|
|
211
|
+
].join("\n");
|
|
212
|
+
}
|
|
@@ -195,8 +195,8 @@ export function devCommand(options: DevOptions): Promise<DevCommandResult> {
|
|
|
195
195
|
console.log();
|
|
196
196
|
console.log(
|
|
197
197
|
banner({
|
|
198
|
-
title: "Veryfront",
|
|
199
|
-
subtitle: "is
|
|
198
|
+
title: "Veryfront Code",
|
|
199
|
+
subtitle: "is running",
|
|
200
200
|
info: {
|
|
201
201
|
url: serverUrl,
|
|
202
202
|
...(projectSlug ? { project: projectSlug } : {}),
|
|
@@ -154,7 +154,7 @@ export async function newCommand(
|
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
const tui = createTui({ title: "Veryfront", showLogs: true });
|
|
157
|
+
const tui = createTui({ title: "Veryfront Code", showLogs: true });
|
|
158
158
|
const restore = interceptConsole(tui);
|
|
159
159
|
|
|
160
160
|
const localUrl = `http://${name}.veryfront.me:${port}`;
|
|
@@ -24,7 +24,7 @@ import { mergeCommand, parseMergeArgs } from "../commands/merge.js";
|
|
|
24
24
|
import { deployCommand, parseDeployArgs } from "../commands/deploy.js";
|
|
25
25
|
import { parseUpArgs, upCommand } from "../commands/up.js";
|
|
26
26
|
import { newCommand, parseNewArgs } from "../commands/new.js";
|
|
27
|
-
import { promptProjectName
|
|
27
|
+
import { promptProjectName } from "../commands/main.js";
|
|
28
28
|
import { issuesCommand } from "../commands/issues.js";
|
|
29
29
|
import { login, logout, whoami } from "../auth/index.js";
|
|
30
30
|
import { COMMANDS } from "../help/command-definitions.js";
|
|
@@ -41,7 +41,7 @@ import { handleBuildCommand } from "./build-handler.js";
|
|
|
41
41
|
import { handleDevCommand } from "./dev-handler.js";
|
|
42
42
|
import { handleGenerateCommand } from "./generate-handler.js";
|
|
43
43
|
import { handleStudioCommand } from "./studio-handler.js";
|
|
44
|
-
import {
|
|
44
|
+
import { handleStartCommand } from "./start-handler.js";
|
|
45
45
|
import type { ParsedArgs } from "./types.js";
|
|
46
46
|
import type { InitTemplate } from "../commands/init/types.js";
|
|
47
47
|
import type { IntegrationName } from "../templates/types.js";
|
|
@@ -437,11 +437,10 @@ export async function routeCommand(args: ParsedArgs): Promise<void> {
|
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
case "mcp": {
|
|
440
|
-
const
|
|
441
|
-
await
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
await server.stop();
|
|
440
|
+
const { createMCPServer } = await import("../mcp/server.js");
|
|
441
|
+
const mcpServer = await createMCPServer({ stdio: true });
|
|
442
|
+
await new Promise(() => {});
|
|
443
|
+
await mcpServer.stop();
|
|
445
444
|
break;
|
|
446
445
|
}
|
|
447
446
|
|
|
@@ -454,40 +453,10 @@ export async function routeCommand(args: ParsedArgs): Promise<void> {
|
|
|
454
453
|
exitProcess(0);
|
|
455
454
|
return;
|
|
456
455
|
|
|
457
|
-
case undefined:
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
switch (action) {
|
|
461
|
-
case "new": {
|
|
462
|
-
const name = await promptProjectName();
|
|
463
|
-
if (name) {
|
|
464
|
-
await newCommand(name, {});
|
|
465
|
-
}
|
|
466
|
-
break;
|
|
467
|
-
}
|
|
468
|
-
case "dev":
|
|
469
|
-
await handleDevCommand(args);
|
|
470
|
-
break;
|
|
471
|
-
case "deploy": {
|
|
472
|
-
const result = parseDeployArgs(args);
|
|
473
|
-
if (result.success) {
|
|
474
|
-
await deployCommand(result.data);
|
|
475
|
-
}
|
|
476
|
-
break;
|
|
477
|
-
}
|
|
478
|
-
case "login":
|
|
479
|
-
await login();
|
|
480
|
-
break;
|
|
481
|
-
case "help":
|
|
482
|
-
showHelp();
|
|
483
|
-
break;
|
|
484
|
-
case "exit":
|
|
485
|
-
case null:
|
|
486
|
-
exitProcess(0);
|
|
487
|
-
break;
|
|
488
|
-
}
|
|
456
|
+
case undefined:
|
|
457
|
+
// Default: run full TUI dashboard (like `deno task start`)
|
|
458
|
+
await handleStartCommand(args);
|
|
489
459
|
break;
|
|
490
|
-
}
|
|
491
460
|
|
|
492
461
|
default:
|
|
493
462
|
cliLogger.error(`Unknown command: ${command}\n`);
|