@hapico/cli 0.0.16 → 0.0.18

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
@@ -51,10 +51,15 @@ const Babel = __importStar(require("@babel/standalone"));
51
51
  const qrcode_terminal_1 = __importDefault(require("qrcode-terminal"));
52
52
  const open_1 = __importDefault(require("open"));
53
53
  const crypto_1 = require("crypto");
54
+ const child_process_1 = require("child_process");
55
+ const util_1 = require("util");
56
+ const chalk_1 = __importDefault(require("chalk"));
57
+ // Promisify exec for async usage
58
+ const execPromise = (0, util_1.promisify)(child_process_1.exec);
54
59
  // Directory to store the token and project config
55
60
  const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE || ".", ".hapico");
56
61
  const TOKEN_FILE = path.join(CONFIG_DIR, "auth_token.json");
57
- const connected = (0, ora_1.default)("Connected to WebSocket server");
62
+ const connected = (0, ora_1.default)(chalk_1.default.cyan("Connected to WebSocket server"));
58
63
  // Ensure config directory exists
59
64
  if (!fs.existsSync(CONFIG_DIR)) {
60
65
  fs.mkdirSync(CONFIG_DIR, { recursive: true });
@@ -176,7 +181,7 @@ class FileManager {
176
181
  hasChanged = existingContent !== file.content;
177
182
  }
178
183
  catch (readError) {
179
- console.warn(`Warning: Could not read existing file ${fullPath}, treating as changed:`, readError);
184
+ console.warn(chalk_1.default.yellow(`Warning: Could not read existing file ${fullPath}, treating as changed:`), readError);
180
185
  hasChanged = true;
181
186
  }
182
187
  }
@@ -185,7 +190,7 @@ class FileManager {
185
190
  }
186
191
  }
187
192
  catch (error) {
188
- console.error(`Error processing file ${file.path}:`, error);
193
+ console.error(chalk_1.default.red(`Error processing file ${file.path}:`), error);
189
194
  throw error;
190
195
  }
191
196
  }
@@ -205,7 +210,7 @@ class FileManager {
205
210
  }
206
211
  }
207
212
  catch (error) {
208
- console.warn(`Error reading changed file ${fullPath}:`, error);
213
+ console.warn(chalk_1.default.yellow(`Error reading changed file ${fullPath}:`), error);
209
214
  }
210
215
  }
211
216
  });
@@ -244,19 +249,19 @@ class RoomState {
244
249
  if (this.reconnectTimeout) {
245
250
  clearTimeout(this.reconnectTimeout);
246
251
  }
247
- this.ws = new ws_1.WebSocket(`https://base.myworkbeast.com/ws?room=${this.roomId}`);
252
+ this.ws = new ws_1.WebSocket(`https://ws2.myworkbeast.com/ws?room=${this.roomId}`);
248
253
  this.ws.onopen = () => {
249
- console.log(`Connected to room: ${this.roomId}`);
254
+ console.log(chalk_1.default.green(`Connected to room: ${this.roomId}`));
250
255
  this.isConnected = true;
251
256
  this.reconnectAttempts = 0;
252
257
  onConnected === null || onConnected === void 0 ? void 0 : onConnected(); // Call the onConnected callback if provided
253
258
  };
254
259
  this.ws.onclose = () => {
255
- console.log(`Disconnected from room: ${this.roomId}`);
260
+ console.log(chalk_1.default.yellow(`⚠ Disconnected from room: ${this.roomId}`));
256
261
  this.isConnected = false;
257
262
  this.reconnectAttempts++;
258
263
  const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
259
- console.log(`Attempting to reconnect in ${delay / 1000}s...`);
264
+ console.log(chalk_1.default.yellow(`Attempting to reconnect in ${delay / 1000}s...`));
260
265
  this.reconnectTimeout = setTimeout(() => this.connect(onConnected), delay);
261
266
  };
262
267
  this.ws.on("message", (data) => {
@@ -300,12 +305,12 @@ class RoomState {
300
305
  }
301
306
  }
302
307
  catch (e) {
303
- console.error("Error processing message:", e);
308
+ console.error(chalk_1.default.red("Error processing message:"), e);
304
309
  }
305
310
  });
306
311
  this.ws.on("error", (err) => {
307
312
  var _a;
308
- console.error("WebSocket error:", err);
313
+ console.error(chalk_1.default.red("WebSocket error:"), err);
309
314
  (_a = this.ws) === null || _a === void 0 ? void 0 : _a.close();
310
315
  });
311
316
  }
@@ -333,48 +338,48 @@ class RoomState {
333
338
  return this.isConnected;
334
339
  }
335
340
  }
336
- commander_1.program.version("0.0.16").description("Hapico CLI for project management");
341
+ commander_1.program.version("0.0.18").description("Hapico CLI for project management");
337
342
  commander_1.program
338
343
  .command("clone <id>")
339
344
  .description("Clone a project by ID")
340
345
  .action(async (id) => {
341
346
  const { accessToken } = getStoredToken();
342
347
  if (!accessToken) {
343
- console.error("You need to login first. Use 'hapico login' command.");
348
+ console.error(chalk_1.default.red("You need to login first. Use 'hapico login' command."));
344
349
  return;
345
350
  }
346
351
  const projectDir = path.resolve(process.cwd(), id);
347
352
  if (fs.existsSync(projectDir)) {
348
- console.error(`Project directory "${id}" already exists.`);
353
+ console.error(chalk_1.default.red(`✗ Project directory "${id}" already exists.`));
349
354
  return;
350
355
  }
351
356
  let files = [];
352
- const apiSpinner = (0, ora_1.default)("Fetching project data...").start();
357
+ const apiSpinner = (0, ora_1.default)(chalk_1.default.blue("Fetching project data...")).start();
353
358
  try {
354
359
  const response = await axios_1.default.get(`https://base.myworkbeast.com/api/views/${id}`);
355
360
  files = response.data.files || [];
356
- apiSpinner.succeed("Project data fetched successfully!");
357
- const templateSpinner = (0, ora_1.default)("Downloading template...").start();
361
+ apiSpinner.succeed(chalk_1.default.green("Project data fetched successfully!"));
362
+ const templateSpinner = (0, ora_1.default)(chalk_1.default.blue("Downloading template...")).start();
358
363
  const TEMPLATE_URL = "https://files.hcm04.vstorage.vngcloud.vn/assets/template_zalominiapp_devmode.zip";
359
364
  const templateResponse = await axios_1.default.get(TEMPLATE_URL, {
360
365
  responseType: "arraybuffer",
361
366
  });
362
- templateSpinner.succeed("Template downloaded successfully!");
367
+ templateSpinner.succeed(chalk_1.default.green("Template downloaded successfully!"));
363
368
  const outputDir = path.resolve(process.cwd(), id);
364
369
  if (!fs.existsSync(outputDir)) {
365
370
  fs.mkdirSync(outputDir);
366
371
  }
367
- const unzipSpinner = (0, ora_1.default)("Extracting template...").start();
372
+ const unzipSpinner = (0, ora_1.default)(chalk_1.default.blue("Extracting template...")).start();
368
373
  await unzipper_1.default.Open.buffer(templateResponse.data).then((directory) => directory.extract({ path: outputDir }));
369
- unzipSpinner.succeed("Template extracted successfully!");
374
+ unzipSpinner.succeed(chalk_1.default.green("Template extracted successfully!"));
370
375
  const macosxDir = path.join(process.cwd(), id, "__MACOSX");
371
376
  if (fs.existsSync(macosxDir)) {
372
377
  fs.rmSync(macosxDir, { recursive: true, force: true });
373
378
  }
374
379
  // Save project ID to hapico.config.json
375
380
  saveProjectId(outputDir, id);
376
- console.log("Project cloned successfully!");
377
- const saveSpinner = (0, ora_1.default)("Saving project files...").start();
381
+ console.log(chalk_1.default.green("Project cloned successfully!"));
382
+ const saveSpinner = (0, ora_1.default)(chalk_1.default.blue("Saving project files...")).start();
378
383
  files.forEach((file) => {
379
384
  const filePath = path.join(process.cwd(), id, "src", file.path);
380
385
  const dir = path.dirname(filePath);
@@ -383,11 +388,11 @@ commander_1.program
383
388
  }
384
389
  fs.writeFileSync(filePath, file.content);
385
390
  });
386
- saveSpinner.succeed("Project files saved successfully!");
387
- console.log(`Run 'cd ${id} && npm install && hapico dev' to start the project.`);
391
+ saveSpinner.succeed(chalk_1.default.green("Project files saved successfully!"));
392
+ console.log(chalk_1.default.cyan(`💡 Run ${chalk_1.default.bold("cd ${id} && npm install && hapico dev")} to start the project.`));
388
393
  }
389
394
  catch (error) {
390
- apiSpinner.fail(`Error cloning project: ${error.message}`);
395
+ apiSpinner.fail(chalk_1.default.red(`Error cloning project: ${error.message}`));
391
396
  }
392
397
  });
393
398
  commander_1.program
@@ -396,14 +401,14 @@ commander_1.program
396
401
  .action(() => {
397
402
  const { accessToken } = getStoredToken();
398
403
  if (!accessToken) {
399
- console.error("You need to login first. Use 'hapico login' command.");
404
+ console.error(chalk_1.default.red("You need to login first. Use 'hapico login' command."));
400
405
  return;
401
406
  }
402
- const devSpinner = (0, ora_1.default)("Starting the project in development mode...").start();
407
+ const devSpinner = (0, ora_1.default)(chalk_1.default.blue("Starting the project in development mode...")).start();
403
408
  const pwd = process.cwd();
404
409
  const srcDir = path.join(pwd, "src");
405
410
  if (!fs.existsSync(srcDir)) {
406
- devSpinner.fail("Source directory 'src' does not exist. Please clone a project first.");
411
+ devSpinner.fail(chalk_1.default.red("Source directory 'src' does not exist. Please clone a project first."));
407
412
  return;
408
413
  }
409
414
  // Directory to store session config
@@ -440,21 +445,21 @@ commander_1.program
440
445
  // Convert info to base64
441
446
  const projectId = Buffer.from(info).toString("base64");
442
447
  if (!projectId) {
443
- devSpinner.fail("Project ID not found. Please ensure hapico.config.json exists in the project directory.");
448
+ devSpinner.fail(chalk_1.default.red("Project ID not found. Please ensure hapico.config.json exists in the project directory."));
444
449
  return;
445
450
  }
446
- console.log(`Connecting to WebSocket server`);
451
+ console.log(chalk_1.default.cyan("🔗 Connecting to WebSocket server"));
447
452
  const room = new RoomState(`view_${projectId}`, []);
448
453
  const fileManager = new FileManager(srcDir);
449
454
  const initialFiles = fileManager.listFiles();
450
455
  room.files = initialFiles;
451
456
  room.connect(async () => {
452
- devSpinner.succeed("Project started in development mode!");
457
+ devSpinner.succeed(chalk_1.default.green("Project started in development mode!"));
453
458
  room.updateState("view", initialFiles);
454
459
  fileManager.setOnFileChange((filePath, content) => {
455
460
  var _a;
456
461
  const es5 = (_a = (0, exports.compileES5)(content, filePath)) !== null && _a !== void 0 ? _a : "";
457
- console.log(`File changed: ${filePath === null || filePath === void 0 ? void 0 : filePath.replace(srcDir, ".")}`);
462
+ console.log(chalk_1.default.yellow(`📝 File changed: ${filePath === null || filePath === void 0 ? void 0 : filePath.replace(srcDir, ".")}`));
458
463
  const updatedFiles = room.files.map((file) => {
459
464
  if (path.join(srcDir, file.path) === filePath) {
460
465
  return { ...file, content, es5 };
@@ -467,7 +472,7 @@ commander_1.program
467
472
  // Fetch project info
468
473
  const projectInfo = getStoredProjectId(pwd);
469
474
  if (!projectInfo) {
470
- console.error("Project ID not found. Please ensure hapico.config.json exists in the project directory.");
475
+ console.error(chalk_1.default.red("Project ID not found. Please ensure hapico.config.json exists in the project directory."));
471
476
  return;
472
477
  }
473
478
  const project = await axios_1.default.get(`https://base.myworkbeast.com/api/views/${projectInfo}`, {
@@ -477,20 +482,20 @@ commander_1.program
477
482
  },
478
483
  });
479
484
  if (project.status !== 200) {
480
- console.error(`Error fetching project info: ${project.statusText}`);
485
+ console.error(chalk_1.default.red(`✗ Error fetching project info: ${project.statusText}`));
481
486
  return;
482
487
  }
483
488
  const projectType = project.data.type || "view";
484
489
  if (projectType === "zalominiapp") {
485
490
  qrcode_terminal_1.default.generate(`https://zalo.me/s/3218692650896662017/player/${projectId}`, { small: true }, (qrcode) => {
486
- console.log("Scan this QR code to connect to the project:");
491
+ console.log(chalk_1.default.cyan("📱 Scan this QR code to connect to the project:"));
487
492
  console.log(qrcode);
488
493
  });
489
494
  return;
490
495
  }
491
496
  else {
492
497
  const previewUrl = `https://com.ai.vn/dev_preview/${projectId}`;
493
- console.log(`Open this URL in your browser to preview the project: \n${previewUrl}`);
498
+ console.log(chalk_1.default.cyan(`🌐 Open this URL in your browser to preview the project:\n${previewUrl}`));
494
499
  await (0, open_1.default)(previewUrl);
495
500
  }
496
501
  });
@@ -502,23 +507,34 @@ commander_1.program
502
507
  const data = getStoredToken();
503
508
  const { accessToken } = data || {};
504
509
  if (!accessToken) {
505
- console.error("You need to login first. Use 'hapico login' command.");
510
+ console.error(chalk_1.default.red("You need to login first. Use 'hapico login' command."));
506
511
  return;
507
512
  }
508
- const saveSpinner = (0, ora_1.default)("Saving project source code...").start();
513
+ const saveSpinner = (0, ora_1.default)(chalk_1.default.blue("Saving project source code...")).start();
509
514
  const pwd = process.cwd();
510
515
  const projectId = getStoredProjectId(pwd);
511
516
  if (!projectId) {
512
- saveSpinner.fail("Project ID not found. Please ensure hapico.config.json exists in the project directory.");
517
+ saveSpinner.fail(chalk_1.default.red("Project ID not found. Please ensure hapico.config.json exists in the project directory."));
513
518
  return;
514
519
  }
515
520
  const srcDir = path.join(pwd, "src");
516
521
  if (!fs.existsSync(srcDir)) {
517
- saveSpinner.fail("Source directory 'src' does not exist. Please clone a project first.");
522
+ saveSpinner.fail(chalk_1.default.red("Source directory 'src' does not exist. Please clone a project first."));
518
523
  return;
519
524
  }
520
525
  const fileManager = new FileManager(srcDir);
521
526
  const files = fileManager.listFiles();
527
+ // Nếu có file .env
528
+ const envFile = path.join(pwd, ".env");
529
+ console.log(chalk_1.default.cyan(`🔍 Checking for .env file at ${envFile}`));
530
+ if (fs.existsSync(envFile)) {
531
+ console.log(chalk_1.default.green(".env file found, including in push."));
532
+ const content = fs.readFileSync(envFile, { encoding: "utf8" });
533
+ files.push({
534
+ path: "./.env",
535
+ content,
536
+ });
537
+ }
522
538
  const apiUrl = `https://base.myworkbeast.com/api/views/${projectId}`;
523
539
  axios_1.default
524
540
  .put(apiUrl, {
@@ -532,27 +548,27 @@ commander_1.program
532
548
  },
533
549
  })
534
550
  .then(() => {
535
- saveSpinner.succeed("Project source code saved successfully!");
551
+ saveSpinner.succeed(chalk_1.default.green("Project source code saved successfully!"));
536
552
  })
537
553
  .catch((error) => {
538
- saveSpinner.fail(`Error saving project: ${error.message}`);
554
+ saveSpinner.fail(chalk_1.default.red(`✗ Error saving project: ${error.message}`));
539
555
  });
540
556
  });
541
557
  commander_1.program
542
558
  .command("login")
543
559
  .description("Login to the system")
544
560
  .action(async () => {
545
- console.log("Logging in to the system...");
546
- const loginSpinner = (0, ora_1.default)("Initiating login...").start();
561
+ console.log(chalk_1.default.cyan("🔐 Logging in to the system..."));
562
+ const loginSpinner = (0, ora_1.default)(chalk_1.default.blue("Initiating login...")).start();
547
563
  try {
548
564
  const response = await axios_1.default.post("https://auth.myworkbeast.com/auth/device");
549
565
  const { device_code, user_code, verification_url, expires_in, interval } = response.data;
550
- loginSpinner.succeed("Login initiated!");
551
- console.log(`Please open this URL in your browser: ${verification_url}`);
552
- console.log(`And enter this code: ${user_code}`);
553
- console.log("Waiting for authentication...");
566
+ loginSpinner.succeed(chalk_1.default.green("Login initiated!"));
567
+ console.log(chalk_1.default.cyan(`🌐 Please open this URL in your browser: ${verification_url}`));
568
+ console.log(chalk_1.default.yellow(`🔑 And enter this code: ${user_code}`));
569
+ console.log(chalk_1.default.blue("Waiting for authentication..."));
554
570
  await (0, open_1.default)(verification_url);
555
- const pollSpinner = (0, ora_1.default)("Waiting for authentication...").start();
571
+ const pollSpinner = (0, ora_1.default)(chalk_1.default.blue("Waiting for authentication...")).start();
556
572
  let tokens = null;
557
573
  const startTime = Date.now();
558
574
  while (Date.now() - startTime < expires_in * 1000) {
@@ -569,15 +585,15 @@ commander_1.program
569
585
  await new Promise((resolve) => setTimeout(resolve, interval * 1000));
570
586
  }
571
587
  if (tokens) {
572
- pollSpinner.succeed("Login successful!");
588
+ pollSpinner.succeed(chalk_1.default.green("Login successful!"));
573
589
  saveToken(tokens);
574
590
  }
575
591
  else {
576
- pollSpinner.fail("Login failed: Timeout or user did not complete authentication.");
592
+ pollSpinner.fail(chalk_1.default.red("Login failed: Timeout or user did not complete authentication."));
577
593
  }
578
594
  }
579
595
  catch (error) {
580
- loginSpinner.fail(`Login error: ${error.message}`);
596
+ loginSpinner.fail(chalk_1.default.red(`✗ Login error: ${error.message}`));
581
597
  }
582
598
  });
583
599
  commander_1.program
@@ -586,11 +602,11 @@ commander_1.program
586
602
  .action(() => {
587
603
  const accessToken = getStoredToken();
588
604
  if (!accessToken) {
589
- console.log("You are not logged in.");
605
+ console.log(chalk_1.default.yellow("You are not logged in."));
590
606
  return;
591
607
  }
592
608
  fs.unlinkSync(TOKEN_FILE);
593
- console.log("Logout successful!");
609
+ console.log(chalk_1.default.green("Logout successful!"));
594
610
  });
595
611
  // Pull command to fetch the latest project files from the server
596
612
  commander_1.program
@@ -599,16 +615,16 @@ commander_1.program
599
615
  .action(async () => {
600
616
  const token = getStoredToken();
601
617
  if (!token) {
602
- console.error("You need to login first. Use 'hapico login' command.");
618
+ console.error(chalk_1.default.red("You need to login first. Use 'hapico login' command."));
603
619
  return;
604
620
  }
605
621
  const pwd = process.cwd();
606
622
  const projectId = getStoredProjectId(pwd);
607
623
  if (!projectId) {
608
- console.error("Project ID not found. Please ensure hapico.config.json exists in the project directory.");
624
+ console.error(chalk_1.default.red("Project ID not found. Please ensure hapico.config.json exists in the project directory."));
609
625
  return;
610
626
  }
611
- const apiSpinner = (0, ora_1.default)("Fetching latest project files...").start();
627
+ const apiSpinner = (0, ora_1.default)(chalk_1.default.blue("Fetching latest project files...")).start();
612
628
  try {
613
629
  const response = await axios_1.default.get(`https://base.myworkbeast.com/api/views/${projectId}`, {
614
630
  headers: {
@@ -619,10 +635,262 @@ commander_1.program
619
635
  const files = response.data.files || [];
620
636
  const fileManager = new FileManager(path.join(pwd, "src"));
621
637
  fileManager.syncFiles(files);
622
- apiSpinner.succeed("Project files updated successfully!");
638
+ apiSpinner.succeed(chalk_1.default.green("Project files updated successfully!"));
639
+ }
640
+ catch (error) {
641
+ apiSpinner.fail(chalk_1.default.red(`✗ Error fetching project files: ${error.message}`));
642
+ }
643
+ });
644
+ // be {{id}} --be --port {{port}}
645
+ commander_1.program
646
+ .command("fetch <id>")
647
+ .option("--port <port>", "Port to run the backend", "3000")
648
+ .option("--serve", "Flag to indicate serving the backend")
649
+ .option("--libs <libs>", "Additional libraries to install (comma-separated)", "")
650
+ .option("--be", "Flag to indicate backend")
651
+ .description("Open backend for the project")
652
+ .action(async (id, options) => {
653
+ var _a;
654
+ const { accessToken } = getStoredToken();
655
+ if (!accessToken) {
656
+ console.error(chalk_1.default.red("✗ You need to login first. Use 'hapico login' command."));
657
+ return;
658
+ }
659
+ console.log(chalk_1.default.cyan(`🌐 PORT = ${options.port}`));
660
+ // Chọn hỏi user port để vận hành
661
+ let port = 3000;
662
+ const response = await axios_1.default.get(`https://base.myworkbeast.com/api/views/${id}`);
663
+ const portInput = options.port;
664
+ if (portInput) {
665
+ const parsedPort = parseInt(portInput, 10);
666
+ if (!isNaN(parsedPort)) {
667
+ port = parsedPort;
668
+ }
669
+ }
670
+ const projectDir = path.resolve(process.cwd(), id);
671
+ if (!fs.existsSync(projectDir)) {
672
+ // create folder
673
+ fs.mkdirSync(projectDir);
674
+ }
675
+ // download template https://main.hcm04.vstorage.vngcloud.vn/templates/hapico/hapico-basic.zip
676
+ const TEMPLATE_URL = `https://main.hcm04.vstorage.vngcloud.vn/templates/hapico/be.zip?t=${Date.now()}`;
677
+ let templateResponse = undefined;
678
+ try {
679
+ templateResponse = await axios_1.default.get(TEMPLATE_URL, {
680
+ responseType: "arraybuffer",
681
+ });
623
682
  }
624
683
  catch (error) {
625
- apiSpinner.fail(`Error fetching project files: ${error.message}`);
684
+ console.error(chalk_1.default.red("✗ Error downloading template:"), error.message);
685
+ return;
686
+ }
687
+ const outputDir = path.resolve(process.cwd(), id);
688
+ if (!fs.existsSync(outputDir)) {
689
+ fs.mkdirSync(outputDir);
690
+ }
691
+ await unzipper_1.default.Open.buffer(templateResponse.data).then((directory) => directory.extract({ path: outputDir }));
692
+ const macosxDir = path.join(process.cwd(), id, "__MACOSX");
693
+ if (fs.existsSync(macosxDir)) {
694
+ fs.rmSync(macosxDir, { recursive: true, force: true });
695
+ }
696
+ // outputPath/src/server.ts có dòng (3006) thay thành port
697
+ const serverFile = path.join(process.cwd(), id, "src", "index.ts");
698
+ if (fs.existsSync(serverFile)) {
699
+ let serverContent = fs.readFileSync(serverFile, { encoding: "utf8" });
700
+ serverContent = serverContent.split("(3006)").join(`(${port})`);
701
+ fs.writeFileSync(serverFile, serverContent, { encoding: "utf8" });
702
+ }
703
+ // lấy danh sách migrations
704
+ const MIGRATIONS_URL = `https://base.myworkbeast.com/api/client/query?dbCode=${response.data.dbCode || response.data.db_code}&table=migration_logs&operation=select&columns=%5B%22*%22%5D`;
705
+ // Lấy danh sách các migration đã chạy
706
+ let migrations = [];
707
+ const migrationsResponse = await axios_1.default.get(MIGRATIONS_URL, {
708
+ headers: {
709
+ Authorization: `Bearer ${accessToken}`,
710
+ "Content-Type": "application/json",
711
+ "x-db-code": response.data.dbCode || response.data.db_code,
712
+ },
713
+ });
714
+ if (migrationsResponse.status === 200) {
715
+ migrations = (((_a = migrationsResponse.data.data) === null || _a === void 0 ? void 0 : _a.rows) || []).map((item) => ({
716
+ created_at: item.created_at,
717
+ sql: item.sql_statement,
718
+ }));
719
+ }
720
+ console.log(chalk_1.default.cyan(`🔍 Found ${migrations.length} migrations.`));
721
+ // Tạo thư mục migrations nếu chưa tồn tại
722
+ const migrationsDir = path.join(process.cwd(), id, "migrations");
723
+ if (!fs.existsSync(migrationsDir)) {
724
+ fs.mkdirSync(migrationsDir);
725
+ }
726
+ // Lưu từng migration vào file
727
+ migrations.forEach((migration, index) => {
728
+ const timestamp = new Date(migration.created_at)
729
+ .toISOString()
730
+ .replace(/[-:]/g, "")
731
+ .replace(/\..+/, "");
732
+ const filename = path.join(migrationsDir, `${index + 1}_migration_${timestamp}.sql`);
733
+ fs.writeFileSync(filename, migration.sql, { encoding: "utf8" });
734
+ });
735
+ console.log(chalk_1.default.green("✓ Migrations saved successfully!"));
736
+ // Download project files and save to project
737
+ let files = [];
738
+ const apiSpinner = (0, ora_1.default)(chalk_1.default.blue("Fetching project data...")).start();
739
+ try {
740
+ files = response.data.files || [];
741
+ apiSpinner.succeed(chalk_1.default.green("Project data fetched successfully!"));
742
+ const saveSpinner = (0, ora_1.default)(chalk_1.default.blue("Saving project files...")).start();
743
+ files.forEach((file) => {
744
+ // skip ./app, ./components
745
+ if (file.path.startsWith("./app") ||
746
+ file.path.startsWith("./components")) {
747
+ return;
748
+ }
749
+ // hoặc các file có đuôi là .tsx
750
+ if (file.path.endsWith(".tsx")) {
751
+ return;
752
+ }
753
+ try {
754
+ if (file.content.trim().length == 0)
755
+ return;
756
+ const filePath = path.join(process.cwd(), id, "src", file.path);
757
+ const dir = path.dirname(filePath);
758
+ if (!fs.existsSync(dir)) {
759
+ fs.mkdirSync(dir, { recursive: true });
760
+ }
761
+ fs.writeFileSync(filePath, file.content);
762
+ }
763
+ catch (error) {
764
+ console.log(chalk_1.default.red("✗ Error writing file"), file.path, error);
765
+ }
766
+ });
767
+ saveSpinner.succeed(chalk_1.default.green("Project files saved successfully!"));
768
+ }
769
+ catch (error) {
770
+ apiSpinner.fail(chalk_1.default.red(`✗ Error cloning project: ${error.message}`));
771
+ }
772
+ // Download project files and save to project
773
+ console.log(chalk_1.default.green("✓ Project cloned successfully!"));
774
+ // Save project ID to hapico.config.json
775
+ saveProjectId(outputDir, id);
776
+ console.log(chalk_1.default.green("✓ Project setup successfully!"));
777
+ let serveProcess = null;
778
+ if (options.serve) {
779
+ // Run bun install
780
+ const { exec } = require("child_process");
781
+ const installSpinner = (0, ora_1.default)(chalk_1.default.blue("Installing dependencies...")).start();
782
+ // create a loop to check if there is new version of project
783
+ // https://main.hcm04.vstorage.vngcloud.vn/statics/{{id}}/version.json
784
+ let currentVersionStr = fs.existsSync(path.join(outputDir, "version.json"))
785
+ ? JSON.parse(fs.readFileSync(path.join(outputDir, "version.json"), {
786
+ encoding: "utf8",
787
+ })).version
788
+ : "0.0.1";
789
+ const checkVersion = async () => {
790
+ var _a, _b;
791
+ try {
792
+ // check if file version.json exists
793
+ const flyCheck = await axios_1.default.head(`https://statics.hcm04.vstorage.vngcloud.vn/${id}/version.json`);
794
+ if (flyCheck.status !== 200) {
795
+ return;
796
+ }
797
+ // get file version.json
798
+ const versionResponse = await axios_1.default.get(`https://statics.hcm04.vstorage.vngcloud.vn/${id}/version.json`);
799
+ const latestVersionData = versionResponse.data;
800
+ const latestVersion = latestVersionData.version;
801
+ if (latestVersion !== currentVersionStr) {
802
+ console.log(chalk_1.default.yellow(`📦 New version available: ${latestVersion}`));
803
+ // Save new version.json
804
+ fs.writeFileSync(path.join(outputDir, "version.json"), JSON.stringify(latestVersionData, null, 2), { encoding: "utf8" });
805
+ // Install external libraries if any
806
+ if (latestVersionData.external_libraries &&
807
+ latestVersionData.external_libraries.length > 0) {
808
+ console.log(chalk_1.default.blue("Installing new external libraries..."));
809
+ for (const lib of latestVersionData.external_libraries) {
810
+ try {
811
+ await execPromise(`bun add ${lib}`, { cwd: outputDir });
812
+ console.log(chalk_1.default.green(`Added ${lib}`));
813
+ }
814
+ catch (addError) {
815
+ console.error(chalk_1.default.red(`✗ Error adding ${lib}:`), addError.message);
816
+ }
817
+ }
818
+ }
819
+ // Rerun bun install
820
+ try {
821
+ await execPromise("bun install", { cwd: outputDir });
822
+ console.log(chalk_1.default.green("Dependencies reinstalled successfully!"));
823
+ }
824
+ catch (installError) {
825
+ console.error(chalk_1.default.red("✗ Error reinstalling dependencies:"), installError.message);
826
+ }
827
+ // Restart the process
828
+ if (serveProcess && !serveProcess.killed) {
829
+ console.log(chalk_1.default.blue("Restarting backend..."));
830
+ serveProcess.kill("SIGTERM");
831
+ }
832
+ // Start new process
833
+ const newServeProcess = exec("bun run start", { cwd: outputDir });
834
+ (_a = newServeProcess.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (data) => {
835
+ process.stdout.write(data);
836
+ });
837
+ (_b = newServeProcess.stderr) === null || _b === void 0 ? void 0 : _b.on("data", (data) => {
838
+ process.stderr.write(data);
839
+ });
840
+ newServeProcess.on("close", (code) => {
841
+ console.log(chalk_1.default.yellow(`⚠ backend exited with code ${code}`));
842
+ });
843
+ serveProcess = newServeProcess;
844
+ currentVersionStr = latestVersion;
845
+ }
846
+ }
847
+ catch (error) {
848
+ // If the remote version.json does not exist, do not update
849
+ console.error(chalk_1.default.yellow("⚠ Error checking for updates (skipping update):"), error.message);
850
+ }
851
+ };
852
+ // check every 3 seconds
853
+ setInterval(async () => await checkVersion(), 10 * 1000);
854
+ exec("bun install", { cwd: outputDir }, async (error, stdout, stderr) => {
855
+ if (error) {
856
+ installSpinner.fail(chalk_1.default.red(`✗ Error installing dependencies: ${error.message}`));
857
+ return;
858
+ }
859
+ if (stderr) {
860
+ console.error(chalk_1.default.red(`stderr: ${stderr}`));
861
+ }
862
+ installSpinner.succeed(chalk_1.default.green("Dependencies installed successfully!"));
863
+ // Install additional libraries if --libs is provided
864
+ if (options.libs && options.libs.trim()) {
865
+ const libsSpinner = (0, ora_1.default)(chalk_1.default.blue("Installing additional libraries...")).start();
866
+ const additionalLibs = options.libs
867
+ .split(",")
868
+ .map((lib) => lib.trim())
869
+ .filter((lib) => lib);
870
+ for (const lib of additionalLibs) {
871
+ try {
872
+ await execPromise(`bun add ${lib}`, { cwd: outputDir });
873
+ console.log(chalk_1.default.green(`Added ${lib}`));
874
+ }
875
+ catch (addError) {
876
+ console.error(chalk_1.default.red(`✗ Error adding ${lib}:`), addError.message);
877
+ }
878
+ }
879
+ libsSpinner.succeed(chalk_1.default.green("Additional libraries installed successfully!"));
880
+ }
881
+ // Run cd ${id} && bun run serve-be
882
+ console.log(chalk_1.default.blue("🚀 Starting backend"));
883
+ serveProcess = exec("bun run start", { cwd: outputDir });
884
+ serveProcess.stdout.on("data", (data) => {
885
+ process.stdout.write(data);
886
+ });
887
+ serveProcess.stderr.on("data", (data) => {
888
+ process.stderr.write(data);
889
+ });
890
+ serveProcess.on("close", (code) => {
891
+ console.log(chalk_1.default.yellow(`⚠ backend exited with code ${code}`));
892
+ });
893
+ });
626
894
  }
627
895
  });
628
896
  commander_1.program.parse(process.argv);