@hed-hog/cli 0.0.12
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/README.md +98 -0
- package/dist/scripts/deploy.d.ts +1 -0
- package/dist/scripts/deploy.js +84 -0
- package/dist/scripts/deploy.js.map +1 -0
- package/dist/src/app.module.d.ts +2 -0
- package/dist/src/app.module.js +58 -0
- package/dist/src/app.module.js.map +1 -0
- package/dist/src/commands/add.command.d.ts +14 -0
- package/dist/src/commands/add.command.js +58 -0
- package/dist/src/commands/add.command.js.map +1 -0
- package/dist/src/commands/dev.command/backupdb.subcommand.d.ts +10 -0
- package/dist/src/commands/dev.command/backupdb.subcommand.js +47 -0
- package/dist/src/commands/dev.command/backupdb.subcommand.js.map +1 -0
- package/dist/src/commands/dev.command/create-library.subcommand.d.ts +15 -0
- package/dist/src/commands/dev.command/create-library.subcommand.js +88 -0
- package/dist/src/commands/dev.command/create-library.subcommand.js.map +1 -0
- package/dist/src/commands/dev.command/hash-directory.subcommand.d.ts +12 -0
- package/dist/src/commands/dev.command/hash-directory.subcommand.js +70 -0
- package/dist/src/commands/dev.command/hash-directory.subcommand.js.map +1 -0
- package/dist/src/commands/dev.command/initdb.subcommand.d.ts +10 -0
- package/dist/src/commands/dev.command/initdb.subcommand.js +47 -0
- package/dist/src/commands/dev.command/initdb.subcommand.js.map +1 -0
- package/dist/src/commands/dev.command/reset.subcommand.d.ts +10 -0
- package/dist/src/commands/dev.command/reset.subcommand.js +47 -0
- package/dist/src/commands/dev.command/reset.subcommand.js.map +1 -0
- package/dist/src/commands/dev.command/restoredb.subcommand.d.ts +10 -0
- package/dist/src/commands/dev.command/restoredb.subcommand.js +47 -0
- package/dist/src/commands/dev.command/restoredb.subcommand.js.map +1 -0
- package/dist/src/commands/dev.command/route.subcommand.d.ts +10 -0
- package/dist/src/commands/dev.command/route.subcommand.js +47 -0
- package/dist/src/commands/dev.command/route.subcommand.js.map +1 -0
- package/dist/src/commands/dev.command/tsconfig.subcommand.d.ts +10 -0
- package/dist/src/commands/dev.command/tsconfig.subcommand.js +47 -0
- package/dist/src/commands/dev.command/tsconfig.subcommand.js.map +1 -0
- package/dist/src/commands/dev.command.d.ts +4 -0
- package/dist/src/commands/dev.command.js +40 -0
- package/dist/src/commands/dev.command.js.map +1 -0
- package/dist/src/commands/new.command.d.ts +43 -0
- package/dist/src/commands/new.command.js +436 -0
- package/dist/src/commands/new.command.js.map +1 -0
- package/dist/src/functions/to-pascal-case.d.ts +1 -0
- package/dist/src/functions/to-pascal-case.js +10 -0
- package/dist/src/functions/to-pascal-case.js.map +1 -0
- package/dist/src/main.d.ts +2 -0
- package/dist/src/main.js +10 -0
- package/dist/src/main.js.map +1 -0
- package/dist/src/modules/database/database.module.d.ts +2 -0
- package/dist/src/modules/database/database.module.js +22 -0
- package/dist/src/modules/database/database.module.js.map +1 -0
- package/dist/src/modules/database/database.service.d.ts +14 -0
- package/dist/src/modules/database/database.service.js +186 -0
- package/dist/src/modules/database/database.service.js.map +1 -0
- package/dist/src/modules/developer/developer.module.d.ts +2 -0
- package/dist/src/modules/developer/developer.module.js +22 -0
- package/dist/src/modules/developer/developer.module.js.map +1 -0
- package/dist/src/modules/developer/developer.service.d.ts +37 -0
- package/dist/src/modules/developer/developer.service.js +900 -0
- package/dist/src/modules/developer/developer.service.js.map +1 -0
- package/dist/src/modules/git/git.module.d.ts +2 -0
- package/dist/src/modules/git/git.module.js +23 -0
- package/dist/src/modules/git/git.module.js.map +1 -0
- package/dist/src/modules/git/git.service.d.ts +8 -0
- package/dist/src/modules/git/git.service.js +67 -0
- package/dist/src/modules/git/git.service.js.map +1 -0
- package/dist/src/modules/hedhog/hedhog.module.d.ts +2 -0
- package/dist/src/modules/hedhog/hedhog.module.js +41 -0
- package/dist/src/modules/hedhog/hedhog.module.js.map +1 -0
- package/dist/src/modules/hedhog/hedhog.service.d.ts +44 -0
- package/dist/src/modules/hedhog/hedhog.service.js +350 -0
- package/dist/src/modules/hedhog/hedhog.service.js.map +1 -0
- package/dist/src/modules/hedhog/services/file-system.service.d.ts +22 -0
- package/dist/src/modules/hedhog/services/file-system.service.js +230 -0
- package/dist/src/modules/hedhog/services/file-system.service.js.map +1 -0
- package/dist/src/modules/hedhog/services/migration.service.d.ts +39 -0
- package/dist/src/modules/hedhog/services/migration.service.js +767 -0
- package/dist/src/modules/hedhog/services/migration.service.js.map +1 -0
- package/dist/src/modules/hedhog/services/module.service.d.ts +10 -0
- package/dist/src/modules/hedhog/services/module.service.js +135 -0
- package/dist/src/modules/hedhog/services/module.service.js.map +1 -0
- package/dist/src/modules/hedhog/services/table.service.d.ts +49 -0
- package/dist/src/modules/hedhog/services/table.service.js +432 -0
- package/dist/src/modules/hedhog/services/table.service.js.map +1 -0
- package/dist/src/modules/hedhog/services/template.service.d.ts +13 -0
- package/dist/src/modules/hedhog/services/template.service.js +88 -0
- package/dist/src/modules/hedhog/services/template.service.js.map +1 -0
- package/dist/src/modules/package/package.module.d.ts +2 -0
- package/dist/src/modules/package/package.module.js +23 -0
- package/dist/src/modules/package/package.module.js.map +1 -0
- package/dist/src/modules/package/package.service.d.ts +9 -0
- package/dist/src/modules/package/package.service.js +94 -0
- package/dist/src/modules/package/package.service.js.map +1 -0
- package/dist/src/modules/runner/runner.module.d.ts +2 -0
- package/dist/src/modules/runner/runner.module.js +23 -0
- package/dist/src/modules/runner/runner.module.js.map +1 -0
- package/dist/src/modules/runner/runner.service.d.ts +14 -0
- package/dist/src/modules/runner/runner.service.js +69 -0
- package/dist/src/modules/runner/runner.service.js.map +1 -0
- package/dist/src/questions/database.question.d.ts +12 -0
- package/dist/src/questions/database.question.js +61 -0
- package/dist/src/questions/database.question.js.map +1 -0
- package/dist/src/questions/project-name.question.d.ts +3 -0
- package/dist/src/questions/project-name.question.js +34 -0
- package/dist/src/questions/project-name.question.js.map +1 -0
- package/dist/templates/database/touch_updated_at.sql.ejs +9 -0
- package/dist/templates/database/trg_touch_updated_at.sql.ejs +2 -0
- package/dist/templates/library/.eslintrc.js.ejs +9 -0
- package/dist/templates/library/.prettierrc.js.ejs +4 -0
- package/dist/templates/library/index.ts.ejs +1 -0
- package/dist/templates/library/init.app.module.ts.ejs +25 -0
- package/dist/templates/library/init.package.json.ejs +61 -0
- package/dist/templates/library/module.ts.ejs +15 -0
- package/dist/templates/library/package.json.ejs +36 -0
- package/dist/templates/library/tsconfig.json.ejs +11 -0
- package/dist/templates/library/tsconfig.production.json.ejs +46 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1,900 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.DeveloperService = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const chalk = require("chalk");
|
|
15
|
+
const crypto_1 = require("crypto");
|
|
16
|
+
const ejs_1 = require("ejs");
|
|
17
|
+
const fs_1 = require("fs");
|
|
18
|
+
const promises_1 = require("fs/promises");
|
|
19
|
+
const inquirer_1 = require("inquirer");
|
|
20
|
+
const ora = require("ora");
|
|
21
|
+
const pathModule = require("path");
|
|
22
|
+
const to_pascal_case_1 = require("../../functions/to-pascal-case");
|
|
23
|
+
const database_service_1 = require("../database/database.service");
|
|
24
|
+
const file_system_service_1 = require("../hedhog/services/file-system.service");
|
|
25
|
+
const runner_service_1 = require("../runner/runner.service");
|
|
26
|
+
let DeveloperService = class DeveloperService {
|
|
27
|
+
runner;
|
|
28
|
+
fileSystem;
|
|
29
|
+
database;
|
|
30
|
+
verbose = false;
|
|
31
|
+
constructor(runner, fileSystem, database) {
|
|
32
|
+
this.runner = runner;
|
|
33
|
+
this.fileSystem = fileSystem;
|
|
34
|
+
this.database = database;
|
|
35
|
+
}
|
|
36
|
+
log(...args) {
|
|
37
|
+
if (this.verbose) {
|
|
38
|
+
console.log('\n', ...args);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async route(path, verbose = false) {
|
|
42
|
+
this.verbose = verbose;
|
|
43
|
+
const spinner = ora('Analyzing NestJS routes...').start();
|
|
44
|
+
try {
|
|
45
|
+
path = await this.getRootPath(path);
|
|
46
|
+
const apiPath = pathModule.join(path, 'apps', 'api');
|
|
47
|
+
if (!(0, fs_1.existsSync)(apiPath)) {
|
|
48
|
+
spinner.fail('API directory not found. Make sure you are in a NestJS project.');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Find main.ts
|
|
52
|
+
const mainPath = pathModule.join(apiPath, 'src', 'main.ts');
|
|
53
|
+
if (!(0, fs_1.existsSync)(mainPath)) {
|
|
54
|
+
spinner.fail('main.ts not found.');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
spinner.text = 'Reading main.ts...';
|
|
58
|
+
const mainContent = await (0, promises_1.readFile)(mainPath, 'utf8');
|
|
59
|
+
const mainModuleName = this.extractMainModule(mainContent);
|
|
60
|
+
if (!mainModuleName) {
|
|
61
|
+
spinner.fail('Could not determine main module from main.ts');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.log(chalk.blue(`Main module: ${mainModuleName}`));
|
|
65
|
+
// Find app.module.ts
|
|
66
|
+
const appModulePath = pathModule.join(apiPath, 'src', 'app.module.ts');
|
|
67
|
+
if (!(0, fs_1.existsSync)(appModulePath)) {
|
|
68
|
+
spinner.fail('app.module.ts not found.');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
spinner.text = 'Scanning controllers...';
|
|
72
|
+
const routes = await this.extractRoutes(apiPath);
|
|
73
|
+
spinner.succeed('Routes extracted successfully.');
|
|
74
|
+
if (routes.length === 0) {
|
|
75
|
+
console.log(chalk.yellow('\nNo routes found.'));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Display routes
|
|
79
|
+
console.log(chalk.green.bold('\n📋 Routes found:\n'));
|
|
80
|
+
const groupedRoutes = this.groupRoutesByController(routes);
|
|
81
|
+
for (const [controller, controllerRoutes] of Object.entries(groupedRoutes)) {
|
|
82
|
+
console.log(chalk.cyan.bold(`\n${controller}:`));
|
|
83
|
+
controllerRoutes.forEach((route) => {
|
|
84
|
+
const methodColor = this.getMethodColor(route.method);
|
|
85
|
+
const method = route.method.padEnd(7);
|
|
86
|
+
console.log(` ${methodColor(method)} ${chalk.white(route.path)}`);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
console.log(chalk.gray(`\nTotal routes: ${routes.length}\n`));
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
spinner.fail('Failed to analyze routes.');
|
|
93
|
+
console.error(chalk.red('Error analyzing routes:'), error);
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
spinner.stop();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
extractMainModule(content) {
|
|
101
|
+
// Look for patterns like: bootstrap(AppModule) or .create(AppModule)
|
|
102
|
+
const patterns = [
|
|
103
|
+
/NestFactory\.create\((\w+)\)/,
|
|
104
|
+
/bootstrap\((\w+)\)/,
|
|
105
|
+
/\.create\((\w+)\)/,
|
|
106
|
+
];
|
|
107
|
+
for (const pattern of patterns) {
|
|
108
|
+
const match = content.match(pattern);
|
|
109
|
+
if (match) {
|
|
110
|
+
return match[1];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
async extractRoutes(apiPath) {
|
|
116
|
+
const routes = [];
|
|
117
|
+
const srcPath = pathModule.join(apiPath, 'src');
|
|
118
|
+
const findControllers = async (dir) => {
|
|
119
|
+
const entries = await (0, promises_1.readdir)(dir, { withFileTypes: true });
|
|
120
|
+
for (const entry of entries) {
|
|
121
|
+
const fullPath = pathModule.join(dir, entry.name);
|
|
122
|
+
if (entry.isDirectory() && entry.name !== 'node_modules') {
|
|
123
|
+
await findControllers(fullPath);
|
|
124
|
+
}
|
|
125
|
+
else if (entry.isFile() && entry.name.endsWith('.controller.ts')) {
|
|
126
|
+
const controllerRoutes = await this.parseController(fullPath);
|
|
127
|
+
routes.push(...controllerRoutes);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
await findControllers(srcPath);
|
|
132
|
+
return routes;
|
|
133
|
+
}
|
|
134
|
+
async parseController(filePath) {
|
|
135
|
+
const content = await (0, promises_1.readFile)(filePath, 'utf8');
|
|
136
|
+
const routes = [];
|
|
137
|
+
// Extract controller name and base path
|
|
138
|
+
const controllerMatch = content.match(/@Controller\(['"](.*?)['"]\)/);
|
|
139
|
+
const basePath = controllerMatch ? controllerMatch[1] : '';
|
|
140
|
+
// Extract class name
|
|
141
|
+
const classMatch = content.match(/export\s+class\s+(\w+Controller)/);
|
|
142
|
+
const controllerName = classMatch
|
|
143
|
+
? classMatch[1]
|
|
144
|
+
: pathModule.basename(filePath, '.ts');
|
|
145
|
+
// HTTP methods to look for
|
|
146
|
+
const httpMethods = [
|
|
147
|
+
'Get',
|
|
148
|
+
'Post',
|
|
149
|
+
'Put',
|
|
150
|
+
'Delete',
|
|
151
|
+
'Patch',
|
|
152
|
+
'Options',
|
|
153
|
+
'Head',
|
|
154
|
+
];
|
|
155
|
+
for (const method of httpMethods) {
|
|
156
|
+
// Match patterns like @Get(), @Get('path'), @Get(':id')
|
|
157
|
+
const methodRegex = new RegExp(`@${method}\\((?:['"](.*?)['"])?\\)`, 'g');
|
|
158
|
+
let match;
|
|
159
|
+
while ((match = methodRegex.exec(content)) !== null) {
|
|
160
|
+
const routePath = match[1] || '';
|
|
161
|
+
const fullPath = this.buildFullPath(basePath, routePath);
|
|
162
|
+
routes.push({
|
|
163
|
+
controller: controllerName,
|
|
164
|
+
method: method.toUpperCase(),
|
|
165
|
+
path: fullPath,
|
|
166
|
+
filePath,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return routes;
|
|
171
|
+
}
|
|
172
|
+
buildFullPath(basePath, routePath) {
|
|
173
|
+
const base = basePath.startsWith('/') ? basePath : `/${basePath}`;
|
|
174
|
+
const route = routePath.startsWith('/') ? routePath : `/${routePath}`;
|
|
175
|
+
if (!routePath) {
|
|
176
|
+
return base || '/';
|
|
177
|
+
}
|
|
178
|
+
if (!basePath) {
|
|
179
|
+
return route;
|
|
180
|
+
}
|
|
181
|
+
return `${base}${route}`.replace(/\/+/g, '/');
|
|
182
|
+
}
|
|
183
|
+
groupRoutesByController(routes) {
|
|
184
|
+
const grouped = {};
|
|
185
|
+
for (const route of routes) {
|
|
186
|
+
if (!grouped[route.controller]) {
|
|
187
|
+
grouped[route.controller] = [];
|
|
188
|
+
}
|
|
189
|
+
grouped[route.controller].push({
|
|
190
|
+
method: route.method,
|
|
191
|
+
path: route.path,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
// Sort routes within each controller
|
|
195
|
+
for (const controller in grouped) {
|
|
196
|
+
grouped[controller].sort((a, b) => {
|
|
197
|
+
if (a.path !== b.path) {
|
|
198
|
+
return a.path.localeCompare(b.path);
|
|
199
|
+
}
|
|
200
|
+
return a.method.localeCompare(b.method);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return grouped;
|
|
204
|
+
}
|
|
205
|
+
getMethodColor(method) {
|
|
206
|
+
const colors = {
|
|
207
|
+
GET: chalk.green,
|
|
208
|
+
POST: chalk.blue,
|
|
209
|
+
PUT: chalk.yellow,
|
|
210
|
+
DELETE: chalk.red,
|
|
211
|
+
PATCH: chalk.magenta,
|
|
212
|
+
OPTIONS: chalk.gray,
|
|
213
|
+
HEAD: chalk.cyan,
|
|
214
|
+
};
|
|
215
|
+
return colors[method] || chalk.white;
|
|
216
|
+
}
|
|
217
|
+
async createLibrary(path, libraryName, force = false, verbose = false, skipInstall = false) {
|
|
218
|
+
this.verbose = verbose;
|
|
219
|
+
this.log(chalk.blue('Starting library creation...'));
|
|
220
|
+
if (!libraryName) {
|
|
221
|
+
console.error(chalk.red('Library name is required.'));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const libraryPath = pathModule.join(path, 'libraries', libraryName);
|
|
225
|
+
if ((0, fs_1.existsSync)(libraryPath)) {
|
|
226
|
+
if (force) {
|
|
227
|
+
this.log(chalk.yellow(`Library ${libraryName} already exists. Overwriting...`));
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
console.error(chalk.red(`Library ${libraryName} already exists.`));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
await (0, promises_1.mkdir)(pathModule.join(libraryPath, 'src'), { recursive: true });
|
|
235
|
+
this.log(chalk.green(`Created directory: ${libraryPath}/src`));
|
|
236
|
+
const vars = {
|
|
237
|
+
path,
|
|
238
|
+
libraryPath,
|
|
239
|
+
libraryNamePascalCase: this.toPascalCase(libraryName),
|
|
240
|
+
libraryNameSnackCase: this.toSnackCase(libraryName),
|
|
241
|
+
libraryNameCamelCase: this.toCamelCase(libraryName),
|
|
242
|
+
libraryNameKebabCase: this.toKebabCase(libraryName),
|
|
243
|
+
libraryName: libraryName,
|
|
244
|
+
};
|
|
245
|
+
const files = [
|
|
246
|
+
{
|
|
247
|
+
template: '.eslintrc.js.ejs',
|
|
248
|
+
destination: `/.eslintrc.js`,
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
template: '.prettierrc.js.ejs',
|
|
252
|
+
destination: `/.prettierrc.js`,
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
template: 'package.json.ejs',
|
|
256
|
+
destination: `/package.json`,
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
template: 'tsconfig.json.ejs',
|
|
260
|
+
destination: `/tsconfig.json`,
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
template: 'tsconfig.production.json.ejs',
|
|
264
|
+
destination: `/tsconfig.production.json`,
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
template: 'module.ts.ejs',
|
|
268
|
+
destination: `/src/${vars.libraryNameKebabCase}.module.ts`,
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
template: 'index.ts.ejs',
|
|
272
|
+
destination: `/src/index.ts`,
|
|
273
|
+
},
|
|
274
|
+
];
|
|
275
|
+
for (const file of files) {
|
|
276
|
+
const templatePath = pathModule.join(__dirname, '..', '..', 'templates', 'library', file.template);
|
|
277
|
+
const destinationPath = pathModule.join(path, 'libraries', libraryName, file.destination);
|
|
278
|
+
this.log(chalk.blue(`Rendering template: ${file.template}`));
|
|
279
|
+
const content = await (0, promises_1.readFile)(templatePath, 'utf8');
|
|
280
|
+
const renderedContent = await (0, ejs_1.render)(content, vars);
|
|
281
|
+
await (0, promises_1.writeFile)(destinationPath, renderedContent, 'utf8');
|
|
282
|
+
this.log(chalk.blue(`Created file: ${destinationPath}`));
|
|
283
|
+
}
|
|
284
|
+
this.log(chalk.green('Library files created successfully.'));
|
|
285
|
+
await this.updateTsconfigFiles(path, verbose);
|
|
286
|
+
if (!skipInstall) {
|
|
287
|
+
this.log(chalk.blue(`Installing dependencies for ${libraryName}...`));
|
|
288
|
+
await this.runner.executeCommand(runner_service_1.ProgramName.PNPM, ['install'], {
|
|
289
|
+
cwd: libraryPath,
|
|
290
|
+
}, true);
|
|
291
|
+
this.log(chalk.green(`Dependencies installed for ${libraryName}.`));
|
|
292
|
+
}
|
|
293
|
+
this.log(chalk.green(`Library ${libraryName} created successfully at ${libraryPath}.`));
|
|
294
|
+
}
|
|
295
|
+
async hashDirectory(path) {
|
|
296
|
+
const sum = (0, crypto_1.createHash)('sha256');
|
|
297
|
+
const paths = [];
|
|
298
|
+
async function scanDirectory(dir) {
|
|
299
|
+
const entries = await (0, promises_1.readdir)(dir, { withFileTypes: true });
|
|
300
|
+
for (const entry of entries) {
|
|
301
|
+
if (entry.name === 'node_modules')
|
|
302
|
+
continue;
|
|
303
|
+
const path = `${dir}/${entry.name}`;
|
|
304
|
+
if (entry.isDirectory()) {
|
|
305
|
+
await scanDirectory(path);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
const fileContent = await (0, promises_1.readFile)(path, 'utf8');
|
|
309
|
+
paths.push({ path, content: fileContent });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
await scanDirectory(path);
|
|
314
|
+
const hash = sum.update(JSON.stringify(paths)).digest('hex');
|
|
315
|
+
return hash;
|
|
316
|
+
}
|
|
317
|
+
async updateTsconfigFiles(path, verbose = false) {
|
|
318
|
+
this.verbose = verbose;
|
|
319
|
+
this.log(chalk.blue('Updating tsconfig files...'));
|
|
320
|
+
const directories = [];
|
|
321
|
+
const scanDirs = ['libraries', 'packages'];
|
|
322
|
+
try {
|
|
323
|
+
for (const dir of scanDirs) {
|
|
324
|
+
const baseDir = pathModule.join(path, dir);
|
|
325
|
+
if (!(0, fs_1.existsSync)(baseDir) || !(0, fs_1.statSync)(baseDir).isDirectory())
|
|
326
|
+
continue;
|
|
327
|
+
const subDirs = (0, fs_1.readdirSync)(baseDir, { withFileTypes: true })
|
|
328
|
+
.filter((entry) => entry.isDirectory())
|
|
329
|
+
.map((entry) => pathModule.join(baseDir, entry.name));
|
|
330
|
+
for (const subDir of subDirs) {
|
|
331
|
+
const tsconfigPath = pathModule.join(subDir, 'tsconfig.json');
|
|
332
|
+
const tsconfigProdPath = pathModule.join(subDir, 'tsconfig.production.json');
|
|
333
|
+
if ((0, fs_1.existsSync)(tsconfigPath) || (0, fs_1.existsSync)(tsconfigProdPath)) {
|
|
334
|
+
directories.push(subDir);
|
|
335
|
+
this.log(chalk.blue(`Found tsconfig in: ${subDir}`));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const destinationPath = '/packages/typescript-config';
|
|
340
|
+
this.log(chalk.blue('Loading base tsconfig...'));
|
|
341
|
+
const baseData = require(pathModule.join(path, destinationPath, 'base.json'));
|
|
342
|
+
baseData.compilerOptions.paths = {
|
|
343
|
+
'@prisma/client': [
|
|
344
|
+
'../../packages/api-prisma/node_modules/.prisma/client',
|
|
345
|
+
],
|
|
346
|
+
'@prisma/client/*': [
|
|
347
|
+
'../../packages/api-prisma/node_modules/.prisma/client/*',
|
|
348
|
+
],
|
|
349
|
+
};
|
|
350
|
+
for (const subDir of directories) {
|
|
351
|
+
const packageName = require(pathModule.join(subDir, 'package.json')).name;
|
|
352
|
+
const relativePath = subDir.split(pathModule.sep).slice(-2);
|
|
353
|
+
baseData.compilerOptions.paths[packageName] = [
|
|
354
|
+
`../../${relativePath.join('/')}/src`,
|
|
355
|
+
];
|
|
356
|
+
baseData.compilerOptions.paths[`${packageName}/*`] = [
|
|
357
|
+
`../../${relativePath.join('/')}/src/*`,
|
|
358
|
+
];
|
|
359
|
+
this.log(chalk.blue(`Added path mapping for: ${packageName}`));
|
|
360
|
+
}
|
|
361
|
+
await (0, promises_1.writeFile)(pathModule.join(path, destinationPath, 'base.json'), JSON.stringify(baseData, null, 2), 'utf-8');
|
|
362
|
+
this.log(chalk.green('Updated base tsconfig.json successfully.'));
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
console.error(error);
|
|
366
|
+
this.log(chalk.red('Failed to update tsconfig files.'));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
toCamelCase(str) {
|
|
370
|
+
return str
|
|
371
|
+
.replace(/[-_](\w)/g, (_, c) => (c ? c.toUpperCase() : ''))
|
|
372
|
+
.replace(/^\w/, (c) => c.toLowerCase());
|
|
373
|
+
}
|
|
374
|
+
toKebabCase(str) {
|
|
375
|
+
return str
|
|
376
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
377
|
+
.replace(/[_\s]+/g, '-')
|
|
378
|
+
.toLowerCase();
|
|
379
|
+
}
|
|
380
|
+
toPascalCase(str) {
|
|
381
|
+
return (0, to_pascal_case_1.toPascalCase)(str);
|
|
382
|
+
}
|
|
383
|
+
toSnackCase(str) {
|
|
384
|
+
return str
|
|
385
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
386
|
+
.replace(/[_\s]+/g, '-')
|
|
387
|
+
.toLowerCase()
|
|
388
|
+
.replace(/-/g, '_');
|
|
389
|
+
}
|
|
390
|
+
async resetDevelopmentEnvironment(cwd, verbose = false) {
|
|
391
|
+
this.verbose = verbose;
|
|
392
|
+
const spinner = ora('Resetting development environment...').start();
|
|
393
|
+
try {
|
|
394
|
+
await this.removeLibrariesDirectory(cwd, spinner);
|
|
395
|
+
await this.removeMigrationFiles(cwd, spinner);
|
|
396
|
+
await this.resetAppModuleFile(cwd, spinner);
|
|
397
|
+
await this.resetPackageJsonFile(cwd, spinner);
|
|
398
|
+
await this.resetDatabase(cwd, spinner);
|
|
399
|
+
await this.resetHedhogFile(cwd, spinner);
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
spinner.fail('Failed to reset development environment.');
|
|
403
|
+
console.error(chalk.red('Error resetting development environment:'), error);
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
406
|
+
finally {
|
|
407
|
+
spinner.stop();
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
async resetHedhogFile(cwd, spinner) {
|
|
411
|
+
this.log('Resetting .hedhog file...');
|
|
412
|
+
spinner.start('Resetting .hedhog file...');
|
|
413
|
+
const hedHogFilePath = pathModule.join(cwd, 'hedhog.json');
|
|
414
|
+
if ((0, fs_1.existsSync)(hedHogFilePath)) {
|
|
415
|
+
try {
|
|
416
|
+
const currentFileParsed = JSON.parse(await this.fileSystem.readFileAsString(hedHogFilePath));
|
|
417
|
+
spinner.text =
|
|
418
|
+
'Loaded existing hedhog.json file. Resetting contents...';
|
|
419
|
+
currentFileParsed.libraries = [];
|
|
420
|
+
currentFileParsed.installed = false;
|
|
421
|
+
currentFileParsed.developerMode = false;
|
|
422
|
+
currentFileParsed.buildCache = {};
|
|
423
|
+
await this.fileSystem.writeJsonFile(hedHogFilePath, currentFileParsed);
|
|
424
|
+
spinner.text = 'hedhog.json file reset.';
|
|
425
|
+
this.log('hedhog.json file reset.');
|
|
426
|
+
spinner.succeed('HedHog file reset successfully.');
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
spinner.fail('Failed to reset hedhog.json file. Proceeding to reset.');
|
|
430
|
+
this.log(chalk.red('Error parsing existing hedhog.json file. Proceeding to reset.'));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async getRootPath(cwd) {
|
|
435
|
+
let currentPath = cwd;
|
|
436
|
+
while (true) {
|
|
437
|
+
if ((0, fs_1.existsSync)(pathModule.join(currentPath, 'hedhog.json'))) {
|
|
438
|
+
return currentPath;
|
|
439
|
+
}
|
|
440
|
+
const parentPath = pathModule.dirname(currentPath);
|
|
441
|
+
if (parentPath === currentPath) {
|
|
442
|
+
throw new Error('Could not find root path with hedhog.json');
|
|
443
|
+
}
|
|
444
|
+
currentPath = parentPath;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
async resetDatabase(cwd, spinner) {
|
|
448
|
+
cwd = await this.getRootPath(cwd);
|
|
449
|
+
this.log('Resetting database...');
|
|
450
|
+
spinner.start('Resetting database...');
|
|
451
|
+
await this.database.executeQuery(pathModule.join(cwd, 'apps', 'api'), 'DROP SCHEMA public CASCADE; CREATE SCHEMA public;');
|
|
452
|
+
spinner.succeed('Database reset successfully.');
|
|
453
|
+
this.log('Database reset successfully.');
|
|
454
|
+
}
|
|
455
|
+
async removeLibrariesDirectory(cwd, spinner) {
|
|
456
|
+
this.log('Removing libraries directory...');
|
|
457
|
+
spinner.start('Removing libraries directory...');
|
|
458
|
+
const librariesPath = pathModule.join(cwd, 'libraries');
|
|
459
|
+
if ((0, fs_1.existsSync)(librariesPath)) {
|
|
460
|
+
// Give Windows time to release file handles
|
|
461
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
462
|
+
await this.fileSystem.remove(librariesPath);
|
|
463
|
+
this.log('Libraries directory removed.');
|
|
464
|
+
}
|
|
465
|
+
spinner.succeed('Libraries directory removed successfully.');
|
|
466
|
+
}
|
|
467
|
+
async removeMigrationFiles(cwd, spinner) {
|
|
468
|
+
this.log('Removing migration files...');
|
|
469
|
+
spinner.start('Removing migration files...');
|
|
470
|
+
const migrationsPath = pathModule.join(cwd, 'apps', 'api', 'prisma', 'migrations');
|
|
471
|
+
if ((0, fs_1.existsSync)(migrationsPath)) {
|
|
472
|
+
await this.fileSystem.remove(migrationsPath);
|
|
473
|
+
this.log('Migration files removed.');
|
|
474
|
+
}
|
|
475
|
+
spinner.succeed('Migration files removed successfully.');
|
|
476
|
+
}
|
|
477
|
+
async resetAppModuleFile(cwd, spinner) {
|
|
478
|
+
this.log('Reset AppModule.ts file...');
|
|
479
|
+
spinner.start('Resetting AppModule.ts file...');
|
|
480
|
+
const appModulePath = pathModule.join(cwd, 'apps', 'api', 'src', 'app.module.ts');
|
|
481
|
+
if ((0, fs_1.existsSync)(appModulePath)) {
|
|
482
|
+
let templateContent;
|
|
483
|
+
this.log(chalk.yellow('Failed to fetch app.module.ts from remote. Using local template.'));
|
|
484
|
+
const localTemplatePath = pathModule.join(__dirname, '..', '..', 'templates', 'library', 'init.app.module.ts.ejs');
|
|
485
|
+
templateContent = await (0, promises_1.readFile)(localTemplatePath, 'utf8');
|
|
486
|
+
await this.fileSystem.writeFileContent(appModulePath, templateContent);
|
|
487
|
+
this.log('AppModule.ts file reset.');
|
|
488
|
+
}
|
|
489
|
+
spinner.succeed('AppModule.ts file reset successfully.');
|
|
490
|
+
}
|
|
491
|
+
async resetPackageJsonFile(cwd, spinner) {
|
|
492
|
+
this.log('Resetting package.json file...');
|
|
493
|
+
spinner.start('Resetting package.json file...');
|
|
494
|
+
const packageJsonPath = pathModule.join(cwd, 'apps', 'api', 'package.json');
|
|
495
|
+
if ((0, fs_1.existsSync)(packageJsonPath)) {
|
|
496
|
+
let templateContent;
|
|
497
|
+
this.log(chalk.yellow('Failed to fetch package.json from remote. Using local template.'));
|
|
498
|
+
const localTemplatePath = pathModule.join(__dirname, '..', '..', 'templates', 'library', 'init.package.json.ejs');
|
|
499
|
+
templateContent = await (0, promises_1.readFile)(localTemplatePath, 'utf8');
|
|
500
|
+
await this.fileSystem.writeFileContent(packageJsonPath, templateContent);
|
|
501
|
+
this.log('package.json file reset.');
|
|
502
|
+
}
|
|
503
|
+
spinner.succeed('package.json file reset successfully.');
|
|
504
|
+
}
|
|
505
|
+
async getRunningDatabaseService(cwd) {
|
|
506
|
+
try {
|
|
507
|
+
// Get list of running services
|
|
508
|
+
const result = await this.runner.executeCommand(runner_service_1.ProgramName.DOCKER_COMPOSE, ['ps', '--services', '--filter', 'status=running'], { cwd }, true);
|
|
509
|
+
const services = result.stdout
|
|
510
|
+
.trim()
|
|
511
|
+
.split('\n')
|
|
512
|
+
.filter((s) => s.length > 0);
|
|
513
|
+
this.log(`Running services: ${services.join(', ')}`);
|
|
514
|
+
if (services.length === 0) {
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
// Common database service names
|
|
518
|
+
const dbServiceNames = [
|
|
519
|
+
'db',
|
|
520
|
+
'postgres',
|
|
521
|
+
'postgresql',
|
|
522
|
+
'database',
|
|
523
|
+
'mysql',
|
|
524
|
+
'mariadb',
|
|
525
|
+
];
|
|
526
|
+
// Try to find a database service
|
|
527
|
+
for (const dbName of dbServiceNames) {
|
|
528
|
+
if (services.includes(dbName)) {
|
|
529
|
+
this.log(`Found database service: ${dbName}`);
|
|
530
|
+
return dbName;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
// If multiple services and no standard name found, ask user to choose
|
|
534
|
+
if (services.length > 1) {
|
|
535
|
+
console.log(chalk.yellow('\nMultiple services found. Please select the database service:'));
|
|
536
|
+
const answer = await inquirer_1.default.prompt([
|
|
537
|
+
{
|
|
538
|
+
type: 'list',
|
|
539
|
+
name: 'service',
|
|
540
|
+
message: 'Which service is your database?',
|
|
541
|
+
choices: services,
|
|
542
|
+
},
|
|
543
|
+
]);
|
|
544
|
+
return answer.service;
|
|
545
|
+
}
|
|
546
|
+
// Only one service, use it
|
|
547
|
+
this.log(`Using the only available service: ${services[0]}`);
|
|
548
|
+
return services[0];
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
this.log(chalk.red('Error getting running services:'), error);
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
async backupDatabase(cwd, verbose = false) {
|
|
556
|
+
this.verbose = verbose;
|
|
557
|
+
this.log('Backing up database...');
|
|
558
|
+
const spinner = ora('Backing up database...').start();
|
|
559
|
+
try {
|
|
560
|
+
cwd = await this.getRootPath(cwd);
|
|
561
|
+
// Get database configuration
|
|
562
|
+
const dbConfig = await this.database.getDatabaseConfig(pathModule.join(cwd, 'apps', 'api'));
|
|
563
|
+
if (!dbConfig) {
|
|
564
|
+
throw new Error('Failed to get database configuration');
|
|
565
|
+
}
|
|
566
|
+
// Detect running database service
|
|
567
|
+
spinner.text = 'Detecting database service...';
|
|
568
|
+
const dbService = await this.getRunningDatabaseService(cwd);
|
|
569
|
+
if (!dbService) {
|
|
570
|
+
throw new Error('No running database service found. Please start your database with "docker-compose up -d"');
|
|
571
|
+
}
|
|
572
|
+
this.log(`Using database service: ${dbService}`);
|
|
573
|
+
spinner.text = `Using database service: ${dbService}`;
|
|
574
|
+
// Create backup directory if it doesn't exist
|
|
575
|
+
const backupDir = pathModule.join(cwd, 'backup');
|
|
576
|
+
if (!(0, fs_1.existsSync)(backupDir)) {
|
|
577
|
+
await (0, promises_1.mkdir)(backupDir, { recursive: true });
|
|
578
|
+
this.log(`Created backup directory: ${backupDir}`);
|
|
579
|
+
}
|
|
580
|
+
// Generate backup filename with timestamp
|
|
581
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
582
|
+
const backupFileName = `backup-${timestamp}.dump`;
|
|
583
|
+
const backupFile = pathModule.join(backupDir, backupFileName);
|
|
584
|
+
const containerBackupPath = `/tmp/${backupFileName}`;
|
|
585
|
+
spinner.text = 'Creating database dump inside container...';
|
|
586
|
+
this.log(`Creating database dump at: ${backupFile}`);
|
|
587
|
+
// Execute pg_dump inside the Docker container
|
|
588
|
+
const dockerExecArgs = [
|
|
589
|
+
'exec',
|
|
590
|
+
'-T',
|
|
591
|
+
dbService,
|
|
592
|
+
'pg_dump',
|
|
593
|
+
'-h',
|
|
594
|
+
'localhost',
|
|
595
|
+
'-p',
|
|
596
|
+
'5432',
|
|
597
|
+
'-U',
|
|
598
|
+
dbConfig.user,
|
|
599
|
+
'-d',
|
|
600
|
+
dbConfig.database,
|
|
601
|
+
'-F',
|
|
602
|
+
'c', // custom format
|
|
603
|
+
'-f',
|
|
604
|
+
containerBackupPath,
|
|
605
|
+
];
|
|
606
|
+
// Set PGPASSWORD environment variable
|
|
607
|
+
const env = { ...process.env, PGPASSWORD: dbConfig.password };
|
|
608
|
+
await this.runner.executeCommand(runner_service_1.ProgramName.DOCKER_COMPOSE, dockerExecArgs, { cwd, env }, true);
|
|
609
|
+
spinner.text = 'Copying backup from Docker container to host machine...';
|
|
610
|
+
this.log('Copying backup from container to host...');
|
|
611
|
+
// Copy backup file from container to host machine
|
|
612
|
+
const dockerCpArgs = [
|
|
613
|
+
'cp',
|
|
614
|
+
`${dbService}:${containerBackupPath}`,
|
|
615
|
+
backupFile,
|
|
616
|
+
];
|
|
617
|
+
await this.runner.executeCommand(runner_service_1.ProgramName.DOCKER_COMPOSE, dockerCpArgs, { cwd }, true);
|
|
618
|
+
spinner.succeed(`Database backup created successfully at: ${backupFile}`);
|
|
619
|
+
this.log(chalk.green(`Database backup created successfully at: ${backupFile}`));
|
|
620
|
+
}
|
|
621
|
+
catch (error) {
|
|
622
|
+
spinner.fail('Failed to backup database.');
|
|
623
|
+
console.error(chalk.red('Error backing up database:'), error);
|
|
624
|
+
throw error;
|
|
625
|
+
}
|
|
626
|
+
finally {
|
|
627
|
+
spinner.stop();
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
async initDatabase(cwd, verbose = false) {
|
|
631
|
+
this.verbose = verbose;
|
|
632
|
+
this.log('Initializing database...');
|
|
633
|
+
const spinner = ora('Initializing database...').start();
|
|
634
|
+
try {
|
|
635
|
+
cwd = await this.getRootPath(cwd);
|
|
636
|
+
const dockerComposeYmlPath = pathModule.join(cwd, 'docker-compose.yml');
|
|
637
|
+
const dockerComposeYamlPath = pathModule.join(cwd, 'docker-compose.yaml');
|
|
638
|
+
const dockerComposeYmlPathExists = (0, fs_1.existsSync)(dockerComposeYmlPath);
|
|
639
|
+
const dockerComposeYamlPathExists = (0, fs_1.existsSync)(dockerComposeYamlPath);
|
|
640
|
+
const dockerComposePath = dockerComposeYmlPathExists
|
|
641
|
+
? dockerComposeYmlPath
|
|
642
|
+
: dockerComposeYamlPath;
|
|
643
|
+
if (!dockerComposeYmlPathExists && !dockerComposeYamlPathExists) {
|
|
644
|
+
spinner.fail('docker-compose.yml or docker-compose.yaml not found in project root.');
|
|
645
|
+
throw new Error('docker-compose.yml or docker-compose.yaml not found in project root.');
|
|
646
|
+
}
|
|
647
|
+
spinner.text = 'Starting database container using Docker Compose...';
|
|
648
|
+
await this.runner.executeCommand(runner_service_1.ProgramName.DOCKER_COMPOSE, ['up', '-d'], { cwd }, true);
|
|
649
|
+
spinner.succeed('Database container started successfully.');
|
|
650
|
+
this.log(chalk.green('Database container started successfully.'));
|
|
651
|
+
spinner.start('Checking database connection...');
|
|
652
|
+
// Check if .env file exists in the api and admin app directory. If not exists then create it from .env.example and replace variables comparing with env vars in docker-compose
|
|
653
|
+
await this.database.generateEnvFileIfNotExists(pathModule.join(cwd, 'apps', 'api'), dockerComposePath);
|
|
654
|
+
spinner.succeed('Environment file checked/created successfully.');
|
|
655
|
+
this.log(chalk.green('Environment file checked/created successfully.'));
|
|
656
|
+
spinner.start('Running database migrations...');
|
|
657
|
+
await this.runner.executeCommand(runner_service_1.ProgramName.PNPM, ['prisma:deploy'], { cwd: pathModule.join(cwd, 'apps', 'api') }, true);
|
|
658
|
+
spinner.succeed('Database initialized and migrations applied successfully.');
|
|
659
|
+
this.log(chalk.green('Database initialized and migrations applied successfully.'));
|
|
660
|
+
spinner.start('Updating Prisma client...');
|
|
661
|
+
await this.runner.executeCommand(runner_service_1.ProgramName.PNPM, ['prisma:update'], { cwd: pathModule.join(cwd, 'apps', 'api') }, true);
|
|
662
|
+
spinner.succeed('Prisma client updated successfully.');
|
|
663
|
+
this.log(chalk.green('Prisma client updated successfully.'));
|
|
664
|
+
spinner.start('Creating .env files for applications...');
|
|
665
|
+
const appsDir = pathModule.join(cwd, 'apps');
|
|
666
|
+
if ((0, fs_1.existsSync)(appsDir)) {
|
|
667
|
+
const appDirs = (0, fs_1.readdirSync)(appsDir, { withFileTypes: true })
|
|
668
|
+
.filter((entry) => entry.isDirectory())
|
|
669
|
+
.map((entry) => pathModule.join(appsDir, entry.name));
|
|
670
|
+
for (const appDir of appDirs) {
|
|
671
|
+
spinner.text = `Checking .env file for ${appDir}...`;
|
|
672
|
+
const envPath = pathModule.join(appDir, '.env');
|
|
673
|
+
const envExamplePath = pathModule.join(appDir, '.env.example');
|
|
674
|
+
if (!(0, fs_1.existsSync)(envPath) && (0, fs_1.existsSync)(envExamplePath)) {
|
|
675
|
+
const envExampleContent = await (0, promises_1.readFile)(envExamplePath, 'utf8');
|
|
676
|
+
await (0, promises_1.writeFile)(envPath, envExampleContent, 'utf8');
|
|
677
|
+
this.log(chalk.green(`Created .env file for ${appDir}`));
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
spinner.succeed('.env files created/checked successfully.');
|
|
682
|
+
this.log(chalk.green('.env files created/checked successfully.'));
|
|
683
|
+
}
|
|
684
|
+
catch (error) {
|
|
685
|
+
spinner.fail('Failed to initialize database.');
|
|
686
|
+
console.error(chalk.red('Error initializing database:'), error);
|
|
687
|
+
throw error;
|
|
688
|
+
}
|
|
689
|
+
finally {
|
|
690
|
+
spinner.stop();
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
async restoreDatabase(cwd, verbose = false) {
|
|
694
|
+
this.verbose = verbose;
|
|
695
|
+
this.log('Restoring database from backup...');
|
|
696
|
+
const spinner = ora('Restoring database from backup...').start();
|
|
697
|
+
try {
|
|
698
|
+
cwd = await this.getRootPath(cwd);
|
|
699
|
+
// Get database configuration
|
|
700
|
+
const dbConfig = await this.database.getDatabaseConfig(pathModule.join(cwd, 'apps', 'api'));
|
|
701
|
+
if (!dbConfig) {
|
|
702
|
+
throw new Error('Failed to get database configuration');
|
|
703
|
+
}
|
|
704
|
+
// Detect running database service
|
|
705
|
+
spinner.text = 'Detecting database service...';
|
|
706
|
+
const dbService = await this.getRunningDatabaseService(cwd);
|
|
707
|
+
if (!dbService) {
|
|
708
|
+
throw new Error('No running database service found. Please start your database with "docker-compose up -d"');
|
|
709
|
+
}
|
|
710
|
+
this.log(`Using database service: ${dbService}`);
|
|
711
|
+
// Check backup directory
|
|
712
|
+
const backupDir = pathModule.join(cwd, 'backup');
|
|
713
|
+
if (!(0, fs_1.existsSync)(backupDir)) {
|
|
714
|
+
spinner.fail('Backup directory not found.');
|
|
715
|
+
throw new Error(`Backup directory not found at: ${backupDir}. Please create a backup first.`);
|
|
716
|
+
}
|
|
717
|
+
// List all .dump files in backup directory
|
|
718
|
+
spinner.text = 'Searching for backup files...';
|
|
719
|
+
const files = (0, fs_1.readdirSync)(backupDir)
|
|
720
|
+
.filter((file) => file.endsWith('.dump'))
|
|
721
|
+
.map((file) => {
|
|
722
|
+
const filePath = pathModule.join(backupDir, file);
|
|
723
|
+
const stats = (0, fs_1.statSync)(filePath);
|
|
724
|
+
return {
|
|
725
|
+
name: file,
|
|
726
|
+
path: filePath,
|
|
727
|
+
mtime: stats.mtime,
|
|
728
|
+
};
|
|
729
|
+
})
|
|
730
|
+
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); // Most recent first
|
|
731
|
+
if (files.length === 0) {
|
|
732
|
+
spinner.fail('No backup files found.');
|
|
733
|
+
throw new Error(`No backup files found in: ${backupDir}. Please create a backup first.`);
|
|
734
|
+
}
|
|
735
|
+
spinner.stop();
|
|
736
|
+
// Let user select a backup file
|
|
737
|
+
console.log(chalk.blue(`\nFound ${files.length} backup file(s):`));
|
|
738
|
+
const { selectedBackup } = await inquirer_1.default.prompt([
|
|
739
|
+
{
|
|
740
|
+
type: 'list',
|
|
741
|
+
name: 'selectedBackup',
|
|
742
|
+
message: 'Select a backup file to restore:',
|
|
743
|
+
choices: files.map((file) => ({
|
|
744
|
+
name: `${file.name} (${file.mtime.toLocaleString()})`,
|
|
745
|
+
value: file.path,
|
|
746
|
+
})),
|
|
747
|
+
},
|
|
748
|
+
]);
|
|
749
|
+
// Check if database has data
|
|
750
|
+
spinner.start('Checking database for existing data...');
|
|
751
|
+
let hasData = false;
|
|
752
|
+
try {
|
|
753
|
+
const checkResult = await this.database.executeQuery(pathModule.join(cwd, 'apps', 'api'), "SELECT COUNT(*) as table_count FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE';");
|
|
754
|
+
hasData =
|
|
755
|
+
checkResult && parseInt(checkResult[0]?.table_count || '0') > 0;
|
|
756
|
+
this.log(`Database has data: ${hasData}`);
|
|
757
|
+
}
|
|
758
|
+
catch (error) {
|
|
759
|
+
this.log(chalk.yellow('Could not check for existing data. Proceeding...'));
|
|
760
|
+
}
|
|
761
|
+
spinner.stop();
|
|
762
|
+
// Confirm if database has data
|
|
763
|
+
if (hasData) {
|
|
764
|
+
console.log(chalk.yellow('\n⚠️ Warning: The database contains existing data.'));
|
|
765
|
+
const { confirmRestore } = await inquirer_1.default.prompt([
|
|
766
|
+
{
|
|
767
|
+
type: 'confirm',
|
|
768
|
+
name: 'confirmRestore',
|
|
769
|
+
message: 'Are you sure you want to replace all existing data with the backup?',
|
|
770
|
+
default: false,
|
|
771
|
+
},
|
|
772
|
+
]);
|
|
773
|
+
if (!confirmRestore) {
|
|
774
|
+
console.log(chalk.blue('Restore operation cancelled.'));
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
spinner.start('Copying backup file to container...');
|
|
779
|
+
this.log(`Restoring from: ${selectedBackup}`);
|
|
780
|
+
const backupFileName = pathModule.basename(selectedBackup);
|
|
781
|
+
const containerBackupPath = `/tmp/${backupFileName}`;
|
|
782
|
+
// Copy backup file from host to container
|
|
783
|
+
const dockerCpArgs = [
|
|
784
|
+
'cp',
|
|
785
|
+
selectedBackup,
|
|
786
|
+
`${dbService}:${containerBackupPath}`,
|
|
787
|
+
];
|
|
788
|
+
await this.runner.executeCommand(runner_service_1.ProgramName.DOCKER_COMPOSE, dockerCpArgs, { cwd }, true);
|
|
789
|
+
spinner.text = 'Dropping and recreating database...';
|
|
790
|
+
this.log('Dropping and recreating database...');
|
|
791
|
+
// Drop the database
|
|
792
|
+
const dropDbArgs = [
|
|
793
|
+
'exec',
|
|
794
|
+
'-T',
|
|
795
|
+
dbService,
|
|
796
|
+
'dropdb',
|
|
797
|
+
'-h',
|
|
798
|
+
'localhost',
|
|
799
|
+
'-p',
|
|
800
|
+
'5432',
|
|
801
|
+
'-U',
|
|
802
|
+
dbConfig.user,
|
|
803
|
+
'--if-exists',
|
|
804
|
+
dbConfig.database,
|
|
805
|
+
];
|
|
806
|
+
const env = { ...process.env, PGPASSWORD: dbConfig.password };
|
|
807
|
+
try {
|
|
808
|
+
await this.runner.executeCommand(runner_service_1.ProgramName.DOCKER_COMPOSE, dropDbArgs, { cwd, env }, true);
|
|
809
|
+
}
|
|
810
|
+
catch (error) {
|
|
811
|
+
// Check if error is due to active connections
|
|
812
|
+
if (error.message &&
|
|
813
|
+
error.message.includes('is being accessed by other users')) {
|
|
814
|
+
spinner.fail('Cannot drop database - active connections detected.');
|
|
815
|
+
console.log('');
|
|
816
|
+
console.log(chalk.yellow('⚠️ Database Connection Error'));
|
|
817
|
+
console.log('');
|
|
818
|
+
console.log(chalk.white('The database cannot be restored because there are active connections.'));
|
|
819
|
+
console.log('');
|
|
820
|
+
console.log(chalk.cyan('📋 Please follow these steps:'));
|
|
821
|
+
console.log('');
|
|
822
|
+
console.log(chalk.white(' 1. Close all applications connected to the database:'));
|
|
823
|
+
console.log(chalk.gray(' • API servers or backend applications'));
|
|
824
|
+
console.log(chalk.gray(' • Database management tools (DBeaver, pgAdmin, etc.)'));
|
|
825
|
+
console.log(chalk.gray(' • Any running database queries or scripts'));
|
|
826
|
+
console.log('');
|
|
827
|
+
console.log(chalk.white(' 2. After closing all connections, run this command again'));
|
|
828
|
+
console.log('');
|
|
829
|
+
console.log(chalk.blue('💡 Tip: You can also restart the database container to force close all connections:'));
|
|
830
|
+
console.log(chalk.gray(' docker-compose restart db'));
|
|
831
|
+
console.log('');
|
|
832
|
+
throw new Error('Database restore cancelled due to active connections');
|
|
833
|
+
}
|
|
834
|
+
throw error;
|
|
835
|
+
}
|
|
836
|
+
// Create the database
|
|
837
|
+
const createDbArgs = [
|
|
838
|
+
'exec',
|
|
839
|
+
'-T',
|
|
840
|
+
dbService,
|
|
841
|
+
'createdb',
|
|
842
|
+
'-h',
|
|
843
|
+
'localhost',
|
|
844
|
+
'-p',
|
|
845
|
+
'5432',
|
|
846
|
+
'-U',
|
|
847
|
+
dbConfig.user,
|
|
848
|
+
dbConfig.database,
|
|
849
|
+
];
|
|
850
|
+
await this.runner.executeCommand(runner_service_1.ProgramName.DOCKER_COMPOSE, createDbArgs, { cwd, env }, true);
|
|
851
|
+
spinner.text = 'Restoring database from backup file...';
|
|
852
|
+
this.log('Restoring database from backup file...');
|
|
853
|
+
// Restore database using pg_restore
|
|
854
|
+
const restoreArgs = [
|
|
855
|
+
'exec',
|
|
856
|
+
'-T',
|
|
857
|
+
dbService,
|
|
858
|
+
'pg_restore',
|
|
859
|
+
'-h',
|
|
860
|
+
'localhost',
|
|
861
|
+
'-p',
|
|
862
|
+
'5432',
|
|
863
|
+
'-U',
|
|
864
|
+
dbConfig.user,
|
|
865
|
+
'-d',
|
|
866
|
+
dbConfig.database,
|
|
867
|
+
'-v',
|
|
868
|
+
containerBackupPath,
|
|
869
|
+
];
|
|
870
|
+
await this.runner.executeCommand(runner_service_1.ProgramName.DOCKER_COMPOSE, restoreArgs, { cwd, env }, true);
|
|
871
|
+
spinner.text = 'Cleaning up temporary files...';
|
|
872
|
+
this.log('Cleaning up temporary files...');
|
|
873
|
+
// Remove temporary backup file from container
|
|
874
|
+
const cleanupArgs = ['exec', '-T', dbService, 'rm', containerBackupPath];
|
|
875
|
+
await this.runner.executeCommand(runner_service_1.ProgramName.DOCKER_COMPOSE, cleanupArgs, { cwd }, true);
|
|
876
|
+
spinner.succeed(chalk.green('Database restored successfully from backup!'));
|
|
877
|
+
this.log(chalk.green('Database restored successfully from backup!'));
|
|
878
|
+
}
|
|
879
|
+
catch (error) {
|
|
880
|
+
spinner.fail('Failed to restore database.');
|
|
881
|
+
// Only show detailed error if it's not the active connections error
|
|
882
|
+
if (!error.message ||
|
|
883
|
+
!error.message.includes('Database restore cancelled due to active connections')) {
|
|
884
|
+
console.error(chalk.red('Error restoring database:'), error);
|
|
885
|
+
}
|
|
886
|
+
throw error;
|
|
887
|
+
}
|
|
888
|
+
finally {
|
|
889
|
+
spinner.stop();
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
exports.DeveloperService = DeveloperService;
|
|
894
|
+
exports.DeveloperService = DeveloperService = __decorate([
|
|
895
|
+
(0, common_1.Injectable)(),
|
|
896
|
+
__metadata("design:paramtypes", [runner_service_1.RunnerService,
|
|
897
|
+
file_system_service_1.FileSystemService,
|
|
898
|
+
database_service_1.DatabaseService])
|
|
899
|
+
], DeveloperService);
|
|
900
|
+
//# sourceMappingURL=developer.service.js.map
|