@hapico/cli 0.0.14 → 0.0.16
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 +61 -26
- package/bun.lock +6 -0
- package/dist/index.js +61 -26
- package/index.ts +86 -31
- package/package.json +3 -1
package/bin/index.js
CHANGED
|
@@ -38,6 +38,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
};
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
40
|
exports.compileES5 = void 0;
|
|
41
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
42
|
+
const lodash_1 = require("lodash");
|
|
41
43
|
const commander_1 = require("commander");
|
|
42
44
|
const axios_1 = __importDefault(require("axios"));
|
|
43
45
|
const fs = __importStar(require("fs"));
|
|
@@ -226,7 +228,7 @@ class FileManager {
|
|
|
226
228
|
}
|
|
227
229
|
}
|
|
228
230
|
class RoomState {
|
|
229
|
-
constructor(roomId) {
|
|
231
|
+
constructor(roomId, onChangeListeners) {
|
|
230
232
|
this.files = [];
|
|
231
233
|
this.roomId = roomId;
|
|
232
234
|
this.state = {};
|
|
@@ -234,6 +236,7 @@ class RoomState {
|
|
|
234
236
|
this.ws = null;
|
|
235
237
|
this.reconnectTimeout = null;
|
|
236
238
|
this.reconnectAttempts = 0;
|
|
239
|
+
this.onChangeListeners = onChangeListeners;
|
|
237
240
|
}
|
|
238
241
|
connect(onConnected) {
|
|
239
242
|
if (this.ws && this.ws.readyState === ws_1.WebSocket.OPEN)
|
|
@@ -242,25 +245,58 @@ class RoomState {
|
|
|
242
245
|
clearTimeout(this.reconnectTimeout);
|
|
243
246
|
}
|
|
244
247
|
this.ws = new ws_1.WebSocket(`https://base.myworkbeast.com/ws?room=${this.roomId}`);
|
|
245
|
-
this.ws.
|
|
248
|
+
this.ws.onopen = () => {
|
|
249
|
+
console.log(`Connected to room: ${this.roomId}`);
|
|
246
250
|
this.isConnected = true;
|
|
247
251
|
this.reconnectAttempts = 0;
|
|
248
|
-
onConnected === null || onConnected === void 0 ? void 0 : onConnected();
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
+
onConnected === null || onConnected === void 0 ? void 0 : onConnected(); // Call the onConnected callback if provided
|
|
253
|
+
};
|
|
254
|
+
this.ws.onclose = () => {
|
|
255
|
+
console.log(`Disconnected from room: ${this.roomId}`);
|
|
252
256
|
this.isConnected = false;
|
|
253
257
|
this.reconnectAttempts++;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
258
|
+
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
|
|
259
|
+
console.log(`Attempting to reconnect in ${delay / 1000}s...`);
|
|
260
|
+
this.reconnectTimeout = setTimeout(() => this.connect(onConnected), delay);
|
|
261
|
+
};
|
|
257
262
|
this.ws.on("message", (data) => {
|
|
258
263
|
try {
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
+
const jsonStr = typeof data === "string" ? data : data.toString();
|
|
265
|
+
const parsedData = JSON.parse(jsonStr);
|
|
266
|
+
const includes = [
|
|
267
|
+
"view",
|
|
268
|
+
"coding",
|
|
269
|
+
"refresh_key",
|
|
270
|
+
"useActiveIdFocus",
|
|
271
|
+
"activeId",
|
|
272
|
+
"active_file",
|
|
273
|
+
"figma",
|
|
274
|
+
];
|
|
275
|
+
let newState;
|
|
276
|
+
let changedKeys;
|
|
277
|
+
if (parsedData.type === "state") {
|
|
278
|
+
// Full state (e.g., on initial connect)
|
|
279
|
+
newState = parsedData.state;
|
|
280
|
+
changedKeys = Object.keys(newState).filter((key) => !(0, lodash_1.isEqual)(newState[key], this.state[key]));
|
|
281
|
+
}
|
|
282
|
+
else if (parsedData.type === "update") {
|
|
283
|
+
// Delta update
|
|
284
|
+
const delta = parsedData.state;
|
|
285
|
+
newState = { ...this.state, ...delta };
|
|
286
|
+
changedKeys = Object.keys(delta).filter((key) => !(0, lodash_1.isEqual)(delta[key], this.state[key]));
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const filteredChangedKeys = changedKeys.filter((key) => includes.includes(key));
|
|
292
|
+
if (filteredChangedKeys.length > 0) {
|
|
293
|
+
filteredChangedKeys.forEach((key) => {
|
|
294
|
+
const listener = (0, lodash_1.find)(this.onChangeListeners, (l) => l.key === key);
|
|
295
|
+
if (listener) {
|
|
296
|
+
listener.callback(newState[key]);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
this.state = newState;
|
|
264
300
|
}
|
|
265
301
|
}
|
|
266
302
|
catch (e) {
|
|
@@ -280,16 +316,13 @@ class RoomState {
|
|
|
280
316
|
state: { [key]: value },
|
|
281
317
|
}));
|
|
282
318
|
}
|
|
283
|
-
else {
|
|
284
|
-
console.log("Cannot update state: Not connected");
|
|
285
|
-
}
|
|
286
319
|
}
|
|
287
320
|
disconnect() {
|
|
288
321
|
if (this.reconnectTimeout) {
|
|
289
322
|
clearTimeout(this.reconnectTimeout);
|
|
290
323
|
}
|
|
291
324
|
if (this.ws) {
|
|
292
|
-
this.ws.
|
|
325
|
+
this.ws.onclose = null; // Prevent reconnect on intentional close
|
|
293
326
|
this.ws.close();
|
|
294
327
|
}
|
|
295
328
|
}
|
|
@@ -300,7 +333,7 @@ class RoomState {
|
|
|
300
333
|
return this.isConnected;
|
|
301
334
|
}
|
|
302
335
|
}
|
|
303
|
-
commander_1.program.version("0.0.
|
|
336
|
+
commander_1.program.version("0.0.16").description("Hapico CLI for project management");
|
|
304
337
|
commander_1.program
|
|
305
338
|
.command("clone <id>")
|
|
306
339
|
.description("Clone a project by ID")
|
|
@@ -411,22 +444,25 @@ commander_1.program
|
|
|
411
444
|
return;
|
|
412
445
|
}
|
|
413
446
|
console.log(`Connecting to WebSocket server`);
|
|
414
|
-
const room = new RoomState(`view_${projectId}
|
|
447
|
+
const room = new RoomState(`view_${projectId}`, []);
|
|
448
|
+
const fileManager = new FileManager(srcDir);
|
|
449
|
+
const initialFiles = fileManager.listFiles();
|
|
450
|
+
room.files = initialFiles;
|
|
415
451
|
room.connect(async () => {
|
|
416
452
|
devSpinner.succeed("Project started in development mode!");
|
|
417
|
-
const fileManager = new FileManager(srcDir);
|
|
418
|
-
const initialFiles = fileManager.listFiles();
|
|
419
453
|
room.updateState("view", initialFiles);
|
|
420
454
|
fileManager.setOnFileChange((filePath, content) => {
|
|
421
|
-
|
|
455
|
+
var _a;
|
|
456
|
+
const es5 = (_a = (0, exports.compileES5)(content, filePath)) !== null && _a !== void 0 ? _a : "";
|
|
422
457
|
console.log(`File changed: ${filePath === null || filePath === void 0 ? void 0 : filePath.replace(srcDir, ".")}`);
|
|
423
|
-
const
|
|
458
|
+
const updatedFiles = room.files.map((file) => {
|
|
424
459
|
if (path.join(srcDir, file.path) === filePath) {
|
|
425
460
|
return { ...file, content, es5 };
|
|
426
461
|
}
|
|
427
462
|
return file;
|
|
428
463
|
});
|
|
429
|
-
room.
|
|
464
|
+
room.files = updatedFiles;
|
|
465
|
+
room.updateState("view", updatedFiles);
|
|
430
466
|
});
|
|
431
467
|
// Fetch project info
|
|
432
468
|
const projectInfo = getStoredProjectId(pwd);
|
|
@@ -445,7 +481,6 @@ commander_1.program
|
|
|
445
481
|
return;
|
|
446
482
|
}
|
|
447
483
|
const projectType = project.data.type || "view";
|
|
448
|
-
console.log("Project type:", projectType);
|
|
449
484
|
if (projectType === "zalominiapp") {
|
|
450
485
|
qrcode_terminal_1.default.generate(`https://zalo.me/s/3218692650896662017/player/${projectId}`, { small: true }, (qrcode) => {
|
|
451
486
|
console.log("Scan this QR code to connect to the project:");
|
package/bun.lock
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"@babel/standalone": "^7.28.2",
|
|
8
8
|
"axios": "^1.11.0",
|
|
9
9
|
"commander": "^14.0.0",
|
|
10
|
+
"lodash": "^4.17.21",
|
|
10
11
|
"open": "^10.2.0",
|
|
11
12
|
"ora": "^8.2.0",
|
|
12
13
|
"qrcode-terminal": "^0.12.0",
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
"devDependencies": {
|
|
17
18
|
"@types/babel__standalone": "^7.1.9",
|
|
18
19
|
"@types/commander": "^2.12.5",
|
|
20
|
+
"@types/lodash": "^4.17.20",
|
|
19
21
|
"@types/node": "^24.1.0",
|
|
20
22
|
"@types/qrcode-terminal": "^0.12.2",
|
|
21
23
|
"@types/unzipper": "^0.10.11",
|
|
@@ -47,6 +49,8 @@
|
|
|
47
49
|
|
|
48
50
|
"@types/commander": ["@types/commander@2.12.5", "", { "dependencies": { "commander": "*" } }, "sha512-YXGZ/rz+s57VbzcvEV9fUoXeJlBt5HaKu5iUheiIWNsJs23bz6AnRuRiZBRVBLYyPnixNvVnuzM5pSaxr8Yp/g=="],
|
|
49
51
|
|
|
52
|
+
"@types/lodash": ["@types/lodash@4.17.20", "", {}, "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA=="],
|
|
53
|
+
|
|
50
54
|
"@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="],
|
|
51
55
|
|
|
52
56
|
"@types/qrcode-terminal": ["@types/qrcode-terminal@0.12.2", "", {}, "sha512-v+RcIEJ+Uhd6ygSQ0u5YYY7ZM+la7GgPbs0V/7l/kFs2uO4S8BcIUEMoP7za4DNIqNnUD5npf0A/7kBhrCKG5Q=="],
|
|
@@ -141,6 +145,8 @@
|
|
|
141
145
|
|
|
142
146
|
"jsonfile": ["jsonfile@6.1.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="],
|
|
143
147
|
|
|
148
|
+
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
|
|
149
|
+
|
|
144
150
|
"log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="],
|
|
145
151
|
|
|
146
152
|
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
};
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
40
|
exports.compileES5 = void 0;
|
|
41
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
42
|
+
const lodash_1 = require("lodash");
|
|
41
43
|
const commander_1 = require("commander");
|
|
42
44
|
const axios_1 = __importDefault(require("axios"));
|
|
43
45
|
const fs = __importStar(require("fs"));
|
|
@@ -226,7 +228,7 @@ class FileManager {
|
|
|
226
228
|
}
|
|
227
229
|
}
|
|
228
230
|
class RoomState {
|
|
229
|
-
constructor(roomId) {
|
|
231
|
+
constructor(roomId, onChangeListeners) {
|
|
230
232
|
this.files = [];
|
|
231
233
|
this.roomId = roomId;
|
|
232
234
|
this.state = {};
|
|
@@ -234,6 +236,7 @@ class RoomState {
|
|
|
234
236
|
this.ws = null;
|
|
235
237
|
this.reconnectTimeout = null;
|
|
236
238
|
this.reconnectAttempts = 0;
|
|
239
|
+
this.onChangeListeners = onChangeListeners;
|
|
237
240
|
}
|
|
238
241
|
connect(onConnected) {
|
|
239
242
|
if (this.ws && this.ws.readyState === ws_1.WebSocket.OPEN)
|
|
@@ -242,25 +245,58 @@ class RoomState {
|
|
|
242
245
|
clearTimeout(this.reconnectTimeout);
|
|
243
246
|
}
|
|
244
247
|
this.ws = new ws_1.WebSocket(`https://base.myworkbeast.com/ws?room=${this.roomId}`);
|
|
245
|
-
this.ws.
|
|
248
|
+
this.ws.onopen = () => {
|
|
249
|
+
console.log(`Connected to room: ${this.roomId}`);
|
|
246
250
|
this.isConnected = true;
|
|
247
251
|
this.reconnectAttempts = 0;
|
|
248
|
-
onConnected === null || onConnected === void 0 ? void 0 : onConnected();
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
+
onConnected === null || onConnected === void 0 ? void 0 : onConnected(); // Call the onConnected callback if provided
|
|
253
|
+
};
|
|
254
|
+
this.ws.onclose = () => {
|
|
255
|
+
console.log(`Disconnected from room: ${this.roomId}`);
|
|
252
256
|
this.isConnected = false;
|
|
253
257
|
this.reconnectAttempts++;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
258
|
+
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
|
|
259
|
+
console.log(`Attempting to reconnect in ${delay / 1000}s...`);
|
|
260
|
+
this.reconnectTimeout = setTimeout(() => this.connect(onConnected), delay);
|
|
261
|
+
};
|
|
257
262
|
this.ws.on("message", (data) => {
|
|
258
263
|
try {
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
+
const jsonStr = typeof data === "string" ? data : data.toString();
|
|
265
|
+
const parsedData = JSON.parse(jsonStr);
|
|
266
|
+
const includes = [
|
|
267
|
+
"view",
|
|
268
|
+
"coding",
|
|
269
|
+
"refresh_key",
|
|
270
|
+
"useActiveIdFocus",
|
|
271
|
+
"activeId",
|
|
272
|
+
"active_file",
|
|
273
|
+
"figma",
|
|
274
|
+
];
|
|
275
|
+
let newState;
|
|
276
|
+
let changedKeys;
|
|
277
|
+
if (parsedData.type === "state") {
|
|
278
|
+
// Full state (e.g., on initial connect)
|
|
279
|
+
newState = parsedData.state;
|
|
280
|
+
changedKeys = Object.keys(newState).filter((key) => !(0, lodash_1.isEqual)(newState[key], this.state[key]));
|
|
281
|
+
}
|
|
282
|
+
else if (parsedData.type === "update") {
|
|
283
|
+
// Delta update
|
|
284
|
+
const delta = parsedData.state;
|
|
285
|
+
newState = { ...this.state, ...delta };
|
|
286
|
+
changedKeys = Object.keys(delta).filter((key) => !(0, lodash_1.isEqual)(delta[key], this.state[key]));
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const filteredChangedKeys = changedKeys.filter((key) => includes.includes(key));
|
|
292
|
+
if (filteredChangedKeys.length > 0) {
|
|
293
|
+
filteredChangedKeys.forEach((key) => {
|
|
294
|
+
const listener = (0, lodash_1.find)(this.onChangeListeners, (l) => l.key === key);
|
|
295
|
+
if (listener) {
|
|
296
|
+
listener.callback(newState[key]);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
this.state = newState;
|
|
264
300
|
}
|
|
265
301
|
}
|
|
266
302
|
catch (e) {
|
|
@@ -280,16 +316,13 @@ class RoomState {
|
|
|
280
316
|
state: { [key]: value },
|
|
281
317
|
}));
|
|
282
318
|
}
|
|
283
|
-
else {
|
|
284
|
-
console.log("Cannot update state: Not connected");
|
|
285
|
-
}
|
|
286
319
|
}
|
|
287
320
|
disconnect() {
|
|
288
321
|
if (this.reconnectTimeout) {
|
|
289
322
|
clearTimeout(this.reconnectTimeout);
|
|
290
323
|
}
|
|
291
324
|
if (this.ws) {
|
|
292
|
-
this.ws.
|
|
325
|
+
this.ws.onclose = null; // Prevent reconnect on intentional close
|
|
293
326
|
this.ws.close();
|
|
294
327
|
}
|
|
295
328
|
}
|
|
@@ -300,7 +333,7 @@ class RoomState {
|
|
|
300
333
|
return this.isConnected;
|
|
301
334
|
}
|
|
302
335
|
}
|
|
303
|
-
commander_1.program.version("0.0.
|
|
336
|
+
commander_1.program.version("0.0.16").description("Hapico CLI for project management");
|
|
304
337
|
commander_1.program
|
|
305
338
|
.command("clone <id>")
|
|
306
339
|
.description("Clone a project by ID")
|
|
@@ -411,22 +444,25 @@ commander_1.program
|
|
|
411
444
|
return;
|
|
412
445
|
}
|
|
413
446
|
console.log(`Connecting to WebSocket server`);
|
|
414
|
-
const room = new RoomState(`view_${projectId}
|
|
447
|
+
const room = new RoomState(`view_${projectId}`, []);
|
|
448
|
+
const fileManager = new FileManager(srcDir);
|
|
449
|
+
const initialFiles = fileManager.listFiles();
|
|
450
|
+
room.files = initialFiles;
|
|
415
451
|
room.connect(async () => {
|
|
416
452
|
devSpinner.succeed("Project started in development mode!");
|
|
417
|
-
const fileManager = new FileManager(srcDir);
|
|
418
|
-
const initialFiles = fileManager.listFiles();
|
|
419
453
|
room.updateState("view", initialFiles);
|
|
420
454
|
fileManager.setOnFileChange((filePath, content) => {
|
|
421
|
-
|
|
455
|
+
var _a;
|
|
456
|
+
const es5 = (_a = (0, exports.compileES5)(content, filePath)) !== null && _a !== void 0 ? _a : "";
|
|
422
457
|
console.log(`File changed: ${filePath === null || filePath === void 0 ? void 0 : filePath.replace(srcDir, ".")}`);
|
|
423
|
-
const
|
|
458
|
+
const updatedFiles = room.files.map((file) => {
|
|
424
459
|
if (path.join(srcDir, file.path) === filePath) {
|
|
425
460
|
return { ...file, content, es5 };
|
|
426
461
|
}
|
|
427
462
|
return file;
|
|
428
463
|
});
|
|
429
|
-
room.
|
|
464
|
+
room.files = updatedFiles;
|
|
465
|
+
room.updateState("view", updatedFiles);
|
|
430
466
|
});
|
|
431
467
|
// Fetch project info
|
|
432
468
|
const projectInfo = getStoredProjectId(pwd);
|
|
@@ -445,7 +481,6 @@ commander_1.program
|
|
|
445
481
|
return;
|
|
446
482
|
}
|
|
447
483
|
const projectType = project.data.type || "view";
|
|
448
|
-
console.log("Project type:", projectType);
|
|
449
484
|
if (projectType === "zalominiapp") {
|
|
450
485
|
qrcode_terminal_1.default.generate(`https://zalo.me/s/3218692650896662017/player/${projectId}`, { small: true }, (qrcode) => {
|
|
451
486
|
console.log("Scan this QR code to connect to the project:");
|
package/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
import { isEqual, find } from "lodash";
|
|
3
4
|
import { program } from "commander";
|
|
4
5
|
import axios from "axios";
|
|
5
6
|
import * as fs from "fs";
|
|
@@ -249,6 +250,10 @@ class FileManager {
|
|
|
249
250
|
}
|
|
250
251
|
}
|
|
251
252
|
|
|
253
|
+
interface FileContent {
|
|
254
|
+
// Define the structure of FileContent if needed
|
|
255
|
+
}
|
|
256
|
+
|
|
252
257
|
class RoomState {
|
|
253
258
|
private roomId: string;
|
|
254
259
|
private state: RoomStateData;
|
|
@@ -256,16 +261,24 @@ class RoomState {
|
|
|
256
261
|
private ws: WebSocket | null;
|
|
257
262
|
private reconnectTimeout: NodeJS.Timeout | null;
|
|
258
263
|
private reconnectAttempts: number;
|
|
264
|
+
private onChangeListeners: Array<{
|
|
265
|
+
key: string;
|
|
266
|
+
callback: (value: any) => void;
|
|
267
|
+
}>;
|
|
259
268
|
|
|
260
269
|
public files: FileContent[] = [];
|
|
261
270
|
|
|
262
|
-
constructor(
|
|
271
|
+
constructor(
|
|
272
|
+
roomId: string,
|
|
273
|
+
onChangeListeners: Array<{ key: string; callback: (value: any) => void }>
|
|
274
|
+
) {
|
|
263
275
|
this.roomId = roomId;
|
|
264
276
|
this.state = {};
|
|
265
277
|
this.isConnected = false;
|
|
266
278
|
this.ws = null;
|
|
267
279
|
this.reconnectTimeout = null;
|
|
268
280
|
this.reconnectAttempts = 0;
|
|
281
|
+
this.onChangeListeners = onChangeListeners;
|
|
269
282
|
}
|
|
270
283
|
|
|
271
284
|
connect(onConnected?: () => void): void {
|
|
@@ -279,36 +292,79 @@ class RoomState {
|
|
|
279
292
|
`https://base.myworkbeast.com/ws?room=${this.roomId}`
|
|
280
293
|
);
|
|
281
294
|
|
|
282
|
-
this.ws.
|
|
295
|
+
this.ws.onopen = () => {
|
|
296
|
+
console.log(`Connected to room: ${this.roomId}`);
|
|
283
297
|
this.isConnected = true;
|
|
284
298
|
this.reconnectAttempts = 0;
|
|
285
|
-
onConnected?.();
|
|
286
|
-
|
|
287
|
-
});
|
|
299
|
+
onConnected?.(); // Call the onConnected callback if provided
|
|
300
|
+
};
|
|
288
301
|
|
|
289
|
-
this.ws.
|
|
302
|
+
this.ws.onclose = () => {
|
|
303
|
+
console.log(`Disconnected from room: ${this.roomId}`);
|
|
290
304
|
this.isConnected = false;
|
|
291
305
|
|
|
292
306
|
this.reconnectAttempts++;
|
|
293
|
-
|
|
307
|
+
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
|
|
308
|
+
console.log(`Attempting to reconnect in ${delay / 1000}s...`);
|
|
294
309
|
|
|
295
|
-
this.
|
|
296
|
-
|
|
310
|
+
this.reconnectTimeout = setTimeout(
|
|
311
|
+
() => this.connect(onConnected),
|
|
312
|
+
delay
|
|
313
|
+
);
|
|
314
|
+
};
|
|
297
315
|
|
|
298
|
-
this.ws.on("message", (data: Buffer) => {
|
|
316
|
+
this.ws.on("message", (data: Buffer | string) => {
|
|
299
317
|
try {
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
318
|
+
const jsonStr = typeof data === "string" ? data : data.toString();
|
|
319
|
+
const parsedData = JSON.parse(jsonStr);
|
|
320
|
+
const includes = [
|
|
321
|
+
"view",
|
|
322
|
+
"coding",
|
|
323
|
+
"refresh_key",
|
|
324
|
+
"useActiveIdFocus",
|
|
325
|
+
"activeId",
|
|
326
|
+
"active_file",
|
|
327
|
+
"figma",
|
|
328
|
+
];
|
|
329
|
+
|
|
330
|
+
let newState: RoomStateData;
|
|
331
|
+
let changedKeys;
|
|
332
|
+
|
|
333
|
+
if (parsedData.type === "state") {
|
|
334
|
+
// Full state (e.g., on initial connect)
|
|
335
|
+
newState = parsedData.state;
|
|
336
|
+
changedKeys = Object.keys(newState).filter(
|
|
337
|
+
(key) => !isEqual(newState[key], this.state[key])
|
|
338
|
+
);
|
|
339
|
+
} else if (parsedData.type === "update") {
|
|
340
|
+
// Delta update
|
|
341
|
+
const delta = parsedData.state;
|
|
342
|
+
newState = { ...this.state, ...delta };
|
|
343
|
+
changedKeys = Object.keys(delta).filter(
|
|
344
|
+
(key) => !isEqual(delta[key], this.state[key])
|
|
345
|
+
);
|
|
346
|
+
} else {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const filteredChangedKeys = changedKeys.filter((key) =>
|
|
351
|
+
includes.includes(key)
|
|
352
|
+
);
|
|
353
|
+
if (filteredChangedKeys.length > 0) {
|
|
354
|
+
filteredChangedKeys.forEach((key) => {
|
|
355
|
+
const listener = find(this.onChangeListeners, (l) => l.key === key);
|
|
356
|
+
if (listener) {
|
|
357
|
+
listener.callback(newState[key]);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
this.state = newState;
|
|
305
361
|
}
|
|
306
362
|
} catch (e) {
|
|
307
363
|
console.error("Error processing message:", e);
|
|
308
364
|
}
|
|
309
365
|
});
|
|
310
366
|
|
|
311
|
-
this.ws.on("error", (err
|
|
367
|
+
this.ws.on("error", (err) => {
|
|
312
368
|
console.error("WebSocket error:", err);
|
|
313
369
|
this.ws?.close();
|
|
314
370
|
});
|
|
@@ -322,8 +378,6 @@ class RoomState {
|
|
|
322
378
|
state: { [key]: value },
|
|
323
379
|
})
|
|
324
380
|
);
|
|
325
|
-
} else {
|
|
326
|
-
console.log("Cannot update state: Not connected");
|
|
327
381
|
}
|
|
328
382
|
}
|
|
329
383
|
|
|
@@ -332,7 +386,7 @@ class RoomState {
|
|
|
332
386
|
clearTimeout(this.reconnectTimeout);
|
|
333
387
|
}
|
|
334
388
|
if (this.ws) {
|
|
335
|
-
this.ws.
|
|
389
|
+
this.ws.onclose = null; // Prevent reconnect on intentional close
|
|
336
390
|
this.ws.close();
|
|
337
391
|
}
|
|
338
392
|
}
|
|
@@ -346,7 +400,7 @@ class RoomState {
|
|
|
346
400
|
}
|
|
347
401
|
}
|
|
348
402
|
|
|
349
|
-
program.version("0.0.
|
|
403
|
+
program.version("0.0.16").description("Hapico CLI for project management");
|
|
350
404
|
|
|
351
405
|
program
|
|
352
406
|
.command("clone <id>")
|
|
@@ -431,7 +485,9 @@ program
|
|
|
431
485
|
console.error("You need to login first. Use 'hapico login' command.");
|
|
432
486
|
return;
|
|
433
487
|
}
|
|
434
|
-
const devSpinner = ora(
|
|
488
|
+
const devSpinner = ora(
|
|
489
|
+
"Starting the project in development mode..."
|
|
490
|
+
).start();
|
|
435
491
|
const pwd = process.cwd();
|
|
436
492
|
const srcDir = path.join(pwd, "src");
|
|
437
493
|
if (!fs.existsSync(srcDir)) {
|
|
@@ -492,26 +548,27 @@ program
|
|
|
492
548
|
}
|
|
493
549
|
|
|
494
550
|
console.log(`Connecting to WebSocket server`);
|
|
495
|
-
const room = new RoomState(`view_${projectId}
|
|
551
|
+
const room = new RoomState(`view_${projectId}`, []);
|
|
552
|
+
const fileManager = new FileManager(srcDir);
|
|
553
|
+
const initialFiles = fileManager.listFiles();
|
|
554
|
+
room.files = initialFiles;
|
|
496
555
|
|
|
497
556
|
room.connect(async () => {
|
|
498
557
|
devSpinner.succeed("Project started in development mode!");
|
|
499
558
|
|
|
500
|
-
const fileManager = new FileManager(srcDir);
|
|
501
|
-
|
|
502
|
-
const initialFiles = fileManager.listFiles();
|
|
503
559
|
room.updateState("view", initialFiles);
|
|
504
560
|
|
|
505
561
|
fileManager.setOnFileChange((filePath, content) => {
|
|
506
|
-
const es5 = compileES5(content, filePath);
|
|
562
|
+
const es5 = compileES5(content, filePath) ?? "";
|
|
507
563
|
console.log(`File changed: ${filePath?.replace(srcDir, ".")}`);
|
|
508
|
-
const
|
|
564
|
+
const updatedFiles = room.files.map((file) => {
|
|
509
565
|
if (path.join(srcDir, file.path) === filePath) {
|
|
510
566
|
return { ...file, content, es5 };
|
|
511
567
|
}
|
|
512
568
|
return file;
|
|
513
569
|
});
|
|
514
|
-
room.
|
|
570
|
+
room.files = updatedFiles;
|
|
571
|
+
room.updateState("view", updatedFiles);
|
|
515
572
|
});
|
|
516
573
|
|
|
517
574
|
// Fetch project info
|
|
@@ -539,8 +596,6 @@ program
|
|
|
539
596
|
|
|
540
597
|
const projectType = project.data.type || "view";
|
|
541
598
|
|
|
542
|
-
console.log("Project type:", projectType);
|
|
543
|
-
|
|
544
599
|
if (projectType === "zalominiapp") {
|
|
545
600
|
QRCode.generate(
|
|
546
601
|
`https://zalo.me/s/3218692650896662017/player/${projectId}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hapico/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"description": "A simple CLI tool for project management",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"@babel/standalone": "^7.28.2",
|
|
24
24
|
"axios": "^1.11.0",
|
|
25
25
|
"commander": "^14.0.0",
|
|
26
|
+
"lodash": "^4.17.21",
|
|
26
27
|
"open": "^10.2.0",
|
|
27
28
|
"ora": "^8.2.0",
|
|
28
29
|
"qrcode-terminal": "^0.12.0",
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"@types/babel__standalone": "^7.1.9",
|
|
34
35
|
"@types/commander": "^2.12.5",
|
|
36
|
+
"@types/lodash": "^4.17.20",
|
|
35
37
|
"@types/node": "^24.1.0",
|
|
36
38
|
"@types/qrcode-terminal": "^0.12.2",
|
|
37
39
|
"@types/unzipper": "^0.10.11",
|