@gxp-dev/tools 2.0.5
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/.github/workflows/npm-publish.yml +48 -0
- package/CLAUDE.md +400 -0
- package/README.md +247 -0
- package/REFACTOR_PLAN.md +194 -0
- package/bin/gx-devtools.js +87 -0
- package/bin/lib/cli.js +251 -0
- package/bin/lib/commands/assets.js +337 -0
- package/bin/lib/commands/build.js +259 -0
- package/bin/lib/commands/datastore.js +433 -0
- package/bin/lib/commands/dev.js +328 -0
- package/bin/lib/commands/extensions.js +298 -0
- package/bin/lib/commands/index.js +35 -0
- package/bin/lib/commands/init.js +307 -0
- package/bin/lib/commands/publish.js +189 -0
- package/bin/lib/commands/socket.js +158 -0
- package/bin/lib/commands/ssl.js +47 -0
- package/bin/lib/constants.js +120 -0
- package/bin/lib/tui/App.tsx +600 -0
- package/bin/lib/tui/components/CommandInput.tsx +278 -0
- package/bin/lib/tui/components/GeminiPanel.tsx +161 -0
- package/bin/lib/tui/components/Header.tsx +27 -0
- package/bin/lib/tui/components/LogPanel.tsx +122 -0
- package/bin/lib/tui/components/TabBar.tsx +56 -0
- package/bin/lib/tui/components/WelcomeScreen.tsx +80 -0
- package/bin/lib/tui/index.tsx +63 -0
- package/bin/lib/tui/services/ExtensionService.ts +122 -0
- package/bin/lib/tui/services/GeminiService.ts +395 -0
- package/bin/lib/tui/services/ServiceManager.ts +336 -0
- package/bin/lib/tui/services/SocketService.ts +204 -0
- package/bin/lib/tui/services/ViteService.ts +107 -0
- package/bin/lib/tui/services/index.ts +13 -0
- package/bin/lib/utils/files.js +180 -0
- package/bin/lib/utils/index.js +17 -0
- package/bin/lib/utils/paths.js +138 -0
- package/bin/lib/utils/prompts.js +71 -0
- package/bin/lib/utils/ssl.js +233 -0
- package/browser-extensions/README.md +1 -0
- package/browser-extensions/chrome/background.js +857 -0
- package/browser-extensions/chrome/content.js +51 -0
- package/browser-extensions/chrome/devtools.html +9 -0
- package/browser-extensions/chrome/devtools.js +23 -0
- package/browser-extensions/chrome/icons/gx_off_128.png +0 -0
- package/browser-extensions/chrome/icons/gx_off_16.png +0 -0
- package/browser-extensions/chrome/icons/gx_off_32.png +0 -0
- package/browser-extensions/chrome/icons/gx_off_64.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_128.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_16.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_32.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_64.png +0 -0
- package/browser-extensions/chrome/inspector.js +1087 -0
- package/browser-extensions/chrome/manifest.json +70 -0
- package/browser-extensions/chrome/panel.html +638 -0
- package/browser-extensions/chrome/panel.js +862 -0
- package/browser-extensions/chrome/popup.html +399 -0
- package/browser-extensions/chrome/popup.js +515 -0
- package/browser-extensions/chrome/rules.json +1 -0
- package/browser-extensions/chrome/test-chrome.html +145 -0
- package/browser-extensions/chrome/test-mixed-content.html +190 -0
- package/browser-extensions/chrome/test-uri-pattern.html +199 -0
- package/browser-extensions/firefox/README.md +134 -0
- package/browser-extensions/firefox/background.js +804 -0
- package/browser-extensions/firefox/content.js +120 -0
- package/browser-extensions/firefox/debug-errors.html +229 -0
- package/browser-extensions/firefox/debug-https.html +113 -0
- package/browser-extensions/firefox/devtools.html +9 -0
- package/browser-extensions/firefox/devtools.js +24 -0
- package/browser-extensions/firefox/icons/gx_off_128.png +0 -0
- package/browser-extensions/firefox/icons/gx_off_16.png +0 -0
- package/browser-extensions/firefox/icons/gx_off_32.png +0 -0
- package/browser-extensions/firefox/icons/gx_off_64.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_128.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_16.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_32.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_64.png +0 -0
- package/browser-extensions/firefox/inspector.js +1087 -0
- package/browser-extensions/firefox/manifest.json +67 -0
- package/browser-extensions/firefox/panel.html +638 -0
- package/browser-extensions/firefox/panel.js +862 -0
- package/browser-extensions/firefox/popup.html +525 -0
- package/browser-extensions/firefox/popup.js +536 -0
- package/browser-extensions/firefox/test-gramercy.html +126 -0
- package/browser-extensions/firefox/test-imports.html +58 -0
- package/browser-extensions/firefox/test-masking.html +147 -0
- package/browser-extensions/firefox/test-uri-pattern.html +199 -0
- package/docs/DOCUSAURUS_IMPORT.md +378 -0
- package/docs/_category_.json +8 -0
- package/docs/app-manifest.md +272 -0
- package/docs/building-for-platform.md +315 -0
- package/docs/dev-tools.md +291 -0
- package/docs/getting-started.md +180 -0
- package/docs/gxp-store.md +305 -0
- package/docs/index.md +44 -0
- package/package.json +77 -0
- package/runtime/PortalContainer.vue +326 -0
- package/runtime/dev-tools/DevToolsModal.vue +217 -0
- package/runtime/dev-tools/LayoutSwitcher.vue +221 -0
- package/runtime/dev-tools/MockDataEditor.vue +621 -0
- package/runtime/dev-tools/SocketSimulator.vue +562 -0
- package/runtime/dev-tools/StoreInspector.vue +644 -0
- package/runtime/dev-tools/index.js +6 -0
- package/runtime/gxpStringsPlugin.js +428 -0
- package/runtime/index.html +22 -0
- package/runtime/main.js +32 -0
- package/runtime/mock-api/auth-middleware.js +97 -0
- package/runtime/mock-api/image-generator.js +221 -0
- package/runtime/mock-api/index.js +197 -0
- package/runtime/mock-api/response-generator.js +394 -0
- package/runtime/mock-api/route-generator.js +323 -0
- package/runtime/mock-api/socket-triggers.js +371 -0
- package/runtime/mock-api/spec-loader.js +300 -0
- package/runtime/server.js +180 -0
- package/runtime/stores/gxpPortalConfigStore.js +554 -0
- package/runtime/stores/index.js +6 -0
- package/runtime/vite-inspector-plugin.js +749 -0
- package/runtime/vite-source-tracker-plugin.js +232 -0
- package/runtime/vite.config.js +402 -0
- package/scripts/launch-chrome.js +90 -0
- package/scripts/pack-chrome.js +91 -0
- package/socket-events/AiSessionMessageCreated.json +18 -0
- package/socket-events/SocialStreamPostCreated.json +24 -0
- package/socket-events/SocialStreamPostVariantCompleted.json +23 -0
- package/template/README.md +332 -0
- package/template/app-manifest.json +32 -0
- package/template/dev-assets/images/avatar-placeholder.png +0 -0
- package/template/dev-assets/images/background-placeholder.jpg +0 -0
- package/template/dev-assets/images/banner-placeholder.jpg +0 -0
- package/template/dev-assets/images/icon-placeholder.png +0 -0
- package/template/dev-assets/images/logo-placeholder.png +0 -0
- package/template/dev-assets/images/product-placeholder.jpg +0 -0
- package/template/dev-assets/images/thumbnail-placeholder.jpg +0 -0
- package/template/env.example +51 -0
- package/template/gitignore +53 -0
- package/template/index.html +22 -0
- package/template/main.js +28 -0
- package/template/src/DemoPage.vue +459 -0
- package/template/src/Plugin.vue +38 -0
- package/template/src/stores/index.js +9 -0
- package/template/src/stores/test-data.json +173 -0
- package/template/theme-layouts/AdditionalStyling.css +0 -0
- package/template/theme-layouts/PrivateLayout.vue +39 -0
- package/template/theme-layouts/PublicLayout.vue +39 -0
- package/template/theme-layouts/SystemLayout.vue +39 -0
- package/template/vite.config.js +333 -0
- package/tsconfig.tui.json +21 -0
- package/vite.config.js +164 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { spawn, ChildProcess, execSync } from 'child_process';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
|
|
4
|
+
export type ServiceStatus = 'stopped' | 'starting' | 'running' | 'error';
|
|
5
|
+
|
|
6
|
+
export interface ServiceConfig {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
command: string;
|
|
10
|
+
args: string[];
|
|
11
|
+
cwd: string;
|
|
12
|
+
env?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Store configs for restart capability
|
|
16
|
+
const serviceConfigs: Map<string, ServiceConfig> = new Map();
|
|
17
|
+
|
|
18
|
+
export interface ServiceState {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
status: ServiceStatus;
|
|
22
|
+
logs: string[];
|
|
23
|
+
process?: ChildProcess;
|
|
24
|
+
pid?: number;
|
|
25
|
+
error?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class ServiceManager extends EventEmitter {
|
|
29
|
+
private services: Map<string, ServiceState> = new Map();
|
|
30
|
+
private maxLogLines = 1000;
|
|
31
|
+
private cleanupRegistered = false;
|
|
32
|
+
|
|
33
|
+
constructor() {
|
|
34
|
+
super();
|
|
35
|
+
this.registerCleanupHandlers();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Register cleanup handlers to kill all services on exit
|
|
39
|
+
private registerCleanupHandlers(): void {
|
|
40
|
+
if (this.cleanupRegistered) return;
|
|
41
|
+
this.cleanupRegistered = true;
|
|
42
|
+
|
|
43
|
+
const cleanup = () => {
|
|
44
|
+
this.forceStopAll();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Handle various exit scenarios
|
|
48
|
+
process.on('exit', cleanup);
|
|
49
|
+
process.on('SIGINT', cleanup);
|
|
50
|
+
process.on('SIGTERM', cleanup);
|
|
51
|
+
process.on('SIGHUP', cleanup);
|
|
52
|
+
process.on('beforeExit', cleanup);
|
|
53
|
+
|
|
54
|
+
// Handle uncaught exceptions
|
|
55
|
+
process.on('uncaughtException', (err) => {
|
|
56
|
+
console.error('Uncaught exception:', err);
|
|
57
|
+
cleanup();
|
|
58
|
+
process.exit(1);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getService(id: string): ServiceState | undefined {
|
|
63
|
+
return this.services.get(id);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getAllServices(): ServiceState[] {
|
|
67
|
+
return Array.from(this.services.values());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
isRunning(id: string): boolean {
|
|
71
|
+
const service = this.services.get(id);
|
|
72
|
+
return service?.status === 'running' || service?.status === 'starting';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
start(config: ServiceConfig): ServiceState {
|
|
76
|
+
// Store config for restart capability
|
|
77
|
+
serviceConfigs.set(config.id, config);
|
|
78
|
+
|
|
79
|
+
// Check if already running
|
|
80
|
+
const existing = this.services.get(config.id);
|
|
81
|
+
if (existing && (existing.status === 'running' || existing.status === 'starting')) {
|
|
82
|
+
this.addLog(config.id, `[${config.name}] Already running`);
|
|
83
|
+
return existing;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Create or reset service state
|
|
87
|
+
const state: ServiceState = {
|
|
88
|
+
id: config.id,
|
|
89
|
+
name: config.name,
|
|
90
|
+
status: 'starting',
|
|
91
|
+
logs: existing?.logs || [],
|
|
92
|
+
};
|
|
93
|
+
this.services.set(config.id, state);
|
|
94
|
+
|
|
95
|
+
this.addLog(config.id, `[${config.name}] Starting...`);
|
|
96
|
+
this.emit('statusChange', config.id, 'starting');
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
// Spawn the process with environment variables to prevent stdin access
|
|
100
|
+
// CI=true disables interactive prompts in many tools
|
|
101
|
+
// These prevent child processes from trying to use raw stdin mode
|
|
102
|
+
// detached: true creates a new process group that doesn't share the parent's tty
|
|
103
|
+
const proc = spawn(config.command, config.args, {
|
|
104
|
+
cwd: config.cwd,
|
|
105
|
+
env: {
|
|
106
|
+
...process.env,
|
|
107
|
+
...config.env,
|
|
108
|
+
FORCE_COLOR: '1',
|
|
109
|
+
CI: 'true',
|
|
110
|
+
},
|
|
111
|
+
shell: true,
|
|
112
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
113
|
+
detached: true,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
state.process = proc;
|
|
117
|
+
state.pid = proc.pid;
|
|
118
|
+
|
|
119
|
+
// Handle stdout
|
|
120
|
+
proc.stdout?.on('data', (data: Buffer) => {
|
|
121
|
+
const lines = data.toString().split('\n').filter(line => line.trim());
|
|
122
|
+
lines.forEach(line => {
|
|
123
|
+
this.addLog(config.id, line);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Detect when service is ready
|
|
127
|
+
const output = data.toString();
|
|
128
|
+
if (state.status === 'starting') {
|
|
129
|
+
// Vite ready indicators
|
|
130
|
+
if (output.includes('ready in') || output.includes('Local:') || output.includes('VITE')) {
|
|
131
|
+
state.status = 'running';
|
|
132
|
+
this.emit('statusChange', config.id, 'running');
|
|
133
|
+
}
|
|
134
|
+
// Socket.IO ready indicator
|
|
135
|
+
if (output.includes('Socket.IO server') || output.includes('listening on port')) {
|
|
136
|
+
state.status = 'running';
|
|
137
|
+
this.emit('statusChange', config.id, 'running');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Handle stderr
|
|
143
|
+
proc.stderr?.on('data', (data: Buffer) => {
|
|
144
|
+
const lines = data.toString().split('\n').filter(line => line.trim());
|
|
145
|
+
lines.forEach(line => {
|
|
146
|
+
this.addLog(config.id, `[stderr] ${line}`);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Handle process exit
|
|
151
|
+
proc.on('close', (code) => {
|
|
152
|
+
state.process = undefined;
|
|
153
|
+
if (code === 0 || code === null) {
|
|
154
|
+
state.status = 'stopped';
|
|
155
|
+
this.addLog(config.id, `[${config.name}] Stopped`);
|
|
156
|
+
} else {
|
|
157
|
+
state.status = 'error';
|
|
158
|
+
state.error = `Process exited with code ${code}`;
|
|
159
|
+
this.addLog(config.id, `[${config.name}] Error: exited with code ${code}`);
|
|
160
|
+
}
|
|
161
|
+
this.emit('statusChange', config.id, state.status);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Handle spawn errors
|
|
165
|
+
proc.on('error', (err) => {
|
|
166
|
+
state.status = 'error';
|
|
167
|
+
state.error = err.message;
|
|
168
|
+
state.process = undefined;
|
|
169
|
+
this.addLog(config.id, `[${config.name}] Error: ${err.message}`);
|
|
170
|
+
this.emit('statusChange', config.id, 'error');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Set running after a short delay if no ready message detected
|
|
174
|
+
setTimeout(() => {
|
|
175
|
+
if (state.status === 'starting' && state.process) {
|
|
176
|
+
state.status = 'running';
|
|
177
|
+
this.emit('statusChange', config.id, 'running');
|
|
178
|
+
}
|
|
179
|
+
}, 3000);
|
|
180
|
+
|
|
181
|
+
} catch (err) {
|
|
182
|
+
state.status = 'error';
|
|
183
|
+
state.error = err instanceof Error ? err.message : 'Unknown error';
|
|
184
|
+
this.addLog(config.id, `[${config.name}] Failed to start: ${state.error}`);
|
|
185
|
+
this.emit('statusChange', config.id, 'error');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return state;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
stop(id: string): boolean {
|
|
192
|
+
const service = this.services.get(id);
|
|
193
|
+
if (!service || !service.process) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
this.addLog(id, `[${service.name}] Stopping...`);
|
|
198
|
+
|
|
199
|
+
// Kill the process tree
|
|
200
|
+
try {
|
|
201
|
+
process.kill(-service.process.pid!, 'SIGTERM');
|
|
202
|
+
} catch {
|
|
203
|
+
// Process group kill failed, try direct kill
|
|
204
|
+
service.process.kill('SIGTERM');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Force kill after timeout
|
|
208
|
+
setTimeout(() => {
|
|
209
|
+
if (service.process && !service.process.killed) {
|
|
210
|
+
try {
|
|
211
|
+
process.kill(-service.process.pid!, 'SIGKILL');
|
|
212
|
+
} catch {
|
|
213
|
+
service.process.kill('SIGKILL');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}, 2000);
|
|
217
|
+
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
stopAll(): void {
|
|
222
|
+
for (const [id] of this.services) {
|
|
223
|
+
this.stop(id);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Force stop all services synchronously - used during process exit
|
|
228
|
+
forceStopAll(): void {
|
|
229
|
+
for (const [id, service] of this.services) {
|
|
230
|
+
if (service.pid) {
|
|
231
|
+
try {
|
|
232
|
+
// Try to kill the process group first
|
|
233
|
+
process.kill(-service.pid, 'SIGKILL');
|
|
234
|
+
} catch {
|
|
235
|
+
// Process group kill failed, try direct kill
|
|
236
|
+
try {
|
|
237
|
+
process.kill(service.pid, 'SIGKILL');
|
|
238
|
+
} catch {
|
|
239
|
+
// Process may already be dead
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (service.process && !service.process.killed) {
|
|
244
|
+
try {
|
|
245
|
+
service.process.kill('SIGKILL');
|
|
246
|
+
} catch {
|
|
247
|
+
// Ignore errors
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Also try to kill any orphaned vite/nodemon processes using lsof on common ports
|
|
253
|
+
try {
|
|
254
|
+
// Kill processes on typical dev ports synchronously
|
|
255
|
+
execSync('lsof -ti :3060 | xargs kill -9 2>/dev/null || true', { stdio: 'ignore' });
|
|
256
|
+
execSync('lsof -ti :3069 | xargs kill -9 2>/dev/null || true', { stdio: 'ignore' });
|
|
257
|
+
} catch {
|
|
258
|
+
// Ignore errors - best effort cleanup
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
restart(id: string): boolean {
|
|
263
|
+
const config = serviceConfigs.get(id);
|
|
264
|
+
if (!config) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const service = this.services.get(id);
|
|
269
|
+
if (service?.process) {
|
|
270
|
+
// Stop first, then restart after process exits
|
|
271
|
+
this.addLog(id, `[${config.name}] Restarting...`);
|
|
272
|
+
|
|
273
|
+
const onExit = () => {
|
|
274
|
+
// Small delay to ensure cleanup
|
|
275
|
+
setTimeout(() => {
|
|
276
|
+
this.start(config);
|
|
277
|
+
}, 500);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// Listen for process exit once
|
|
281
|
+
service.process.once('close', onExit);
|
|
282
|
+
|
|
283
|
+
// Kill the process
|
|
284
|
+
try {
|
|
285
|
+
process.kill(-service.process.pid!, 'SIGTERM');
|
|
286
|
+
} catch {
|
|
287
|
+
service.process.kill('SIGTERM');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Force kill after timeout
|
|
291
|
+
setTimeout(() => {
|
|
292
|
+
if (service.process && !service.process.killed) {
|
|
293
|
+
try {
|
|
294
|
+
process.kill(-service.process.pid!, 'SIGKILL');
|
|
295
|
+
} catch {
|
|
296
|
+
service.process.kill('SIGKILL');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}, 2000);
|
|
300
|
+
} else {
|
|
301
|
+
// Not running, just start
|
|
302
|
+
this.start(config);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
getConfig(id: string): ServiceConfig | undefined {
|
|
309
|
+
return serviceConfigs.get(id);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
clearLogs(id: string): void {
|
|
313
|
+
const service = this.services.get(id);
|
|
314
|
+
if (service) {
|
|
315
|
+
service.logs = [];
|
|
316
|
+
this.emit('logsCleared', id);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private addLog(id: string, message: string): void {
|
|
321
|
+
const service = this.services.get(id);
|
|
322
|
+
if (!service) return;
|
|
323
|
+
|
|
324
|
+
service.logs.push(message);
|
|
325
|
+
|
|
326
|
+
// Trim logs if too many
|
|
327
|
+
if (service.logs.length > this.maxLogLines) {
|
|
328
|
+
service.logs = service.logs.slice(-this.maxLogLines);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
this.emit('log', id, message);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Singleton instance
|
|
336
|
+
export const serviceManager = new ServiceManager();
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { serviceManager, ServiceConfig } from './ServiceManager.js';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import https from 'https';
|
|
6
|
+
import http from 'http';
|
|
7
|
+
|
|
8
|
+
export interface SocketOptions {
|
|
9
|
+
cwd?: string;
|
|
10
|
+
withMock?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SocketEvent {
|
|
14
|
+
name: string;
|
|
15
|
+
event: string;
|
|
16
|
+
channel: string;
|
|
17
|
+
data: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Get toolkit root
|
|
21
|
+
function getToolkitRoot(): string {
|
|
22
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
// From dist/tui/services/ go up 3 levels to toolkit root
|
|
24
|
+
return path.resolve(__dirname, '..', '..', '..');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Get the path to the runtime server.js
|
|
28
|
+
function getServerPath(): string {
|
|
29
|
+
const toolkitRoot = getToolkitRoot();
|
|
30
|
+
return path.join(toolkitRoot, 'runtime', 'server.js');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Get the socket events directory
|
|
34
|
+
function getSocketEventsDir(cwd: string): string | null {
|
|
35
|
+
// Check local project first
|
|
36
|
+
const localDir = path.join(cwd, 'socket-events');
|
|
37
|
+
if (fs.existsSync(localDir)) {
|
|
38
|
+
return localDir;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Fall back to toolkit's socket-events
|
|
42
|
+
const toolkitDir = path.join(getToolkitRoot(), 'socket-events');
|
|
43
|
+
if (fs.existsSync(toolkitDir)) {
|
|
44
|
+
return toolkitDir;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function startSocket(options: SocketOptions = {}): void {
|
|
51
|
+
const cwd = options.cwd || process.cwd();
|
|
52
|
+
const withMock = options.withMock || false;
|
|
53
|
+
|
|
54
|
+
const env: Record<string, string> = {
|
|
55
|
+
FORCE_COLOR: '1',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Enable mock API if requested
|
|
59
|
+
if (withMock) {
|
|
60
|
+
env.MOCK_API_ENABLED = 'true';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const config: ServiceConfig = {
|
|
64
|
+
id: 'socket',
|
|
65
|
+
name: withMock ? 'Socket.IO + Mock API' : 'Socket.IO',
|
|
66
|
+
command: 'node',
|
|
67
|
+
args: [getServerPath()],
|
|
68
|
+
cwd,
|
|
69
|
+
env,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
serviceManager.start(config);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function stopSocket(): boolean {
|
|
76
|
+
return serviceManager.stop('socket');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function isSocketRunning(): boolean {
|
|
80
|
+
return serviceManager.isRunning('socket');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// List available socket events
|
|
84
|
+
export function listSocketEvents(cwd?: string): SocketEvent[] {
|
|
85
|
+
const eventsDir = getSocketEventsDir(cwd || process.cwd());
|
|
86
|
+
if (!eventsDir) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const events: SocketEvent[] = [];
|
|
91
|
+
const files = fs.readdirSync(eventsDir).filter(f => f.endsWith('.json'));
|
|
92
|
+
|
|
93
|
+
for (const file of files) {
|
|
94
|
+
try {
|
|
95
|
+
const content = fs.readFileSync(path.join(eventsDir, file), 'utf-8');
|
|
96
|
+
const data = JSON.parse(content);
|
|
97
|
+
events.push({
|
|
98
|
+
name: path.basename(file, '.json'),
|
|
99
|
+
event: data.event,
|
|
100
|
+
channel: data.channel,
|
|
101
|
+
data: data.data,
|
|
102
|
+
});
|
|
103
|
+
} catch {
|
|
104
|
+
// Skip invalid files
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return events;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Send a socket event
|
|
112
|
+
export async function sendSocketEvent(
|
|
113
|
+
eventName: string,
|
|
114
|
+
identifier?: string,
|
|
115
|
+
cwd?: string
|
|
116
|
+
): Promise<{ success: boolean; message: string }> {
|
|
117
|
+
const eventsDir = getSocketEventsDir(cwd || process.cwd());
|
|
118
|
+
if (!eventsDir) {
|
|
119
|
+
return { success: false, message: 'Socket events directory not found' };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const eventPath = path.join(eventsDir, `${eventName}.json`);
|
|
123
|
+
if (!fs.existsSync(eventPath)) {
|
|
124
|
+
return { success: false, message: `Event "${eventName}" not found` };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const content = fs.readFileSync(eventPath, 'utf-8');
|
|
129
|
+
const eventData = JSON.parse(content);
|
|
130
|
+
|
|
131
|
+
// Update channel if identifier provided
|
|
132
|
+
if (identifier) {
|
|
133
|
+
const channelParts = eventData.channel.split('.');
|
|
134
|
+
if (channelParts.length >= 2) {
|
|
135
|
+
const model = channelParts[1];
|
|
136
|
+
eventData.channel = `private.${model}.${identifier}`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const socketPort = process.env.SOCKET_IO_PORT || 3069;
|
|
141
|
+
const payload = JSON.stringify({
|
|
142
|
+
event: eventData.event,
|
|
143
|
+
channel: eventData.channel,
|
|
144
|
+
data: eventData.data,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return new Promise((resolve) => {
|
|
148
|
+
// Try HTTPS first, then HTTP
|
|
149
|
+
const tryRequest = (useHttps: boolean) => {
|
|
150
|
+
const protocol = useHttps ? https : http;
|
|
151
|
+
const url = `${useHttps ? 'https' : 'http'}://localhost:${socketPort}/emit`;
|
|
152
|
+
|
|
153
|
+
const req = protocol.request(
|
|
154
|
+
url,
|
|
155
|
+
{
|
|
156
|
+
method: 'POST',
|
|
157
|
+
headers: {
|
|
158
|
+
'Content-Type': 'application/json',
|
|
159
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
160
|
+
},
|
|
161
|
+
rejectUnauthorized: false, // Allow self-signed certs
|
|
162
|
+
},
|
|
163
|
+
(res) => {
|
|
164
|
+
if (res.statusCode === 200) {
|
|
165
|
+
resolve({
|
|
166
|
+
success: true,
|
|
167
|
+
message: `Sent "${eventData.event}" to channel "${eventData.channel}"`,
|
|
168
|
+
});
|
|
169
|
+
} else {
|
|
170
|
+
resolve({
|
|
171
|
+
success: false,
|
|
172
|
+
message: `Server returned status ${res.statusCode}`,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
req.on('error', (err) => {
|
|
179
|
+
if (useHttps && err.message.includes('ECONNREFUSED')) {
|
|
180
|
+
// Try HTTP if HTTPS fails
|
|
181
|
+
tryRequest(false);
|
|
182
|
+
} else {
|
|
183
|
+
resolve({
|
|
184
|
+
success: false,
|
|
185
|
+
message: err.message.includes('ECONNREFUSED')
|
|
186
|
+
? 'Socket.IO server not running. Start it with /socket'
|
|
187
|
+
: `Error: ${err.message}`,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
req.write(payload);
|
|
193
|
+
req.end();
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
tryRequest(true);
|
|
197
|
+
});
|
|
198
|
+
} catch (err) {
|
|
199
|
+
return {
|
|
200
|
+
success: false,
|
|
201
|
+
message: `Error reading event file: ${err instanceof Error ? err.message : 'Unknown error'}`,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { serviceManager, ServiceConfig } from './ServiceManager.js';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import dotenv from 'dotenv';
|
|
6
|
+
|
|
7
|
+
export interface ViteOptions {
|
|
8
|
+
noHttps?: boolean;
|
|
9
|
+
cwd?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Get the toolkit root directory
|
|
13
|
+
function getToolkitRoot(): string {
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
// Navigate from dist/tui/services to project root (3 levels up)
|
|
16
|
+
return path.resolve(__dirname, '..', '..', '..');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Find existing SSL certificates in a directory
|
|
20
|
+
function findExistingCertificates(certsDir: string): { certPath: string; keyPath: string } | null {
|
|
21
|
+
if (!fs.existsSync(certsDir)) return null;
|
|
22
|
+
|
|
23
|
+
const files = fs.readdirSync(certsDir);
|
|
24
|
+
const certFile = files.find(f => f.endsWith('.pem') && !f.includes('key'));
|
|
25
|
+
const keyFile = files.find(f => f.includes('key') && f.endsWith('.pem'));
|
|
26
|
+
|
|
27
|
+
if (certFile && keyFile) {
|
|
28
|
+
return {
|
|
29
|
+
certPath: path.join(certsDir, certFile),
|
|
30
|
+
keyPath: path.join(certsDir, keyFile),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function startVite(options: ViteOptions = {}): void {
|
|
37
|
+
const cwd = options.cwd || process.cwd();
|
|
38
|
+
const toolkitRoot = getToolkitRoot();
|
|
39
|
+
|
|
40
|
+
// Load .env file if it exists
|
|
41
|
+
const envPath = path.join(cwd, '.env');
|
|
42
|
+
let envVars: Record<string, string> = {};
|
|
43
|
+
|
|
44
|
+
if (fs.existsSync(envPath)) {
|
|
45
|
+
const envResult = dotenv.config({ path: envPath });
|
|
46
|
+
if (envResult.parsed) {
|
|
47
|
+
envVars = { ...envResult.parsed };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Determine HTTPS settings
|
|
52
|
+
let useHttps = !options.noHttps;
|
|
53
|
+
let certPath = '';
|
|
54
|
+
let keyPath = '';
|
|
55
|
+
|
|
56
|
+
if (useHttps) {
|
|
57
|
+
const certsDir = path.join(cwd, '.certs');
|
|
58
|
+
const existingCerts = findExistingCertificates(certsDir);
|
|
59
|
+
if (existingCerts) {
|
|
60
|
+
certPath = existingCerts.certPath;
|
|
61
|
+
keyPath = existingCerts.keyPath;
|
|
62
|
+
} else {
|
|
63
|
+
useHttps = false; // No certs found, fall back to HTTP
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Determine the port (priority: .env > default)
|
|
68
|
+
const port = envVars.NODE_PORT || '3060';
|
|
69
|
+
|
|
70
|
+
// Find vite.config.js (local or toolkit runtime)
|
|
71
|
+
const localViteConfig = path.join(cwd, 'vite.config.js');
|
|
72
|
+
// The correct vite.config.js is in the runtime directory, not the root
|
|
73
|
+
const toolkitViteConfig = path.join(toolkitRoot, 'runtime', 'vite.config.js');
|
|
74
|
+
const viteConfigPath = fs.existsSync(localViteConfig) ? localViteConfig : toolkitViteConfig;
|
|
75
|
+
|
|
76
|
+
// Build final environment variables
|
|
77
|
+
const env: Record<string, string> = {
|
|
78
|
+
...envVars,
|
|
79
|
+
FORCE_COLOR: '1',
|
|
80
|
+
NODE_PORT: port,
|
|
81
|
+
USE_HTTPS: useHttps ? 'true' : 'false',
|
|
82
|
+
CERT_PATH: certPath,
|
|
83
|
+
KEY_PATH: keyPath,
|
|
84
|
+
COMPONENT_PATH: envVars.COMPONENT_PATH || './src/Plugin.vue',
|
|
85
|
+
NODE_LOG_LEVEL: envVars.NODE_LOG_LEVEL || 'info',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Run vite directly with the correct config
|
|
89
|
+
const config: ServiceConfig = {
|
|
90
|
+
id: 'vite',
|
|
91
|
+
name: 'Vite',
|
|
92
|
+
command: 'npx',
|
|
93
|
+
args: ['vite', 'dev', '--config', viteConfigPath],
|
|
94
|
+
cwd,
|
|
95
|
+
env,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
serviceManager.start(config);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function stopVite(): boolean {
|
|
102
|
+
return serviceManager.stop('vite');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function isViteRunning(): boolean {
|
|
106
|
+
return serviceManager.isRunning('vite');
|
|
107
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { serviceManager, ServiceManager, ServiceStatus, ServiceConfig, ServiceState } from './ServiceManager.js';
|
|
2
|
+
export { startVite, stopVite, isViteRunning, ViteOptions } from './ViteService.js';
|
|
3
|
+
export { startSocket, stopSocket, isSocketRunning, listSocketEvents, sendSocketEvent, SocketOptions, SocketEvent } from './SocketService.js';
|
|
4
|
+
export { startExtension, stopExtension, isExtensionRunning, BrowserType, ExtensionOptions } from './ExtensionService.js';
|
|
5
|
+
export {
|
|
6
|
+
geminiService,
|
|
7
|
+
GeminiService,
|
|
8
|
+
GeminiConfig,
|
|
9
|
+
isAuthenticated,
|
|
10
|
+
loadGeminiConfig,
|
|
11
|
+
saveGeminiConfig,
|
|
12
|
+
clearAuthTokens
|
|
13
|
+
} from './GeminiService.js';
|