@hapico/cli 0.0.18 → 0.0.20
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 +279 -68
- package/bun.lock +6 -0
- package/dist/index.js +279 -68
- package/index.ts +368 -93
- package/package.json +3 -1
- package/test.tsx +0 -0
package/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
-
import { isEqual, find } from "lodash";
|
|
3
|
+
import { isEqual, find, map } from "lodash";
|
|
4
4
|
import { program } from "commander";
|
|
5
5
|
import axios from "axios";
|
|
6
6
|
import * as fs from "fs";
|
|
@@ -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);
|
|
@@ -74,14 +74,25 @@ const saveProjectId = (projectDir: string, id: string) => {
|
|
|
74
74
|
};
|
|
75
75
|
|
|
76
76
|
// Function to get stored project ID
|
|
77
|
-
const getStoredProjectId = (
|
|
77
|
+
const getStoredProjectId = (
|
|
78
|
+
projectDir: string
|
|
79
|
+
): {
|
|
80
|
+
projectId: string | null;
|
|
81
|
+
replicate?: string[];
|
|
82
|
+
} => {
|
|
78
83
|
const configFile = path.join(projectDir, "hapico.config.json");
|
|
79
84
|
if (fs.existsSync(configFile)) {
|
|
80
85
|
const data = fs.readFileSync(configFile, { encoding: "utf8" });
|
|
81
86
|
const json = JSON.parse(data);
|
|
82
|
-
return
|
|
87
|
+
return {
|
|
88
|
+
projectId: json.projectId || null,
|
|
89
|
+
replicate: json.replicate || [],
|
|
90
|
+
};
|
|
83
91
|
}
|
|
84
|
-
return
|
|
92
|
+
return {
|
|
93
|
+
projectId: null,
|
|
94
|
+
replicate: [],
|
|
95
|
+
};
|
|
85
96
|
};
|
|
86
97
|
|
|
87
98
|
// Khởi tạo cache bằng Map để lưu trữ kết quả compile
|
|
@@ -95,7 +106,7 @@ interface FileContent {
|
|
|
95
106
|
|
|
96
107
|
interface ApiResponse {
|
|
97
108
|
data: {
|
|
98
|
-
|
|
109
|
+
code: string;
|
|
99
110
|
type: "view" | "zalominiapp" | string;
|
|
100
111
|
dbCode: string;
|
|
101
112
|
db_code: string;
|
|
@@ -111,6 +122,14 @@ interface WebSocketMessage {
|
|
|
111
122
|
state?: RoomStateData;
|
|
112
123
|
}
|
|
113
124
|
|
|
125
|
+
export const tryJSONParse = (str: string): any => {
|
|
126
|
+
try {
|
|
127
|
+
return JSON.parse(str);
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
114
133
|
export const compileES5 = (code: string, filePath?: string) => {
|
|
115
134
|
if (filePath && !filePath.endsWith(".ts") && !filePath.endsWith(".tsx")) {
|
|
116
135
|
return code;
|
|
@@ -131,7 +150,8 @@ export const compileES5 = (code: string, filePath?: string) => {
|
|
|
131
150
|
|
|
132
151
|
compileCache.set(code, result.code || "");
|
|
133
152
|
return result.code;
|
|
134
|
-
} catch (error) {
|
|
153
|
+
} catch (error: any) {
|
|
154
|
+
console.log(chalk.red(`Error compiling code: ${error?.message}`));
|
|
135
155
|
return "";
|
|
136
156
|
}
|
|
137
157
|
};
|
|
@@ -303,18 +323,19 @@ class RoomState {
|
|
|
303
323
|
clearTimeout(this.reconnectTimeout);
|
|
304
324
|
}
|
|
305
325
|
|
|
306
|
-
this.ws = new WebSocket(
|
|
307
|
-
`https://ws2.myworkbeast.com/ws?room=${this.roomId}`
|
|
308
|
-
);
|
|
326
|
+
this.ws = new WebSocket(`wss://ws3.myworkbeast.com/ws?room=${this.roomId}`);
|
|
309
327
|
|
|
310
|
-
|
|
328
|
+
// Set binaryType to 'arraybuffer' to handle binary data
|
|
329
|
+
this.ws.binaryType = "arraybuffer";
|
|
330
|
+
|
|
331
|
+
this.ws.on("open", () => {
|
|
311
332
|
console.log(chalk.green(`Connected to room: ${this.roomId}`));
|
|
312
333
|
this.isConnected = true;
|
|
313
334
|
this.reconnectAttempts = 0;
|
|
314
335
|
onConnected?.(); // Call the onConnected callback if provided
|
|
315
|
-
};
|
|
336
|
+
});
|
|
316
337
|
|
|
317
|
-
this.ws.
|
|
338
|
+
this.ws.on("close", () => {
|
|
318
339
|
console.log(chalk.yellow(`⚠ Disconnected from room: ${this.roomId}`));
|
|
319
340
|
this.isConnected = false;
|
|
320
341
|
|
|
@@ -328,11 +349,20 @@ class RoomState {
|
|
|
328
349
|
() => this.connect(onConnected),
|
|
329
350
|
delay
|
|
330
351
|
);
|
|
331
|
-
};
|
|
352
|
+
});
|
|
332
353
|
|
|
333
|
-
this.ws.on("message", (data:
|
|
354
|
+
this.ws.on("message", (data: any) => {
|
|
334
355
|
try {
|
|
335
|
-
|
|
356
|
+
let jsonStr: string;
|
|
357
|
+
if (data instanceof ArrayBuffer) {
|
|
358
|
+
jsonStr = pako.inflate(data, { to: "string" });
|
|
359
|
+
} else if (typeof data === "string") {
|
|
360
|
+
jsonStr = data;
|
|
361
|
+
} else if (Buffer.isBuffer(data)) {
|
|
362
|
+
jsonStr = pako.inflate(data, { to: "string" });
|
|
363
|
+
} else {
|
|
364
|
+
jsonStr = data.toString(); // Fallback nếu không nén
|
|
365
|
+
}
|
|
336
366
|
const parsedData = JSON.parse(jsonStr);
|
|
337
367
|
const includes = [
|
|
338
368
|
"view",
|
|
@@ -389,12 +419,12 @@ class RoomState {
|
|
|
389
419
|
|
|
390
420
|
updateState(key: string, value: any): void {
|
|
391
421
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
);
|
|
422
|
+
const message = JSON.stringify({
|
|
423
|
+
type: "update",
|
|
424
|
+
state: { [key]: value },
|
|
425
|
+
});
|
|
426
|
+
const compressed = pako.deflate(message); // Nén dữ liệu
|
|
427
|
+
this.ws.send(compressed); // Gửi binary
|
|
398
428
|
}
|
|
399
429
|
}
|
|
400
430
|
|
|
@@ -403,7 +433,7 @@ class RoomState {
|
|
|
403
433
|
clearTimeout(this.reconnectTimeout);
|
|
404
434
|
}
|
|
405
435
|
if (this.ws) {
|
|
406
|
-
this.ws.
|
|
436
|
+
this.ws.removeAllListeners("close"); // Prevent reconnect on intentional close
|
|
407
437
|
this.ws.close();
|
|
408
438
|
}
|
|
409
439
|
}
|
|
@@ -417,7 +447,7 @@ class RoomState {
|
|
|
417
447
|
}
|
|
418
448
|
}
|
|
419
449
|
|
|
420
|
-
program.version("0.0.
|
|
450
|
+
program.version("0.0.20").description("Hapico CLI for project management");
|
|
421
451
|
|
|
422
452
|
program
|
|
423
453
|
.command("clone <id>")
|
|
@@ -443,7 +473,12 @@ program
|
|
|
443
473
|
const response: ApiResponse = await axios.get(
|
|
444
474
|
`https://base.myworkbeast.com/api/views/${id}`
|
|
445
475
|
);
|
|
446
|
-
|
|
476
|
+
const code = response?.data?.code;
|
|
477
|
+
const decompressedCode = pako.inflate(
|
|
478
|
+
Uint8Array.from(atob(code), (c) => c.charCodeAt(0)),
|
|
479
|
+
{ to: "string" }
|
|
480
|
+
);
|
|
481
|
+
files = tryJSONParse(decompressedCode)?.files || [];
|
|
447
482
|
apiSpinner.succeed(chalk.green("Project data fetched successfully!"));
|
|
448
483
|
|
|
449
484
|
const templateSpinner: Ora = ora(
|
|
@@ -566,7 +601,7 @@ program
|
|
|
566
601
|
const info = JSON.stringify({
|
|
567
602
|
id: sessionId,
|
|
568
603
|
createdAt: new Date().toISOString(),
|
|
569
|
-
viewId: getStoredProjectId(pwd),
|
|
604
|
+
viewId: getStoredProjectId(pwd)?.projectId,
|
|
570
605
|
});
|
|
571
606
|
|
|
572
607
|
// Convert info to base64
|
|
@@ -584,12 +619,47 @@ program
|
|
|
584
619
|
const room = new RoomState(`view_${projectId}`, []);
|
|
585
620
|
const fileManager = new FileManager(srcDir);
|
|
586
621
|
const initialFiles = fileManager.listFiles();
|
|
587
|
-
|
|
622
|
+
// get tsconfig.json
|
|
623
|
+
const tsconfigPath = path.join(srcDir, "..", "tsconfig.json");
|
|
624
|
+
if (fs.existsSync(tsconfigPath)) {
|
|
625
|
+
const content = fs.readFileSync(tsconfigPath, { encoding: "utf8" });
|
|
626
|
+
initialFiles.push({
|
|
627
|
+
path: "./tsconfig.json",
|
|
628
|
+
content,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
// Remove All binary files
|
|
632
|
+
const supportExtensions = [
|
|
633
|
+
".ts",
|
|
634
|
+
".tsx",
|
|
635
|
+
".js",
|
|
636
|
+
".jsx",
|
|
637
|
+
".json",
|
|
638
|
+
".css",
|
|
639
|
+
".html",
|
|
640
|
+
".env",
|
|
641
|
+
".env.local",
|
|
642
|
+
".env.development",
|
|
643
|
+
".env.production",
|
|
644
|
+
".jsonc",
|
|
645
|
+
".yml",
|
|
646
|
+
".yaml",
|
|
647
|
+
".md",
|
|
648
|
+
".markdown",
|
|
649
|
+
".txt",
|
|
650
|
+
".xml",
|
|
651
|
+
".config",
|
|
652
|
+
];
|
|
653
|
+
const filteredFiles = initialFiles.filter((file) => {
|
|
654
|
+
return supportExtensions.some((ext) => file.path.endsWith(ext));
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
room.files = filteredFiles;
|
|
588
658
|
|
|
589
659
|
room.connect(async () => {
|
|
590
660
|
devSpinner.succeed(chalk.green("Project started in development mode!"));
|
|
591
661
|
|
|
592
|
-
room.updateState("view",
|
|
662
|
+
room.updateState("view", filteredFiles);
|
|
593
663
|
|
|
594
664
|
fileManager.setOnFileChange((filePath, content) => {
|
|
595
665
|
const es5 = compileES5(content, filePath) ?? "";
|
|
@@ -607,8 +677,8 @@ program
|
|
|
607
677
|
});
|
|
608
678
|
|
|
609
679
|
// Fetch project info
|
|
610
|
-
const
|
|
611
|
-
if (!
|
|
680
|
+
const store = getStoredProjectId(pwd);
|
|
681
|
+
if (!store.projectId) {
|
|
612
682
|
console.error(
|
|
613
683
|
chalk.red(
|
|
614
684
|
"✗ Project ID not found. Please ensure hapico.config.json exists in the project directory."
|
|
@@ -617,7 +687,7 @@ program
|
|
|
617
687
|
return;
|
|
618
688
|
}
|
|
619
689
|
const project = await axios.get(
|
|
620
|
-
`https://base.myworkbeast.com/api/views/${
|
|
690
|
+
`https://base.myworkbeast.com/api/views/${store.projectId}`,
|
|
621
691
|
{
|
|
622
692
|
headers: {
|
|
623
693
|
Authorization: `Bearer ${accessToken}`,
|
|
@@ -637,7 +707,7 @@ program
|
|
|
637
707
|
|
|
638
708
|
if (projectType === "zalominiapp") {
|
|
639
709
|
QRCode.generate(
|
|
640
|
-
`https://zalo.me/s/3218692650896662017/player/${projectId}`,
|
|
710
|
+
`https://zalo.me/s/3218692650896662017/player/${projectId}?env=TESTING&version=75`,
|
|
641
711
|
{ small: true },
|
|
642
712
|
(qrcode) => {
|
|
643
713
|
console.log(
|
|
@@ -659,23 +729,95 @@ program
|
|
|
659
729
|
});
|
|
660
730
|
});
|
|
661
731
|
|
|
732
|
+
// Hàm tái sử dụng để push mã nguồn lên server
|
|
733
|
+
async function pushProject(spinner: Ora, projectId: string): Promise<boolean> {
|
|
734
|
+
const data = getStoredToken();
|
|
735
|
+
const { accessToken } = data || {};
|
|
736
|
+
if (!accessToken) {
|
|
737
|
+
spinner.fail(
|
|
738
|
+
chalk.red("✗ You need to login first. Use 'hapico login' command.")
|
|
739
|
+
);
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
742
|
+
const pwd = process.cwd();
|
|
743
|
+
const srcDir: string = path.join(pwd, "src");
|
|
744
|
+
if (!fs.existsSync(srcDir)) {
|
|
745
|
+
spinner.fail(
|
|
746
|
+
chalk.red(
|
|
747
|
+
"✗ Source directory 'src' does not exist. Please clone a project first."
|
|
748
|
+
)
|
|
749
|
+
);
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
const fileManager = new FileManager(srcDir);
|
|
753
|
+
const files = fileManager.listFiles();
|
|
754
|
+
|
|
755
|
+
// Supported files
|
|
756
|
+
const SUPPORT_FILES = [
|
|
757
|
+
"./.env",
|
|
758
|
+
"./.env.local",
|
|
759
|
+
"./.env.development",
|
|
760
|
+
"./.env.production",
|
|
761
|
+
"./package.json",
|
|
762
|
+
"./tsconfig.json",
|
|
763
|
+
];
|
|
764
|
+
|
|
765
|
+
// Include supported files
|
|
766
|
+
SUPPORT_FILES.forEach((relativePath) => {
|
|
767
|
+
const fullPath = path.join(pwd, relativePath);
|
|
768
|
+
if (fs.existsSync(fullPath)) {
|
|
769
|
+
console.log(
|
|
770
|
+
chalk.green(
|
|
771
|
+
`Including ${relativePath} in push for project ${projectId}.`
|
|
772
|
+
)
|
|
773
|
+
);
|
|
774
|
+
const content = fs.readFileSync(fullPath, { encoding: "utf8" });
|
|
775
|
+
files.push({
|
|
776
|
+
path: relativePath,
|
|
777
|
+
content,
|
|
778
|
+
es5: compileES5(content, fullPath) ?? "",
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
spinner.text = `Pushing project source code to server for project ${projectId}...`;
|
|
783
|
+
|
|
784
|
+
const apiUrl = `https://base.myworkbeast.com/api/views/${projectId}`;
|
|
785
|
+
try {
|
|
786
|
+
await axios.put(
|
|
787
|
+
apiUrl,
|
|
788
|
+
{
|
|
789
|
+
code: JSON.stringify({
|
|
790
|
+
files,
|
|
791
|
+
}),
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
headers: {
|
|
795
|
+
Authorization: `Bearer ${accessToken}`,
|
|
796
|
+
"Content-Type": "application/json",
|
|
797
|
+
},
|
|
798
|
+
}
|
|
799
|
+
);
|
|
800
|
+
return true;
|
|
801
|
+
} catch (error) {
|
|
802
|
+
spinner.fail(
|
|
803
|
+
chalk.red(
|
|
804
|
+
`✗ Error saving project ${projectId}: ${(error as Error).message}`
|
|
805
|
+
)
|
|
806
|
+
);
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
662
811
|
program
|
|
663
812
|
.command("push")
|
|
664
813
|
.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
|
-
}
|
|
814
|
+
.action(async () => {
|
|
674
815
|
const saveSpinner: Ora = ora(
|
|
675
816
|
chalk.blue("Saving project source code...")
|
|
676
817
|
).start();
|
|
677
818
|
const pwd = process.cwd();
|
|
678
|
-
const projectId = getStoredProjectId(pwd);
|
|
819
|
+
const { projectId, replicate } = getStoredProjectId(pwd);
|
|
820
|
+
|
|
679
821
|
if (!projectId) {
|
|
680
822
|
saveSpinner.fail(
|
|
681
823
|
chalk.red(
|
|
@@ -684,53 +826,40 @@ program
|
|
|
684
826
|
);
|
|
685
827
|
return;
|
|
686
828
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
829
|
+
|
|
830
|
+
// Push to the main project
|
|
831
|
+
const mainSuccess = await pushProject(saveSpinner, projectId);
|
|
832
|
+
let allSuccess = mainSuccess;
|
|
833
|
+
|
|
834
|
+
// Push to replicated projects if replicate array exists
|
|
835
|
+
if (replicate && Array.isArray(replicate) && replicate.length > 0) {
|
|
836
|
+
saveSpinner.text = chalk.blue("Pushing to replicated projects...");
|
|
837
|
+
for (const repId of replicate) {
|
|
838
|
+
const success = await pushProject(saveSpinner, repId);
|
|
839
|
+
if (!success) {
|
|
840
|
+
allSuccess = false;
|
|
841
|
+
console.warn(
|
|
842
|
+
chalk.yellow(
|
|
843
|
+
`⚠ Failed to push to replicated project ${repId}. Continuing...`
|
|
844
|
+
)
|
|
845
|
+
);
|
|
846
|
+
} else {
|
|
847
|
+
console.log(
|
|
848
|
+
chalk.green(`✓ Successfully pushed to replicated project ${repId}.`)
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
695
852
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
files.push({
|
|
706
|
-
path: "./.env",
|
|
707
|
-
content,
|
|
708
|
-
});
|
|
853
|
+
|
|
854
|
+
if (allSuccess) {
|
|
855
|
+
saveSpinner.succeed(
|
|
856
|
+
chalk.green("Project source code saved successfully to all projects!")
|
|
857
|
+
);
|
|
858
|
+
} else {
|
|
859
|
+
saveSpinner.warn(
|
|
860
|
+
chalk.yellow("Project source code saved with some errors.")
|
|
861
|
+
);
|
|
709
862
|
}
|
|
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
863
|
});
|
|
735
864
|
|
|
736
865
|
program
|
|
@@ -821,7 +950,7 @@ program
|
|
|
821
950
|
return;
|
|
822
951
|
}
|
|
823
952
|
const pwd: string = process.cwd();
|
|
824
|
-
const projectId = getStoredProjectId(pwd);
|
|
953
|
+
const { projectId } = getStoredProjectId(pwd);
|
|
825
954
|
if (!projectId) {
|
|
826
955
|
console.error(
|
|
827
956
|
chalk.red(
|
|
@@ -835,15 +964,22 @@ program
|
|
|
835
964
|
).start();
|
|
836
965
|
try {
|
|
837
966
|
const response: ApiResponse = await axios.get(
|
|
838
|
-
`https://base.myworkbeast.com/api/views/${projectId}`,
|
|
967
|
+
`https://base.myworkbeast.com/api/views/v3/${projectId}`,
|
|
839
968
|
{
|
|
840
969
|
headers: {
|
|
841
|
-
Authorization: `Bearer ${token}`,
|
|
970
|
+
Authorization: `Bearer ${token.accessToken}`,
|
|
842
971
|
"Content-Type": "application/json",
|
|
843
972
|
},
|
|
844
973
|
}
|
|
845
974
|
);
|
|
846
|
-
|
|
975
|
+
|
|
976
|
+
const code = response?.data?.code;
|
|
977
|
+
const decompressedCode = pako.inflate(
|
|
978
|
+
Uint8Array.from(atob(code), (c) => c.charCodeAt(0)),
|
|
979
|
+
{ to: "string" }
|
|
980
|
+
);
|
|
981
|
+
|
|
982
|
+
const files: FileContent[] = tryJSONParse(decompressedCode)?.files || [];
|
|
847
983
|
const fileManager = new FileManager(path.join(pwd, "src"));
|
|
848
984
|
fileManager.syncFiles(files);
|
|
849
985
|
apiSpinner.succeed(chalk.green("Project files updated successfully!"));
|
|
@@ -982,7 +1118,9 @@ program
|
|
|
982
1118
|
migrationsDir,
|
|
983
1119
|
`${index + 1}_migration_${timestamp}.sql`
|
|
984
1120
|
);
|
|
985
|
-
fs.writeFileSync(filename, (migration as any).sql, {
|
|
1121
|
+
fs.writeFileSync(filename, (migration as any).sql, {
|
|
1122
|
+
encoding: "utf8",
|
|
1123
|
+
});
|
|
986
1124
|
});
|
|
987
1125
|
|
|
988
1126
|
console.log(chalk.green("✓ Migrations saved successfully!"));
|
|
@@ -994,7 +1132,6 @@ program
|
|
|
994
1132
|
).start();
|
|
995
1133
|
|
|
996
1134
|
try {
|
|
997
|
-
files = response.data.files || [];
|
|
998
1135
|
apiSpinner.succeed(chalk.green("Project data fetched successfully!"));
|
|
999
1136
|
|
|
1000
1137
|
const saveSpinner: Ora = ora(
|
|
@@ -1213,4 +1350,142 @@ program
|
|
|
1213
1350
|
}
|
|
1214
1351
|
);
|
|
1215
1352
|
|
|
1353
|
+
// hapico publish
|
|
1354
|
+
program.command("publish").action(async () => {
|
|
1355
|
+
const publishSpinner: Ora = ora(
|
|
1356
|
+
chalk.blue("Publishing to Hapico...")
|
|
1357
|
+
).start();
|
|
1358
|
+
const pwd = process.cwd();
|
|
1359
|
+
const { projectId, replicate } = getStoredProjectId(pwd);
|
|
1360
|
+
|
|
1361
|
+
if (!projectId) {
|
|
1362
|
+
publishSpinner.fail(
|
|
1363
|
+
chalk.red(
|
|
1364
|
+
"✗ Project ID not found. Please ensure hapico.config.json exists in the project directory."
|
|
1365
|
+
)
|
|
1366
|
+
);
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// Step 1: Push source code to main project and replicas
|
|
1371
|
+
const pushSuccess = await pushProject(publishSpinner, projectId);
|
|
1372
|
+
if (!pushSuccess) {
|
|
1373
|
+
return; // Stop if push to main project fails
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// Push to replicated projects
|
|
1377
|
+
let allPushSuccess = true;
|
|
1378
|
+
if (replicate && Array.isArray(replicate) && replicate.length > 0) {
|
|
1379
|
+
publishSpinner.text = chalk.blue("Pushing to replicated projects...");
|
|
1380
|
+
for (const repId of replicate) {
|
|
1381
|
+
const success = await pushProject(publishSpinner, repId);
|
|
1382
|
+
if (!success) {
|
|
1383
|
+
allPushSuccess = false;
|
|
1384
|
+
console.warn(
|
|
1385
|
+
chalk.yellow(
|
|
1386
|
+
`⚠ Failed to push to replicated project ${repId}. Continuing...`
|
|
1387
|
+
)
|
|
1388
|
+
);
|
|
1389
|
+
} else {
|
|
1390
|
+
console.log(
|
|
1391
|
+
chalk.green(`✓ Successfully pushed to replicated project ${repId}.`)
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// Step 2: Publish main project
|
|
1398
|
+
const { accessToken } = getStoredToken();
|
|
1399
|
+
const apiUrl = "https://base.myworkbeast.com/api/views/publish";
|
|
1400
|
+
let allPublishSuccess = true;
|
|
1401
|
+
try {
|
|
1402
|
+
await axios.post(
|
|
1403
|
+
apiUrl,
|
|
1404
|
+
{ view_id: parseInt(projectId, 10) },
|
|
1405
|
+
{
|
|
1406
|
+
headers: {
|
|
1407
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1408
|
+
"Content-Type": "application/json",
|
|
1409
|
+
},
|
|
1410
|
+
}
|
|
1411
|
+
);
|
|
1412
|
+
publishSpinner.succeed(
|
|
1413
|
+
chalk.green(`Project ${projectId} published successfully!`)
|
|
1414
|
+
);
|
|
1415
|
+
} catch (error) {
|
|
1416
|
+
allPublishSuccess = false;
|
|
1417
|
+
publishSpinner.fail(
|
|
1418
|
+
chalk.red(
|
|
1419
|
+
`✗ Error publishing project ${projectId}: ${(error as Error).message}`
|
|
1420
|
+
)
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// Step 3: Publish replicated projects
|
|
1425
|
+
if (replicate && Array.isArray(replicate) && replicate.length > 0) {
|
|
1426
|
+
publishSpinner.text = chalk.blue("Publishing replicated projects...");
|
|
1427
|
+
for (const repId of replicate) {
|
|
1428
|
+
try {
|
|
1429
|
+
await axios.post(
|
|
1430
|
+
apiUrl,
|
|
1431
|
+
{ view_id: parseInt(repId, 10) },
|
|
1432
|
+
{
|
|
1433
|
+
headers: {
|
|
1434
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1435
|
+
"Content-Type": "application/json",
|
|
1436
|
+
},
|
|
1437
|
+
}
|
|
1438
|
+
);
|
|
1439
|
+
console.log(
|
|
1440
|
+
chalk.green(`✓ Successfully published replicated project ${repId}.`)
|
|
1441
|
+
);
|
|
1442
|
+
} catch (error) {
|
|
1443
|
+
allPublishSuccess = false;
|
|
1444
|
+
console.warn(
|
|
1445
|
+
chalk.yellow(
|
|
1446
|
+
`⚠ Error publishing replicated project ${repId}: ${(error as Error).message}`
|
|
1447
|
+
)
|
|
1448
|
+
);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
if (allPushSuccess && allPublishSuccess) {
|
|
1454
|
+
publishSpinner.succeed(chalk.green("All projects published successfully!"));
|
|
1455
|
+
} else {
|
|
1456
|
+
publishSpinner.warn(chalk.yellow("Publishing completed with some errors."));
|
|
1457
|
+
}
|
|
1458
|
+
});
|
|
1459
|
+
|
|
1460
|
+
program.command("mirror").action(() => {
|
|
1461
|
+
console.log(chalk.cyan("🌐 Starting mirror mode..."));
|
|
1462
|
+
const pwd = process.cwd();
|
|
1463
|
+
const srcDir = path.join(pwd, "src");
|
|
1464
|
+
if (!fs.existsSync(srcDir)) {
|
|
1465
|
+
console.error(
|
|
1466
|
+
chalk.red(
|
|
1467
|
+
"✗ Source directory 'src' does not exist. Please clone a project first."
|
|
1468
|
+
)
|
|
1469
|
+
);
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
const fileManager = new FileManager(srcDir);
|
|
1474
|
+
const initialFiles = fileManager.listFiles();
|
|
1475
|
+
|
|
1476
|
+
// Lấy danh sách file và viết ra 1 file .txt
|
|
1477
|
+
let content = ``;
|
|
1478
|
+
map(initialFiles, (file) => {
|
|
1479
|
+
content += `\`\`\`typescript
|
|
1480
|
+
// Path: ${file.path}
|
|
1481
|
+
${file.content}
|
|
1482
|
+
|
|
1483
|
+
\`\`\`
|
|
1484
|
+
`;
|
|
1485
|
+
});
|
|
1486
|
+
const outputFile = path.join(pwd, "hapico_files.txt");
|
|
1487
|
+
fs.writeFileSync(outputFile, content, { encoding: "utf8" });
|
|
1488
|
+
console.log(chalk.green(`✓ File list saved to ${outputFile}`));
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1216
1491
|
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.20",
|
|
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",
|
package/test.tsx
ADDED
|
File without changes
|