@hapico/cli 0.0.30 → 0.0.31
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 +382 -490
- package/bin/tools/nativewind/index.js +80 -0
- package/bin/tools/vibe/index.js +79 -13
- package/bin/tools/vibe/patch.utils.js +243 -0
- package/bun.lock +874 -0
- package/dist/index.js +382 -490
- package/dist/tools/nativewind/index.js +80 -0
- package/dist/tools/vibe/index.js +79 -13
- package/dist/tools/vibe/patch.utils.js +243 -0
- package/index.ts +475 -787
- package/package.json +5 -1
- package/tools/nativewind/index.ts +87 -0
- package/tools/vibe/index.ts +82 -13
- package/tools/vibe/patch.utils.ts +273 -0
package/bin/index.js
CHANGED
|
@@ -56,6 +56,7 @@ const util_1 = require("util");
|
|
|
56
56
|
const chalk_1 = __importDefault(require("chalk"));
|
|
57
57
|
const pako_1 = __importDefault(require("pako"));
|
|
58
58
|
const vibe_1 = require("./tools/vibe");
|
|
59
|
+
const nativewind_1 = require("./tools/nativewind");
|
|
59
60
|
// Promisify exec for async usage
|
|
60
61
|
const execPromise = (0, util_1.promisify)(child_process_1.exec);
|
|
61
62
|
// Directory to store the token and project config
|
|
@@ -113,7 +114,7 @@ const getStoredProjectId = (projectDir) => {
|
|
|
113
114
|
replicate: [],
|
|
114
115
|
};
|
|
115
116
|
};
|
|
116
|
-
//
|
|
117
|
+
// Initialize cache using Map to store compilation results
|
|
117
118
|
const compileCache = new Map();
|
|
118
119
|
const tryJSONParse = (str) => {
|
|
119
120
|
try {
|
|
@@ -173,7 +174,7 @@ class FileManager {
|
|
|
173
174
|
}
|
|
174
175
|
}
|
|
175
176
|
replaceSplashWithSeparator(filePath) {
|
|
176
|
-
//
|
|
177
|
+
// Replace "\" with "/" in the file path
|
|
177
178
|
return filePath.replace(/\\/g, "/");
|
|
178
179
|
}
|
|
179
180
|
listFiles() {
|
|
@@ -186,11 +187,11 @@ class FileManager {
|
|
|
186
187
|
traverseDirectory(fullPath);
|
|
187
188
|
}
|
|
188
189
|
else if (entry.isFile()) {
|
|
189
|
-
// skip
|
|
190
|
+
// skip files in ignored folders
|
|
190
191
|
if (ignoreFolders.some((folder) => fullPath.includes(folder))) {
|
|
191
192
|
return;
|
|
192
193
|
}
|
|
193
|
-
//
|
|
194
|
+
// only support files located in ./src
|
|
194
195
|
if (!fullPath.includes("/src/") && !fullPath.includes("\\src\\")) {
|
|
195
196
|
return;
|
|
196
197
|
}
|
|
@@ -310,7 +311,9 @@ class RoomState {
|
|
|
310
311
|
// Set binaryType to 'arraybuffer' to handle binary data
|
|
311
312
|
this.ws.binaryType = "arraybuffer";
|
|
312
313
|
this.ws.on("open", () => {
|
|
313
|
-
|
|
314
|
+
if (this.reconnectAttempts > 0) {
|
|
315
|
+
console.log(chalk_1.default.greenBright(`\n✨ Sync connection restored for session: ${this.roomId}`));
|
|
316
|
+
}
|
|
314
317
|
this.isConnected = true;
|
|
315
318
|
this.reconnectAttempts = 0;
|
|
316
319
|
onConnected === null || onConnected === void 0 ? void 0 : onConnected(); // Call the onConnected callback if provided
|
|
@@ -336,7 +339,7 @@ class RoomState {
|
|
|
336
339
|
jsonStr = pako_1.default.inflate(data, { to: "string" });
|
|
337
340
|
}
|
|
338
341
|
else {
|
|
339
|
-
jsonStr = data.toString(); // Fallback
|
|
342
|
+
jsonStr = data.toString(); // Fallback if not compressed
|
|
340
343
|
}
|
|
341
344
|
const parsedData = JSON.parse(jsonStr);
|
|
342
345
|
const includes = [
|
|
@@ -391,8 +394,8 @@ class RoomState {
|
|
|
391
394
|
type: "update",
|
|
392
395
|
state: { [key]: value },
|
|
393
396
|
});
|
|
394
|
-
const compressed = pako_1.default.deflate(message); //
|
|
395
|
-
this.ws.send(compressed); //
|
|
397
|
+
const compressed = pako_1.default.deflate(message); // Compress state data
|
|
398
|
+
this.ws.send(compressed); // Send binary payload
|
|
396
399
|
}
|
|
397
400
|
}
|
|
398
401
|
disconnect() {
|
|
@@ -411,92 +414,84 @@ class RoomState {
|
|
|
411
414
|
return this.isConnected;
|
|
412
415
|
}
|
|
413
416
|
}
|
|
414
|
-
commander_1.program.version("0.0.
|
|
417
|
+
commander_1.program.version("0.0.31").description("Hapico CLI for project management");
|
|
415
418
|
commander_1.program
|
|
416
419
|
.command("clone <id>")
|
|
417
420
|
.description("Clone a project by ID")
|
|
418
421
|
.action(async (id) => {
|
|
419
422
|
var _a, _b, _c, _d;
|
|
423
|
+
console.clear();
|
|
424
|
+
console.log(chalk_1.default.bold.blue("\n📦 HAPICO CLONE PIPELINE"));
|
|
425
|
+
console.log(chalk_1.default.gray("──────────────────────────────────────────────────────────────────"));
|
|
420
426
|
const { accessToken } = getStoredToken();
|
|
421
427
|
if (!accessToken) {
|
|
422
|
-
console.
|
|
428
|
+
console.log(chalk_1.default.red("✗ Authentication required. Please run 'hapico login' first."));
|
|
423
429
|
return;
|
|
424
430
|
}
|
|
425
|
-
const
|
|
426
|
-
if (fs.existsSync(
|
|
427
|
-
console.
|
|
431
|
+
const outputDir = path.resolve(process.cwd(), id);
|
|
432
|
+
if (fs.existsSync(outputDir)) {
|
|
433
|
+
console.log(chalk_1.default.red(`✗ Destination directory "${id}" already exists.`));
|
|
428
434
|
return;
|
|
429
435
|
}
|
|
430
|
-
|
|
431
|
-
const apiSpinner = (0, ora_1.default)(chalk_1.default.blue("Fetching project data...")).start();
|
|
436
|
+
const statusSpinner = (0, ora_1.default)(chalk_1.default.blue("Fetching project metadata...")).start();
|
|
432
437
|
try {
|
|
433
|
-
//
|
|
438
|
+
// 1. Fetch metadata and initial files
|
|
434
439
|
const response = await axios_1.default.get(`https://base.myworkbeast.com/api/views/${id}`);
|
|
440
|
+
const projectType = response.data.type || "view";
|
|
435
441
|
const code = (_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.code;
|
|
436
442
|
const decompressedCode = pako_1.default.inflate(Uint8Array.from(atob(code), (c) => c.charCodeAt(0)), { to: "string" });
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
443
|
+
const publicFiles = ((_b = (0, exports.tryJSONParse)(decompressedCode)) === null || _b === void 0 ? void 0 : _b.files) || [];
|
|
444
|
+
statusSpinner.succeed(chalk_1.default.green("Project metadata retrieved."));
|
|
445
|
+
console.log(chalk_1.default.bold.white("Project Details:"));
|
|
446
|
+
console.log(`${chalk_1.default.gray("├─")} ${chalk_1.default.bold("ID :")} ${chalk_1.default.cyan(id)}`);
|
|
447
|
+
console.log(`${chalk_1.default.gray("└─")} ${chalk_1.default.bold("Type :")} ${chalk_1.default.magenta(projectType.toUpperCase())}\n`);
|
|
448
|
+
// 2. Setup Template
|
|
449
|
+
statusSpinner.start(chalk_1.default.blue("Initializing project template..."));
|
|
441
450
|
const TEMPLATE_URL = "https://files.hcm04.vstorage.vngcloud.vn/assets/template_zalominiapp_devmode.zip";
|
|
442
|
-
const templateResponse = await axios_1.default.get(TEMPLATE_URL, {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
templateSpinner.succeed(chalk_1.default.green("Template downloaded successfully!"));
|
|
446
|
-
const outputDir = path.resolve(process.cwd(), id);
|
|
447
|
-
if (!fs.existsSync(outputDir)) {
|
|
448
|
-
fs.mkdirSync(outputDir);
|
|
449
|
-
}
|
|
450
|
-
const unzipSpinner = (0, ora_1.default)(chalk_1.default.blue("Extracting template...")).start();
|
|
451
|
+
const templateResponse = await axios_1.default.get(TEMPLATE_URL, { responseType: "arraybuffer" });
|
|
452
|
+
if (!fs.existsSync(outputDir))
|
|
453
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
451
454
|
await unzipper_1.default.Open.buffer(templateResponse.data).then((directory) => directory.extract({ path: outputDir }));
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
if (fs.existsSync(macosxDir)) {
|
|
455
|
+
const macosxDir = path.join(outputDir, "__MACOSX");
|
|
456
|
+
if (fs.existsSync(macosxDir))
|
|
455
457
|
fs.rmSync(macosxDir, { recursive: true, force: true });
|
|
456
|
-
}
|
|
457
|
-
// Save project ID
|
|
458
458
|
saveProjectId(outputDir, id);
|
|
459
|
-
|
|
460
|
-
//
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
fs.writeFileSync(filePath, file.content);
|
|
469
|
-
});
|
|
470
|
-
saveSpinner.succeed(chalk_1.default.green("Project files saved successfully!"));
|
|
471
|
-
// ===== 4. TỰ ĐỘNG PULL PHIÊN BẢN MỚI NHẤT (draft) – ĐÂY LÀ PHẦN MỚI =====
|
|
472
|
-
const pullSpinner = (0, ora_1.default)(chalk_1.default.blue("Pulling latest changes from server...")).start();
|
|
459
|
+
statusSpinner.succeed(chalk_1.default.green("Base template initialized."));
|
|
460
|
+
// 3. Sync Source Files
|
|
461
|
+
statusSpinner.start(chalk_1.default.blue("Syncing source files..."));
|
|
462
|
+
const srcPath = path.join(outputDir, "src");
|
|
463
|
+
const fileManager = new FileManager(srcPath);
|
|
464
|
+
fileManager.syncFiles(publicFiles);
|
|
465
|
+
statusSpinner.succeed(chalk_1.default.green(`${publicFiles.length} files synchronized from production.`));
|
|
466
|
+
// 4. Pull Latest Draft (v3)
|
|
467
|
+
statusSpinner.start(chalk_1.default.blue("Checking for latest development changes..."));
|
|
473
468
|
try {
|
|
474
469
|
const pullResponse = await axios_1.default.get(`https://base.myworkbeast.com/api/views/v3/${id}`, {
|
|
475
|
-
headers: {
|
|
476
|
-
Authorization: `Bearer ${accessToken}`,
|
|
477
|
-
"Content-Type": "application/json",
|
|
478
|
-
},
|
|
470
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
479
471
|
});
|
|
480
472
|
const pullCode = (_c = pullResponse === null || pullResponse === void 0 ? void 0 : pullResponse.data) === null || _c === void 0 ? void 0 : _c.code;
|
|
481
|
-
if (
|
|
482
|
-
pullSpinner.info(chalk_1.default.cyan("No draft version found – using published version."));
|
|
483
|
-
}
|
|
484
|
-
else {
|
|
473
|
+
if (pullCode) {
|
|
485
474
|
const pullDecompressed = pako_1.default.inflate(Uint8Array.from(atob(pullCode), (c) => c.charCodeAt(0)), { to: "string" });
|
|
486
475
|
const latestFiles = ((_d = (0, exports.tryJSONParse)(pullDecompressed)) === null || _d === void 0 ? void 0 : _d.files) || [];
|
|
487
|
-
const fileManager = new FileManager(path.join(outputDir, "src"));
|
|
488
476
|
fileManager.syncFiles(latestFiles);
|
|
489
|
-
|
|
477
|
+
statusSpinner.succeed(chalk_1.default.green("Latest development changes integrated."));
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
statusSpinner.info(chalk_1.default.gray("No draft version found, using published code."));
|
|
490
481
|
}
|
|
491
482
|
}
|
|
492
|
-
catch (
|
|
493
|
-
|
|
483
|
+
catch (e) {
|
|
484
|
+
statusSpinner.warn(chalk_1.default.yellow("Draft sync skipped (using production code)."));
|
|
494
485
|
}
|
|
495
|
-
|
|
496
|
-
console.log(chalk_1.default.
|
|
486
|
+
console.log(chalk_1.default.gray("──────────────────────────────────────────────────────────────────"));
|
|
487
|
+
console.log(`\n${chalk_1.default.bold.white.bgGreen(" SUCCESS ")} ${chalk_1.default.green("Project cloned successfully!\n")}`);
|
|
488
|
+
console.log(chalk_1.default.bold.white("Next steps:"));
|
|
489
|
+
console.log(`${chalk_1.default.gray("1.")} ${chalk_1.default.cyan(`cd ${id}`)}`);
|
|
490
|
+
console.log(`${chalk_1.default.gray("2.")} ${chalk_1.default.cyan("npm install")}`);
|
|
491
|
+
console.log(`${chalk_1.default.gray("3.")} ${chalk_1.default.cyan("hapico dev")}\n`);
|
|
497
492
|
}
|
|
498
493
|
catch (error) {
|
|
499
|
-
|
|
494
|
+
statusSpinner.fail(chalk_1.default.red(`Clone failed: ${error.message}`));
|
|
500
495
|
}
|
|
501
496
|
});
|
|
502
497
|
commander_1.program
|
|
@@ -510,71 +505,75 @@ commander_1.program
|
|
|
510
505
|
.command("dev")
|
|
511
506
|
.description("Start the project in development mode")
|
|
512
507
|
.option("--zversion <version>", "Zalo version for QR code")
|
|
513
|
-
.action((options) => {
|
|
514
|
-
var _a;
|
|
508
|
+
.action(async (options) => {
|
|
515
509
|
const { accessToken } = getStoredToken();
|
|
516
510
|
if (!accessToken) {
|
|
517
511
|
console.error(chalk_1.default.red("✗ You need to login first. Use 'hapico login' command."));
|
|
518
512
|
return;
|
|
519
513
|
}
|
|
520
|
-
|
|
514
|
+
console.clear();
|
|
515
|
+
console.log(chalk_1.default.bold.cyan("\n🚀 HAPICO DEVELOPMENT SERVER"));
|
|
516
|
+
console.log(chalk_1.default.gray("─────────────────────────────────────────"));
|
|
517
|
+
const devSpinner = (0, ora_1.default)(chalk_1.default.blue("Initializing environment...")).start();
|
|
521
518
|
const pwd = process.cwd();
|
|
522
519
|
const srcDir = path.join(pwd, "src");
|
|
523
520
|
if (!fs.existsSync(srcDir)) {
|
|
524
|
-
devSpinner.fail(chalk_1.default.red("
|
|
521
|
+
devSpinner.fail(chalk_1.default.red("Source directory 'src' not found. Ensure you are in a valid project folder."));
|
|
525
522
|
return;
|
|
526
523
|
}
|
|
527
|
-
// Directory to store session config
|
|
528
524
|
const tmpDir = path.join(pwd, ".tmp");
|
|
529
525
|
const sessionConfigFile = path.join(tmpDir, "config.json");
|
|
530
|
-
|
|
531
|
-
if (!fs.existsSync(tmpDir)) {
|
|
526
|
+
if (!fs.existsSync(tmpDir))
|
|
532
527
|
fs.mkdirSync(tmpDir, { recursive: true });
|
|
533
|
-
}
|
|
534
|
-
// Function to get stored session ID
|
|
535
528
|
const getStoredSessionId = () => {
|
|
536
529
|
if (fs.existsSync(sessionConfigFile)) {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
530
|
+
try {
|
|
531
|
+
return JSON.parse(fs.readFileSync(sessionConfigFile, "utf8")).sessionId;
|
|
532
|
+
}
|
|
533
|
+
catch (_a) {
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
540
536
|
}
|
|
541
537
|
return null;
|
|
542
538
|
};
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
};
|
|
547
|
-
// Get or generate session ID
|
|
548
|
-
let sessionId = getStoredSessionId();
|
|
549
|
-
if (!sessionId) {
|
|
550
|
-
sessionId = (0, crypto_1.randomUUID)();
|
|
551
|
-
saveSessionId(sessionId);
|
|
552
|
-
}
|
|
539
|
+
let sessionId = getStoredSessionId() || (0, crypto_1.randomUUID)();
|
|
540
|
+
fs.writeFileSync(sessionConfigFile, JSON.stringify({ sessionId }, null, 2));
|
|
541
|
+
const projectConfig = getStoredProjectId(pwd);
|
|
553
542
|
const info = JSON.stringify({
|
|
554
543
|
id: sessionId,
|
|
555
544
|
createdAt: new Date().toISOString(),
|
|
556
|
-
viewId:
|
|
545
|
+
viewId: projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.projectId,
|
|
557
546
|
});
|
|
558
|
-
// Convert info to base64
|
|
559
547
|
const projectId = Buffer.from(info).toString("base64");
|
|
560
|
-
if (!projectId) {
|
|
561
|
-
devSpinner.fail(chalk_1.default.red("
|
|
548
|
+
if (!(projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.projectId)) {
|
|
549
|
+
devSpinner.fail(chalk_1.default.red("Project ID missing in hapico.config.json"));
|
|
562
550
|
return;
|
|
563
551
|
}
|
|
564
|
-
|
|
565
|
-
const room = new RoomState(`view_${projectId}`, []);
|
|
552
|
+
devSpinner.text = chalk_1.default.blue("Scanning project directory...");
|
|
566
553
|
const fileManager = new FileManager(srcDir);
|
|
567
554
|
const initialFiles = fileManager.listFiles();
|
|
568
|
-
//
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
555
|
+
// Supported files outside src
|
|
556
|
+
const SUPPORT_FILES = [
|
|
557
|
+
"./.env",
|
|
558
|
+
"./.env.local",
|
|
559
|
+
"./.env.development",
|
|
560
|
+
"./.env.production",
|
|
561
|
+
"./package.json",
|
|
562
|
+
"./tsconfig.json",
|
|
563
|
+
"./nativewind.json"
|
|
564
|
+
];
|
|
565
|
+
SUPPORT_FILES.forEach((relativePath) => {
|
|
566
|
+
var _a;
|
|
567
|
+
const fullPath = path.join(pwd, relativePath);
|
|
568
|
+
if (fs.existsSync(fullPath)) {
|
|
569
|
+
const content = fs.readFileSync(fullPath, { encoding: "utf8" });
|
|
570
|
+
initialFiles.push({
|
|
571
|
+
path: relativePath,
|
|
572
|
+
content,
|
|
573
|
+
es5: (_a = (0, exports.compileES5)(content, fullPath)) !== null && _a !== void 0 ? _a : "",
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
});
|
|
578
577
|
const supportExtensions = [
|
|
579
578
|
".ts",
|
|
580
579
|
".tsx",
|
|
@@ -587,100 +586,118 @@ commander_1.program
|
|
|
587
586
|
".env.development",
|
|
588
587
|
".env.production",
|
|
589
588
|
];
|
|
590
|
-
const filteredFiles = initialFiles.filter((file) =>
|
|
591
|
-
|
|
592
|
-
});
|
|
589
|
+
const filteredFiles = initialFiles.filter((file) => supportExtensions.some((ext) => file.path.endsWith(ext)));
|
|
590
|
+
devSpinner.text = chalk_1.default.blue("Establishing WebSocket connection...");
|
|
591
|
+
const room = new RoomState(`view_${projectId}`, []);
|
|
593
592
|
room.files = filteredFiles;
|
|
594
593
|
room.connect(async () => {
|
|
595
|
-
devSpinner.succeed(chalk_1.default.
|
|
594
|
+
devSpinner.succeed(chalk_1.default.greenBright("Sync Engine Ready"));
|
|
595
|
+
console.log(`\n${chalk_1.default.bold.white.bgGreen(" SUCCESS ")} ${chalk_1.default.green("Connection established with workspace.\n")}`);
|
|
596
|
+
console.log(chalk_1.default.bold.white("Workspace Configuration:"));
|
|
597
|
+
console.log(`${chalk_1.default.gray("├─")} ${chalk_1.default.bold("Project ID :")} ${chalk_1.default.cyan(projectConfig.projectId)}`);
|
|
598
|
+
console.log(`${chalk_1.default.gray("├─")} ${chalk_1.default.bold("Session :")} ${chalk_1.default.gray(sessionId)}`);
|
|
599
|
+
console.log(`${chalk_1.default.gray("└─")} ${chalk_1.default.bold("Local Path :")} ${chalk_1.default.gray(pwd)}`);
|
|
600
|
+
console.log("");
|
|
596
601
|
room.updateState("view", filteredFiles);
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}, 200);
|
|
601
|
-
fileManager.setOnFileChange((filePath, content) => {
|
|
602
|
-
var _a;
|
|
603
|
-
const es5 = (_a = (0, exports.compileES5)(content, filePath)) !== null && _a !== void 0 ? _a : "";
|
|
604
|
-
console.log(chalk_1.default.yellow(`📝 File changed: ${filePath === null || filePath === void 0 ? void 0 : filePath.replace(srcDir, ".")}`));
|
|
605
|
-
const updatedFiles = room.files.map((file) => {
|
|
606
|
-
if (path.join(srcDir, file.path) === filePath) {
|
|
607
|
-
return { ...file, content, es5 };
|
|
608
|
-
}
|
|
609
|
-
return file;
|
|
602
|
+
try {
|
|
603
|
+
const project = await axios_1.default.get(`https://base.myworkbeast.com/api/views/${projectConfig.projectId}`, {
|
|
604
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
610
605
|
});
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
606
|
+
const projectType = project.data.type || "view";
|
|
607
|
+
const isExpo = projectType === "expo_app" || projectType === "emg_edu_lesson";
|
|
608
|
+
console.log(chalk_1.default.bold.white("Environment Status:"));
|
|
609
|
+
console.log(`${chalk_1.default.gray("├─")} ${chalk_1.default.bold("Type :")} ${chalk_1.default.magenta(projectType.toUpperCase())}`);
|
|
610
|
+
console.log(`${chalk_1.default.gray("└─")} ${chalk_1.default.bold("Watcher :")} ${chalk_1.default.greenBright("Active & Synchronizing")}`);
|
|
611
|
+
console.log(chalk_1.default.gray("──────────────────────────────────────────────────────────────────\n"));
|
|
612
|
+
if (isExpo) {
|
|
613
|
+
try {
|
|
614
|
+
await (0, nativewind_1.compileAndSaveToFolder)("src", "nativewind.json");
|
|
615
|
+
}
|
|
616
|
+
catch (e) { }
|
|
617
|
+
}
|
|
618
|
+
const debouncedNativeWind = (0, lodash_1.debounce)(async () => {
|
|
619
|
+
if (isExpo) {
|
|
620
|
+
process.stdout.write(chalk_1.default.gray(" [System] Style assets synchronized successfully.\r"));
|
|
621
|
+
try {
|
|
622
|
+
await (0, nativewind_1.compileAndSaveToFolder)("src", "nativewind.json");
|
|
623
|
+
}
|
|
624
|
+
catch (e) { }
|
|
625
|
+
}
|
|
626
|
+
}, 500);
|
|
627
|
+
const debouncedUpdate = (0, lodash_1.debounce)((updatedFiles) => {
|
|
628
|
+
room.updateState("view", updatedFiles);
|
|
629
|
+
}, 200);
|
|
630
|
+
fileManager.setOnFileChange((filePath, content) => {
|
|
631
|
+
var _a;
|
|
632
|
+
const timestamp = new Date().toLocaleTimeString([], { hour12: false });
|
|
633
|
+
const relativePath = filePath.replace(srcDir, ".");
|
|
634
|
+
console.log(`${chalk_1.default.gray(`[${timestamp}]`)} ${chalk_1.default.yellow.bold("CHANGE")} ${chalk_1.default.white(relativePath)}`);
|
|
635
|
+
const es5 = (_a = (0, exports.compileES5)(content, filePath)) !== null && _a !== void 0 ? _a : "";
|
|
636
|
+
const updatedFiles = room.files.map((file) => {
|
|
637
|
+
if (path.join(srcDir, file.path) === filePath)
|
|
638
|
+
return { ...file, content, es5 };
|
|
639
|
+
return file;
|
|
640
|
+
});
|
|
641
|
+
room.files = updatedFiles;
|
|
642
|
+
debouncedUpdate(updatedFiles);
|
|
643
|
+
if (isExpo)
|
|
644
|
+
debouncedNativeWind();
|
|
638
645
|
});
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
qrcode_terminal_1.default.generate(
|
|
645
|
-
console.log(chalk_1.default.
|
|
646
|
-
console.log(qrcode);
|
|
646
|
+
// UI Access Section
|
|
647
|
+
if (isExpo) {
|
|
648
|
+
const EXPO_GROUP_ID = "6f033d8b-7ed5-48dc-ab6f-f8348cf6993b";
|
|
649
|
+
const link = `exp://u.expo.dev/e362c6df-abe8-4503-8723-1362f015d167/group/${EXPO_GROUP_ID}?sessionKey=${projectId}&mode=development`;
|
|
650
|
+
console.log(chalk_1.default.bold.black.bgCyan(" 📱 EXPO GO ACCESS "));
|
|
651
|
+
qrcode_terminal_1.default.generate(link, { small: true }, (code) => {
|
|
652
|
+
console.log(chalk_1.default.white(code));
|
|
647
653
|
});
|
|
654
|
+
console.log(chalk_1.default.cyan(` Link: ${chalk_1.default.underline(link)}\n`));
|
|
648
655
|
}
|
|
649
|
-
else {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
656
|
+
else if (projectType === "zalominiapp" || projectType === "zalominiapp2") {
|
|
657
|
+
const zversion = options.zversion;
|
|
658
|
+
const zaloUrl = zversion
|
|
659
|
+
? `https://zalo.me/s/3218692650896662017/player/${projectId}?env=TESTING&version=${zversion}`
|
|
660
|
+
: `https://zalo.me/s/3218692650896662017/player/${projectId}`;
|
|
661
|
+
const label = projectType === "zalominiapp2" ? " 🧩 ZALO MINI APP 2.0 " : " 🧩 ZALO MINI APP ";
|
|
662
|
+
console.log(chalk_1.default.bold.black.bgBlue(label));
|
|
663
|
+
qrcode_terminal_1.default.generate(zaloUrl, { small: true }, (code) => {
|
|
664
|
+
console.log(chalk_1.default.white(code));
|
|
653
665
|
});
|
|
666
|
+
console.log(chalk_1.default.blue(` Link: ${chalk_1.default.underline(zaloUrl)}\n`));
|
|
654
667
|
}
|
|
655
|
-
|
|
668
|
+
else {
|
|
669
|
+
const previewUrl = `https://com.ai.vn/dev_preview/${projectId}`;
|
|
670
|
+
const label = projectType === "web2" ? " 🌐 WEB 2.0 PREVIEW " : " 🌐 BROWSER PREVIEW ";
|
|
671
|
+
console.log(chalk_1.default.bold.black.bgGreen(label));
|
|
672
|
+
console.log(chalk_1.default.green(` URL: ${chalk_1.default.underline(previewUrl)}\n`));
|
|
673
|
+
await (0, open_1.default)(previewUrl);
|
|
674
|
+
}
|
|
675
|
+
console.log(chalk_1.default.gray("👀 Monitoring file changes... (Press Ctrl+C to exit)"));
|
|
656
676
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
console.log(chalk_1.default.
|
|
660
|
-
await (0, open_1.default)(previewUrl);
|
|
677
|
+
catch (err) {
|
|
678
|
+
console.log(chalk_1.default.yellow(`\n⚠️ Environment metadata could not be fetched: ${err.message}`));
|
|
679
|
+
console.log(chalk_1.default.gray("──────────────────────────────────────────────────────────────────\n"));
|
|
661
680
|
}
|
|
662
681
|
});
|
|
663
682
|
});
|
|
664
|
-
//
|
|
665
|
-
async function pushProject(
|
|
683
|
+
// Chuyên nghiệp hóa việc push source code lên server
|
|
684
|
+
async function pushProject(projectId) {
|
|
666
685
|
const data = getStoredToken();
|
|
667
686
|
const { accessToken } = data || {};
|
|
668
687
|
if (!accessToken) {
|
|
669
|
-
|
|
670
|
-
return false;
|
|
688
|
+
return { success: false, projectId, totalSize: 0, fileCount: 0, error: "Authentication required. Please run 'hapico login'." };
|
|
671
689
|
}
|
|
672
690
|
const pwd = process.cwd();
|
|
673
691
|
const srcDir = path.join(pwd, "src");
|
|
674
692
|
if (!fs.existsSync(srcDir)) {
|
|
675
|
-
|
|
676
|
-
return false;
|
|
693
|
+
return { success: false, projectId, totalSize: 0, fileCount: 0, error: "Source directory 'src' not found. Ensure you are in a valid project folder." };
|
|
677
694
|
}
|
|
678
695
|
const fileManager = new FileManager(srcDir);
|
|
679
696
|
const files = fileManager.listFiles().filter((file) => {
|
|
680
697
|
const extname = path.extname(file.path);
|
|
681
698
|
return supportedExtensions.includes(extname);
|
|
682
699
|
});
|
|
683
|
-
// Supported files
|
|
700
|
+
// Supported extra files at root
|
|
684
701
|
const SUPPORT_FILES = [
|
|
685
702
|
"./.env",
|
|
686
703
|
"./.env.local",
|
|
@@ -688,13 +705,12 @@ async function pushProject(spinner, projectId) {
|
|
|
688
705
|
"./.env.production",
|
|
689
706
|
"./package.json",
|
|
690
707
|
"./tsconfig.json",
|
|
708
|
+
"./nativewind.json"
|
|
691
709
|
];
|
|
692
|
-
// Include supported files
|
|
693
710
|
SUPPORT_FILES.forEach((relativePath) => {
|
|
694
711
|
var _a;
|
|
695
712
|
const fullPath = path.join(pwd, relativePath);
|
|
696
713
|
if (fs.existsSync(fullPath)) {
|
|
697
|
-
console.log(chalk_1.default.green(`Including ${relativePath} in push for project ${projectId}.`));
|
|
698
714
|
const content = fs.readFileSync(fullPath, { encoding: "utf8" });
|
|
699
715
|
files.push({
|
|
700
716
|
path: relativePath,
|
|
@@ -703,79 +719,112 @@ async function pushProject(spinner, projectId) {
|
|
|
703
719
|
});
|
|
704
720
|
}
|
|
705
721
|
});
|
|
706
|
-
// Show ra thông tin dung lượng mã nguồn
|
|
707
722
|
const totalSize = files.reduce((acc, file) => acc + file.content.length, 0);
|
|
708
|
-
console.log(chalk_1.default.cyan(`Total source code size for project ${projectId}: ${(totalSize / 1024).toFixed(2)} KB`));
|
|
709
|
-
spinner.text = `Pushing project source code to server for project ${projectId}...`;
|
|
710
723
|
const apiUrl = `https://base.myworkbeast.com/api/views/${projectId}`;
|
|
711
724
|
try {
|
|
712
|
-
await axios_1.default.put(apiUrl, {
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
725
|
+
await axios_1.default.put(apiUrl, { code: JSON.stringify({ files }) }, {
|
|
726
|
+
headers: {
|
|
727
|
+
Authorization: `Bearer ${accessToken}`,
|
|
728
|
+
"Content-Type": "application/json",
|
|
729
|
+
},
|
|
730
|
+
});
|
|
731
|
+
return { success: true, projectId, totalSize, fileCount: files.length };
|
|
732
|
+
}
|
|
733
|
+
catch (error) {
|
|
734
|
+
return { success: false, projectId, totalSize, fileCount: files.length, error: error.message };
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
async function publishProjectApi(projectId) {
|
|
738
|
+
const { accessToken } = getStoredToken();
|
|
739
|
+
const apiUrl = "https://base.myworkbeast.com/api/views/publish/v2";
|
|
740
|
+
try {
|
|
741
|
+
await axios_1.default.post(apiUrl, { view_id: parseInt(projectId, 10) }, {
|
|
717
742
|
headers: {
|
|
718
743
|
Authorization: `Bearer ${accessToken}`,
|
|
719
744
|
"Content-Type": "application/json",
|
|
720
745
|
},
|
|
721
746
|
});
|
|
722
|
-
return true;
|
|
747
|
+
return { success: true };
|
|
723
748
|
}
|
|
724
749
|
catch (error) {
|
|
725
|
-
|
|
726
|
-
return false;
|
|
750
|
+
return { success: false, error: error.message };
|
|
727
751
|
}
|
|
728
752
|
}
|
|
729
753
|
commander_1.program
|
|
730
754
|
.command("push")
|
|
731
755
|
.description("Push the project source code to the server")
|
|
732
756
|
.action(async () => {
|
|
733
|
-
|
|
757
|
+
console.clear();
|
|
758
|
+
console.log(chalk_1.default.bold.cyan("\n🚀 HAPICO PUSH PIPELINE"));
|
|
759
|
+
console.log(chalk_1.default.gray("──────────────────────────────────────────────────────────────────"));
|
|
734
760
|
const pwd = process.cwd();
|
|
735
761
|
const { projectId, replicate } = getStoredProjectId(pwd);
|
|
736
762
|
if (!projectId) {
|
|
737
|
-
|
|
763
|
+
console.log(chalk_1.default.red("✗ Project ID not found. Ensure hapico.config.json exists."));
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
console.log(chalk_1.default.bold.white("Target Environments:"));
|
|
767
|
+
console.log(`${chalk_1.default.gray("├─")} ${chalk_1.default.bold("Main Project :")} ${chalk_1.default.cyan(projectId)}`);
|
|
768
|
+
if (replicate && replicate.length > 0) {
|
|
769
|
+
console.log(`${chalk_1.default.gray("└─")} ${chalk_1.default.bold("Replicas :")} ${chalk_1.default.cyan(replicate.join(", "))}`);
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
console.log(`${chalk_1.default.gray("└─")} ${chalk_1.default.bold("Replicas :")} ${chalk_1.default.gray("None")}`);
|
|
773
|
+
}
|
|
774
|
+
console.log("");
|
|
775
|
+
const pushSpinner = (0, ora_1.default)(chalk_1.default.blue("Syncing source code with main project...")).start();
|
|
776
|
+
const mainResult = await pushProject(projectId);
|
|
777
|
+
if (mainResult.success) {
|
|
778
|
+
pushSpinner.succeed(chalk_1.default.green(`Main Project [${projectId}] code synced successfully.`));
|
|
779
|
+
console.log(`${chalk_1.default.gray(" ├─")} ${chalk_1.default.bold("Files :")} ${chalk_1.default.white(mainResult.fileCount)}`);
|
|
780
|
+
console.log(`${chalk_1.default.gray(" └─")} ${chalk_1.default.bold("Size :")} ${chalk_1.default.white((mainResult.totalSize / 1024).toFixed(2) + " KB")}\n`);
|
|
781
|
+
}
|
|
782
|
+
else {
|
|
783
|
+
pushSpinner.fail(chalk_1.default.red(`Main Project [${projectId}] sync failed: ${mainResult.error}`));
|
|
738
784
|
return;
|
|
739
785
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
let allSuccess = mainSuccess;
|
|
743
|
-
// Push to replicated projects if replicate array exists
|
|
744
|
-
if (replicate && Array.isArray(replicate) && replicate.length > 0) {
|
|
745
|
-
saveSpinner.text = chalk_1.default.blue("Pushing to replicated projects...");
|
|
786
|
+
let allSuccess = true;
|
|
787
|
+
if (replicate && replicate.length > 0) {
|
|
746
788
|
for (const repId of replicate) {
|
|
747
|
-
const
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
789
|
+
const repSpinner = (0, ora_1.default)(chalk_1.default.blue(`Syncing replica [${repId}]...`)).start();
|
|
790
|
+
const repResult = await pushProject(repId);
|
|
791
|
+
if (repResult.success) {
|
|
792
|
+
repSpinner.succeed(chalk_1.default.green(`Replica [${repId}] code synced successfully.`));
|
|
793
|
+
console.log(`${chalk_1.default.gray(" ├─")} ${chalk_1.default.bold("Files :")} ${chalk_1.default.white(repResult.fileCount)}`);
|
|
794
|
+
console.log(`${chalk_1.default.gray(" └─")} ${chalk_1.default.bold("Size :")} ${chalk_1.default.white((repResult.totalSize / 1024).toFixed(2) + " KB")}\n`);
|
|
751
795
|
}
|
|
752
796
|
else {
|
|
753
|
-
|
|
797
|
+
repSpinner.fail(chalk_1.default.red(`Replica [${repId}] sync failed: ${repResult.error}`));
|
|
798
|
+
allSuccess = false;
|
|
754
799
|
}
|
|
755
800
|
}
|
|
756
801
|
}
|
|
802
|
+
console.log(chalk_1.default.gray("──────────────────────────────────────────────────────────────────"));
|
|
757
803
|
if (allSuccess) {
|
|
758
|
-
|
|
804
|
+
console.log(`\n${chalk_1.default.bold.white.bgGreen(" SUCCESS ")} ${chalk_1.default.green("All source codes pushed successfully!\n")}`);
|
|
759
805
|
}
|
|
760
806
|
else {
|
|
761
|
-
|
|
807
|
+
console.log(`\n${chalk_1.default.bold.white.bgYellow(" WARNING ")} ${chalk_1.default.yellow("Push completed with some replica errors.\n")}`);
|
|
762
808
|
}
|
|
763
809
|
});
|
|
764
810
|
commander_1.program
|
|
765
811
|
.command("login")
|
|
766
|
-
.description("
|
|
812
|
+
.description("Authenticate Hapico CLI with your account")
|
|
767
813
|
.action(async () => {
|
|
768
|
-
console.
|
|
769
|
-
|
|
814
|
+
console.clear();
|
|
815
|
+
console.log(chalk_1.default.bold.yellow("\n🔐 HAPICO SECURE LOGIN"));
|
|
816
|
+
console.log(chalk_1.default.gray("──────────────────────────────────────────────────────────────────"));
|
|
817
|
+
const loginSpinner = (0, ora_1.default)(chalk_1.default.blue("Requesting device authorization...")).start();
|
|
770
818
|
try {
|
|
771
819
|
const response = await axios_1.default.post("https://auth.myworkbeast.com/auth/device");
|
|
772
820
|
const { device_code, user_code, verification_url, expires_in, interval } = response.data;
|
|
773
|
-
loginSpinner.succeed(chalk_1.default.green("
|
|
774
|
-
console.log(chalk_1.default.
|
|
775
|
-
console.log(chalk_1.default.
|
|
776
|
-
console.log(chalk_1.default.
|
|
821
|
+
loginSpinner.succeed(chalk_1.default.green("Authorization request created."));
|
|
822
|
+
console.log(`\n${chalk_1.default.bold.white("ACTION REQUIRED")}`);
|
|
823
|
+
console.log(`${chalk_1.default.gray("1.")} Open URL : ${chalk_1.default.underline.cyan(verification_url)}`);
|
|
824
|
+
console.log(`${chalk_1.default.gray("2.")} Enter Code: ${chalk_1.default.bold.bgWhite.black(` ${user_code} `)}`);
|
|
825
|
+
console.log(chalk_1.default.gray(`\n(Code expires in ${Math.floor(expires_in / 60)} minutes)`));
|
|
777
826
|
await (0, open_1.default)(verification_url);
|
|
778
|
-
const pollSpinner = (0, ora_1.default)(chalk_1.default.blue("Waiting for
|
|
827
|
+
const pollSpinner = (0, ora_1.default)(chalk_1.default.blue("Waiting for browser confirmation...")).start();
|
|
779
828
|
let tokens = null;
|
|
780
829
|
const startTime = Date.now();
|
|
781
830
|
while (Date.now() - startTime < expires_in * 1000) {
|
|
@@ -786,21 +835,22 @@ commander_1.program
|
|
|
786
835
|
break;
|
|
787
836
|
}
|
|
788
837
|
}
|
|
789
|
-
catch (
|
|
790
|
-
// Ignore temporary errors and continue polling
|
|
791
|
-
}
|
|
838
|
+
catch (e) { /* Poll interval logic */ }
|
|
792
839
|
await new Promise((resolve) => setTimeout(resolve, interval * 1000));
|
|
793
840
|
}
|
|
794
841
|
if (tokens) {
|
|
795
|
-
pollSpinner.succeed(chalk_1.default.green("Login successful!"));
|
|
796
842
|
saveToken(tokens);
|
|
843
|
+
pollSpinner.succeed(chalk_1.default.green("Authentication successful."));
|
|
844
|
+
console.log(chalk_1.default.gray("──────────────────────────────────────────────────────────────────"));
|
|
845
|
+
console.log(`\n${chalk_1.default.bold.white.bgGreen(" SUCCESS ")} ${chalk_1.default.green("You are now logged in.")}`);
|
|
846
|
+
console.log(`${chalk_1.default.gray("Token stored at:")} ${chalk_1.default.gray(TOKEN_FILE)}\n`);
|
|
797
847
|
}
|
|
798
848
|
else {
|
|
799
|
-
pollSpinner.fail(chalk_1.default.red("
|
|
849
|
+
pollSpinner.fail(chalk_1.default.red("Login timed out or was cancelled."));
|
|
800
850
|
}
|
|
801
851
|
}
|
|
802
852
|
catch (error) {
|
|
803
|
-
loginSpinner.fail(chalk_1.default.red(
|
|
853
|
+
loginSpinner.fail(chalk_1.default.red(`Login failed: ${error.message}`));
|
|
804
854
|
}
|
|
805
855
|
});
|
|
806
856
|
commander_1.program
|
|
@@ -851,329 +901,171 @@ commander_1.program
|
|
|
851
901
|
apiSpinner.fail(chalk_1.default.red(`✗ Error fetching project files: ${error.message}`));
|
|
852
902
|
}
|
|
853
903
|
});
|
|
854
|
-
// be {{id}} --be --port {{port}}
|
|
855
904
|
commander_1.program
|
|
856
905
|
.command("fetch <id>")
|
|
857
|
-
.
|
|
858
|
-
.
|
|
859
|
-
.option("--libs <libs>", "Additional libraries to install (comma-separated)", "")
|
|
860
|
-
.option("--be", "Flag to indicate backend")
|
|
861
|
-
.description("Open backend for the project")
|
|
862
|
-
.action(async (id, options) => {
|
|
863
|
-
var _a;
|
|
906
|
+
.description("Fetch Zalo Mini App template for the project")
|
|
907
|
+
.action(async (id) => {
|
|
864
908
|
const { accessToken } = getStoredToken();
|
|
865
909
|
if (!accessToken) {
|
|
866
910
|
console.error(chalk_1.default.red("✗ You need to login first. Use 'hapico login' command."));
|
|
867
911
|
return;
|
|
868
912
|
}
|
|
869
|
-
|
|
870
|
-
// Chọn hỏi user port để vận hành
|
|
871
|
-
let port = 3000;
|
|
872
|
-
const response = await axios_1.default.get(`https://base.myworkbeast.com/api/views/${id}`);
|
|
873
|
-
const portInput = options.port;
|
|
874
|
-
if (portInput) {
|
|
875
|
-
const parsedPort = parseInt(portInput, 10);
|
|
876
|
-
if (!isNaN(parsedPort)) {
|
|
877
|
-
port = parsedPort;
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
const projectDir = path.resolve(process.cwd(), id);
|
|
881
|
-
if (!fs.existsSync(projectDir)) {
|
|
882
|
-
// create folder
|
|
883
|
-
fs.mkdirSync(projectDir);
|
|
884
|
-
}
|
|
885
|
-
// download template https://main.hcm04.vstorage.vngcloud.vn/templates/hapico/hapico-basic.zip
|
|
886
|
-
const TEMPLATE_URL = `https://main.hcm04.vstorage.vngcloud.vn/templates/hapico/be.zip?t=${Date.now()}`;
|
|
887
|
-
let templateResponse = undefined;
|
|
913
|
+
const apiSpinner = (0, ora_1.default)(chalk_1.default.blue("Fetching project data...")).start();
|
|
888
914
|
try {
|
|
889
|
-
|
|
915
|
+
const response = await axios_1.default.get(`https://base.myworkbeast.com/api/views/${id}`);
|
|
916
|
+
const projectType = response.data.type;
|
|
917
|
+
if (projectType !== "zalominiapp" && projectType !== "zalominiapp2") {
|
|
918
|
+
apiSpinner.fail(chalk_1.default.red(`✗ Error: Project type is "${projectType}". Only "zalominiapp" and "zalominiapp2" are supported for fetch command.`));
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
apiSpinner.succeed(chalk_1.default.green("Project data fetched successfully!"));
|
|
922
|
+
const outputDir = path.resolve(process.cwd(), id);
|
|
923
|
+
const zaloDir = path.join(outputDir, "zalo");
|
|
924
|
+
const backendDir = path.join(outputDir, "backend");
|
|
925
|
+
const templateSpinner = (0, ora_1.default)(chalk_1.default.blue("Downloading Zalo template...")).start();
|
|
926
|
+
const TEMPLATE_URL = "https://hapico.hcm04.vstorage.vngcloud.vn/templates/zalominiapp_v0.zip";
|
|
927
|
+
const templateResponse = await axios_1.default.get(TEMPLATE_URL, {
|
|
890
928
|
responseType: "arraybuffer",
|
|
891
929
|
});
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
const outputDir = path.resolve(process.cwd(), id);
|
|
898
|
-
if (!fs.existsSync(outputDir)) {
|
|
899
|
-
fs.mkdirSync(outputDir);
|
|
900
|
-
}
|
|
901
|
-
await unzipper_1.default.Open.buffer(templateResponse.data).then((directory) => directory.extract({ path: outputDir }));
|
|
902
|
-
const macosxDir = path.join(process.cwd(), id, "__MACOSX");
|
|
903
|
-
if (fs.existsSync(macosxDir)) {
|
|
904
|
-
fs.rmSync(macosxDir, { recursive: true, force: true });
|
|
905
|
-
}
|
|
906
|
-
// outputPath/src/server.ts có dòng (3006) thay thành port
|
|
907
|
-
const serverFile = path.join(process.cwd(), id, "src", "index.ts");
|
|
908
|
-
if (fs.existsSync(serverFile)) {
|
|
909
|
-
let serverContent = fs.readFileSync(serverFile, { encoding: "utf8" });
|
|
910
|
-
serverContent = serverContent.split("(3006)").join(`(${port})`);
|
|
911
|
-
fs.writeFileSync(serverFile, serverContent, { encoding: "utf8" });
|
|
912
|
-
}
|
|
913
|
-
// lấy danh sách migrations
|
|
914
|
-
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`;
|
|
915
|
-
// Lấy danh sách các migration đã chạy
|
|
916
|
-
let migrations = [];
|
|
917
|
-
const migrationsResponse = await axios_1.default.get(MIGRATIONS_URL, {
|
|
918
|
-
headers: {
|
|
919
|
-
Authorization: `Bearer ${accessToken}`,
|
|
920
|
-
"Content-Type": "application/json",
|
|
921
|
-
"x-db-code": response.data.dbCode || response.data.db_code,
|
|
922
|
-
},
|
|
923
|
-
});
|
|
924
|
-
if (migrationsResponse.status === 200) {
|
|
925
|
-
migrations = (((_a = migrationsResponse.data.data) === null || _a === void 0 ? void 0 : _a.rows) || []).map((item) => ({
|
|
926
|
-
created_at: item.created_at,
|
|
927
|
-
sql: item.sql_statement,
|
|
928
|
-
}));
|
|
929
|
-
}
|
|
930
|
-
console.log(chalk_1.default.cyan(`🔍 Found ${migrations.length} migrations.`));
|
|
931
|
-
// Tạo thư mục migrations nếu chưa tồn tại
|
|
932
|
-
const migrationsDir = path.join(process.cwd(), id, "migrations");
|
|
933
|
-
if (!fs.existsSync(migrationsDir)) {
|
|
934
|
-
fs.mkdirSync(migrationsDir);
|
|
935
|
-
}
|
|
936
|
-
// Lưu từng migration vào file
|
|
937
|
-
migrations.forEach((migration, index) => {
|
|
938
|
-
const timestamp = new Date(migration.created_at)
|
|
939
|
-
.toISOString()
|
|
940
|
-
.replace(/[-:]/g, "")
|
|
941
|
-
.replace(/\..+/, "");
|
|
942
|
-
const filename = path.join(migrationsDir, `${index + 1}_migration_${timestamp}.sql`);
|
|
943
|
-
fs.writeFileSync(filename, migration.sql, {
|
|
944
|
-
encoding: "utf8",
|
|
930
|
+
templateSpinner.succeed(chalk_1.default.green("Zalo template downloaded successfully!"));
|
|
931
|
+
const backendSpinner = (0, ora_1.default)(chalk_1.default.blue("Downloading backend template...")).start();
|
|
932
|
+
const BACKEND_URL = "https://hapico.hcm04.vstorage.vngcloud.vn/templates/backend_v0.zip";
|
|
933
|
+
const backendResponse = await axios_1.default.get(BACKEND_URL, {
|
|
934
|
+
responseType: "arraybuffer",
|
|
945
935
|
});
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
try {
|
|
965
|
-
if (file.content.trim().length == 0)
|
|
966
|
-
return;
|
|
967
|
-
const filePath = path.join(process.cwd(), id, "src", file.path);
|
|
968
|
-
const dir = path.dirname(filePath);
|
|
969
|
-
if (!fs.existsSync(dir)) {
|
|
970
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
971
|
-
}
|
|
972
|
-
fs.writeFileSync(filePath, file.content);
|
|
973
|
-
}
|
|
974
|
-
catch (error) {
|
|
975
|
-
console.log(chalk_1.default.red("✗ Error writing file"), file.path, error);
|
|
936
|
+
backendSpinner.succeed(chalk_1.default.green("Backend template downloaded successfully!"));
|
|
937
|
+
if (!fs.existsSync(outputDir)) {
|
|
938
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
939
|
+
}
|
|
940
|
+
if (!fs.existsSync(zaloDir))
|
|
941
|
+
fs.mkdirSync(zaloDir, { recursive: true });
|
|
942
|
+
if (!fs.existsSync(backendDir))
|
|
943
|
+
fs.mkdirSync(backendDir, { recursive: true });
|
|
944
|
+
const unzipZaloSpinner = (0, ora_1.default)(chalk_1.default.blue("Extracting Zalo template...")).start();
|
|
945
|
+
await unzipper_1.default.Open.buffer(templateResponse.data).then((directory) => directory.extract({ path: zaloDir }));
|
|
946
|
+
unzipZaloSpinner.succeed(chalk_1.default.green("Zalo template extracted successfully!"));
|
|
947
|
+
const unzipBackendSpinner = (0, ora_1.default)(chalk_1.default.blue("Extracting backend template...")).start();
|
|
948
|
+
await unzipper_1.default.Open.buffer(backendResponse.data).then((directory) => directory.extract({ path: backendDir }));
|
|
949
|
+
unzipBackendSpinner.succeed(chalk_1.default.green("Backend template extracted successfully!"));
|
|
950
|
+
[zaloDir, backendDir].forEach((dir) => {
|
|
951
|
+
const macosxDir = path.join(dir, "__MACOSX");
|
|
952
|
+
if (fs.existsSync(macosxDir)) {
|
|
953
|
+
fs.rmSync(macosxDir, { recursive: true, force: true });
|
|
976
954
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
// create a loop to check if there is new version of project
|
|
994
|
-
// https://main.hcm04.vstorage.vngcloud.vn/statics/{{id}}/version.json
|
|
995
|
-
let currentVersionStr = fs.existsSync(path.join(outputDir, "version.json"))
|
|
996
|
-
? JSON.parse(fs.readFileSync(path.join(outputDir, "version.json"), {
|
|
997
|
-
encoding: "utf8",
|
|
998
|
-
})).version
|
|
999
|
-
: "0.0.1";
|
|
1000
|
-
const checkVersion = async () => {
|
|
1001
|
-
var _a, _b;
|
|
1002
|
-
try {
|
|
1003
|
-
// check if file version.json exists
|
|
1004
|
-
const flyCheck = await axios_1.default.head(`https://statics.hcm04.vstorage.vngcloud.vn/${id}/version.json`);
|
|
1005
|
-
if (flyCheck.status !== 200) {
|
|
1006
|
-
return;
|
|
1007
|
-
}
|
|
1008
|
-
// get file version.json
|
|
1009
|
-
const versionResponse = await axios_1.default.get(`https://statics.hcm04.vstorage.vngcloud.vn/${id}/version.json`);
|
|
1010
|
-
const latestVersionData = versionResponse.data;
|
|
1011
|
-
const latestVersion = latestVersionData.version;
|
|
1012
|
-
if (latestVersion !== currentVersionStr) {
|
|
1013
|
-
console.log(chalk_1.default.yellow(`📦 New version available: ${latestVersion}`));
|
|
1014
|
-
// Save new version.json
|
|
1015
|
-
fs.writeFileSync(path.join(outputDir, "version.json"), JSON.stringify(latestVersionData, null, 2), { encoding: "utf8" });
|
|
1016
|
-
// Install external libraries if any
|
|
1017
|
-
if (latestVersionData.external_libraries &&
|
|
1018
|
-
latestVersionData.external_libraries.length > 0) {
|
|
1019
|
-
console.log(chalk_1.default.blue("Installing new external libraries..."));
|
|
1020
|
-
for (const lib of latestVersionData.external_libraries) {
|
|
1021
|
-
try {
|
|
1022
|
-
await execPromise(`bun add ${lib}`, { cwd: outputDir });
|
|
1023
|
-
console.log(chalk_1.default.green(`Added ${lib}`));
|
|
1024
|
-
}
|
|
1025
|
-
catch (addError) {
|
|
1026
|
-
console.error(chalk_1.default.red(`✗ Error adding ${lib}:`), addError.message);
|
|
955
|
+
// Replace {{APP_ID}} in .ts and .tsx files
|
|
956
|
+
const traverseAndReplace = (currentDir) => {
|
|
957
|
+
fs.readdirSync(currentDir, { withFileTypes: true }).forEach((entry) => {
|
|
958
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
959
|
+
if (entry.isDirectory()) {
|
|
960
|
+
traverseAndReplace(fullPath);
|
|
961
|
+
}
|
|
962
|
+
else if (entry.isFile()) {
|
|
963
|
+
const ext = path.extname(fullPath);
|
|
964
|
+
if (ext === ".ts" || ext === ".tsx") {
|
|
965
|
+
const content = fs.readFileSync(fullPath, { encoding: "utf8" });
|
|
966
|
+
if (content.includes("{{APP_ID}}")) {
|
|
967
|
+
const updatedContent = content.replace(/{{APP_ID}}/g, id);
|
|
968
|
+
fs.writeFileSync(fullPath, updatedContent, {
|
|
969
|
+
encoding: "utf8",
|
|
970
|
+
});
|
|
1027
971
|
}
|
|
1028
972
|
}
|
|
1029
973
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
console.log(chalk_1.default.green("Dependencies reinstalled successfully!"));
|
|
1034
|
-
}
|
|
1035
|
-
catch (installError) {
|
|
1036
|
-
console.error(chalk_1.default.red("✗ Error reinstalling dependencies:"), installError.message);
|
|
1037
|
-
}
|
|
1038
|
-
// Restart the process
|
|
1039
|
-
if (serveProcess && !serveProcess.killed) {
|
|
1040
|
-
console.log(chalk_1.default.blue("Restarting backend..."));
|
|
1041
|
-
serveProcess.kill("SIGTERM");
|
|
1042
|
-
}
|
|
1043
|
-
// Start new process
|
|
1044
|
-
const newServeProcess = exec("bun run start", { cwd: outputDir });
|
|
1045
|
-
(_a = newServeProcess.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (data) => {
|
|
1046
|
-
process.stdout.write(data);
|
|
1047
|
-
});
|
|
1048
|
-
(_b = newServeProcess.stderr) === null || _b === void 0 ? void 0 : _b.on("data", (data) => {
|
|
1049
|
-
process.stderr.write(data);
|
|
1050
|
-
});
|
|
1051
|
-
newServeProcess.on("close", (code) => {
|
|
1052
|
-
console.log(chalk_1.default.yellow(`⚠ backend exited with code ${code}`));
|
|
1053
|
-
});
|
|
1054
|
-
serveProcess = newServeProcess;
|
|
1055
|
-
currentVersionStr = latestVersion;
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
catch (error) {
|
|
1059
|
-
// If the remote version.json does not exist, do not update
|
|
1060
|
-
console.error(chalk_1.default.yellow("⚠ Error checking for updates (skipping update):"), error.message);
|
|
1061
|
-
}
|
|
1062
|
-
};
|
|
1063
|
-
// check every 3 seconds
|
|
1064
|
-
setInterval(async () => await checkVersion(), 10 * 1000);
|
|
1065
|
-
exec("bun install", { cwd: outputDir }, async (error, stdout, stderr) => {
|
|
1066
|
-
if (error) {
|
|
1067
|
-
installSpinner.fail(chalk_1.default.red(`✗ Error installing dependencies: ${error.message}`));
|
|
1068
|
-
return;
|
|
1069
|
-
}
|
|
1070
|
-
if (stderr) {
|
|
1071
|
-
console.error(chalk_1.default.red(`stderr: ${stderr}`));
|
|
1072
|
-
}
|
|
1073
|
-
installSpinner.succeed(chalk_1.default.green("Dependencies installed successfully!"));
|
|
1074
|
-
// Install additional libraries if --libs is provided
|
|
1075
|
-
if (options.libs && options.libs.trim()) {
|
|
1076
|
-
const libsSpinner = (0, ora_1.default)(chalk_1.default.blue("Installing additional libraries...")).start();
|
|
1077
|
-
const additionalLibs = options.libs
|
|
1078
|
-
.split(",")
|
|
1079
|
-
.map((lib) => lib.trim())
|
|
1080
|
-
.filter((lib) => lib);
|
|
1081
|
-
for (const lib of additionalLibs) {
|
|
1082
|
-
try {
|
|
1083
|
-
await execPromise(`bun add ${lib}`, { cwd: outputDir });
|
|
1084
|
-
console.log(chalk_1.default.green(`Added ${lib}`));
|
|
1085
|
-
}
|
|
1086
|
-
catch (addError) {
|
|
1087
|
-
console.error(chalk_1.default.red(`✗ Error adding ${lib}:`), addError.message);
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
libsSpinner.succeed(chalk_1.default.green("Additional libraries installed successfully!"));
|
|
1091
|
-
}
|
|
1092
|
-
// Run cd ${id} && bun run serve-be
|
|
1093
|
-
console.log(chalk_1.default.blue("🚀 Starting backend"));
|
|
1094
|
-
serveProcess = exec("bun run start", { cwd: outputDir });
|
|
1095
|
-
serveProcess.stdout.on("data", (data) => {
|
|
1096
|
-
process.stdout.write(data);
|
|
1097
|
-
});
|
|
1098
|
-
serveProcess.stderr.on("data", (data) => {
|
|
1099
|
-
process.stderr.write(data);
|
|
1100
|
-
});
|
|
1101
|
-
serveProcess.on("close", (code) => {
|
|
1102
|
-
console.log(chalk_1.default.yellow(`⚠ backend exited with code ${code}`));
|
|
1103
|
-
});
|
|
974
|
+
});
|
|
975
|
+
};
|
|
976
|
+
traverseAndReplace(dir);
|
|
1104
977
|
});
|
|
978
|
+
// Save project ID
|
|
979
|
+
saveProjectId(outputDir, id);
|
|
980
|
+
console.log(chalk_1.default.green(`✓ Project "${id}" fetched and setup successfully!`));
|
|
981
|
+
}
|
|
982
|
+
catch (error) {
|
|
983
|
+
apiSpinner.fail(chalk_1.default.red(`✗ Error: ${error.message}`));
|
|
1105
984
|
}
|
|
1106
985
|
});
|
|
1107
986
|
// hapico publish
|
|
1108
|
-
commander_1.program
|
|
1109
|
-
|
|
987
|
+
commander_1.program
|
|
988
|
+
.command("publish")
|
|
989
|
+
.description("Publish the project to production environment")
|
|
990
|
+
.action(async () => {
|
|
991
|
+
console.clear();
|
|
992
|
+
console.log(chalk_1.default.bold.magenta("\n🚀 HAPICO PUBLISH PIPELINE"));
|
|
993
|
+
console.log(chalk_1.default.gray("──────────────────────────────────────────────────────────────────"));
|
|
1110
994
|
const pwd = process.cwd();
|
|
1111
995
|
const { projectId, replicate } = getStoredProjectId(pwd);
|
|
1112
996
|
if (!projectId) {
|
|
1113
|
-
|
|
997
|
+
console.log(chalk_1.default.red("✗ Project ID not found. Ensure hapico.config.json exists."));
|
|
1114
998
|
return;
|
|
1115
999
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
if (
|
|
1119
|
-
|
|
1000
|
+
console.log(chalk_1.default.bold.white("Target Environments:"));
|
|
1001
|
+
console.log(`${chalk_1.default.gray("├─")} ${chalk_1.default.bold("Main Project :")} ${chalk_1.default.magenta(projectId)}`);
|
|
1002
|
+
if (replicate && replicate.length > 0) {
|
|
1003
|
+
console.log(`${chalk_1.default.gray("└─")} ${chalk_1.default.bold("Replicas :")} ${chalk_1.default.magenta(replicate.join(", "))}`);
|
|
1004
|
+
}
|
|
1005
|
+
else {
|
|
1006
|
+
console.log(`${chalk_1.default.gray("└─")} ${chalk_1.default.bold("Replicas :")} ${chalk_1.default.gray("None")}`);
|
|
1007
|
+
}
|
|
1008
|
+
console.log("");
|
|
1009
|
+
const pushSpinner = (0, ora_1.default)(chalk_1.default.blue("Phase 1/2: Syncing source code with main project...")).start();
|
|
1010
|
+
// Step 1: Push Main
|
|
1011
|
+
const mainResult = await pushProject(projectId);
|
|
1012
|
+
if (mainResult.success) {
|
|
1013
|
+
pushSpinner.succeed(chalk_1.default.green(`Main Project [${projectId}] code synced successfully.`));
|
|
1014
|
+
console.log(`${chalk_1.default.gray(" ├─")} ${chalk_1.default.bold("Files :")} ${chalk_1.default.white(mainResult.fileCount)}`);
|
|
1015
|
+
console.log(`${chalk_1.default.gray(" └─")} ${chalk_1.default.bold("Size :")} ${chalk_1.default.white((mainResult.totalSize / 1024).toFixed(2) + " KB")}\n`);
|
|
1120
1016
|
}
|
|
1121
|
-
|
|
1017
|
+
else {
|
|
1018
|
+
pushSpinner.fail(chalk_1.default.red(`Main Project [${projectId}] sync failed: ${mainResult.error}`));
|
|
1019
|
+
return; // Stop if push to main fails
|
|
1020
|
+
}
|
|
1021
|
+
// Push Replicas
|
|
1122
1022
|
let allPushSuccess = true;
|
|
1123
|
-
if (replicate &&
|
|
1124
|
-
publishSpinner.text = chalk_1.default.blue("Pushing to replicated projects...");
|
|
1023
|
+
if (replicate && replicate.length > 0) {
|
|
1125
1024
|
for (const repId of replicate) {
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1025
|
+
const repSpinner = (0, ora_1.default)(chalk_1.default.blue(`Phase 1/2: Syncing replica code [${repId}]...`)).start();
|
|
1026
|
+
const repResult = await pushProject(repId);
|
|
1027
|
+
if (repResult.success) {
|
|
1028
|
+
repSpinner.succeed(chalk_1.default.green(`Replica [${repId}] code synced successfully.`));
|
|
1029
|
+
console.log(`${chalk_1.default.gray(" ├─")} ${chalk_1.default.bold("Files :")} ${chalk_1.default.white(repResult.fileCount)}`);
|
|
1030
|
+
console.log(`${chalk_1.default.gray(" └─")} ${chalk_1.default.bold("Size :")} ${chalk_1.default.white((repResult.totalSize / 1024).toFixed(2) + " KB")}\n`);
|
|
1130
1031
|
}
|
|
1131
1032
|
else {
|
|
1132
|
-
|
|
1033
|
+
repSpinner.fail(chalk_1.default.red(`Replica [${repId}] sync failed: ${repResult.error}`));
|
|
1034
|
+
allPushSuccess = false;
|
|
1133
1035
|
}
|
|
1134
1036
|
}
|
|
1135
1037
|
}
|
|
1136
|
-
// Step 2: Publish
|
|
1137
|
-
const
|
|
1138
|
-
const
|
|
1038
|
+
// Step 2: Publish Main
|
|
1039
|
+
const publishSpinner = (0, ora_1.default)(chalk_1.default.blue(`Phase 2/2: Publishing main project [${projectId}]...`)).start();
|
|
1040
|
+
const pubMainResult = await publishProjectApi(projectId);
|
|
1139
1041
|
let allPublishSuccess = true;
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
headers: {
|
|
1143
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1144
|
-
"Content-Type": "application/json",
|
|
1145
|
-
},
|
|
1146
|
-
});
|
|
1147
|
-
publishSpinner.succeed(chalk_1.default.green(`Project ${projectId} published successfully!`));
|
|
1042
|
+
if (pubMainResult.success) {
|
|
1043
|
+
publishSpinner.succeed(chalk_1.default.green(`Main Project [${projectId}] published successfully.`));
|
|
1148
1044
|
}
|
|
1149
|
-
|
|
1045
|
+
else {
|
|
1046
|
+
publishSpinner.fail(chalk_1.default.red(`Main Project [${projectId}] publish failed: ${pubMainResult.error}`));
|
|
1150
1047
|
allPublishSuccess = false;
|
|
1151
|
-
publishSpinner.fail(chalk_1.default.red(`✗ Error publishing project ${projectId}: ${error.message}`));
|
|
1152
1048
|
}
|
|
1153
|
-
//
|
|
1154
|
-
if (replicate &&
|
|
1155
|
-
publishSpinner.text = chalk_1.default.blue("Publishing replicated projects...");
|
|
1049
|
+
// Publish Replicas
|
|
1050
|
+
if (replicate && replicate.length > 0) {
|
|
1156
1051
|
for (const repId of replicate) {
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
"Content-Type": "application/json",
|
|
1162
|
-
},
|
|
1163
|
-
});
|
|
1164
|
-
console.log(chalk_1.default.green(`✓ Successfully published replicated project ${repId}.`));
|
|
1052
|
+
const repPubSpinner = (0, ora_1.default)(chalk_1.default.blue(`Phase 2/2: Publishing replica [${repId}]...`)).start();
|
|
1053
|
+
const pubRepResult = await publishProjectApi(repId);
|
|
1054
|
+
if (pubRepResult.success) {
|
|
1055
|
+
repPubSpinner.succeed(chalk_1.default.green(`Replica [${repId}] published successfully.`));
|
|
1165
1056
|
}
|
|
1166
|
-
|
|
1057
|
+
else {
|
|
1058
|
+
repPubSpinner.fail(chalk_1.default.red(`Replica [${repId}] publish failed: ${pubRepResult.error}`));
|
|
1167
1059
|
allPublishSuccess = false;
|
|
1168
|
-
console.warn(chalk_1.default.yellow(`⚠ Error publishing replicated project ${repId}: ${error.message}`));
|
|
1169
1060
|
}
|
|
1170
1061
|
}
|
|
1171
1062
|
}
|
|
1063
|
+
console.log(chalk_1.default.gray("──────────────────────────────────────────────────────────────────"));
|
|
1172
1064
|
if (allPushSuccess && allPublishSuccess) {
|
|
1173
|
-
|
|
1065
|
+
console.log(`\n${chalk_1.default.bold.white.bgGreen(" SUCCESS ")} ${chalk_1.default.green("Deployment pipeline completed successfully!\n")}`);
|
|
1174
1066
|
}
|
|
1175
1067
|
else {
|
|
1176
|
-
|
|
1068
|
+
console.log(`\n${chalk_1.default.bold.white.bgYellow(" WARNING ")} ${chalk_1.default.yellow("Deployment pipeline completed with some errors.\n")}`);
|
|
1177
1069
|
}
|
|
1178
1070
|
});
|
|
1179
1071
|
commander_1.program.command("mirror").action(() => {
|
|
@@ -1186,7 +1078,7 @@ commander_1.program.command("mirror").action(() => {
|
|
|
1186
1078
|
}
|
|
1187
1079
|
const fileManager = new FileManager(srcDir);
|
|
1188
1080
|
const initialFiles = fileManager.listFiles();
|
|
1189
|
-
//
|
|
1081
|
+
// Collect project files and generate a single summary document
|
|
1190
1082
|
let content = ``;
|
|
1191
1083
|
(0, lodash_1.map)(initialFiles, (file) => {
|
|
1192
1084
|
content += `\`\`\`typescript
|