@ebowwa/pkg-ops 0.1.20 → 0.1.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ebowwa/pkg-ops",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "description": "Package operations CLI - installs @ebowwa/* npm packages and manages systemd services",
6
6
  "bin": {
@@ -14,7 +14,6 @@
14
14
  },
15
15
  "files": [
16
16
  "dist",
17
- "src",
18
17
  "rust/src",
19
18
  "rust/Cargo.toml",
20
19
  "rust/Cargo.lock",
package/rust/src/lib.rs CHANGED
@@ -838,6 +838,8 @@ pub fn get_installed_info() -> Vec<InstalledPackageInfo> {
838
838
  version: pkg.version.clone(),
839
839
  dist_size_bytes: dist_size,
840
840
  installed_at,
841
+ total_versions: None,
842
+ versions: None,
841
843
  });
842
844
  }
843
845
 
@@ -1086,7 +1088,7 @@ pub fn switch_version(package_name: &str, target_version: &str) -> SwitchResult
1086
1088
  SwitchResult {
1087
1089
  success: true,
1088
1090
  package_name: package_name.to_string(),
1089
- from_version: current_version,
1091
+ from_version: current_version.clone(),
1090
1092
  to_version: target_version.to_string(),
1091
1093
  message: format!("Switched from {} to {}", current_version, target_version),
1092
1094
  }
package/src/bridge.ts DELETED
@@ -1,506 +0,0 @@
1
- /**
2
- * Bridge to Rust worker for package operations.
3
- *
4
- * Communicates with the Rust core via JSON-RPC 2.0 over stdin/stdout.
5
- *
6
- * @example
7
- * ```typescript
8
- * import { RustBridge } from "@ebowwa/pkg-ops/bridge";
9
- *
10
- * const bridge = new RustBridge();
11
- * await bridge.start();
12
- *
13
- * // Install a package
14
- * const result = await bridge.install("@ebowwa/stack", "0.7.12");
15
- * console.log(result);
16
- *
17
- * // List packages
18
- * const packages = await bridge.list();
19
- *
20
- * await bridge.stop();
21
- * ```
22
- */
23
-
24
- import { spawn, type ChildProcess } from "child_process";
25
- import { accessSync, constants as fsConstants } from "fs";
26
- import { join, dirname } from "path";
27
- import { fileURLToPath } from "url";
28
-
29
- // ---------------------------------------------------------------------------
30
- // Types
31
- // ---------------------------------------------------------------------------
32
-
33
- export interface InstallResult {
34
- success: boolean;
35
- version: string;
36
- previousVersion?: string;
37
- message: string;
38
- }
39
-
40
- export interface PackageInfo {
41
- name: string;
42
- version: string;
43
- installed: boolean;
44
- service?: string;
45
- }
46
-
47
- export interface RollbackResult {
48
- success: boolean;
49
- previousVersion: string;
50
- currentVersion: string;
51
- message: string;
52
- }
53
-
54
- export interface HealthCheckResult {
55
- healthy: boolean;
56
- services: Array<{
57
- name: string;
58
- status: string;
59
- pid: number;
60
- }>;
61
- }
62
-
63
- export interface VerifyResult {
64
- packageName: string;
65
- version: string;
66
- success: boolean;
67
- distExists: boolean;
68
- checksum: string | null;
69
- message: string;
70
- }
71
-
72
- export interface AuditResult {
73
- packageName: string;
74
- severity: string;
75
- vulnerability: string;
76
- description: string;
77
- }
78
-
79
- export interface BundleSize {
80
- packageName: string;
81
- version: string;
82
- distSizeBytes: number;
83
- fileCount: number;
84
- }
85
-
86
- export interface InstalledPackageInfo {
87
- packageName: string;
88
- version: string;
89
- distSizeBytes: number | null;
90
- installedAt: string | null;
91
- /** Total number of installed versions */
92
- totalVersions?: number;
93
- /** All installed versions */
94
- versions?: VersionInfo[];
95
- }
96
-
97
- /**
98
- * Version information for a package.
99
- */
100
- export interface VersionInfo {
101
- version: string;
102
- installedAt: string;
103
- distSizeBytes: number | null;
104
- fileCount: number | null;
105
- active: boolean;
106
- }
107
-
108
- /**
109
- * Result of switching versions.
110
- */
111
- export interface SwitchResult {
112
- success: boolean;
113
- packageName: string;
114
- fromVersion: string;
115
- toVersion: string;
116
- message: string;
117
- }
118
-
119
- /**
120
- * Result of pruning old versions.
121
- */
122
- export interface PruneResult {
123
- success: boolean;
124
- packageName: string;
125
- removedVersions: string[];
126
- keptVersions: string[];
127
- freedBytes: number;
128
- message: string;
129
- }
130
-
131
- interface JsonRpcRequest {
132
- jsonrpc: "2.0";
133
- id: string;
134
- method: string;
135
- params?: Record<string, unknown>;
136
- }
137
-
138
- interface JsonRpcResponse {
139
- jsonrpc: "2.0";
140
- id: string;
141
- result?: unknown;
142
- error?: {
143
- code: number;
144
- message: string;
145
- data?: unknown;
146
- };
147
- }
148
-
149
- // ---------------------------------------------------------------------------
150
- // Constants
151
- // ---------------------------------------------------------------------------
152
-
153
- const REQUEST_TIMEOUT = 30000;
154
-
155
- // ---------------------------------------------------------------------------
156
- // Rust Bridge Class
157
- // ---------------------------------------------------------------------------
158
-
159
- /**
160
- * Bridge to the Rust worker process.
161
- *
162
- * Handles JSON-RPC communication over stdin/stdout.
163
- */
164
- export class RustBridge {
165
- private process: ChildProcess | null = null;
166
- private pendingRequests = new Map<string, {
167
- resolve: (value: unknown) => void;
168
- reject: (error: Error) => void;
169
- timeout: ReturnType<typeof setTimeout>;
170
- }>();
171
- private requestId = 0;
172
- private buffer = "";
173
-
174
- /**
175
- * Start the Rust worker process.
176
- */
177
- async start(): Promise<void> {
178
- if (this.process) {
179
- return;
180
- }
181
-
182
- const binaryPath = this.getBinaryPath();
183
-
184
- // Check if binary exists
185
- try {
186
- accessSync(binaryPath, fsConstants.X_OK);
187
- } catch {
188
- throw new Error(
189
- `Rust binary not found at ${binaryPath}. ` +
190
- `Run 'bun run build:rust' in the pkg-ops package.`
191
- );
192
- }
193
-
194
- this.process = spawn(binaryPath, [], {
195
- stdio: ["pipe", "pipe", "pipe"],
196
- });
197
-
198
- if (!this.process.stdin || !this.process.stdout) {
199
- throw new Error("Failed to create stdin/stdout pipes");
200
- }
201
-
202
- // Handle stdout (JSON-RPC responses)
203
- this.process.stdout.on("data", (data: Buffer) => {
204
- this.buffer += data.toString("utf-8");
205
- this.processBuffer();
206
- });
207
-
208
- // Handle stderr (logs)
209
- this.process.stderr?.on("data", (data: Buffer) => {
210
- console.error("[rust-worker]", data.toString("utf-8").trim());
211
- });
212
-
213
- // Handle process exit
214
- this.process.on("exit", (code: number | null) => {
215
- console.error(`Rust worker exited with code ${code}`);
216
- this.process = null;
217
- });
218
- }
219
-
220
- /**
221
- * Stop the Rust worker process.
222
- */
223
- async stop(): Promise<void> {
224
- if (!this.process) {
225
- return;
226
- }
227
-
228
- // Send shutdown request
229
- try {
230
- await this.sendRequest("shutdown", {});
231
- } catch {
232
- // Ignore errors on shutdown
233
- }
234
-
235
- // Give it time to shutdown gracefully
236
- await new Promise((resolve) => setTimeout(resolve, 1000));
237
-
238
- // Force kill if still running
239
- if (this.process) {
240
- this.process.kill("SIGTERM");
241
- this.process = null;
242
- }
243
-
244
- // Clear pending requests
245
- for (const [id, pending] of this.pendingRequests) {
246
- clearTimeout(pending.timeout);
247
- pending.reject(new Error("Bridge shutdown"));
248
- }
249
- this.pendingRequests.clear();
250
- }
251
-
252
- /**
253
- * Install a package.
254
- */
255
- async install(packageName: string, version: string): Promise<InstallResult> {
256
- return this.sendRequest("install", { packageName, version }) as Promise<InstallResult>;
257
- }
258
-
259
- /**
260
- * Update a package to latest version.
261
- */
262
- async update(packageName: string): Promise<InstallResult> {
263
- return this.sendRequest("update", { packageName }) as Promise<InstallResult>;
264
- }
265
-
266
- /**
267
- * Update all managed packages.
268
- */
269
- async updateAll(): Promise<InstallResult[]> {
270
- return this.sendRequest("updateAll", {}) as Promise<InstallResult[]>;
271
- }
272
-
273
- /**
274
- * List installed packages.
275
- */
276
- async list(): Promise<PackageInfo[]> {
277
- return this.sendRequest("list", {}) as Promise<PackageInfo[]>;
278
- }
279
-
280
- /**
281
- * Rollback a package to previous version.
282
- */
283
- async rollback(packageName: string): Promise<RollbackResult> {
284
- return this.sendRequest("rollback", { packageName }) as Promise<RollbackResult>;
285
- }
286
-
287
- /**
288
- * Check health of all services.
289
- */
290
- async health(): Promise<HealthCheckResult> {
291
- return this.sendRequest("health", {}) as Promise<HealthCheckResult>;
292
- }
293
-
294
- /**
295
- * Verify package integrity.
296
- */
297
- async verify(packageName: string): Promise<VerifyResult> {
298
- return this.sendRequest("verify", { packageName }) as Promise<VerifyResult>;
299
- }
300
-
301
- /**
302
- * Run vulnerability scan.
303
- */
304
- async audit(): Promise<AuditResult[]> {
305
- return this.sendRequest("audit", {}) as Promise<AuditResult[]>;
306
- }
307
-
308
- /**
309
- * Get bundle sizes.
310
- */
311
- async getBundleSizes(): Promise<BundleSize[]> {
312
- return this.sendRequest("sizes", {}) as Promise<BundleSize[]>;
313
- }
314
-
315
- /**
316
- * Get detailed installed package info.
317
- */
318
- async getInstalledInfo(): Promise<InstalledPackageInfo[]> {
319
- return this.sendRequest("installedInfo", {}) as Promise<InstalledPackageInfo[]>;
320
- }
321
-
322
- // ---------------------------------------------------------------------------
323
- // Multi-Version Methods
324
- // ---------------------------------------------------------------------------
325
-
326
- /**
327
- * List all installed versions of a package.
328
- */
329
- async listVersions(packageName: string): Promise<VersionInfo[]> {
330
- return this.sendRequest("listVersions", { packageName }) as Promise<VersionInfo[]>;
331
- }
332
-
333
- /**
334
- * Switch to a specific installed version.
335
- */
336
- async switchVersion(packageName: string, version: string): Promise<SwitchResult> {
337
- return this.sendRequest("switchVersion", { packageName, version }) as Promise<SwitchResult>;
338
- }
339
-
340
- /**
341
- * Remove old versions, keeping only the N most recent.
342
- */
343
- async pruneVersions(packageName: string, keepCount: number): Promise<PruneResult> {
344
- return this.sendRequest("pruneVersions", { packageName, keepCount }) as Promise<PruneResult>;
345
- }
346
-
347
- /**
348
- * Remove a specific version.
349
- */
350
- async removeVersion(packageName: string, version: string): Promise<{ success: boolean; message: string }> {
351
- return this.sendRequest("removeVersion", { packageName, version }) as Promise<{ success: boolean; message: string }>;
352
- }
353
-
354
- /**
355
- * Get packages with multiple versions installed.
356
- */
357
- async getMultiVersionPackages(): Promise<Array<{
358
- packageName: string;
359
- activeVersion: string;
360
- totalVersions: number;
361
- versions: string[];
362
- }>> {
363
- return this.sendRequest("getMultiVersionPackages", {}) as Promise<Array<{
364
- packageName: string;
365
- activeVersion: string;
366
- totalVersions: number;
367
- versions: string[];
368
- }>>;
369
- }
370
-
371
- // ---------------------------------------------------------------------------
372
- // Private Methods
373
- // ---------------------------------------------------------------------------
374
-
375
- private getBinaryPath(): string {
376
- // Get the directory of this module
377
- const currentDir = dirname(fileURLToPath(import.meta.url));
378
-
379
- // Paths to check (in order of preference)
380
- const paths = [
381
- // Cargo build output (works on VPS after `cargo build --release`)
382
- join(currentDir, "..", "rust", "target", "release", "pkg-ops-core"),
383
- // Pre-built binary in rust folder (for npm package distribution)
384
- join(currentDir, "..", "rust", "pkg-ops-core"),
385
- // Linux-specific cargo output
386
- join(currentDir, "..", "rust", "target", "x86_64-unknown-linux-gnu", "release", "pkg-ops-core"),
387
- ];
388
-
389
- for (const path of paths) {
390
- try {
391
- accessSync(path, fsConstants.X_OK);
392
- return path;
393
- } catch {
394
- // Try next path
395
- }
396
- }
397
-
398
- // Return the most likely path for error message
399
- throw new Error(
400
- `Rust binary not found. Tried:\n${paths.map(p => ` - ${p}`).join("\n")}\n` +
401
- `Run 'bun run build:rust' or 'cargo build --release' in the pkg-ops package.`
402
- );
403
- }
404
-
405
- private sendRequest<T>(method: string, params: Record<string, unknown>): Promise<T> {
406
- return new Promise((resolve, reject) => {
407
- if (!this.process?.stdin) {
408
- reject(new Error("Rust worker not started"));
409
- return;
410
- }
411
-
412
- const id = String(++this.requestId);
413
- const request: JsonRpcRequest = {
414
- jsonrpc: "2.0",
415
- id,
416
- method,
417
- params,
418
- };
419
-
420
- // Set up timeout
421
- const timeout = setTimeout(() => {
422
- this.pendingRequests.delete(id);
423
- reject(new Error(`Request timeout: ${method}`));
424
- }, REQUEST_TIMEOUT);
425
-
426
- // Store pending request
427
- this.pendingRequests.set(id, {
428
- resolve: resolve as (value: unknown) => void,
429
- reject,
430
- timeout,
431
- });
432
-
433
- // Send request
434
- const json = JSON.stringify(request) + "\n";
435
- this.process.stdin.write(json, "utf-8", (err: Error | null | undefined) => {
436
- if (err) {
437
- this.pendingRequests.delete(id);
438
- clearTimeout(timeout);
439
- reject(err);
440
- }
441
- });
442
- });
443
- }
444
-
445
- private processBuffer(): void {
446
- const lines = this.buffer.split("\n");
447
- this.buffer = lines.pop() ?? "";
448
-
449
- for (const line of lines) {
450
- if (!line.trim()) continue;
451
-
452
- try {
453
- const response: JsonRpcResponse = JSON.parse(line);
454
- const pending = this.pendingRequests.get(response.id);
455
-
456
- if (pending) {
457
- clearTimeout(pending.timeout);
458
- this.pendingRequests.delete(response.id);
459
-
460
- if (response.error) {
461
- pending.reject(new Error(response.error.message));
462
- } else {
463
- pending.resolve(response.result);
464
- }
465
- }
466
- } catch (err) {
467
- console.error("Failed to parse response:", line, err);
468
- }
469
- }
470
- }
471
- }
472
-
473
- // ---------------------------------------------------------------------------
474
- // Singleton Instance
475
- // ---------------------------------------------------------------------------
476
-
477
- let defaultBridge: RustBridge | null = null;
478
-
479
- /**
480
- * Get the default bridge instance.
481
- */
482
- export function getBridge(): RustBridge {
483
- if (!defaultBridge) {
484
- defaultBridge = new RustBridge();
485
- }
486
- return defaultBridge;
487
- }
488
-
489
- /**
490
- * Start the default bridge.
491
- */
492
- export async function startBridge(): Promise<RustBridge> {
493
- const bridge = getBridge();
494
- await bridge.start();
495
- return bridge;
496
- }
497
-
498
- /**
499
- * Stop the default bridge.
500
- */
501
- export async function stopBridge(): Promise<void> {
502
- if (defaultBridge) {
503
- await defaultBridge.stop();
504
- defaultBridge = null;
505
- }
506
- }