@rayburst/cli 0.1.18 → 0.2.0

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.
@@ -0,0 +1,287 @@
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
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ // Global rayburst directory in user's home
9
+ const RAYBURST_DIR = path.join(os.homedir(), '.rayburst');
10
+ const PROJECTS_FILE = path.join(RAYBURST_DIR, 'projects.json');
11
+ const ANALYZED_DIR = path.join(RAYBURST_DIR, 'analyzed');
12
+ /**
13
+ * Ensure the .rayburst directory and subdirectories exist
14
+ */
15
+ export function ensureRayburstDir() {
16
+ if (!fs.existsSync(RAYBURST_DIR)) {
17
+ fs.mkdirSync(RAYBURST_DIR, { recursive: true });
18
+ }
19
+ if (!fs.existsSync(ANALYZED_DIR)) {
20
+ fs.mkdirSync(ANALYZED_DIR, { recursive: true });
21
+ }
22
+ if (!fs.existsSync(PROJECTS_FILE)) {
23
+ fs.writeFileSync(PROJECTS_FILE, JSON.stringify({ projects: [] }, null, 2));
24
+ }
25
+ }
26
+ /**
27
+ * Read the projects registry
28
+ */
29
+ export function readRegistry() {
30
+ ensureRayburstDir();
31
+ try {
32
+ const data = fs.readFileSync(PROJECTS_FILE, 'utf-8');
33
+ return JSON.parse(data);
34
+ }
35
+ catch (error) {
36
+ console.error('Error reading registry:', error.message);
37
+ return { projects: [] };
38
+ }
39
+ }
40
+ /**
41
+ * Write to the projects registry
42
+ */
43
+ export function writeRegistry(registry) {
44
+ ensureRayburstDir();
45
+ try {
46
+ fs.writeFileSync(PROJECTS_FILE, JSON.stringify(registry, null, 2));
47
+ return true;
48
+ }
49
+ catch (error) {
50
+ console.error('Error writing registry:', error.message);
51
+ return false;
52
+ }
53
+ }
54
+ /**
55
+ * Generate a unique project ID from the project path
56
+ */
57
+ export function generateProjectId(projectPath) {
58
+ const hash = crypto.createHash('md5').update(projectPath).digest('hex');
59
+ return hash.substring(0, 12);
60
+ }
61
+ /**
62
+ * Check if a directory is a valid project (has package.json)
63
+ */
64
+ export function isValidProject(projectPath) {
65
+ const packageJsonPath = path.join(projectPath, 'package.json');
66
+ return fs.existsSync(packageJsonPath);
67
+ }
68
+ /**
69
+ * Read package.json from a project
70
+ */
71
+ export function readPackageJson(projectPath) {
72
+ const packageJsonPath = path.join(projectPath, 'package.json');
73
+ try {
74
+ const data = fs.readFileSync(packageJsonPath, 'utf-8');
75
+ return JSON.parse(data);
76
+ }
77
+ catch (error) {
78
+ console.error(`Error reading package.json:`, error.message);
79
+ return null;
80
+ }
81
+ }
82
+ /**
83
+ * Validate if a project's path still exists and is valid
84
+ */
85
+ export function validateProjectPath(project) {
86
+ return fs.existsSync(project.path) && isValidProject(project.path);
87
+ }
88
+ /**
89
+ * Validate all registered projects
90
+ * Returns array of projects with validation status
91
+ */
92
+ export function validateAllProjects() {
93
+ const registry = readRegistry();
94
+ return registry.projects.map(project => ({
95
+ ...project,
96
+ isValid: validateProjectPath(project),
97
+ pathExists: fs.existsSync(project.path)
98
+ }));
99
+ }
100
+ /**
101
+ * Update a project's path
102
+ * Validates the new path and ensures it's the same project
103
+ */
104
+ export function updateProjectPath(projectId, newPath) {
105
+ const absolutePath = path.resolve(newPath);
106
+ // Validate new path
107
+ if (!isValidProject(absolutePath)) {
108
+ throw new Error(`Not a valid project: ${absolutePath} (missing package.json)`);
109
+ }
110
+ const registry = readRegistry();
111
+ const project = registry.projects.find(p => p.id === projectId);
112
+ if (!project) {
113
+ throw new Error(`Project not found: ${projectId}`);
114
+ }
115
+ // Check if package.json matches (to confirm it's the same project)
116
+ const oldPackageJson = project.packageJson;
117
+ const newPackageJson = readPackageJson(absolutePath);
118
+ if (!newPackageJson) {
119
+ throw new Error(`Failed to read package.json from ${absolutePath}`);
120
+ }
121
+ if (oldPackageJson.name !== newPackageJson.name) {
122
+ throw new Error(`Project mismatch: expected "${oldPackageJson.name}", found "${newPackageJson.name}"`);
123
+ }
124
+ // Update path and package.json info
125
+ project.path = absolutePath;
126
+ project.packageJson = {
127
+ name: newPackageJson.name,
128
+ version: newPackageJson.version,
129
+ description: newPackageJson.description,
130
+ };
131
+ project.updatedAt = new Date().toISOString();
132
+ if (writeRegistry(registry)) {
133
+ return project;
134
+ }
135
+ else {
136
+ throw new Error('Failed to update registry');
137
+ }
138
+ }
139
+ /**
140
+ * Register a new project
141
+ */
142
+ export function registerProject(projectPath) {
143
+ const absolutePath = path.resolve(projectPath);
144
+ // Validate project
145
+ if (!isValidProject(absolutePath)) {
146
+ throw new Error(`Not a valid project: ${absolutePath} (missing package.json)`);
147
+ }
148
+ const registry = readRegistry();
149
+ const projectId = generateProjectId(absolutePath);
150
+ // Check if already registered
151
+ const existingIndex = registry.projects.findIndex(p => p.id === projectId);
152
+ if (existingIndex >= 0) {
153
+ throw new Error(`Project already registered: ${absolutePath}`);
154
+ }
155
+ // Read package.json
156
+ const packageJson = readPackageJson(absolutePath);
157
+ if (!packageJson) {
158
+ throw new Error(`Failed to read package.json from ${absolutePath}`);
159
+ }
160
+ // Create project entry
161
+ const project = {
162
+ id: projectId,
163
+ name: packageJson.name || path.basename(absolutePath),
164
+ path: absolutePath,
165
+ registeredAt: new Date().toISOString(),
166
+ lastAnalyzed: null,
167
+ packageJson: {
168
+ name: packageJson.name,
169
+ version: packageJson.version,
170
+ description: packageJson.description,
171
+ },
172
+ };
173
+ registry.projects.push(project);
174
+ if (writeRegistry(registry)) {
175
+ return project;
176
+ }
177
+ else {
178
+ throw new Error('Failed to write registry');
179
+ }
180
+ }
181
+ /**
182
+ * Unregister a project
183
+ */
184
+ export function unregisterProject(projectPathOrId) {
185
+ const registry = readRegistry();
186
+ const absolutePath = path.resolve(projectPathOrId);
187
+ const projectId = generateProjectId(absolutePath);
188
+ // Find by ID or path
189
+ const index = registry.projects.findIndex(p => p.id === projectId || p.id === projectPathOrId || p.path === absolutePath);
190
+ if (index === -1) {
191
+ throw new Error(`Project not found: ${projectPathOrId}`);
192
+ }
193
+ const project = registry.projects[index];
194
+ registry.projects.splice(index, 1);
195
+ // Remove analysis data if it exists
196
+ const analysisFile = path.join(ANALYZED_DIR, `${project.id}.json`);
197
+ if (fs.existsSync(analysisFile)) {
198
+ fs.unlinkSync(analysisFile);
199
+ }
200
+ if (writeRegistry(registry)) {
201
+ return project;
202
+ }
203
+ else {
204
+ throw new Error('Failed to write registry');
205
+ }
206
+ }
207
+ /**
208
+ * List all registered projects
209
+ */
210
+ export function listProjects() {
211
+ const registry = readRegistry();
212
+ return registry.projects;
213
+ }
214
+ /**
215
+ * Get a specific project by ID or path
216
+ */
217
+ export function getProject(projectIdOrPath) {
218
+ const registry = readRegistry();
219
+ const absolutePath = path.resolve(projectIdOrPath);
220
+ const projectId = generateProjectId(absolutePath);
221
+ return registry.projects.find(p => p.id === projectId || p.id === projectIdOrPath || p.path === absolutePath);
222
+ }
223
+ /**
224
+ * Update project's lastAnalyzed timestamp
225
+ */
226
+ export function updateProjectAnalysis(projectId) {
227
+ const registry = readRegistry();
228
+ const project = registry.projects.find(p => p.id === projectId);
229
+ if (!project) {
230
+ throw new Error(`Project not found: ${projectId}`);
231
+ }
232
+ project.lastAnalyzed = new Date().toISOString();
233
+ if (writeRegistry(registry)) {
234
+ return project;
235
+ }
236
+ else {
237
+ throw new Error('Failed to update registry');
238
+ }
239
+ }
240
+ /**
241
+ * Get path to analysis file for a project
242
+ */
243
+ export function getAnalysisFilePath(projectId) {
244
+ return path.join(ANALYZED_DIR, `${projectId}.json`);
245
+ }
246
+ /**
247
+ * Read analysis data for a project
248
+ */
249
+ export function readAnalysisData(projectId) {
250
+ const analysisFile = getAnalysisFilePath(projectId);
251
+ if (!fs.existsSync(analysisFile)) {
252
+ return null;
253
+ }
254
+ try {
255
+ const data = fs.readFileSync(analysisFile, 'utf-8');
256
+ return JSON.parse(data);
257
+ }
258
+ catch (error) {
259
+ console.error(`Error reading analysis data:`, error.message);
260
+ return null;
261
+ }
262
+ }
263
+ /**
264
+ * Write analysis data for a project
265
+ */
266
+ export function writeAnalysisData(projectId, analysisData) {
267
+ ensureRayburstDir();
268
+ const analysisFile = getAnalysisFilePath(projectId);
269
+ try {
270
+ fs.writeFileSync(analysisFile, JSON.stringify(analysisData, null, 2));
271
+ return true;
272
+ }
273
+ catch (error) {
274
+ console.error(`Error writing analysis data:`, error.message);
275
+ return false;
276
+ }
277
+ }
278
+ /**
279
+ * Get registry paths for external access
280
+ */
281
+ export function getRegistryPaths() {
282
+ return {
283
+ rayburstDir: RAYBURST_DIR,
284
+ projectsFile: PROJECTS_FILE,
285
+ analyzedDir: ANALYZED_DIR,
286
+ };
287
+ }
@@ -0,0 +1,7 @@
1
+ import type { Plugin } from 'vite';
2
+ export interface RayburstPluginOptions {
3
+ enabled?: boolean;
4
+ debounceMs?: number;
5
+ outputPath?: string;
6
+ }
7
+ export declare function rayburstPlugin(options?: RayburstPluginOptions): Plugin;
@@ -0,0 +1,109 @@
1
+ import { analyzeProject } from './analysis/analyze-project';
2
+ import { ensureRayburstDir, readLocalAnalysis, writeLocalAnalysis, addGitignoreEntry, } from './local-storage';
3
+ import { generateDiff } from './incremental-analyzer';
4
+ import chalk from 'chalk';
5
+ export function rayburstPlugin(options = {}) {
6
+ const { enabled = process.env.CI !== 'true', // Disabled in CI by default
7
+ debounceMs = 1500, } = options;
8
+ let config;
9
+ let debounceTimer = null;
10
+ let isAnalyzing = false;
11
+ const runAnalysis = async (triggerFile) => {
12
+ if (isAnalyzing)
13
+ return;
14
+ isAnalyzing = true;
15
+ try {
16
+ const projectPath = config.root;
17
+ // Read previous analysis
18
+ const previousAnalysis = await readLocalAnalysis(projectPath);
19
+ // Run analysis
20
+ const newAnalysis = await analyzeProject(projectPath);
21
+ // Write to .rayburst/analysis.json
22
+ await writeLocalAnalysis(projectPath, newAnalysis);
23
+ // Show diff if previous exists
24
+ if (previousAnalysis) {
25
+ const diff = generateDiff(previousAnalysis, newAnalysis);
26
+ console.log(chalk.green('[Rayburst] Analysis updated:'));
27
+ console.log(chalk.gray(` Added: ${diff.added.nodes.length} nodes, ${diff.added.edges.length} edges`));
28
+ console.log(chalk.gray(` Removed: ${diff.removed.nodeIds.length} nodes, ${diff.removed.edgeIds.length} edges`));
29
+ if (diff.modified.nodes.length > 0) {
30
+ console.log(chalk.gray(` Modified: ${diff.modified.nodes.length} nodes`));
31
+ }
32
+ }
33
+ else {
34
+ const firstBranch = Object.keys(newAnalysis.planData)[0];
35
+ const nodeCount = firstBranch ? newAnalysis.planData[firstBranch].nodes.length : 0;
36
+ const edgeCount = firstBranch ? newAnalysis.planData[firstBranch].edges.length : 0;
37
+ console.log(chalk.green(`[Rayburst] Initial analysis complete: ${nodeCount} nodes, ${edgeCount} edges`));
38
+ }
39
+ }
40
+ catch (error) {
41
+ const message = error instanceof Error ? error.message : String(error);
42
+ console.error(chalk.red('[Rayburst] Analysis failed:'), message);
43
+ }
44
+ finally {
45
+ isAnalyzing = false;
46
+ }
47
+ };
48
+ return {
49
+ name: 'rayburst-analyzer',
50
+ // Hook 1: Store resolved config
51
+ configResolved(resolvedConfig) {
52
+ config = resolvedConfig;
53
+ },
54
+ // Hook 2: Run initial analysis when dev server starts
55
+ async buildStart() {
56
+ // Only run in dev mode (serve), not during build
57
+ if (!enabled || config.command !== 'serve') {
58
+ return;
59
+ }
60
+ const projectPath = config.root;
61
+ // Initialize project
62
+ await ensureRayburstDir(projectPath);
63
+ await addGitignoreEntry(projectPath);
64
+ console.log(chalk.blue('[Rayburst] Starting code analysis...'));
65
+ // Run initial analysis
66
+ await runAnalysis();
67
+ },
68
+ // Hook 3: Watch for changes using Vite's built-in watcher
69
+ configureServer(server) {
70
+ if (!enabled)
71
+ return;
72
+ const handleFileChange = (filePath) => {
73
+ // Only analyze TypeScript/JavaScript files
74
+ if (!filePath.match(/\.(ts|tsx|js|jsx)$/)) {
75
+ return;
76
+ }
77
+ // Skip non-relevant files
78
+ if (filePath.includes('node_modules') ||
79
+ filePath.includes('.rayburst') ||
80
+ filePath.includes('.test.') ||
81
+ filePath.includes('.spec.')) {
82
+ return;
83
+ }
84
+ // Debounce rapid changes
85
+ if (debounceTimer) {
86
+ clearTimeout(debounceTimer);
87
+ }
88
+ debounceTimer = setTimeout(() => {
89
+ const relativePath = filePath.replace(config.root, '').replace(/^\//, '');
90
+ console.log(chalk.dim(`[Rayburst] File changed: ${relativePath}`));
91
+ runAnalysis(filePath);
92
+ }, debounceMs);
93
+ };
94
+ // Use Vite's built-in file watcher
95
+ server.watcher.on('change', handleFileChange);
96
+ server.watcher.on('add', handleFileChange);
97
+ server.watcher.on('unlink', handleFileChange);
98
+ // Cleanup function
99
+ return () => {
100
+ server.watcher.off('change', handleFileChange);
101
+ server.watcher.off('add', handleFileChange);
102
+ server.watcher.off('unlink', handleFileChange);
103
+ if (debounceTimer) {
104
+ clearTimeout(debounceTimer);
105
+ }
106
+ };
107
+ },
108
+ };
109
+ }
package/package.json CHANGED
@@ -1,44 +1,55 @@
1
1
  {
2
2
  "name": "@rayburst/cli",
3
- "version": "0.1.18",
4
- "description": "Rayburst CLI - A module federation host for Rayburst app",
3
+ "version": "0.2.0",
4
+ "description": "Rayburst - Automatic code analysis for TypeScript/JavaScript projects via Vite plugin",
5
5
  "type": "module",
6
- "bin": {
7
- "rayburst": "./bin/rayburst.js"
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./vite": {
14
+ "types": "./dist/vite-plugin.d.ts",
15
+ "import": "./dist/vite-plugin.js"
16
+ }
8
17
  },
9
- "main": "./server.js",
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
10
22
  "scripts": {
11
- "dev": "vite",
12
- "build": "vite build",
13
- "start": "node server.js"
23
+ "build": "tsc",
24
+ "dev": "tsc --watch"
14
25
  },
15
26
  "keywords": [
16
27
  "rayburst",
17
- "cli",
18
- "module-federation"
28
+ "vite-plugin",
29
+ "code-analysis",
30
+ "typescript",
31
+ "javascript",
32
+ "dependency-graph"
19
33
  ],
20
34
  "author": "",
21
35
  "license": "MIT",
36
+ "peerDependencies": {
37
+ "vite": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "vite": {
41
+ "optional": true
42
+ }
43
+ },
22
44
  "dependencies": {
45
+ "@rayburst/types": "^0.1.1",
23
46
  "chalk": "^5.3.0",
24
- "chokidar": "^4.0.3",
25
- "commander": "^11.1.0",
26
- "express": "^4.18.2",
27
47
  "ts-morph": "^21.0.1",
28
- "ws": "^8.18.3"
48
+ "zod": "^4.2.0"
29
49
  },
30
50
  "devDependencies": {
31
- "@module-federation/vite": "^1.9.0",
32
- "@tailwindcss/vite": "^4.1.17",
33
- "@vitejs/plugin-react": "^4.2.1",
34
- "react": "^19.0.0",
35
- "react-dom": "^19.0.0",
36
- "tailwindcss": "^4.1.17",
37
- "tw-animate-css": "^1.4.0",
51
+ "@types/node": "^25.0.2",
52
+ "typescript": "^5.9.3",
38
53
  "vite": "^7.1.7"
39
- },
40
- "peerDependencies": {
41
- "react": "^19.0.0",
42
- "react-dom": "^19.0.0"
43
54
  }
44
55
  }