@hapico/cli 0.0.18 → 0.0.20

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/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /* eslint-disable @typescript-eslint/no-explicit-any */
3
- import { isEqual, find } from "lodash";
3
+ import { isEqual, find, map } from "lodash";
4
4
  import { program } from "commander";
5
5
  import axios from "axios";
6
6
  import * as fs from "fs";
@@ -12,10 +12,10 @@ import * as Babel from "@babel/standalone";
12
12
  import QRCode from "qrcode-terminal";
13
13
  import open from "open";
14
14
  import { randomUUID } from "crypto";
15
- import inquirer from "inquirer";
16
15
  import { exec } from "child_process";
17
16
  import { promisify } from "util";
18
17
  import chalk from "chalk";
18
+ import pako from "pako";
19
19
 
20
20
  // Promisify exec for async usage
21
21
  const execPromise = promisify(exec);
@@ -74,14 +74,25 @@ const saveProjectId = (projectDir: string, id: string) => {
74
74
  };
75
75
 
76
76
  // Function to get stored project ID
77
- const getStoredProjectId = (projectDir: string): string | null => {
77
+ const getStoredProjectId = (
78
+ projectDir: string
79
+ ): {
80
+ projectId: string | null;
81
+ replicate?: string[];
82
+ } => {
78
83
  const configFile = path.join(projectDir, "hapico.config.json");
79
84
  if (fs.existsSync(configFile)) {
80
85
  const data = fs.readFileSync(configFile, { encoding: "utf8" });
81
86
  const json = JSON.parse(data);
82
- return json.projectId || null;
87
+ return {
88
+ projectId: json.projectId || null,
89
+ replicate: json.replicate || [],
90
+ };
83
91
  }
84
- return null;
92
+ return {
93
+ projectId: null,
94
+ replicate: [],
95
+ };
85
96
  };
86
97
 
87
98
  // Khởi tạo cache bằng Map để lưu trữ kết quả compile
@@ -95,7 +106,7 @@ interface FileContent {
95
106
 
96
107
  interface ApiResponse {
97
108
  data: {
98
- files?: FileContent[];
109
+ code: string;
99
110
  type: "view" | "zalominiapp" | string;
100
111
  dbCode: string;
101
112
  db_code: string;
@@ -111,6 +122,14 @@ interface WebSocketMessage {
111
122
  state?: RoomStateData;
112
123
  }
113
124
 
125
+ export const tryJSONParse = (str: string): any => {
126
+ try {
127
+ return JSON.parse(str);
128
+ } catch {
129
+ return null;
130
+ }
131
+ };
132
+
114
133
  export const compileES5 = (code: string, filePath?: string) => {
115
134
  if (filePath && !filePath.endsWith(".ts") && !filePath.endsWith(".tsx")) {
116
135
  return code;
@@ -131,7 +150,8 @@ export const compileES5 = (code: string, filePath?: string) => {
131
150
 
132
151
  compileCache.set(code, result.code || "");
133
152
  return result.code;
134
- } catch (error) {
153
+ } catch (error: any) {
154
+ console.log(chalk.red(`Error compiling code: ${error?.message}`));
135
155
  return "";
136
156
  }
137
157
  };
@@ -303,18 +323,19 @@ class RoomState {
303
323
  clearTimeout(this.reconnectTimeout);
304
324
  }
305
325
 
306
- this.ws = new WebSocket(
307
- `https://ws2.myworkbeast.com/ws?room=${this.roomId}`
308
- );
326
+ this.ws = new WebSocket(`wss://ws3.myworkbeast.com/ws?room=${this.roomId}`);
309
327
 
310
- this.ws.onopen = () => {
328
+ // Set binaryType to 'arraybuffer' to handle binary data
329
+ this.ws.binaryType = "arraybuffer";
330
+
331
+ this.ws.on("open", () => {
311
332
  console.log(chalk.green(`Connected to room: ${this.roomId}`));
312
333
  this.isConnected = true;
313
334
  this.reconnectAttempts = 0;
314
335
  onConnected?.(); // Call the onConnected callback if provided
315
- };
336
+ });
316
337
 
317
- this.ws.onclose = () => {
338
+ this.ws.on("close", () => {
318
339
  console.log(chalk.yellow(`⚠ Disconnected from room: ${this.roomId}`));
319
340
  this.isConnected = false;
320
341
 
@@ -328,11 +349,20 @@ class RoomState {
328
349
  () => this.connect(onConnected),
329
350
  delay
330
351
  );
331
- };
352
+ });
332
353
 
333
- this.ws.on("message", (data: Buffer | string) => {
354
+ this.ws.on("message", (data: any) => {
334
355
  try {
335
- const jsonStr = typeof data === "string" ? data : data.toString();
356
+ let jsonStr: string;
357
+ if (data instanceof ArrayBuffer) {
358
+ jsonStr = pako.inflate(data, { to: "string" });
359
+ } else if (typeof data === "string") {
360
+ jsonStr = data;
361
+ } else if (Buffer.isBuffer(data)) {
362
+ jsonStr = pako.inflate(data, { to: "string" });
363
+ } else {
364
+ jsonStr = data.toString(); // Fallback nếu không nén
365
+ }
336
366
  const parsedData = JSON.parse(jsonStr);
337
367
  const includes = [
338
368
  "view",
@@ -389,12 +419,12 @@ class RoomState {
389
419
 
390
420
  updateState(key: string, value: any): void {
391
421
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
392
- this.ws.send(
393
- JSON.stringify({
394
- type: "update",
395
- state: { [key]: value },
396
- })
397
- );
422
+ const message = JSON.stringify({
423
+ type: "update",
424
+ state: { [key]: value },
425
+ });
426
+ const compressed = pako.deflate(message); // Nén dữ liệu
427
+ this.ws.send(compressed); // Gửi binary
398
428
  }
399
429
  }
400
430
 
@@ -403,7 +433,7 @@ class RoomState {
403
433
  clearTimeout(this.reconnectTimeout);
404
434
  }
405
435
  if (this.ws) {
406
- this.ws.onclose = null; // Prevent reconnect on intentional close
436
+ this.ws.removeAllListeners("close"); // Prevent reconnect on intentional close
407
437
  this.ws.close();
408
438
  }
409
439
  }
@@ -417,7 +447,7 @@ class RoomState {
417
447
  }
418
448
  }
419
449
 
420
- program.version("0.0.18").description("Hapico CLI for project management");
450
+ program.version("0.0.20").description("Hapico CLI for project management");
421
451
 
422
452
  program
423
453
  .command("clone <id>")
@@ -443,7 +473,12 @@ program
443
473
  const response: ApiResponse = await axios.get(
444
474
  `https://base.myworkbeast.com/api/views/${id}`
445
475
  );
446
- files = response.data.files || [];
476
+ const code = response?.data?.code;
477
+ const decompressedCode = pako.inflate(
478
+ Uint8Array.from(atob(code), (c) => c.charCodeAt(0)),
479
+ { to: "string" }
480
+ );
481
+ files = tryJSONParse(decompressedCode)?.files || [];
447
482
  apiSpinner.succeed(chalk.green("Project data fetched successfully!"));
448
483
 
449
484
  const templateSpinner: Ora = ora(
@@ -566,7 +601,7 @@ program
566
601
  const info = JSON.stringify({
567
602
  id: sessionId,
568
603
  createdAt: new Date().toISOString(),
569
- viewId: getStoredProjectId(pwd),
604
+ viewId: getStoredProjectId(pwd)?.projectId,
570
605
  });
571
606
 
572
607
  // Convert info to base64
@@ -584,12 +619,47 @@ program
584
619
  const room = new RoomState(`view_${projectId}`, []);
585
620
  const fileManager = new FileManager(srcDir);
586
621
  const initialFiles = fileManager.listFiles();
587
- room.files = initialFiles;
622
+ // get tsconfig.json
623
+ const tsconfigPath = path.join(srcDir, "..", "tsconfig.json");
624
+ if (fs.existsSync(tsconfigPath)) {
625
+ const content = fs.readFileSync(tsconfigPath, { encoding: "utf8" });
626
+ initialFiles.push({
627
+ path: "./tsconfig.json",
628
+ content,
629
+ });
630
+ }
631
+ // Remove All binary files
632
+ const supportExtensions = [
633
+ ".ts",
634
+ ".tsx",
635
+ ".js",
636
+ ".jsx",
637
+ ".json",
638
+ ".css",
639
+ ".html",
640
+ ".env",
641
+ ".env.local",
642
+ ".env.development",
643
+ ".env.production",
644
+ ".jsonc",
645
+ ".yml",
646
+ ".yaml",
647
+ ".md",
648
+ ".markdown",
649
+ ".txt",
650
+ ".xml",
651
+ ".config",
652
+ ];
653
+ const filteredFiles = initialFiles.filter((file) => {
654
+ return supportExtensions.some((ext) => file.path.endsWith(ext));
655
+ });
656
+
657
+ room.files = filteredFiles;
588
658
 
589
659
  room.connect(async () => {
590
660
  devSpinner.succeed(chalk.green("Project started in development mode!"));
591
661
 
592
- room.updateState("view", initialFiles);
662
+ room.updateState("view", filteredFiles);
593
663
 
594
664
  fileManager.setOnFileChange((filePath, content) => {
595
665
  const es5 = compileES5(content, filePath) ?? "";
@@ -607,8 +677,8 @@ program
607
677
  });
608
678
 
609
679
  // Fetch project info
610
- const projectInfo = getStoredProjectId(pwd);
611
- if (!projectInfo) {
680
+ const store = getStoredProjectId(pwd);
681
+ if (!store.projectId) {
612
682
  console.error(
613
683
  chalk.red(
614
684
  "✗ Project ID not found. Please ensure hapico.config.json exists in the project directory."
@@ -617,7 +687,7 @@ program
617
687
  return;
618
688
  }
619
689
  const project = await axios.get(
620
- `https://base.myworkbeast.com/api/views/${projectInfo}`,
690
+ `https://base.myworkbeast.com/api/views/${store.projectId}`,
621
691
  {
622
692
  headers: {
623
693
  Authorization: `Bearer ${accessToken}`,
@@ -637,7 +707,7 @@ program
637
707
 
638
708
  if (projectType === "zalominiapp") {
639
709
  QRCode.generate(
640
- `https://zalo.me/s/3218692650896662017/player/${projectId}`,
710
+ `https://zalo.me/s/3218692650896662017/player/${projectId}?env=TESTING&version=75`,
641
711
  { small: true },
642
712
  (qrcode) => {
643
713
  console.log(
@@ -659,23 +729,95 @@ program
659
729
  });
660
730
  });
661
731
 
732
+ // Hàm tái sử dụng để push mã nguồn lên server
733
+ async function pushProject(spinner: Ora, projectId: string): Promise<boolean> {
734
+ const data = getStoredToken();
735
+ const { accessToken } = data || {};
736
+ if (!accessToken) {
737
+ spinner.fail(
738
+ chalk.red("✗ You need to login first. Use 'hapico login' command.")
739
+ );
740
+ return false;
741
+ }
742
+ const pwd = process.cwd();
743
+ const srcDir: string = path.join(pwd, "src");
744
+ if (!fs.existsSync(srcDir)) {
745
+ spinner.fail(
746
+ chalk.red(
747
+ "✗ Source directory 'src' does not exist. Please clone a project first."
748
+ )
749
+ );
750
+ return false;
751
+ }
752
+ const fileManager = new FileManager(srcDir);
753
+ const files = fileManager.listFiles();
754
+
755
+ // Supported files
756
+ const SUPPORT_FILES = [
757
+ "./.env",
758
+ "./.env.local",
759
+ "./.env.development",
760
+ "./.env.production",
761
+ "./package.json",
762
+ "./tsconfig.json",
763
+ ];
764
+
765
+ // Include supported files
766
+ SUPPORT_FILES.forEach((relativePath) => {
767
+ const fullPath = path.join(pwd, relativePath);
768
+ if (fs.existsSync(fullPath)) {
769
+ console.log(
770
+ chalk.green(
771
+ `Including ${relativePath} in push for project ${projectId}.`
772
+ )
773
+ );
774
+ const content = fs.readFileSync(fullPath, { encoding: "utf8" });
775
+ files.push({
776
+ path: relativePath,
777
+ content,
778
+ es5: compileES5(content, fullPath) ?? "",
779
+ });
780
+ }
781
+ });
782
+ spinner.text = `Pushing project source code to server for project ${projectId}...`;
783
+
784
+ const apiUrl = `https://base.myworkbeast.com/api/views/${projectId}`;
785
+ try {
786
+ await axios.put(
787
+ apiUrl,
788
+ {
789
+ code: JSON.stringify({
790
+ files,
791
+ }),
792
+ },
793
+ {
794
+ headers: {
795
+ Authorization: `Bearer ${accessToken}`,
796
+ "Content-Type": "application/json",
797
+ },
798
+ }
799
+ );
800
+ return true;
801
+ } catch (error) {
802
+ spinner.fail(
803
+ chalk.red(
804
+ `✗ Error saving project ${projectId}: ${(error as Error).message}`
805
+ )
806
+ );
807
+ return false;
808
+ }
809
+ }
810
+
662
811
  program
663
812
  .command("push")
664
813
  .description("Push the project source code to the server")
665
- .action(() => {
666
- const data = getStoredToken();
667
- const { accessToken } = data || {};
668
- if (!accessToken) {
669
- console.error(
670
- chalk.red("✗ You need to login first. Use 'hapico login' command.")
671
- );
672
- return;
673
- }
814
+ .action(async () => {
674
815
  const saveSpinner: Ora = ora(
675
816
  chalk.blue("Saving project source code...")
676
817
  ).start();
677
818
  const pwd = process.cwd();
678
- const projectId = getStoredProjectId(pwd);
819
+ const { projectId, replicate } = getStoredProjectId(pwd);
820
+
679
821
  if (!projectId) {
680
822
  saveSpinner.fail(
681
823
  chalk.red(
@@ -684,53 +826,40 @@ program
684
826
  );
685
827
  return;
686
828
  }
687
- const srcDir: string = path.join(pwd, "src");
688
- if (!fs.existsSync(srcDir)) {
689
- saveSpinner.fail(
690
- chalk.red(
691
- "✗ Source directory 'src' does not exist. Please clone a project first."
692
- )
693
- );
694
- return;
829
+
830
+ // Push to the main project
831
+ const mainSuccess = await pushProject(saveSpinner, projectId);
832
+ let allSuccess = mainSuccess;
833
+
834
+ // Push to replicated projects if replicate array exists
835
+ if (replicate && Array.isArray(replicate) && replicate.length > 0) {
836
+ saveSpinner.text = chalk.blue("Pushing to replicated projects...");
837
+ for (const repId of replicate) {
838
+ const success = await pushProject(saveSpinner, repId);
839
+ if (!success) {
840
+ allSuccess = false;
841
+ console.warn(
842
+ chalk.yellow(
843
+ `⚠ Failed to push to replicated project ${repId}. Continuing...`
844
+ )
845
+ );
846
+ } else {
847
+ console.log(
848
+ chalk.green(`✓ Successfully pushed to replicated project ${repId}.`)
849
+ );
850
+ }
851
+ }
695
852
  }
696
- const fileManager = new FileManager(srcDir);
697
- const files = fileManager.listFiles();
698
-
699
- // Nếu file .env
700
- const envFile = path.join(pwd, ".env");
701
- console.log(chalk.cyan(`🔍 Checking for .env file at ${envFile}`));
702
- if (fs.existsSync(envFile)) {
703
- console.log(chalk.green(".env file found, including in push."));
704
- const content = fs.readFileSync(envFile, { encoding: "utf8" });
705
- files.push({
706
- path: "./.env",
707
- content,
708
- });
853
+
854
+ if (allSuccess) {
855
+ saveSpinner.succeed(
856
+ chalk.green("Project source code saved successfully to all projects!")
857
+ );
858
+ } else {
859
+ saveSpinner.warn(
860
+ chalk.yellow("Project source code saved with some errors.")
861
+ );
709
862
  }
710
- const apiUrl = `https://base.myworkbeast.com/api/views/${projectId}`;
711
- axios
712
- .put(
713
- apiUrl,
714
- {
715
- code: JSON.stringify({
716
- files,
717
- }),
718
- },
719
- {
720
- headers: {
721
- Authorization: `Bearer ${accessToken}`,
722
- "Content-Type": "application/json",
723
- },
724
- }
725
- )
726
- .then(() => {
727
- saveSpinner.succeed(
728
- chalk.green("Project source code saved successfully!")
729
- );
730
- })
731
- .catch((error) => {
732
- saveSpinner.fail(chalk.red(`✗ Error saving project: ${error.message}`));
733
- });
734
863
  });
735
864
 
736
865
  program
@@ -821,7 +950,7 @@ program
821
950
  return;
822
951
  }
823
952
  const pwd: string = process.cwd();
824
- const projectId = getStoredProjectId(pwd);
953
+ const { projectId } = getStoredProjectId(pwd);
825
954
  if (!projectId) {
826
955
  console.error(
827
956
  chalk.red(
@@ -835,15 +964,22 @@ program
835
964
  ).start();
836
965
  try {
837
966
  const response: ApiResponse = await axios.get(
838
- `https://base.myworkbeast.com/api/views/${projectId}`,
967
+ `https://base.myworkbeast.com/api/views/v3/${projectId}`,
839
968
  {
840
969
  headers: {
841
- Authorization: `Bearer ${token}`,
970
+ Authorization: `Bearer ${token.accessToken}`,
842
971
  "Content-Type": "application/json",
843
972
  },
844
973
  }
845
974
  );
846
- const files: FileContent[] = response.data.files || [];
975
+
976
+ const code = response?.data?.code;
977
+ const decompressedCode = pako.inflate(
978
+ Uint8Array.from(atob(code), (c) => c.charCodeAt(0)),
979
+ { to: "string" }
980
+ );
981
+
982
+ const files: FileContent[] = tryJSONParse(decompressedCode)?.files || [];
847
983
  const fileManager = new FileManager(path.join(pwd, "src"));
848
984
  fileManager.syncFiles(files);
849
985
  apiSpinner.succeed(chalk.green("Project files updated successfully!"));
@@ -982,7 +1118,9 @@ program
982
1118
  migrationsDir,
983
1119
  `${index + 1}_migration_${timestamp}.sql`
984
1120
  );
985
- fs.writeFileSync(filename, (migration as any).sql, { encoding: "utf8" });
1121
+ fs.writeFileSync(filename, (migration as any).sql, {
1122
+ encoding: "utf8",
1123
+ });
986
1124
  });
987
1125
 
988
1126
  console.log(chalk.green("✓ Migrations saved successfully!"));
@@ -994,7 +1132,6 @@ program
994
1132
  ).start();
995
1133
 
996
1134
  try {
997
- files = response.data.files || [];
998
1135
  apiSpinner.succeed(chalk.green("Project data fetched successfully!"));
999
1136
 
1000
1137
  const saveSpinner: Ora = ora(
@@ -1213,4 +1350,142 @@ program
1213
1350
  }
1214
1351
  );
1215
1352
 
1353
+ // hapico publish
1354
+ program.command("publish").action(async () => {
1355
+ const publishSpinner: Ora = ora(
1356
+ chalk.blue("Publishing to Hapico...")
1357
+ ).start();
1358
+ const pwd = process.cwd();
1359
+ const { projectId, replicate } = getStoredProjectId(pwd);
1360
+
1361
+ if (!projectId) {
1362
+ publishSpinner.fail(
1363
+ chalk.red(
1364
+ "✗ Project ID not found. Please ensure hapico.config.json exists in the project directory."
1365
+ )
1366
+ );
1367
+ return;
1368
+ }
1369
+
1370
+ // Step 1: Push source code to main project and replicas
1371
+ const pushSuccess = await pushProject(publishSpinner, projectId);
1372
+ if (!pushSuccess) {
1373
+ return; // Stop if push to main project fails
1374
+ }
1375
+
1376
+ // Push to replicated projects
1377
+ let allPushSuccess = true;
1378
+ if (replicate && Array.isArray(replicate) && replicate.length > 0) {
1379
+ publishSpinner.text = chalk.blue("Pushing to replicated projects...");
1380
+ for (const repId of replicate) {
1381
+ const success = await pushProject(publishSpinner, repId);
1382
+ if (!success) {
1383
+ allPushSuccess = false;
1384
+ console.warn(
1385
+ chalk.yellow(
1386
+ `⚠ Failed to push to replicated project ${repId}. Continuing...`
1387
+ )
1388
+ );
1389
+ } else {
1390
+ console.log(
1391
+ chalk.green(`✓ Successfully pushed to replicated project ${repId}.`)
1392
+ );
1393
+ }
1394
+ }
1395
+ }
1396
+
1397
+ // Step 2: Publish main project
1398
+ const { accessToken } = getStoredToken();
1399
+ const apiUrl = "https://base.myworkbeast.com/api/views/publish";
1400
+ let allPublishSuccess = true;
1401
+ try {
1402
+ await axios.post(
1403
+ apiUrl,
1404
+ { view_id: parseInt(projectId, 10) },
1405
+ {
1406
+ headers: {
1407
+ Authorization: `Bearer ${accessToken}`,
1408
+ "Content-Type": "application/json",
1409
+ },
1410
+ }
1411
+ );
1412
+ publishSpinner.succeed(
1413
+ chalk.green(`Project ${projectId} published successfully!`)
1414
+ );
1415
+ } catch (error) {
1416
+ allPublishSuccess = false;
1417
+ publishSpinner.fail(
1418
+ chalk.red(
1419
+ `✗ Error publishing project ${projectId}: ${(error as Error).message}`
1420
+ )
1421
+ );
1422
+ }
1423
+
1424
+ // Step 3: Publish replicated projects
1425
+ if (replicate && Array.isArray(replicate) && replicate.length > 0) {
1426
+ publishSpinner.text = chalk.blue("Publishing replicated projects...");
1427
+ for (const repId of replicate) {
1428
+ try {
1429
+ await axios.post(
1430
+ apiUrl,
1431
+ { view_id: parseInt(repId, 10) },
1432
+ {
1433
+ headers: {
1434
+ Authorization: `Bearer ${accessToken}`,
1435
+ "Content-Type": "application/json",
1436
+ },
1437
+ }
1438
+ );
1439
+ console.log(
1440
+ chalk.green(`✓ Successfully published replicated project ${repId}.`)
1441
+ );
1442
+ } catch (error) {
1443
+ allPublishSuccess = false;
1444
+ console.warn(
1445
+ chalk.yellow(
1446
+ `⚠ Error publishing replicated project ${repId}: ${(error as Error).message}`
1447
+ )
1448
+ );
1449
+ }
1450
+ }
1451
+ }
1452
+
1453
+ if (allPushSuccess && allPublishSuccess) {
1454
+ publishSpinner.succeed(chalk.green("All projects published successfully!"));
1455
+ } else {
1456
+ publishSpinner.warn(chalk.yellow("Publishing completed with some errors."));
1457
+ }
1458
+ });
1459
+
1460
+ program.command("mirror").action(() => {
1461
+ console.log(chalk.cyan("🌐 Starting mirror mode..."));
1462
+ const pwd = process.cwd();
1463
+ const srcDir = path.join(pwd, "src");
1464
+ if (!fs.existsSync(srcDir)) {
1465
+ console.error(
1466
+ chalk.red(
1467
+ "✗ Source directory 'src' does not exist. Please clone a project first."
1468
+ )
1469
+ );
1470
+ return;
1471
+ }
1472
+
1473
+ const fileManager = new FileManager(srcDir);
1474
+ const initialFiles = fileManager.listFiles();
1475
+
1476
+ // Lấy danh sách file và viết ra 1 file .txt
1477
+ let content = ``;
1478
+ map(initialFiles, (file) => {
1479
+ content += `\`\`\`typescript
1480
+ // Path: ${file.path}
1481
+ ${file.content}
1482
+
1483
+ \`\`\`
1484
+ `;
1485
+ });
1486
+ const outputFile = path.join(pwd, "hapico_files.txt");
1487
+ fs.writeFileSync(outputFile, content, { encoding: "utf8" });
1488
+ console.log(chalk.green(`✓ File list saved to ${outputFile}`));
1489
+ });
1490
+
1216
1491
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hapico/cli",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "description": "A simple CLI tool for project management",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -27,6 +27,7 @@
27
27
  "lodash": "^4.17.21",
28
28
  "open": "^10.2.0",
29
29
  "ora": "^8.2.0",
30
+ "pako": "^2.1.0",
30
31
  "prettier": "^3.6.2",
31
32
  "qrcode-terminal": "^0.12.0",
32
33
  "unzipper": "^0.12.3",
@@ -38,6 +39,7 @@
38
39
  "@types/commander": "^2.12.5",
39
40
  "@types/lodash": "^4.17.20",
40
41
  "@types/node": "^24.1.0",
42
+ "@types/pako": "^2.0.4",
41
43
  "@types/qrcode-terminal": "^0.12.2",
42
44
  "@types/unzipper": "^0.10.11",
43
45
  "@types/ws": "^8.18.1",
package/test.tsx ADDED
File without changes