@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 +94 -38
- package/bun.lock +6 -0
- package/dist/index.js +94 -38
- package/index.ts +139 -82
- package/package.json +3 -1
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(`
|
|
253
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
504
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
540
|
-
.put(apiUrl, {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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(`
|
|
253
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
504
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
540
|
-
.put(apiUrl, {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
`
|
|
307
|
+
`wss://ws3.myworkbeast.com/ws?room=${this.roomId}`
|
|
308
308
|
);
|
|
309
309
|
|
|
310
|
-
|
|
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.
|
|
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:
|
|
336
|
+
this.ws.on("message", (data: any) => {
|
|
334
337
|
try {
|
|
335
|
-
|
|
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
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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.
|
|
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.
|
|
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
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|