@hapico/cli 0.0.13 → 0.0.15
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 +75 -26
- package/bun.lock +6 -0
- package/dist/index.js +75 -26
- package/index.ts +110 -34
- 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,50 @@ 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 = ["view", "coding", "refresh_key", "useActiveIdFocus", "activeId", "active_file", "figma"];
|
|
267
|
+
let newState;
|
|
268
|
+
let changedKeys;
|
|
269
|
+
if (parsedData.type === "state") {
|
|
270
|
+
// Full state (e.g., on initial connect)
|
|
271
|
+
newState = parsedData.state;
|
|
272
|
+
changedKeys = Object.keys(newState).filter((key) => !(0, lodash_1.isEqual)(newState[key], this.state[key]));
|
|
273
|
+
}
|
|
274
|
+
else if (parsedData.type === "update") {
|
|
275
|
+
// Delta update
|
|
276
|
+
const delta = parsedData.state;
|
|
277
|
+
newState = { ...this.state, ...delta };
|
|
278
|
+
changedKeys = Object.keys(delta).filter((key) => !(0, lodash_1.isEqual)(delta[key], this.state[key]));
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const filteredChangedKeys = changedKeys.filter((key) => includes.includes(key));
|
|
284
|
+
if (filteredChangedKeys.length > 0) {
|
|
285
|
+
filteredChangedKeys.forEach((key) => {
|
|
286
|
+
const listener = (0, lodash_1.find)(this.onChangeListeners, (l) => l.key === key);
|
|
287
|
+
if (listener) {
|
|
288
|
+
listener.callback(newState[key]);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
this.state = newState;
|
|
264
292
|
}
|
|
265
293
|
}
|
|
266
294
|
catch (e) {
|
|
@@ -280,16 +308,13 @@ class RoomState {
|
|
|
280
308
|
state: { [key]: value },
|
|
281
309
|
}));
|
|
282
310
|
}
|
|
283
|
-
else {
|
|
284
|
-
console.log("Cannot update state: Not connected");
|
|
285
|
-
}
|
|
286
311
|
}
|
|
287
312
|
disconnect() {
|
|
288
313
|
if (this.reconnectTimeout) {
|
|
289
314
|
clearTimeout(this.reconnectTimeout);
|
|
290
315
|
}
|
|
291
316
|
if (this.ws) {
|
|
292
|
-
this.ws.
|
|
317
|
+
this.ws.onclose = null; // Prevent reconnect on intentional close
|
|
293
318
|
this.ws.close();
|
|
294
319
|
}
|
|
295
320
|
}
|
|
@@ -300,7 +325,7 @@ class RoomState {
|
|
|
300
325
|
return this.isConnected;
|
|
301
326
|
}
|
|
302
327
|
}
|
|
303
|
-
commander_1.program.version("0.0.
|
|
328
|
+
commander_1.program.version("0.0.15").description("Hapico CLI for project management");
|
|
304
329
|
commander_1.program
|
|
305
330
|
.command("clone <id>")
|
|
306
331
|
.description("Clone a project by ID")
|
|
@@ -373,19 +398,45 @@ commander_1.program
|
|
|
373
398
|
devSpinner.fail("Source directory 'src' does not exist. Please clone a project first.");
|
|
374
399
|
return;
|
|
375
400
|
}
|
|
401
|
+
// Directory to store session config
|
|
402
|
+
const tmpDir = path.join(pwd, ".tmp");
|
|
403
|
+
const sessionConfigFile = path.join(tmpDir, "config.json");
|
|
404
|
+
// Ensure .tmp directory exists
|
|
405
|
+
if (!fs.existsSync(tmpDir)) {
|
|
406
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
407
|
+
}
|
|
408
|
+
// Function to get stored session ID
|
|
409
|
+
const getStoredSessionId = () => {
|
|
410
|
+
if (fs.existsSync(sessionConfigFile)) {
|
|
411
|
+
const data = fs.readFileSync(sessionConfigFile, { encoding: "utf8" });
|
|
412
|
+
const json = JSON.parse(data);
|
|
413
|
+
return json.sessionId || null;
|
|
414
|
+
}
|
|
415
|
+
return null;
|
|
416
|
+
};
|
|
417
|
+
// Function to save session ID
|
|
418
|
+
const saveSessionId = (sessionId) => {
|
|
419
|
+
fs.writeFileSync(sessionConfigFile, JSON.stringify({ sessionId }, null, 2), { encoding: "utf8" });
|
|
420
|
+
};
|
|
421
|
+
// Get or generate session ID
|
|
422
|
+
let sessionId = getStoredSessionId();
|
|
423
|
+
if (!sessionId) {
|
|
424
|
+
sessionId = (0, crypto_1.randomUUID)();
|
|
425
|
+
saveSessionId(sessionId);
|
|
426
|
+
}
|
|
376
427
|
const info = JSON.stringify({
|
|
377
|
-
id:
|
|
428
|
+
id: sessionId,
|
|
378
429
|
createdAt: new Date().toISOString(),
|
|
379
430
|
viewId: getStoredProjectId(pwd),
|
|
380
431
|
});
|
|
381
|
-
//
|
|
432
|
+
// Convert info to base64
|
|
382
433
|
const projectId = Buffer.from(info).toString("base64");
|
|
383
434
|
if (!projectId) {
|
|
384
435
|
devSpinner.fail("Project ID not found. Please ensure hapico.config.json exists in the project directory.");
|
|
385
436
|
return;
|
|
386
437
|
}
|
|
387
438
|
console.log(`Connecting to WebSocket server`);
|
|
388
|
-
const room = new RoomState(`view_${projectId}
|
|
439
|
+
const room = new RoomState(`view_${projectId}`, []);
|
|
389
440
|
room.connect(async () => {
|
|
390
441
|
devSpinner.succeed("Project started in development mode!");
|
|
391
442
|
const fileManager = new FileManager(srcDir);
|
|
@@ -394,7 +445,7 @@ commander_1.program
|
|
|
394
445
|
fileManager.setOnFileChange((filePath, content) => {
|
|
395
446
|
const es5 = (0, exports.compileES5)(content, filePath);
|
|
396
447
|
console.log(`File changed: ${filePath === null || filePath === void 0 ? void 0 : filePath.replace(srcDir, ".")}`);
|
|
397
|
-
const updatedView =
|
|
448
|
+
const updatedView = initialFiles.map((file) => {
|
|
398
449
|
if (path.join(srcDir, file.path) === filePath) {
|
|
399
450
|
return { ...file, content, es5 };
|
|
400
451
|
}
|
|
@@ -402,7 +453,7 @@ commander_1.program
|
|
|
402
453
|
});
|
|
403
454
|
room.updateState("view", updatedView);
|
|
404
455
|
});
|
|
405
|
-
//
|
|
456
|
+
// Fetch project info
|
|
406
457
|
const projectInfo = getStoredProjectId(pwd);
|
|
407
458
|
if (!projectInfo) {
|
|
408
459
|
console.error("Project ID not found. Please ensure hapico.config.json exists in the project directory.");
|
|
@@ -419,7 +470,6 @@ commander_1.program
|
|
|
419
470
|
return;
|
|
420
471
|
}
|
|
421
472
|
const projectType = project.data.type || "view";
|
|
422
|
-
console.log("Project type:", projectType);
|
|
423
473
|
if (projectType === "zalominiapp") {
|
|
424
474
|
qrcode_terminal_1.default.generate(`https://zalo.me/s/3218692650896662017/player/${projectId}`, { small: true }, (qrcode) => {
|
|
425
475
|
console.log("Scan this QR code to connect to the project:");
|
|
@@ -428,7 +478,6 @@ commander_1.program
|
|
|
428
478
|
return;
|
|
429
479
|
}
|
|
430
480
|
else {
|
|
431
|
-
// show link https://com.ai.vn/dev_preview/{projectId}
|
|
432
481
|
const previewUrl = `https://com.ai.vn/dev_preview/${projectId}`;
|
|
433
482
|
console.log(`Open this URL in your browser to preview the project: \n${previewUrl}`);
|
|
434
483
|
await (0, open_1.default)(previewUrl);
|
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,50 @@ 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 = ["view", "coding", "refresh_key", "useActiveIdFocus", "activeId", "active_file", "figma"];
|
|
267
|
+
let newState;
|
|
268
|
+
let changedKeys;
|
|
269
|
+
if (parsedData.type === "state") {
|
|
270
|
+
// Full state (e.g., on initial connect)
|
|
271
|
+
newState = parsedData.state;
|
|
272
|
+
changedKeys = Object.keys(newState).filter((key) => !(0, lodash_1.isEqual)(newState[key], this.state[key]));
|
|
273
|
+
}
|
|
274
|
+
else if (parsedData.type === "update") {
|
|
275
|
+
// Delta update
|
|
276
|
+
const delta = parsedData.state;
|
|
277
|
+
newState = { ...this.state, ...delta };
|
|
278
|
+
changedKeys = Object.keys(delta).filter((key) => !(0, lodash_1.isEqual)(delta[key], this.state[key]));
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const filteredChangedKeys = changedKeys.filter((key) => includes.includes(key));
|
|
284
|
+
if (filteredChangedKeys.length > 0) {
|
|
285
|
+
filteredChangedKeys.forEach((key) => {
|
|
286
|
+
const listener = (0, lodash_1.find)(this.onChangeListeners, (l) => l.key === key);
|
|
287
|
+
if (listener) {
|
|
288
|
+
listener.callback(newState[key]);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
this.state = newState;
|
|
264
292
|
}
|
|
265
293
|
}
|
|
266
294
|
catch (e) {
|
|
@@ -280,16 +308,13 @@ class RoomState {
|
|
|
280
308
|
state: { [key]: value },
|
|
281
309
|
}));
|
|
282
310
|
}
|
|
283
|
-
else {
|
|
284
|
-
console.log("Cannot update state: Not connected");
|
|
285
|
-
}
|
|
286
311
|
}
|
|
287
312
|
disconnect() {
|
|
288
313
|
if (this.reconnectTimeout) {
|
|
289
314
|
clearTimeout(this.reconnectTimeout);
|
|
290
315
|
}
|
|
291
316
|
if (this.ws) {
|
|
292
|
-
this.ws.
|
|
317
|
+
this.ws.onclose = null; // Prevent reconnect on intentional close
|
|
293
318
|
this.ws.close();
|
|
294
319
|
}
|
|
295
320
|
}
|
|
@@ -300,7 +325,7 @@ class RoomState {
|
|
|
300
325
|
return this.isConnected;
|
|
301
326
|
}
|
|
302
327
|
}
|
|
303
|
-
commander_1.program.version("0.0.
|
|
328
|
+
commander_1.program.version("0.0.15").description("Hapico CLI for project management");
|
|
304
329
|
commander_1.program
|
|
305
330
|
.command("clone <id>")
|
|
306
331
|
.description("Clone a project by ID")
|
|
@@ -373,19 +398,45 @@ commander_1.program
|
|
|
373
398
|
devSpinner.fail("Source directory 'src' does not exist. Please clone a project first.");
|
|
374
399
|
return;
|
|
375
400
|
}
|
|
401
|
+
// Directory to store session config
|
|
402
|
+
const tmpDir = path.join(pwd, ".tmp");
|
|
403
|
+
const sessionConfigFile = path.join(tmpDir, "config.json");
|
|
404
|
+
// Ensure .tmp directory exists
|
|
405
|
+
if (!fs.existsSync(tmpDir)) {
|
|
406
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
407
|
+
}
|
|
408
|
+
// Function to get stored session ID
|
|
409
|
+
const getStoredSessionId = () => {
|
|
410
|
+
if (fs.existsSync(sessionConfigFile)) {
|
|
411
|
+
const data = fs.readFileSync(sessionConfigFile, { encoding: "utf8" });
|
|
412
|
+
const json = JSON.parse(data);
|
|
413
|
+
return json.sessionId || null;
|
|
414
|
+
}
|
|
415
|
+
return null;
|
|
416
|
+
};
|
|
417
|
+
// Function to save session ID
|
|
418
|
+
const saveSessionId = (sessionId) => {
|
|
419
|
+
fs.writeFileSync(sessionConfigFile, JSON.stringify({ sessionId }, null, 2), { encoding: "utf8" });
|
|
420
|
+
};
|
|
421
|
+
// Get or generate session ID
|
|
422
|
+
let sessionId = getStoredSessionId();
|
|
423
|
+
if (!sessionId) {
|
|
424
|
+
sessionId = (0, crypto_1.randomUUID)();
|
|
425
|
+
saveSessionId(sessionId);
|
|
426
|
+
}
|
|
376
427
|
const info = JSON.stringify({
|
|
377
|
-
id:
|
|
428
|
+
id: sessionId,
|
|
378
429
|
createdAt: new Date().toISOString(),
|
|
379
430
|
viewId: getStoredProjectId(pwd),
|
|
380
431
|
});
|
|
381
|
-
//
|
|
432
|
+
// Convert info to base64
|
|
382
433
|
const projectId = Buffer.from(info).toString("base64");
|
|
383
434
|
if (!projectId) {
|
|
384
435
|
devSpinner.fail("Project ID not found. Please ensure hapico.config.json exists in the project directory.");
|
|
385
436
|
return;
|
|
386
437
|
}
|
|
387
438
|
console.log(`Connecting to WebSocket server`);
|
|
388
|
-
const room = new RoomState(`view_${projectId}
|
|
439
|
+
const room = new RoomState(`view_${projectId}`, []);
|
|
389
440
|
room.connect(async () => {
|
|
390
441
|
devSpinner.succeed("Project started in development mode!");
|
|
391
442
|
const fileManager = new FileManager(srcDir);
|
|
@@ -394,7 +445,7 @@ commander_1.program
|
|
|
394
445
|
fileManager.setOnFileChange((filePath, content) => {
|
|
395
446
|
const es5 = (0, exports.compileES5)(content, filePath);
|
|
396
447
|
console.log(`File changed: ${filePath === null || filePath === void 0 ? void 0 : filePath.replace(srcDir, ".")}`);
|
|
397
|
-
const updatedView =
|
|
448
|
+
const updatedView = initialFiles.map((file) => {
|
|
398
449
|
if (path.join(srcDir, file.path) === filePath) {
|
|
399
450
|
return { ...file, content, es5 };
|
|
400
451
|
}
|
|
@@ -402,7 +453,7 @@ commander_1.program
|
|
|
402
453
|
});
|
|
403
454
|
room.updateState("view", updatedView);
|
|
404
455
|
});
|
|
405
|
-
//
|
|
456
|
+
// Fetch project info
|
|
406
457
|
const projectInfo = getStoredProjectId(pwd);
|
|
407
458
|
if (!projectInfo) {
|
|
408
459
|
console.error("Project ID not found. Please ensure hapico.config.json exists in the project directory.");
|
|
@@ -419,7 +470,6 @@ commander_1.program
|
|
|
419
470
|
return;
|
|
420
471
|
}
|
|
421
472
|
const projectType = project.data.type || "view";
|
|
422
|
-
console.log("Project type:", projectType);
|
|
423
473
|
if (projectType === "zalominiapp") {
|
|
424
474
|
qrcode_terminal_1.default.generate(`https://zalo.me/s/3218692650896662017/player/${projectId}`, { small: true }, (qrcode) => {
|
|
425
475
|
console.log("Scan this QR code to connect to the project:");
|
|
@@ -428,7 +478,6 @@ commander_1.program
|
|
|
428
478
|
return;
|
|
429
479
|
}
|
|
430
480
|
else {
|
|
431
|
-
// show link https://com.ai.vn/dev_preview/{projectId}
|
|
432
481
|
const previewUrl = `https://com.ai.vn/dev_preview/${projectId}`;
|
|
433
482
|
console.log(`Open this URL in your browser to preview the project: \n${previewUrl}`);
|
|
434
483
|
await (0, open_1.default)(previewUrl);
|
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,21 @@ class RoomState {
|
|
|
256
261
|
private ws: WebSocket | null;
|
|
257
262
|
private reconnectTimeout: NodeJS.Timeout | null;
|
|
258
263
|
private reconnectAttempts: number;
|
|
264
|
+
private onChangeListeners: Array<{ key: string; callback: (value: any) => void }>;
|
|
259
265
|
|
|
260
266
|
public files: FileContent[] = [];
|
|
261
267
|
|
|
262
|
-
constructor(
|
|
268
|
+
constructor(
|
|
269
|
+
roomId: string,
|
|
270
|
+
onChangeListeners: Array<{ key: string; callback: (value: any) => void }>
|
|
271
|
+
) {
|
|
263
272
|
this.roomId = roomId;
|
|
264
273
|
this.state = {};
|
|
265
274
|
this.isConnected = false;
|
|
266
275
|
this.ws = null;
|
|
267
276
|
this.reconnectTimeout = null;
|
|
268
277
|
this.reconnectAttempts = 0;
|
|
278
|
+
this.onChangeListeners = onChangeListeners;
|
|
269
279
|
}
|
|
270
280
|
|
|
271
281
|
connect(onConnected?: () => void): void {
|
|
@@ -279,36 +289,69 @@ class RoomState {
|
|
|
279
289
|
`https://base.myworkbeast.com/ws?room=${this.roomId}`
|
|
280
290
|
);
|
|
281
291
|
|
|
282
|
-
this.ws.
|
|
292
|
+
this.ws.onopen = () => {
|
|
293
|
+
console.log(`Connected to room: ${this.roomId}`);
|
|
283
294
|
this.isConnected = true;
|
|
284
295
|
this.reconnectAttempts = 0;
|
|
285
|
-
onConnected?.();
|
|
286
|
-
|
|
287
|
-
});
|
|
296
|
+
onConnected?.(); // Call the onConnected callback if provided
|
|
297
|
+
};
|
|
288
298
|
|
|
289
|
-
this.ws.
|
|
299
|
+
this.ws.onclose = () => {
|
|
300
|
+
console.log(`Disconnected from room: ${this.roomId}`);
|
|
290
301
|
this.isConnected = false;
|
|
291
302
|
|
|
292
303
|
this.reconnectAttempts++;
|
|
293
|
-
|
|
304
|
+
const delay = Math.min(
|
|
305
|
+
1000 * Math.pow(2, this.reconnectAttempts),
|
|
306
|
+
30000
|
|
307
|
+
);
|
|
308
|
+
console.log(`Attempting to reconnect in ${delay / 1000}s...`);
|
|
294
309
|
|
|
295
|
-
this.connect();
|
|
296
|
-
}
|
|
310
|
+
this.reconnectTimeout = setTimeout(() => this.connect(onConnected), delay);
|
|
311
|
+
};
|
|
297
312
|
|
|
298
|
-
this.ws.on("message", (data: Buffer) => {
|
|
313
|
+
this.ws.on("message", (data: Buffer | string) => {
|
|
299
314
|
try {
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
315
|
+
const jsonStr = typeof data === "string" ? data : data.toString();
|
|
316
|
+
const parsedData = JSON.parse(jsonStr);
|
|
317
|
+
const includes = ["view", "coding", "refresh_key", "useActiveIdFocus", "activeId", "active_file", "figma"];
|
|
318
|
+
|
|
319
|
+
let newState: RoomStateData;
|
|
320
|
+
let changedKeys;
|
|
321
|
+
|
|
322
|
+
if (parsedData.type === "state") {
|
|
323
|
+
// Full state (e.g., on initial connect)
|
|
324
|
+
newState = parsedData.state;
|
|
325
|
+
changedKeys = Object.keys(newState).filter(
|
|
326
|
+
(key) => !isEqual(newState[key], this.state[key])
|
|
327
|
+
);
|
|
328
|
+
} else if (parsedData.type === "update") {
|
|
329
|
+
// Delta update
|
|
330
|
+
const delta = parsedData.state;
|
|
331
|
+
newState = { ...this.state, ...delta };
|
|
332
|
+
changedKeys = Object.keys(delta).filter(
|
|
333
|
+
(key) => !isEqual(delta[key], this.state[key])
|
|
334
|
+
);
|
|
335
|
+
} else {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const filteredChangedKeys = changedKeys.filter((key) => includes.includes(key));
|
|
340
|
+
if (filteredChangedKeys.length > 0) {
|
|
341
|
+
filteredChangedKeys.forEach((key) => {
|
|
342
|
+
const listener = find(this.onChangeListeners, (l) => l.key === key);
|
|
343
|
+
if (listener) {
|
|
344
|
+
listener.callback(newState[key]);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
this.state = newState;
|
|
305
348
|
}
|
|
306
349
|
} catch (e) {
|
|
307
350
|
console.error("Error processing message:", e);
|
|
308
351
|
}
|
|
309
352
|
});
|
|
310
353
|
|
|
311
|
-
this.ws.on("error", (err
|
|
354
|
+
this.ws.on("error", (err) => {
|
|
312
355
|
console.error("WebSocket error:", err);
|
|
313
356
|
this.ws?.close();
|
|
314
357
|
});
|
|
@@ -322,8 +365,6 @@ class RoomState {
|
|
|
322
365
|
state: { [key]: value },
|
|
323
366
|
})
|
|
324
367
|
);
|
|
325
|
-
} else {
|
|
326
|
-
console.log("Cannot update state: Not connected");
|
|
327
368
|
}
|
|
328
369
|
}
|
|
329
370
|
|
|
@@ -332,7 +373,7 @@ class RoomState {
|
|
|
332
373
|
clearTimeout(this.reconnectTimeout);
|
|
333
374
|
}
|
|
334
375
|
if (this.ws) {
|
|
335
|
-
this.ws.
|
|
376
|
+
this.ws.onclose = null; // Prevent reconnect on intentional close
|
|
336
377
|
this.ws.close();
|
|
337
378
|
}
|
|
338
379
|
}
|
|
@@ -346,7 +387,7 @@ class RoomState {
|
|
|
346
387
|
}
|
|
347
388
|
}
|
|
348
389
|
|
|
349
|
-
program.version("0.0.
|
|
390
|
+
program.version("0.0.15").description("Hapico CLI for project management");
|
|
350
391
|
|
|
351
392
|
program
|
|
352
393
|
.command("clone <id>")
|
|
@@ -431,23 +472,60 @@ program
|
|
|
431
472
|
console.error("You need to login first. Use 'hapico login' command.");
|
|
432
473
|
return;
|
|
433
474
|
}
|
|
434
|
-
const devSpinner
|
|
475
|
+
const devSpinner = ora(
|
|
435
476
|
"Starting the project in development mode..."
|
|
436
477
|
).start();
|
|
437
|
-
const pwd
|
|
438
|
-
const srcDir
|
|
478
|
+
const pwd = process.cwd();
|
|
479
|
+
const srcDir = path.join(pwd, "src");
|
|
439
480
|
if (!fs.existsSync(srcDir)) {
|
|
440
481
|
devSpinner.fail(
|
|
441
482
|
"Source directory 'src' does not exist. Please clone a project first."
|
|
442
483
|
);
|
|
443
484
|
return;
|
|
444
485
|
}
|
|
486
|
+
|
|
487
|
+
// Directory to store session config
|
|
488
|
+
const tmpDir = path.join(pwd, ".tmp");
|
|
489
|
+
const sessionConfigFile = path.join(tmpDir, "config.json");
|
|
490
|
+
|
|
491
|
+
// Ensure .tmp directory exists
|
|
492
|
+
if (!fs.existsSync(tmpDir)) {
|
|
493
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Function to get stored session ID
|
|
497
|
+
const getStoredSessionId = () => {
|
|
498
|
+
if (fs.existsSync(sessionConfigFile)) {
|
|
499
|
+
const data = fs.readFileSync(sessionConfigFile, { encoding: "utf8" });
|
|
500
|
+
const json = JSON.parse(data);
|
|
501
|
+
return json.sessionId || null;
|
|
502
|
+
}
|
|
503
|
+
return null;
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
// Function to save session ID
|
|
507
|
+
const saveSessionId = (sessionId: string) => {
|
|
508
|
+
fs.writeFileSync(
|
|
509
|
+
sessionConfigFile,
|
|
510
|
+
JSON.stringify({ sessionId }, null, 2),
|
|
511
|
+
{ encoding: "utf8" }
|
|
512
|
+
);
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// Get or generate session ID
|
|
516
|
+
let sessionId = getStoredSessionId();
|
|
517
|
+
if (!sessionId) {
|
|
518
|
+
sessionId = randomUUID();
|
|
519
|
+
saveSessionId(sessionId);
|
|
520
|
+
}
|
|
521
|
+
|
|
445
522
|
const info = JSON.stringify({
|
|
446
|
-
id:
|
|
523
|
+
id: sessionId,
|
|
447
524
|
createdAt: new Date().toISOString(),
|
|
448
525
|
viewId: getStoredProjectId(pwd),
|
|
449
526
|
});
|
|
450
|
-
|
|
527
|
+
|
|
528
|
+
// Convert info to base64
|
|
451
529
|
const projectId = Buffer.from(info).toString("base64");
|
|
452
530
|
if (!projectId) {
|
|
453
531
|
devSpinner.fail(
|
|
@@ -455,8 +533,9 @@ program
|
|
|
455
533
|
);
|
|
456
534
|
return;
|
|
457
535
|
}
|
|
536
|
+
|
|
458
537
|
console.log(`Connecting to WebSocket server`);
|
|
459
|
-
const room = new RoomState(`view_${projectId}
|
|
538
|
+
const room = new RoomState(`view_${projectId}`, []);
|
|
460
539
|
|
|
461
540
|
room.connect(async () => {
|
|
462
541
|
devSpinner.succeed("Project started in development mode!");
|
|
@@ -466,10 +545,10 @@ program
|
|
|
466
545
|
const initialFiles = fileManager.listFiles();
|
|
467
546
|
room.updateState("view", initialFiles);
|
|
468
547
|
|
|
469
|
-
fileManager.setOnFileChange((filePath
|
|
548
|
+
fileManager.setOnFileChange((filePath, content) => {
|
|
470
549
|
const es5 = compileES5(content, filePath);
|
|
471
550
|
console.log(`File changed: ${filePath?.replace(srcDir, ".")}`);
|
|
472
|
-
const updatedView =
|
|
551
|
+
const updatedView = initialFiles.map((file) => {
|
|
473
552
|
if (path.join(srcDir, file.path) === filePath) {
|
|
474
553
|
return { ...file, content, es5 };
|
|
475
554
|
}
|
|
@@ -478,7 +557,7 @@ program
|
|
|
478
557
|
room.updateState("view", updatedView);
|
|
479
558
|
});
|
|
480
559
|
|
|
481
|
-
//
|
|
560
|
+
// Fetch project info
|
|
482
561
|
const projectInfo = getStoredProjectId(pwd);
|
|
483
562
|
if (!projectInfo) {
|
|
484
563
|
console.error(
|
|
@@ -503,20 +582,17 @@ program
|
|
|
503
582
|
|
|
504
583
|
const projectType = project.data.type || "view";
|
|
505
584
|
|
|
506
|
-
console.log("Project type:", projectType);
|
|
507
|
-
|
|
508
585
|
if (projectType === "zalominiapp") {
|
|
509
586
|
QRCode.generate(
|
|
510
587
|
`https://zalo.me/s/3218692650896662017/player/${projectId}`,
|
|
511
588
|
{ small: true },
|
|
512
|
-
(qrcode
|
|
589
|
+
(qrcode) => {
|
|
513
590
|
console.log("Scan this QR code to connect to the project:");
|
|
514
591
|
console.log(qrcode);
|
|
515
592
|
}
|
|
516
593
|
);
|
|
517
594
|
return;
|
|
518
595
|
} else {
|
|
519
|
-
// show link https://com.ai.vn/dev_preview/{projectId}
|
|
520
596
|
const previewUrl = `https://com.ai.vn/dev_preview/${projectId}`;
|
|
521
597
|
console.log(
|
|
522
598
|
`Open this URL in your browser to preview the project: \n${previewUrl}`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hapico/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
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",
|