bs9 1.0.0 → 1.3.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,322 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { execSync } from "node:child_process";
4
+ import { existsSync, writeFileSync, mkdirSync } from "node:fs";
5
+ import { join, dirname } from "node:path";
6
+ import { homedir } from "node:os";
7
+ import { getPlatformInfo } from "../platform/detect.js";
8
+
9
+ interface UpdateOptions {
10
+ check?: boolean;
11
+ force?: boolean;
12
+ rollback?: boolean;
13
+ version?: string;
14
+ }
15
+
16
+ interface UpdateInfo {
17
+ currentVersion: string;
18
+ latestVersion: string;
19
+ hasUpdate: boolean;
20
+ releaseNotes?: string;
21
+ downloadUrl?: string;
22
+ }
23
+
24
+ interface BackupInfo {
25
+ version: string;
26
+ timestamp: number;
27
+ files: string[];
28
+ }
29
+
30
+ class BS9Updater {
31
+ private configDir: string;
32
+ private backupDir: string;
33
+ private platformInfo: any;
34
+
35
+ constructor() {
36
+ this.platformInfo = getPlatformInfo();
37
+ this.configDir = join(homedir(), '.bs9');
38
+ this.backupDir = join(this.configDir, 'backups');
39
+ this.ensureDirectories();
40
+ }
41
+
42
+ private ensureDirectories(): void {
43
+ if (!existsSync(this.configDir)) {
44
+ mkdirSync(this.configDir, { recursive: true });
45
+ }
46
+ if (!existsSync(this.backupDir)) {
47
+ mkdirSync(this.backupDir, { recursive: true });
48
+ }
49
+ }
50
+
51
+ private getCurrentVersion(): string {
52
+ try {
53
+ // Use the actual package.json path from the project root
54
+ const packageJson = join(process.cwd(), 'package.json');
55
+ const content = require('fs').readFileSync(packageJson, 'utf-8');
56
+ const pkg = JSON.parse(content);
57
+ return pkg.version;
58
+ } catch {
59
+ return '1.0.0'; // Fallback version
60
+ }
61
+ }
62
+
63
+ private async getLatestVersion(): Promise<string> {
64
+ try {
65
+ const response = await fetch('https://registry.npmjs.org/bs9/latest');
66
+ const data = await response.json();
67
+ return data.version;
68
+ } catch (error) {
69
+ console.warn('⚠️ Failed to fetch latest version from npm, using fallback');
70
+ return '1.3.0'; // Return current version as fallback
71
+ }
72
+ }
73
+
74
+ private compareVersions(v1: string, v2: string): number {
75
+ const parts1 = v1.split('.').map(Number);
76
+ const parts2 = v2.split('.').map(Number);
77
+
78
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
79
+ const part1 = parts1[i] || 0;
80
+ const part2 = parts2[i] || 0;
81
+
82
+ if (part1 > part2) return 1;
83
+ if (part1 < part2) return -1;
84
+ }
85
+
86
+ return 0;
87
+ }
88
+
89
+ public async getUpdateInfo(): Promise<UpdateInfo> {
90
+ const currentVersion = this.getCurrentVersion();
91
+ const latestVersion = await this.getLatestVersion();
92
+
93
+ return {
94
+ currentVersion,
95
+ latestVersion,
96
+ hasUpdate: this.compareVersions(currentVersion, latestVersion) > 0
97
+ };
98
+ }
99
+
100
+ private createBackup(): BackupInfo {
101
+ const timestamp = Date.now();
102
+ const currentVersion = this.getCurrentVersion();
103
+ const backupName = `backup-${currentVersion}-${timestamp}`;
104
+ const backupPath = join(this.backupDir, backupName);
105
+
106
+ mkdirSync(backupPath, { recursive: true });
107
+
108
+ // Backup key files
109
+ const filesToBackup = [
110
+ 'bin/bs9',
111
+ 'package.json',
112
+ 'src',
113
+ 'README.md',
114
+ 'LICENSE'
115
+ ];
116
+
117
+ const backedUpFiles: string[] = [];
118
+
119
+ for (const file of filesToBackup) {
120
+ const sourcePath = join(process.cwd(), file);
121
+ const targetPath = join(backupPath, file);
122
+
123
+ try {
124
+ if (existsSync(sourcePath)) {
125
+ execSync(`cp -r "${sourcePath}" "${targetPath}"`, { stdio: 'ignore' });
126
+ backedUpFiles.push(file);
127
+ }
128
+ } catch (error) {
129
+ console.warn(`⚠️ Failed to backup ${file}: ${error}`);
130
+ }
131
+ }
132
+
133
+ return {
134
+ version: currentVersion,
135
+ timestamp,
136
+ files: backedUpFiles
137
+ };
138
+ }
139
+
140
+ public async performUpdate(targetVersion?: string): Promise<void> {
141
+ console.log('🔄 Starting BS9 update...');
142
+
143
+ // Create backup
144
+ console.log('📦 Creating backup...');
145
+ const backup = this.createBackup();
146
+ console.log(`✅ Backup created: ${backup.version}-${backup.timestamp}`);
147
+
148
+ // Update package.json version
149
+ const packageJsonPath = join(process.cwd(), 'package.json');
150
+ const packageJson = JSON.parse(require('fs').readFileSync(packageJsonPath, 'utf-8'));
151
+ const oldVersion = packageJson.version;
152
+ packageJson.version = targetVersion || await this.getLatestVersion();
153
+
154
+ require('fs').writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
155
+ console.log(`📝 Updated version: ${oldVersion} → ${packageJson.version}`);
156
+
157
+ // Install dependencies
158
+ console.log('📦 Installing dependencies...');
159
+ try {
160
+ execSync('bun install', { stdio: 'inherit', cwd: process.cwd() });
161
+ console.log('✅ Dependencies installed');
162
+ } catch (error) {
163
+ console.error('❌ Failed to install dependencies:', error);
164
+ await this.rollback(backup);
165
+ throw error;
166
+ }
167
+
168
+ // Rebuild
169
+ console.log('🔨 Rebuilding BS9...');
170
+ try {
171
+ execSync('bun run build', { stdio: 'inherit', cwd: process.cwd() });
172
+ console.log('✅ Build completed');
173
+ } catch (error) {
174
+ console.error('❌ Build failed:', error);
175
+ await this.rollback(backup);
176
+ throw error;
177
+ }
178
+
179
+ // Save backup info
180
+ const backupInfoPath = join(this.backupDir, `current-backup.json`);
181
+ writeFileSync(backupInfoPath, JSON.stringify(backup, null, 2));
182
+
183
+ console.log('🎉 BS9 updated successfully!');
184
+ console.log(` Version: ${packageJson.version}`);
185
+ console.log(` Backup: ${backup.version}-${backup.timestamp}`);
186
+ }
187
+
188
+ public async rollback(backup?: BackupInfo): Promise<void> {
189
+ console.log('🔄 Rolling back BS9...');
190
+
191
+ let backupInfo: BackupInfo;
192
+ if (backup) {
193
+ backupInfo = backup;
194
+ } else {
195
+ // Load latest backup
196
+ const backupInfoPath = join(this.backupDir, 'current-backup.json');
197
+ if (!existsSync(backupInfoPath)) {
198
+ console.error('❌ No backup found for rollback');
199
+ return;
200
+ }
201
+ backupInfo = JSON.parse(require('fs').readFileSync(backupInfoPath, 'utf-8'));
202
+ }
203
+
204
+ const backupPath = join(this.backupDir, `backup-${backupInfo.version}-${backupInfo.timestamp}`);
205
+
206
+ if (!existsSync(backupPath)) {
207
+ console.error(`❌ Backup not found: ${backupPath}`);
208
+ return;
209
+ }
210
+
211
+ // Restore files
212
+ for (const file of backupInfo.files) {
213
+ const sourcePath = join(backupPath, file);
214
+ const targetPath = join(process.cwd(), file);
215
+
216
+ try {
217
+ execSync(`cp -r "${sourcePath}" "${targetPath}"`, { stdio: 'ignore' });
218
+ console.log(`✅ Restored ${file}`);
219
+ } catch (error) {
220
+ console.warn(`⚠️ Failed to restore ${file}: ${error}`);
221
+ }
222
+ }
223
+
224
+ // Reinstall dependencies
225
+ console.log('📦 Reinstalling dependencies...');
226
+ try {
227
+ execSync('bun install', { stdio: 'inherit', cwd: process.cwd() });
228
+ console.log('✅ Dependencies reinstalled');
229
+ } catch (error) {
230
+ console.error('❌ Failed to reinstall dependencies:', error);
231
+ }
232
+
233
+ console.log(`🔄 Rollback to version ${backupInfo.version} completed`);
234
+ }
235
+
236
+ private listBackups(): void {
237
+ console.log('📋 BS9 Backup History:');
238
+ console.log('='.repeat(50));
239
+
240
+ try {
241
+ const backups = execSync(`ls -la "${this.backupDir}" | grep backup-`, { encoding: 'utf-8' });
242
+ const lines = backups.trim().split('\n');
243
+
244
+ if (lines.length === 0) {
245
+ console.log('No backups found.');
246
+ return;
247
+ }
248
+
249
+ for (const line of lines) {
250
+ const parts = line.trim().split(/\s+/);
251
+ const backupName = parts[parts.length - 1];
252
+ const match = backupName.match(/backup-(.+)-(\d+)/);
253
+
254
+ if (match) {
255
+ const [, version, timestamp] = match;
256
+ const date = new Date(parseInt(timestamp));
257
+ console.log(`${version.padEnd(10)} ${date.toISOString()} ${backupName}`);
258
+ }
259
+ }
260
+ } catch (error) {
261
+ console.error('❌ Failed to list backups:', error);
262
+ }
263
+ }
264
+
265
+ public async checkForUpdates(options?: { check?: boolean; force?: boolean }): Promise<void> {
266
+ console.log('🔍 Checking for BS9 updates...');
267
+ const updateInfo = await this.getUpdateInfo();
268
+
269
+ console.log(`Current version: ${updateInfo.currentVersion}`);
270
+ console.log(`Latest version: ${updateInfo.latestVersion}`);
271
+
272
+ if (!options?.force && !updateInfo.hasUpdate) {
273
+ console.log('✅ BS9 is up to date');
274
+ console.log(` Current version: ${updateInfo.currentVersion}`);
275
+ return;
276
+ }
277
+
278
+ if (updateInfo.hasUpdate) {
279
+ console.log('✨ Update available!');
280
+ console.log(` Run: bs9 update to install ${updateInfo.latestVersion}`);
281
+ } else {
282
+ console.log('✅ BS9 is up to date');
283
+ console.log(` Current version: ${updateInfo.currentVersion}`);
284
+ }
285
+ }
286
+ }
287
+
288
+ export async function updateCommand(options: UpdateOptions): Promise<void> {
289
+ const updater = new BS9Updater();
290
+
291
+ try {
292
+ if (options.check) {
293
+ await updater.checkForUpdates(options);
294
+ return;
295
+ }
296
+
297
+ if (options.rollback) {
298
+ await updater.rollback();
299
+ return;
300
+ }
301
+
302
+ // Check for updates first
303
+ const updateInfo = await updater.getUpdateInfo();
304
+
305
+ if (!options.force && !updateInfo.hasUpdate) {
306
+ console.log('✅ BS9 is already up to date');
307
+ console.log(` Current version: ${updateInfo.currentVersion}`);
308
+ return;
309
+ }
310
+
311
+ if (updateInfo.hasUpdate) {
312
+ console.log(`📦 Update available: ${updateInfo.currentVersion} → ${updateInfo.latestVersion}`);
313
+ }
314
+
315
+ // Perform update
316
+ await updater.performUpdate(options.version);
317
+
318
+ } catch (error) {
319
+ console.error('❌ Update failed:', error);
320
+ process.exit(1);
321
+ }
322
+ }
@@ -2,26 +2,50 @@
2
2
 
3
3
  import { execSync } from "node:child_process";
4
4
  import { spawn } from "node:child_process";
5
+ import { randomBytes } from "node:crypto";
5
6
 
6
7
  interface WebOptions {
7
8
  port?: string;
8
9
  detach?: boolean;
9
10
  }
10
11
 
12
+ // Security: Port validation
13
+ function isValidPort(port: string): boolean {
14
+ const portNum = Number(port);
15
+ return !isNaN(portNum) && portNum >= 1 && portNum <= 65535;
16
+ }
17
+
18
+ // Security: Generate secure session token
19
+ function generateSessionToken(): string {
20
+ return randomBytes(32).toString('hex');
21
+ }
22
+
11
23
  export async function webCommand(options: WebOptions): Promise<void> {
24
+ // Security: Validate port
12
25
  const port = options.port || "8080";
26
+ if (!isValidPort(port)) {
27
+ console.error(`❌ Security: Invalid port number: ${port}. Must be 1-65535`);
28
+ process.exit(1);
29
+ }
30
+
13
31
  const dashboardPath = `${import.meta.dir}/../web/dashboard.ts`;
14
32
 
33
+ // Security: Generate session token for authentication
34
+ const sessionToken = generateSessionToken();
35
+
15
36
  console.log(`🌐 Starting BS9 Web Dashboard on port ${port}`);
37
+ console.log(`🔐 Session Token: ${sessionToken}`);
16
38
 
17
39
  if (options.detach) {
18
- // Run in background
40
+ // Run in background with security
19
41
  const child = spawn("bun", ["run", dashboardPath], {
20
42
  detached: true,
21
43
  stdio: 'ignore',
22
44
  env: {
23
45
  ...process.env,
24
46
  WEB_DASHBOARD_PORT: port,
47
+ WEB_SESSION_TOKEN: sessionToken,
48
+ NODE_ENV: "production",
25
49
  },
26
50
  });
27
51
 
@@ -31,14 +55,16 @@ export async function webCommand(options: WebOptions): Promise<void> {
31
55
  console.log(` URL: http://localhost:${port}`);
32
56
  console.log(` Process ID: ${child.pid}`);
33
57
  console.log(` Stop with: kill ${child.pid}`);
58
+ console.log(` 🔐 Use session token for API access`);
34
59
  } else {
35
- // Run in foreground
60
+ // Run in foreground with security
36
61
  console.log(` URL: http://localhost:${port}`);
62
+ console.log(` 🔐 Session token: ${sessionToken}`);
37
63
  console.log(` Press Ctrl+C to stop`);
38
64
  console.log('');
39
65
 
40
66
  try {
41
- execSync(`WEB_DASHBOARD_PORT=${port} bun run ${dashboardPath}`, {
67
+ execSync(`WEB_DASHBOARD_PORT=${port} WEB_SESSION_TOKEN=${sessionToken} NODE_ENV=production bun run ${dashboardPath}`, {
42
68
  stdio: "inherit"
43
69
  });
44
70
  } catch (error) {