@hapico/cli 0.0.18 → 0.0.19

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/bin/index.js CHANGED
@@ -54,6 +54,7 @@ const crypto_1 = require("crypto");
54
54
  const child_process_1 = require("child_process");
55
55
  const util_1 = require("util");
56
56
  const chalk_1 = __importDefault(require("chalk"));
57
+ const pako_1 = __importDefault(require("pako"));
57
58
  // Promisify exec for async usage
58
59
  const execPromise = (0, util_1.promisify)(child_process_1.exec);
59
60
  // Directory to store the token and project config
@@ -249,24 +250,38 @@ class RoomState {
249
250
  if (this.reconnectTimeout) {
250
251
  clearTimeout(this.reconnectTimeout);
251
252
  }
252
- this.ws = new ws_1.WebSocket(`https://ws2.myworkbeast.com/ws?room=${this.roomId}`);
253
- this.ws.onopen = () => {
253
+ this.ws = new ws_1.WebSocket(`wss://ws3.myworkbeast.com/ws?room=${this.roomId}`);
254
+ // Set binaryType to 'arraybuffer' to handle binary data
255
+ this.ws.binaryType = 'arraybuffer';
256
+ this.ws.on("open", () => {
254
257
  console.log(chalk_1.default.green(`Connected to room: ${this.roomId}`));
255
258
  this.isConnected = true;
256
259
  this.reconnectAttempts = 0;
257
260
  onConnected === null || onConnected === void 0 ? void 0 : onConnected(); // Call the onConnected callback if provided
258
- };
259
- this.ws.onclose = () => {
261
+ });
262
+ this.ws.on("close", () => {
260
263
  console.log(chalk_1.default.yellow(`⚠ Disconnected from room: ${this.roomId}`));
261
264
  this.isConnected = false;
262
265
  this.reconnectAttempts++;
263
266
  const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
264
267
  console.log(chalk_1.default.yellow(`Attempting to reconnect in ${delay / 1000}s...`));
265
268
  this.reconnectTimeout = setTimeout(() => this.connect(onConnected), delay);
266
- };
269
+ });
267
270
  this.ws.on("message", (data) => {
268
271
  try {
269
- const jsonStr = typeof data === "string" ? data : data.toString();
272
+ let jsonStr;
273
+ if (data instanceof ArrayBuffer) {
274
+ jsonStr = pako_1.default.inflate(data, { to: 'string' });
275
+ }
276
+ else if (typeof data === "string") {
277
+ jsonStr = data;
278
+ }
279
+ else if (Buffer.isBuffer(data)) {
280
+ jsonStr = pako_1.default.inflate(data, { to: 'string' });
281
+ }
282
+ else {
283
+ jsonStr = data.toString(); // Fallback nếu không nén
284
+ }
270
285
  const parsedData = JSON.parse(jsonStr);
271
286
  const includes = [
272
287
  "view",
@@ -316,10 +331,12 @@ class RoomState {
316
331
  }
317
332
  updateState(key, value) {
318
333
  if (this.ws && this.ws.readyState === ws_1.WebSocket.OPEN) {
319
- this.ws.send(JSON.stringify({
334
+ const message = JSON.stringify({
320
335
  type: "update",
321
336
  state: { [key]: value },
322
- }));
337
+ });
338
+ const compressed = pako_1.default.deflate(message); // Nén dữ liệu
339
+ this.ws.send(compressed); // Gửi binary
323
340
  }
324
341
  }
325
342
  disconnect() {
@@ -327,7 +344,7 @@ class RoomState {
327
344
  clearTimeout(this.reconnectTimeout);
328
345
  }
329
346
  if (this.ws) {
330
- this.ws.onclose = null; // Prevent reconnect on intentional close
347
+ this.ws.removeAllListeners("close"); // Prevent reconnect on intentional close
331
348
  this.ws.close();
332
349
  }
333
350
  }
@@ -338,7 +355,7 @@ class RoomState {
338
355
  return this.isConnected;
339
356
  }
340
357
  }
341
- commander_1.program.version("0.0.18").description("Hapico CLI for project management");
358
+ commander_1.program.version("0.0.19").description("Hapico CLI for project management");
342
359
  commander_1.program
343
360
  .command("clone <id>")
344
361
  .description("Clone a project by ID")
@@ -487,7 +504,7 @@ commander_1.program
487
504
  }
488
505
  const projectType = project.data.type || "view";
489
506
  if (projectType === "zalominiapp") {
490
- qrcode_terminal_1.default.generate(`https://zalo.me/s/3218692650896662017/player/${projectId}`, { small: true }, (qrcode) => {
507
+ qrcode_terminal_1.default.generate(`https://zalo.me/s/3218692650896662017/player/${projectId}?env=TESTING&version=75`, { small: true }, (qrcode) => {
491
508
  console.log(chalk_1.default.cyan("📱 Scan this QR code to connect to the project:"));
492
509
  console.log(qrcode);
493
510
  });
@@ -500,27 +517,24 @@ commander_1.program
500
517
  }
501
518
  });
502
519
  });
503
- commander_1.program
504
- .command("push")
505
- .description("Push the project source code to the server")
506
- .action(() => {
520
+ // Hàm tái sử dụng để push mã nguồn lên server
521
+ async function pushProject(spinner) {
507
522
  const data = getStoredToken();
508
523
  const { accessToken } = data || {};
509
524
  if (!accessToken) {
510
- console.error(chalk_1.default.red("✗ You need to login first. Use 'hapico login' command."));
511
- return;
525
+ spinner.fail(chalk_1.default.red("✗ You need to login first. Use 'hapico login' command."));
526
+ return false;
512
527
  }
513
- const saveSpinner = (0, ora_1.default)(chalk_1.default.blue("Saving project source code...")).start();
514
528
  const pwd = process.cwd();
515
529
  const projectId = getStoredProjectId(pwd);
516
530
  if (!projectId) {
517
- saveSpinner.fail(chalk_1.default.red("✗ Project ID not found. Please ensure hapico.config.json exists in the project directory."));
518
- return;
531
+ spinner.fail(chalk_1.default.red("✗ Project ID not found. Please ensure hapico.config.json exists in the project directory."));
532
+ return false;
519
533
  }
520
534
  const srcDir = path.join(pwd, "src");
521
535
  if (!fs.existsSync(srcDir)) {
522
- saveSpinner.fail(chalk_1.default.red("✗ Source directory 'src' does not exist. Please clone a project first."));
523
- return;
536
+ spinner.fail(chalk_1.default.red("✗ Source directory 'src' does not exist. Please clone a project first."));
537
+ return false;
524
538
  }
525
539
  const fileManager = new FileManager(srcDir);
526
540
  const files = fileManager.listFiles();
@@ -536,23 +550,33 @@ commander_1.program
536
550
  });
537
551
  }
538
552
  const apiUrl = `https://base.myworkbeast.com/api/views/${projectId}`;
539
- axios_1.default
540
- .put(apiUrl, {
541
- code: JSON.stringify({
542
- files,
543
- }),
544
- }, {
545
- headers: {
546
- Authorization: `Bearer ${accessToken}`,
547
- "Content-Type": "application/json",
548
- },
549
- })
550
- .then(() => {
553
+ try {
554
+ await axios_1.default.put(apiUrl, {
555
+ code: JSON.stringify({
556
+ files,
557
+ }),
558
+ }, {
559
+ headers: {
560
+ Authorization: `Bearer ${accessToken}`,
561
+ "Content-Type": "application/json",
562
+ },
563
+ });
564
+ return true;
565
+ }
566
+ catch (error) {
567
+ spinner.fail(chalk_1.default.red(`✗ Error saving project: ${error.message}`));
568
+ return false;
569
+ }
570
+ }
571
+ commander_1.program
572
+ .command("push")
573
+ .description("Push the project source code to the server")
574
+ .action(async () => {
575
+ const saveSpinner = (0, ora_1.default)(chalk_1.default.blue("Saving project source code...")).start();
576
+ const success = await pushProject(saveSpinner);
577
+ if (success) {
551
578
  saveSpinner.succeed(chalk_1.default.green("Project source code saved successfully!"));
552
- })
553
- .catch((error) => {
554
- saveSpinner.fail(chalk_1.default.red(`✗ Error saving project: ${error.message}`));
555
- });
579
+ }
556
580
  });
557
581
  commander_1.program
558
582
  .command("login")
@@ -893,4 +917,36 @@ commander_1.program
893
917
  });
894
918
  }
895
919
  });
920
+ // hapico publish
921
+ commander_1.program.command("publish").action(async () => {
922
+ const publishSpinner = (0, ora_1.default)(chalk_1.default.blue("Publishing to Hapico...")).start();
923
+ // Bước 1: Push mã nguồn lên server (tái sử dụng hàm pushProject)
924
+ const pushSuccess = await pushProject(publishSpinner);
925
+ if (!pushSuccess) {
926
+ return; // Dừng nếu push thất bại
927
+ }
928
+ // Bước 2: Gọi API để publish
929
+ const { accessToken } = getStoredToken();
930
+ const pwd = process.cwd();
931
+ const projectId = getStoredProjectId(pwd);
932
+ if (!projectId) {
933
+ publishSpinner.fail(chalk_1.default.red("✗ Project ID not found. Please ensure hapico.config.json exists in the project directory."));
934
+ return;
935
+ }
936
+ const apiUrl = "https://base.myworkbeast.com/api/views/publish";
937
+ try {
938
+ await axios_1.default.post(apiUrl, { view_id: parseInt(projectId, 10) }, // Đảm bảo viewId là number
939
+ {
940
+ headers: {
941
+ Authorization: `Bearer ${accessToken}`,
942
+ "Content-Type": "application/json",
943
+ },
944
+ });
945
+ publishSpinner.succeed(chalk_1.default.green("Project published successfully!"));
946
+ }
947
+ catch (error) {
948
+ console.log(error);
949
+ publishSpinner.fail(chalk_1.default.red(`✗ Error publishing project: ${error.message}`));
950
+ }
951
+ });
896
952
  commander_1.program.parse(process.argv);
package/bun.lock CHANGED
@@ -11,6 +11,7 @@
11
11
  "lodash": "^4.17.21",
12
12
  "open": "^10.2.0",
13
13
  "ora": "^8.2.0",
14
+ "pako": "^2.1.0",
14
15
  "prettier": "^3.6.2",
15
16
  "qrcode-terminal": "^0.12.0",
16
17
  "unzipper": "^0.12.3",
@@ -22,6 +23,7 @@
22
23
  "@types/commander": "^2.12.5",
23
24
  "@types/lodash": "^4.17.20",
24
25
  "@types/node": "^24.1.0",
26
+ "@types/pako": "^2.0.4",
25
27
  "@types/qrcode-terminal": "^0.12.2",
26
28
  "@types/unzipper": "^0.10.11",
27
29
  "@types/ws": "^8.18.1",
@@ -88,6 +90,8 @@
88
90
 
89
91
  "@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="],
90
92
 
93
+ "@types/pako": ["@types/pako@2.0.4", "", {}, "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw=="],
94
+
91
95
  "@types/qrcode-terminal": ["@types/qrcode-terminal@0.12.2", "", {}, "sha512-v+RcIEJ+Uhd6ygSQ0u5YYY7ZM+la7GgPbs0V/7l/kFs2uO4S8BcIUEMoP7za4DNIqNnUD5npf0A/7kBhrCKG5Q=="],
92
96
 
93
97
  "@types/unzipper": ["@types/unzipper@0.10.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-D25im2zjyMCcgL9ag6N46+wbtJBnXIr7SI4zHf9eJD2Dw2tEB5e+p5MYkrxKIVRscs5QV0EhtU9rgXSPx90oJg=="],
@@ -218,6 +222,8 @@
218
222
 
219
223
  "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="],
220
224
 
225
+ "pako": ["pako@2.1.0", "", {}, "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="],
226
+
221
227
  "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
222
228
 
223
229
  "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
package/dist/index.js CHANGED
@@ -54,6 +54,7 @@ const crypto_1 = require("crypto");
54
54
  const child_process_1 = require("child_process");
55
55
  const util_1 = require("util");
56
56
  const chalk_1 = __importDefault(require("chalk"));
57
+ const pako_1 = __importDefault(require("pako"));
57
58
  // Promisify exec for async usage
58
59
  const execPromise = (0, util_1.promisify)(child_process_1.exec);
59
60
  // Directory to store the token and project config
@@ -249,24 +250,38 @@ class RoomState {
249
250
  if (this.reconnectTimeout) {
250
251
  clearTimeout(this.reconnectTimeout);
251
252
  }
252
- this.ws = new ws_1.WebSocket(`https://ws2.myworkbeast.com/ws?room=${this.roomId}`);
253
- this.ws.onopen = () => {
253
+ this.ws = new ws_1.WebSocket(`wss://ws3.myworkbeast.com/ws?room=${this.roomId}`);
254
+ // Set binaryType to 'arraybuffer' to handle binary data
255
+ this.ws.binaryType = 'arraybuffer';
256
+ this.ws.on("open", () => {
254
257
  console.log(chalk_1.default.green(`Connected to room: ${this.roomId}`));
255
258
  this.isConnected = true;
256
259
  this.reconnectAttempts = 0;
257
260
  onConnected === null || onConnected === void 0 ? void 0 : onConnected(); // Call the onConnected callback if provided
258
- };
259
- this.ws.onclose = () => {
261
+ });
262
+ this.ws.on("close", () => {
260
263
  console.log(chalk_1.default.yellow(`⚠ Disconnected from room: ${this.roomId}`));
261
264
  this.isConnected = false;
262
265
  this.reconnectAttempts++;
263
266
  const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
264
267
  console.log(chalk_1.default.yellow(`Attempting to reconnect in ${delay / 1000}s...`));
265
268
  this.reconnectTimeout = setTimeout(() => this.connect(onConnected), delay);
266
- };
269
+ });
267
270
  this.ws.on("message", (data) => {
268
271
  try {
269
- const jsonStr = typeof data === "string" ? data : data.toString();
272
+ let jsonStr;
273
+ if (data instanceof ArrayBuffer) {
274
+ jsonStr = pako_1.default.inflate(data, { to: 'string' });
275
+ }
276
+ else if (typeof data === "string") {
277
+ jsonStr = data;
278
+ }
279
+ else if (Buffer.isBuffer(data)) {
280
+ jsonStr = pako_1.default.inflate(data, { to: 'string' });
281
+ }
282
+ else {
283
+ jsonStr = data.toString(); // Fallback nếu không nén
284
+ }
270
285
  const parsedData = JSON.parse(jsonStr);
271
286
  const includes = [
272
287
  "view",
@@ -316,10 +331,12 @@ class RoomState {
316
331
  }
317
332
  updateState(key, value) {
318
333
  if (this.ws && this.ws.readyState === ws_1.WebSocket.OPEN) {
319
- this.ws.send(JSON.stringify({
334
+ const message = JSON.stringify({
320
335
  type: "update",
321
336
  state: { [key]: value },
322
- }));
337
+ });
338
+ const compressed = pako_1.default.deflate(message); // Nén dữ liệu
339
+ this.ws.send(compressed); // Gửi binary
323
340
  }
324
341
  }
325
342
  disconnect() {
@@ -327,7 +344,7 @@ class RoomState {
327
344
  clearTimeout(this.reconnectTimeout);
328
345
  }
329
346
  if (this.ws) {
330
- this.ws.onclose = null; // Prevent reconnect on intentional close
347
+ this.ws.removeAllListeners("close"); // Prevent reconnect on intentional close
331
348
  this.ws.close();
332
349
  }
333
350
  }
@@ -338,7 +355,7 @@ class RoomState {
338
355
  return this.isConnected;
339
356
  }
340
357
  }
341
- commander_1.program.version("0.0.18").description("Hapico CLI for project management");
358
+ commander_1.program.version("0.0.19").description("Hapico CLI for project management");
342
359
  commander_1.program
343
360
  .command("clone <id>")
344
361
  .description("Clone a project by ID")
@@ -487,7 +504,7 @@ commander_1.program
487
504
  }
488
505
  const projectType = project.data.type || "view";
489
506
  if (projectType === "zalominiapp") {
490
- qrcode_terminal_1.default.generate(`https://zalo.me/s/3218692650896662017/player/${projectId}`, { small: true }, (qrcode) => {
507
+ qrcode_terminal_1.default.generate(`https://zalo.me/s/3218692650896662017/player/${projectId}?env=TESTING&version=75`, { small: true }, (qrcode) => {
491
508
  console.log(chalk_1.default.cyan("📱 Scan this QR code to connect to the project:"));
492
509
  console.log(qrcode);
493
510
  });
@@ -500,27 +517,24 @@ commander_1.program
500
517
  }
501
518
  });
502
519
  });
503
- commander_1.program
504
- .command("push")
505
- .description("Push the project source code to the server")
506
- .action(() => {
520
+ // Hàm tái sử dụng để push mã nguồn lên server
521
+ async function pushProject(spinner) {
507
522
  const data = getStoredToken();
508
523
  const { accessToken } = data || {};
509
524
  if (!accessToken) {
510
- console.error(chalk_1.default.red("✗ You need to login first. Use 'hapico login' command."));
511
- return;
525
+ spinner.fail(chalk_1.default.red("✗ You need to login first. Use 'hapico login' command."));
526
+ return false;
512
527
  }
513
- const saveSpinner = (0, ora_1.default)(chalk_1.default.blue("Saving project source code...")).start();
514
528
  const pwd = process.cwd();
515
529
  const projectId = getStoredProjectId(pwd);
516
530
  if (!projectId) {
517
- saveSpinner.fail(chalk_1.default.red("✗ Project ID not found. Please ensure hapico.config.json exists in the project directory."));
518
- return;
531
+ spinner.fail(chalk_1.default.red("✗ Project ID not found. Please ensure hapico.config.json exists in the project directory."));
532
+ return false;
519
533
  }
520
534
  const srcDir = path.join(pwd, "src");
521
535
  if (!fs.existsSync(srcDir)) {
522
- saveSpinner.fail(chalk_1.default.red("✗ Source directory 'src' does not exist. Please clone a project first."));
523
- return;
536
+ spinner.fail(chalk_1.default.red("✗ Source directory 'src' does not exist. Please clone a project first."));
537
+ return false;
524
538
  }
525
539
  const fileManager = new FileManager(srcDir);
526
540
  const files = fileManager.listFiles();
@@ -536,23 +550,33 @@ commander_1.program
536
550
  });
537
551
  }
538
552
  const apiUrl = `https://base.myworkbeast.com/api/views/${projectId}`;
539
- axios_1.default
540
- .put(apiUrl, {
541
- code: JSON.stringify({
542
- files,
543
- }),
544
- }, {
545
- headers: {
546
- Authorization: `Bearer ${accessToken}`,
547
- "Content-Type": "application/json",
548
- },
549
- })
550
- .then(() => {
553
+ try {
554
+ await axios_1.default.put(apiUrl, {
555
+ code: JSON.stringify({
556
+ files,
557
+ }),
558
+ }, {
559
+ headers: {
560
+ Authorization: `Bearer ${accessToken}`,
561
+ "Content-Type": "application/json",
562
+ },
563
+ });
564
+ return true;
565
+ }
566
+ catch (error) {
567
+ spinner.fail(chalk_1.default.red(`✗ Error saving project: ${error.message}`));
568
+ return false;
569
+ }
570
+ }
571
+ commander_1.program
572
+ .command("push")
573
+ .description("Push the project source code to the server")
574
+ .action(async () => {
575
+ const saveSpinner = (0, ora_1.default)(chalk_1.default.blue("Saving project source code...")).start();
576
+ const success = await pushProject(saveSpinner);
577
+ if (success) {
551
578
  saveSpinner.succeed(chalk_1.default.green("Project source code saved successfully!"));
552
- })
553
- .catch((error) => {
554
- saveSpinner.fail(chalk_1.default.red(`✗ Error saving project: ${error.message}`));
555
- });
579
+ }
556
580
  });
557
581
  commander_1.program
558
582
  .command("login")
@@ -893,4 +917,36 @@ commander_1.program
893
917
  });
894
918
  }
895
919
  });
920
+ // hapico publish
921
+ commander_1.program.command("publish").action(async () => {
922
+ const publishSpinner = (0, ora_1.default)(chalk_1.default.blue("Publishing to Hapico...")).start();
923
+ // Bước 1: Push mã nguồn lên server (tái sử dụng hàm pushProject)
924
+ const pushSuccess = await pushProject(publishSpinner);
925
+ if (!pushSuccess) {
926
+ return; // Dừng nếu push thất bại
927
+ }
928
+ // Bước 2: Gọi API để publish
929
+ const { accessToken } = getStoredToken();
930
+ const pwd = process.cwd();
931
+ const projectId = getStoredProjectId(pwd);
932
+ if (!projectId) {
933
+ publishSpinner.fail(chalk_1.default.red("✗ Project ID not found. Please ensure hapico.config.json exists in the project directory."));
934
+ return;
935
+ }
936
+ const apiUrl = "https://base.myworkbeast.com/api/views/publish";
937
+ try {
938
+ await axios_1.default.post(apiUrl, { view_id: parseInt(projectId, 10) }, // Đảm bảo viewId là number
939
+ {
940
+ headers: {
941
+ Authorization: `Bearer ${accessToken}`,
942
+ "Content-Type": "application/json",
943
+ },
944
+ });
945
+ publishSpinner.succeed(chalk_1.default.green("Project published successfully!"));
946
+ }
947
+ catch (error) {
948
+ console.log(error);
949
+ publishSpinner.fail(chalk_1.default.red(`✗ Error publishing project: ${error.message}`));
950
+ }
951
+ });
896
952
  commander_1.program.parse(process.argv);
package/index.ts CHANGED
@@ -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);
@@ -304,17 +304,20 @@ class RoomState {
304
304
  }
305
305
 
306
306
  this.ws = new WebSocket(
307
- `https://ws2.myworkbeast.com/ws?room=${this.roomId}`
307
+ `wss://ws3.myworkbeast.com/ws?room=${this.roomId}`
308
308
  );
309
309
 
310
- this.ws.onopen = () => {
310
+ // Set binaryType to 'arraybuffer' to handle binary data
311
+ this.ws.binaryType = 'arraybuffer';
312
+
313
+ this.ws.on("open", () => {
311
314
  console.log(chalk.green(`Connected to room: ${this.roomId}`));
312
315
  this.isConnected = true;
313
316
  this.reconnectAttempts = 0;
314
317
  onConnected?.(); // Call the onConnected callback if provided
315
- };
318
+ });
316
319
 
317
- this.ws.onclose = () => {
320
+ this.ws.on("close", () => {
318
321
  console.log(chalk.yellow(`⚠ Disconnected from room: ${this.roomId}`));
319
322
  this.isConnected = false;
320
323
 
@@ -328,11 +331,20 @@ class RoomState {
328
331
  () => this.connect(onConnected),
329
332
  delay
330
333
  );
331
- };
334
+ });
332
335
 
333
- this.ws.on("message", (data: Buffer | string) => {
336
+ this.ws.on("message", (data: any) => {
334
337
  try {
335
- const jsonStr = typeof data === "string" ? data : data.toString();
338
+ let jsonStr: string;
339
+ if (data instanceof ArrayBuffer) {
340
+ jsonStr = pako.inflate(data, { to: 'string' });
341
+ } else if (typeof data === "string") {
342
+ jsonStr = data;
343
+ } else if (Buffer.isBuffer(data)) {
344
+ jsonStr = pako.inflate(data, { to: 'string' });
345
+ } else {
346
+ jsonStr = data.toString(); // Fallback nếu không nén
347
+ }
336
348
  const parsedData = JSON.parse(jsonStr);
337
349
  const includes = [
338
350
  "view",
@@ -389,12 +401,12 @@ class RoomState {
389
401
 
390
402
  updateState(key: string, value: any): void {
391
403
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
392
- this.ws.send(
393
- JSON.stringify({
394
- type: "update",
395
- state: { [key]: value },
396
- })
397
- );
404
+ const message = JSON.stringify({
405
+ type: "update",
406
+ state: { [key]: value },
407
+ });
408
+ const compressed = pako.deflate(message); // Nén dữ liệu
409
+ this.ws.send(compressed); // Gửi binary
398
410
  }
399
411
  }
400
412
 
@@ -403,7 +415,7 @@ class RoomState {
403
415
  clearTimeout(this.reconnectTimeout);
404
416
  }
405
417
  if (this.ws) {
406
- this.ws.onclose = null; // Prevent reconnect on intentional close
418
+ this.ws.removeAllListeners("close"); // Prevent reconnect on intentional close
407
419
  this.ws.close();
408
420
  }
409
421
  }
@@ -417,7 +429,7 @@ class RoomState {
417
429
  }
418
430
  }
419
431
 
420
- program.version("0.0.18").description("Hapico CLI for project management");
432
+ program.version("0.0.19").description("Hapico CLI for project management");
421
433
 
422
434
  program
423
435
  .command("clone <id>")
@@ -637,7 +649,7 @@ program
637
649
 
638
650
  if (projectType === "zalominiapp") {
639
651
  QRCode.generate(
640
- `https://zalo.me/s/3218692650896662017/player/${projectId}`,
652
+ `https://zalo.me/s/3218692650896662017/player/${projectId}?env=TESTING&version=75`,
641
653
  { small: true },
642
654
  (qrcode) => {
643
655
  console.log(
@@ -659,78 +671,83 @@ program
659
671
  });
660
672
  });
661
673
 
674
+ // Hàm tái sử dụng để push mã nguồn lên server
675
+ async function pushProject(spinner: Ora): Promise<boolean> {
676
+ const data = getStoredToken();
677
+ const { accessToken } = data || {};
678
+ if (!accessToken) {
679
+ spinner.fail(chalk.red("✗ You need to login first. Use 'hapico login' command."));
680
+ return false;
681
+ }
682
+ const pwd = process.cwd();
683
+ const projectId = getStoredProjectId(pwd);
684
+ if (!projectId) {
685
+ spinner.fail(
686
+ chalk.red(
687
+ "✗ Project ID not found. Please ensure hapico.config.json exists in the project directory."
688
+ )
689
+ );
690
+ return false;
691
+ }
692
+ const srcDir: string = path.join(pwd, "src");
693
+ if (!fs.existsSync(srcDir)) {
694
+ spinner.fail(
695
+ chalk.red(
696
+ "✗ Source directory 'src' does not exist. Please clone a project first."
697
+ )
698
+ );
699
+ return false;
700
+ }
701
+ const fileManager = new FileManager(srcDir);
702
+ const files = fileManager.listFiles();
703
+
704
+ // Nếu có file .env
705
+ const envFile = path.join(pwd, ".env");
706
+ console.log(chalk.cyan(`🔍 Checking for .env file at ${envFile}`));
707
+ if (fs.existsSync(envFile)) {
708
+ console.log(chalk.green(".env file found, including in push."));
709
+ const content = fs.readFileSync(envFile, { encoding: "utf8" });
710
+ files.push({
711
+ path: "./.env",
712
+ content,
713
+ });
714
+ }
715
+ const apiUrl = `https://base.myworkbeast.com/api/views/${projectId}`;
716
+ try {
717
+ await axios.put(
718
+ apiUrl,
719
+ {
720
+ code: JSON.stringify({
721
+ files,
722
+ }),
723
+ },
724
+ {
725
+ headers: {
726
+ Authorization: `Bearer ${accessToken}`,
727
+ "Content-Type": "application/json",
728
+ },
729
+ }
730
+ );
731
+ return true;
732
+ } catch (error) {
733
+ spinner.fail(chalk.red(`✗ Error saving project: ${(error as Error).message}`));
734
+ return false;
735
+ }
736
+ }
737
+
662
738
  program
663
739
  .command("push")
664
740
  .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
- }
741
+ .action(async () => {
674
742
  const saveSpinner: Ora = ora(
675
743
  chalk.blue("Saving project source code...")
676
744
  ).start();
677
- const pwd = process.cwd();
678
- const projectId = getStoredProjectId(pwd);
679
- if (!projectId) {
680
- saveSpinner.fail(
681
- chalk.red(
682
- "✗ Project ID not found. Please ensure hapico.config.json exists in the project directory."
683
- )
684
- );
685
- return;
686
- }
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
- )
745
+ const success = await pushProject(saveSpinner);
746
+ if (success) {
747
+ saveSpinner.succeed(
748
+ chalk.green("Project source code saved successfully!")
693
749
  );
694
- return;
695
750
  }
696
- const fileManager = new FileManager(srcDir);
697
- const files = fileManager.listFiles();
698
-
699
- // Nếu có 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
- });
709
- }
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
751
  });
735
752
 
736
753
  program
@@ -1213,4 +1230,44 @@ program
1213
1230
  }
1214
1231
  );
1215
1232
 
1216
- program.parse(process.argv);
1233
+ // hapico publish
1234
+ program.command("publish").action(async () => {
1235
+ const publishSpinner: Ora = ora(chalk.blue("Publishing to Hapico...")).start();
1236
+ // Bước 1: Push mã nguồn lên server (tái sử dụng hàm pushProject)
1237
+ const pushSuccess = await pushProject(publishSpinner);
1238
+ if (!pushSuccess) {
1239
+ return; // Dừng nếu push thất bại
1240
+ }
1241
+
1242
+ // Bước 2: Gọi API để publish
1243
+ const { accessToken } = getStoredToken();
1244
+ const pwd = process.cwd();
1245
+ const projectId = getStoredProjectId(pwd);
1246
+ if (!projectId) {
1247
+ publishSpinner.fail(
1248
+ chalk.red(
1249
+ "✗ Project ID not found. Please ensure hapico.config.json exists in the project directory."
1250
+ )
1251
+ );
1252
+ return;
1253
+ }
1254
+ const apiUrl = "https://base.myworkbeast.com/api/views/publish";
1255
+ try {
1256
+ await axios.post(
1257
+ apiUrl,
1258
+ { view_id: parseInt(projectId, 10) }, // Đảm bảo viewId là number
1259
+ {
1260
+ headers: {
1261
+ Authorization: `Bearer ${accessToken}`,
1262
+ "Content-Type": "application/json",
1263
+ },
1264
+ }
1265
+ );
1266
+ publishSpinner.succeed(chalk.green("Project published successfully!"));
1267
+ } catch (error) {
1268
+ console.log(error);
1269
+ publishSpinner.fail(chalk.red(`✗ Error publishing project: ${(error as Error).message}`));
1270
+ }
1271
+ });
1272
+
1273
+ 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.19",
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",