@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 +326 -58
- package/bin/templates/base/.gen/functions.js +126 -0
- package/bin/templates/base/.gen/plugins/router.plugin.js +12 -0
- package/bin/templates/base/plugins/vite-plugin-app-router.js +108 -0
- package/bin/templates/base/src/app.js +18 -0
- package/bin/templates/base/src/db/index.js +132 -0
- package/bin/templates/base/src/server.js +17 -0
- package/bin/utils/codemod/utils/path.mockup.js +84 -0
- package/bun.lock +77 -0
- package/dist/index.js +326 -58
- package/dist/templates/base/.gen/functions.js +126 -0
- package/dist/templates/base/.gen/plugins/router.plugin.js +12 -0
- package/dist/templates/base/plugins/vite-plugin-app-router.js +108 -0
- package/dist/templates/base/src/app.js +18 -0
- package/dist/templates/base/src/db/index.js +132 -0
- package/dist/templates/base/src/server.js +17 -0
- package/dist/utils/codemod/utils/path.mockup.js +84 -0
- package/index.ts +498 -58
- package/package.json +4 -1
- package/utils/codemod/index.tsx +1588 -0
- package/utils/codemod/utils/path.mockup.ts +97 -0
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
|
|
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}
|
|
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}
|
|
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://
|
|
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(
|
|
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.
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
552
|
-
console.log(
|
|
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(
|
|
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
|
-
|
|
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);
|