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.
- package/README.md +105 -9
- package/bin/bs9 +91 -7
- package/dist/bs9-064xs9r9. +148 -0
- package/dist/bs9-0gqcrp5t. +144 -0
- package/dist/bs9-33vcpmb9. +181 -0
- package/dist/bs9-nv7nseny. +197 -0
- package/dist/bs9-r6b9zpw0. +156 -0
- package/dist/bs9.js +2 -0
- package/package.json +3 -4
- package/src/commands/deps.ts +295 -0
- package/src/commands/profile.ts +338 -0
- package/src/commands/restart.ts +28 -3
- package/src/commands/start.ts +201 -21
- package/src/commands/status.ts +154 -109
- package/src/commands/stop.ts +31 -3
- package/src/commands/update.ts +322 -0
- package/src/commands/web.ts +29 -3
- package/src/database/pool.ts +335 -0
- package/src/discovery/consul.ts +285 -0
- package/src/loadbalancer/manager.ts +481 -0
- package/src/macos/launchd.ts +402 -0
- package/src/monitoring/advanced.ts +341 -0
- package/src/platform/detect.ts +137 -0
- package/src/windows/service.ts +391 -0
|
@@ -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
|
+
}
|
package/src/commands/web.ts
CHANGED
|
@@ -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) {
|