@rayburst/cli 0.1.0 → 0.1.8
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 +236 -157
- package/bin/rayburst.js +156 -1
- package/index.html +3 -11
- package/package.json +8 -26
- package/scripts/analyze-project.js +475 -0
- package/server.js +78 -1
- package/src/file-watcher.js +174 -0
- package/src/git-utils.js +105 -0
- package/src/incremental-analyzer.js +295 -0
- package/src/main.tsx +138 -0
- package/src/registry.js +262 -0
- package/vite-plugin-api.js +123 -0
- package/vite.config.ts +72 -0
- package/dist/assets/_commonjsHelpers-B85MJLTf.js +0 -5
- package/dist/assets/hostInit-CYZeRSfr.js +0 -9
- package/dist/assets/index-9R1akZrm.js +0 -578
- package/dist/assets/index-BW-RulSg.js +0 -258
- package/dist/assets/index-Cap7gsMp.js +0 -16597
- package/dist/assets/preload-helper-Dea3Szod.js +0 -54
- package/dist/assets/rayburstCli__loadRemote__rayburstApp_mf_1_App__loadRemote__-CHUYMhiU.js +0 -35
- package/dist/assets/rayburstCli__loadShare__react__loadShare__-CE7VtFm0.js +0 -19
- package/dist/assets/rayburstCli__mf_v__runtimeInit__mf_v__-C_SVfzik.js +0 -4173
- package/dist/assets/remoteEntry-BkHjZ4dx.js +0 -122
- package/dist/assets/virtualExposes-DwA08f_D.js +0 -5
- package/dist/index.html +0 -64
package/src/registry.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import crypto from 'crypto';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
|
|
10
|
+
// Global rayburst directory in user's home
|
|
11
|
+
const RAYBURST_DIR = path.join(os.homedir(), '.rayburst');
|
|
12
|
+
const PROJECTS_FILE = path.join(RAYBURST_DIR, 'projects.json');
|
|
13
|
+
const ANALYZED_DIR = path.join(RAYBURST_DIR, 'analyzed');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Ensure the .rayburst directory and subdirectories exist
|
|
17
|
+
*/
|
|
18
|
+
export function ensureRayburstDir() {
|
|
19
|
+
if (!fs.existsSync(RAYBURST_DIR)) {
|
|
20
|
+
fs.mkdirSync(RAYBURST_DIR, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
if (!fs.existsSync(ANALYZED_DIR)) {
|
|
23
|
+
fs.mkdirSync(ANALYZED_DIR, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
if (!fs.existsSync(PROJECTS_FILE)) {
|
|
26
|
+
fs.writeFileSync(PROJECTS_FILE, JSON.stringify({ projects: [] }, null, 2));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Read the projects registry
|
|
32
|
+
*/
|
|
33
|
+
export function readRegistry() {
|
|
34
|
+
ensureRayburstDir();
|
|
35
|
+
try {
|
|
36
|
+
const data = fs.readFileSync(PROJECTS_FILE, 'utf-8');
|
|
37
|
+
return JSON.parse(data);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Error reading registry:', error.message);
|
|
40
|
+
return { projects: [] };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Write to the projects registry
|
|
46
|
+
*/
|
|
47
|
+
export function writeRegistry(registry) {
|
|
48
|
+
ensureRayburstDir();
|
|
49
|
+
try {
|
|
50
|
+
fs.writeFileSync(PROJECTS_FILE, JSON.stringify(registry, null, 2));
|
|
51
|
+
return true;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('Error writing registry:', error.message);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Generate a unique project ID from the project path
|
|
60
|
+
*/
|
|
61
|
+
export function generateProjectId(projectPath) {
|
|
62
|
+
const hash = crypto.createHash('md5').update(projectPath).digest('hex');
|
|
63
|
+
return hash.substring(0, 12);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if a directory is a valid project (has package.json)
|
|
68
|
+
*/
|
|
69
|
+
export function isValidProject(projectPath) {
|
|
70
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
71
|
+
return fs.existsSync(packageJsonPath);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Read package.json from a project
|
|
76
|
+
*/
|
|
77
|
+
export function readPackageJson(projectPath) {
|
|
78
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
79
|
+
try {
|
|
80
|
+
const data = fs.readFileSync(packageJsonPath, 'utf-8');
|
|
81
|
+
return JSON.parse(data);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error(`Error reading package.json:`, error.message);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Register a new project
|
|
90
|
+
*/
|
|
91
|
+
export function registerProject(projectPath) {
|
|
92
|
+
const absolutePath = path.resolve(projectPath);
|
|
93
|
+
|
|
94
|
+
// Validate project
|
|
95
|
+
if (!isValidProject(absolutePath)) {
|
|
96
|
+
throw new Error(`Not a valid project: ${absolutePath} (missing package.json)`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const registry = readRegistry();
|
|
100
|
+
const projectId = generateProjectId(absolutePath);
|
|
101
|
+
|
|
102
|
+
// Check if already registered
|
|
103
|
+
const existingIndex = registry.projects.findIndex(p => p.id === projectId);
|
|
104
|
+
if (existingIndex >= 0) {
|
|
105
|
+
throw new Error(`Project already registered: ${absolutePath}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Read package.json
|
|
109
|
+
const packageJson = readPackageJson(absolutePath);
|
|
110
|
+
if (!packageJson) {
|
|
111
|
+
throw new Error(`Failed to read package.json from ${absolutePath}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Create project entry
|
|
115
|
+
const project = {
|
|
116
|
+
id: projectId,
|
|
117
|
+
name: packageJson.name || path.basename(absolutePath),
|
|
118
|
+
path: absolutePath,
|
|
119
|
+
registeredAt: new Date().toISOString(),
|
|
120
|
+
lastAnalyzed: null,
|
|
121
|
+
packageJson: {
|
|
122
|
+
name: packageJson.name,
|
|
123
|
+
version: packageJson.version,
|
|
124
|
+
description: packageJson.description,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
registry.projects.push(project);
|
|
129
|
+
|
|
130
|
+
if (writeRegistry(registry)) {
|
|
131
|
+
return project;
|
|
132
|
+
} else {
|
|
133
|
+
throw new Error('Failed to write registry');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Unregister a project
|
|
139
|
+
*/
|
|
140
|
+
export function unregisterProject(projectPathOrId) {
|
|
141
|
+
const registry = readRegistry();
|
|
142
|
+
const absolutePath = path.resolve(projectPathOrId);
|
|
143
|
+
const projectId = generateProjectId(absolutePath);
|
|
144
|
+
|
|
145
|
+
// Find by ID or path
|
|
146
|
+
const index = registry.projects.findIndex(
|
|
147
|
+
p => p.id === projectId || p.id === projectPathOrId || p.path === absolutePath
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
if (index === -1) {
|
|
151
|
+
throw new Error(`Project not found: ${projectPathOrId}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const project = registry.projects[index];
|
|
155
|
+
registry.projects.splice(index, 1);
|
|
156
|
+
|
|
157
|
+
// Remove analysis data if it exists
|
|
158
|
+
const analysisFile = path.join(ANALYZED_DIR, `${project.id}.json`);
|
|
159
|
+
if (fs.existsSync(analysisFile)) {
|
|
160
|
+
fs.unlinkSync(analysisFile);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (writeRegistry(registry)) {
|
|
164
|
+
return project;
|
|
165
|
+
} else {
|
|
166
|
+
throw new Error('Failed to write registry');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* List all registered projects
|
|
172
|
+
*/
|
|
173
|
+
export function listProjects() {
|
|
174
|
+
const registry = readRegistry();
|
|
175
|
+
return registry.projects;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get a specific project by ID or path
|
|
180
|
+
*/
|
|
181
|
+
export function getProject(projectIdOrPath) {
|
|
182
|
+
const registry = readRegistry();
|
|
183
|
+
const absolutePath = path.resolve(projectIdOrPath);
|
|
184
|
+
const projectId = generateProjectId(absolutePath);
|
|
185
|
+
|
|
186
|
+
return registry.projects.find(
|
|
187
|
+
p => p.id === projectId || p.id === projectIdOrPath || p.path === absolutePath
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Update project's lastAnalyzed timestamp
|
|
193
|
+
*/
|
|
194
|
+
export function updateProjectAnalysis(projectId) {
|
|
195
|
+
const registry = readRegistry();
|
|
196
|
+
const project = registry.projects.find(p => p.id === projectId);
|
|
197
|
+
|
|
198
|
+
if (!project) {
|
|
199
|
+
throw new Error(`Project not found: ${projectId}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
project.lastAnalyzed = new Date().toISOString();
|
|
203
|
+
|
|
204
|
+
if (writeRegistry(registry)) {
|
|
205
|
+
return project;
|
|
206
|
+
} else {
|
|
207
|
+
throw new Error('Failed to update registry');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get path to analysis file for a project
|
|
213
|
+
*/
|
|
214
|
+
export function getAnalysisFilePath(projectId) {
|
|
215
|
+
return path.join(ANALYZED_DIR, `${projectId}.json`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Read analysis data for a project
|
|
220
|
+
*/
|
|
221
|
+
export function readAnalysisData(projectId) {
|
|
222
|
+
const analysisFile = getAnalysisFilePath(projectId);
|
|
223
|
+
|
|
224
|
+
if (!fs.existsSync(analysisFile)) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const data = fs.readFileSync(analysisFile, 'utf-8');
|
|
230
|
+
return JSON.parse(data);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.error(`Error reading analysis data:`, error.message);
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Write analysis data for a project
|
|
239
|
+
*/
|
|
240
|
+
export function writeAnalysisData(projectId, analysisData) {
|
|
241
|
+
ensureRayburstDir();
|
|
242
|
+
const analysisFile = getAnalysisFilePath(projectId);
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
fs.writeFileSync(analysisFile, JSON.stringify(analysisData, null, 2));
|
|
246
|
+
return true;
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.error(`Error writing analysis data:`, error.message);
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get registry paths for external access
|
|
255
|
+
*/
|
|
256
|
+
export function getRegistryPaths() {
|
|
257
|
+
return {
|
|
258
|
+
rayburstDir: RAYBURST_DIR,
|
|
259
|
+
projectsFile: PROJECTS_FILE,
|
|
260
|
+
analyzedDir: ANALYZED_DIR,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
listProjects,
|
|
3
|
+
getProject,
|
|
4
|
+
readAnalysisData,
|
|
5
|
+
registerProject,
|
|
6
|
+
unregisterProject,
|
|
7
|
+
} from './src/registry.js';
|
|
8
|
+
import { getAllChangedFiles } from './src/git-utils.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Vite plugin to handle API routes during development
|
|
12
|
+
*/
|
|
13
|
+
export function apiPlugin() {
|
|
14
|
+
return {
|
|
15
|
+
name: 'rayburst-api',
|
|
16
|
+
configureServer(server) {
|
|
17
|
+
server.middlewares.use('/api', (req, res, next) => {
|
|
18
|
+
// Parse JSON body for POST requests
|
|
19
|
+
if (req.method === 'POST' || req.method === 'PUT') {
|
|
20
|
+
let body = '';
|
|
21
|
+
req.on('data', chunk => {
|
|
22
|
+
body += chunk.toString();
|
|
23
|
+
});
|
|
24
|
+
req.on('end', () => {
|
|
25
|
+
try {
|
|
26
|
+
req.body = JSON.parse(body);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
req.body = {};
|
|
29
|
+
}
|
|
30
|
+
handleApiRequest(req, res, next);
|
|
31
|
+
});
|
|
32
|
+
} else {
|
|
33
|
+
handleApiRequest(req, res, next);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function handleApiRequest(req, res, next) {
|
|
41
|
+
const url = (req.url || '').replace('/api', ''); // Remove /api prefix since middleware already matched it
|
|
42
|
+
const method = req.method;
|
|
43
|
+
|
|
44
|
+
// Set JSON response headers
|
|
45
|
+
res.setHeader('Content-Type', 'application/json');
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// GET /api/projects - List all projects
|
|
49
|
+
if (method === 'GET' && url === '/projects') {
|
|
50
|
+
const projects = listProjects();
|
|
51
|
+
res.end(JSON.stringify({ projects }));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// GET /projects/:id/changed-files - Get uncommitted changed files (check before generic project route)
|
|
56
|
+
if (method === 'GET' && url.includes('/changed-files')) {
|
|
57
|
+
const id = url.split('/')[2];
|
|
58
|
+
const project = getProject(id);
|
|
59
|
+
if (!project) {
|
|
60
|
+
res.statusCode = 404;
|
|
61
|
+
res.end(JSON.stringify({ error: 'Project not found' }));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const changedFiles = getAllChangedFiles(project.path);
|
|
65
|
+
res.end(JSON.stringify({ changedFiles }));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// GET /projects/:id/analysis - Get project analysis
|
|
70
|
+
if (method === 'GET' && url.includes('/analysis')) {
|
|
71
|
+
const id = url.split('/')[2];
|
|
72
|
+
const analysisData = readAnalysisData(id);
|
|
73
|
+
if (!analysisData) {
|
|
74
|
+
res.statusCode = 404;
|
|
75
|
+
res.end(JSON.stringify({ error: 'Analysis data not found' }));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
res.end(JSON.stringify(analysisData));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// GET /projects/:id - Get specific project
|
|
83
|
+
if (method === 'GET' && url.startsWith('/projects/') && !url.includes('/analysis') && !url.includes('/changed-files')) {
|
|
84
|
+
const id = url.split('/')[2];
|
|
85
|
+
const project = getProject(id);
|
|
86
|
+
if (!project) {
|
|
87
|
+
res.statusCode = 404;
|
|
88
|
+
res.end(JSON.stringify({ error: 'Project not found' }));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
res.end(JSON.stringify({ project }));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// POST /projects/register - Register project
|
|
96
|
+
if (method === 'POST' && url === '/projects/register') {
|
|
97
|
+
const { path } = req.body || {};
|
|
98
|
+
if (!path) {
|
|
99
|
+
res.statusCode = 400;
|
|
100
|
+
res.end(JSON.stringify({ error: 'Path is required' }));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const project = registerProject(path);
|
|
104
|
+
res.end(JSON.stringify({ project }));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// DELETE /projects/:id - Unregister project
|
|
109
|
+
if (method === 'DELETE' && url.startsWith('/projects/')) {
|
|
110
|
+
const id = url.split('/')[2];
|
|
111
|
+
const project = unregisterProject(id);
|
|
112
|
+
res.end(JSON.stringify({ project }));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// No matching route
|
|
117
|
+
res.statusCode = 404;
|
|
118
|
+
res.end(JSON.stringify({ error: 'API endpoint not found' }));
|
|
119
|
+
} catch (error) {
|
|
120
|
+
res.statusCode = 500;
|
|
121
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
122
|
+
}
|
|
123
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
import { federation } from '@module-federation/vite';
|
|
4
|
+
import { apiPlugin } from './vite-plugin-api.js';
|
|
5
|
+
|
|
6
|
+
// Determine remote URL based on environment
|
|
7
|
+
function getRemoteUrl(env: string): string {
|
|
8
|
+
switch (env) {
|
|
9
|
+
case 'production':
|
|
10
|
+
return 'https://www.rayburst.app';
|
|
11
|
+
case 'staging':
|
|
12
|
+
return 'https://dev.rayburst.app';
|
|
13
|
+
case 'development':
|
|
14
|
+
default:
|
|
15
|
+
return 'http://localhost:3000';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default defineConfig(() => {
|
|
20
|
+
const environment = process.env.NODE_ENV || 'development';
|
|
21
|
+
const remoteUrl = getRemoteUrl(environment);
|
|
22
|
+
|
|
23
|
+
console.log(`🔧 Building CLI for environment: ${environment}`);
|
|
24
|
+
console.log(`📡 Remote URL: ${remoteUrl}`);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
plugins: [
|
|
28
|
+
apiPlugin(),
|
|
29
|
+
react(),
|
|
30
|
+
federation({
|
|
31
|
+
name: 'rayburstCli',
|
|
32
|
+
remotes: {
|
|
33
|
+
rayburstApp: {
|
|
34
|
+
type: 'module',
|
|
35
|
+
name: 'rayburstApp',
|
|
36
|
+
entry: `${remoteUrl}/remoteEntry.js?t=${Date.now()}`,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
shared: {
|
|
40
|
+
react: {
|
|
41
|
+
singleton: true,
|
|
42
|
+
requiredVersion: '^19.0.0',
|
|
43
|
+
},
|
|
44
|
+
'react-dom': {
|
|
45
|
+
singleton: true,
|
|
46
|
+
requiredVersion: '^19.0.0',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
],
|
|
51
|
+
|
|
52
|
+
build: {
|
|
53
|
+
modulePreload: false,
|
|
54
|
+
target: 'esnext',
|
|
55
|
+
minify: false,
|
|
56
|
+
cssCodeSplit: false,
|
|
57
|
+
outDir: 'dist',
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
server: {
|
|
61
|
+
port: 3105,
|
|
62
|
+
strictPort: true,
|
|
63
|
+
host: true,
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
preview: {
|
|
67
|
+
port: 3105,
|
|
68
|
+
strictPort: true,
|
|
69
|
+
host: true,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { _ as __vitePreload } from './preload-helper-Dea3Szod.js';
|
|
2
|
-
|
|
3
|
-
const remoteEntryPromise = __vitePreload(() => import('./remoteEntry-BkHjZ4dx.js'),true ?[]:void 0);
|
|
4
|
-
// __tla only serves as a hack for vite-plugin-top-level-await.
|
|
5
|
-
Promise.resolve(remoteEntryPromise)
|
|
6
|
-
.then(remoteEntry => {
|
|
7
|
-
return Promise.resolve(remoteEntry.__tla)
|
|
8
|
-
.then(remoteEntry.init).catch(remoteEntry.init)
|
|
9
|
-
});
|