@hed-hog/cli 0.0.67 → 0.0.69
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/dist/package.json +1 -1
- package/dist/src/commands/dev.command/deploy-config.subcommand.d.ts +3 -1
- package/dist/src/commands/dev.command/deploy-config.subcommand.js +18 -2
- package/dist/src/commands/dev.command/deploy-config.subcommand.js.map +1 -1
- package/dist/src/modules/developer/developer.service.d.ts +21 -1
- package/dist/src/modules/developer/developer.service.js +310 -112
- package/dist/src/modules/developer/developer.service.js.map +1 -1
- package/dist/src/templates/deployment/DEPLOYMENT.md.ejs +20 -4
- package/dist/src/templates/deployment/k8s.deployment.yaml.ejs +15 -0
- package/dist/src/templates/deployment/workflow.deploy.yml.ejs +46 -23
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
package/dist/package.json
CHANGED
|
@@ -3,8 +3,10 @@ import { DeveloperService } from 'src/modules/developer/developer.service';
|
|
|
3
3
|
export declare class DeployConfigCommand extends CommandRunner {
|
|
4
4
|
private readonly developer;
|
|
5
5
|
constructor(developer: DeveloperService);
|
|
6
|
-
run([]: Iterable<any, void, undefined>, { verbose }: {
|
|
6
|
+
run([]: Iterable<any, void, undefined>, { repo, verbose }: {
|
|
7
|
+
repo: any;
|
|
7
8
|
verbose: any;
|
|
8
9
|
}): Promise<void>;
|
|
9
10
|
parseVerboseOption(): boolean;
|
|
11
|
+
parseRepoOption(repo: string): string;
|
|
10
12
|
}
|
|
@@ -18,12 +18,18 @@ let DeployConfigCommand = class DeployConfigCommand extends nest_commander_1.Com
|
|
|
18
18
|
super();
|
|
19
19
|
this.developer = developer;
|
|
20
20
|
}
|
|
21
|
-
async run([], { verbose }) {
|
|
22
|
-
return this.developer.deployConfig(process.cwd(),
|
|
21
|
+
async run([], { repo, verbose }) {
|
|
22
|
+
return this.developer.deployConfig(process.cwd(), {
|
|
23
|
+
verbose,
|
|
24
|
+
repo,
|
|
25
|
+
});
|
|
23
26
|
}
|
|
24
27
|
parseVerboseOption() {
|
|
25
28
|
return true;
|
|
26
29
|
}
|
|
30
|
+
parseRepoOption(repo) {
|
|
31
|
+
return repo.trim();
|
|
32
|
+
}
|
|
27
33
|
};
|
|
28
34
|
exports.DeployConfigCommand = DeployConfigCommand;
|
|
29
35
|
__decorate([
|
|
@@ -36,6 +42,16 @@ __decorate([
|
|
|
36
42
|
__metadata("design:paramtypes", []),
|
|
37
43
|
__metadata("design:returntype", Boolean)
|
|
38
44
|
], DeployConfigCommand.prototype, "parseVerboseOption", null);
|
|
45
|
+
__decorate([
|
|
46
|
+
(0, nest_commander_1.Option)({
|
|
47
|
+
flags: '-r, --repo <owner/name>',
|
|
48
|
+
description: 'GitHub repository used for secrets configuration',
|
|
49
|
+
required: false,
|
|
50
|
+
}),
|
|
51
|
+
__metadata("design:type", Function),
|
|
52
|
+
__metadata("design:paramtypes", [String]),
|
|
53
|
+
__metadata("design:returntype", String)
|
|
54
|
+
], DeployConfigCommand.prototype, "parseRepoOption", null);
|
|
39
55
|
exports.DeployConfigCommand = DeployConfigCommand = __decorate([
|
|
40
56
|
(0, nest_commander_1.SubCommand)({
|
|
41
57
|
name: 'deploy-config',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy-config.subcommand.js","sourceRoot":"","sources":["../../../../src/commands/dev.command/deploy-config.subcommand.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,mDAAmE;AACnE,iFAA2E;AAOpE,IAAM,mBAAmB,GAAzB,MAAM,mBAAoB,SAAQ,8BAAa;IACvB;IAA7B,YAA6B,SAA2B;QACtD,KAAK,EAAE,CAAC;QADmB,cAAS,GAAT,SAAS,CAAkB;IAExD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE;
|
|
1
|
+
{"version":3,"file":"deploy-config.subcommand.js","sourceRoot":"","sources":["../../../../src/commands/dev.command/deploy-config.subcommand.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,mDAAmE;AACnE,iFAA2E;AAOpE,IAAM,mBAAmB,GAAzB,MAAM,mBAAoB,SAAQ,8BAAa;IACvB;IAA7B,YAA6B,SAA2B;QACtD,KAAK,EAAE,CAAC;QADmB,cAAS,GAAT,SAAS,CAAkB;IAExD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;QAC7B,OAAO,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE;YAChD,OAAO;YACP,IAAI;SACL,CAAC,CAAC;IACL,CAAC;IAOD,kBAAkB;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;IAOD,eAAe,CAAC,IAAY;QAC1B,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;IACrB,CAAC;CACF,CAAA;AA7BY,kDAAmB;AAiB9B;IALC,IAAA,uBAAM,EAAC;QACN,KAAK,EAAE,eAAe;QACtB,WAAW,EAAE,uBAAuB;QACpC,QAAQ,EAAE,KAAK;KAChB,CAAC;;;;6DAGD;AAOD;IALC,IAAA,uBAAM,EAAC;QACN,KAAK,EAAE,yBAAyB;QAChC,WAAW,EAAE,kDAAkD;QAC/D,QAAQ,EAAE,KAAK;KAChB,CAAC;;;;0DAGD;8BA5BU,mBAAmB;IAL/B,IAAA,2BAAU,EAAC;QACV,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE,CAAC,IAAI,CAAC;QACf,WAAW,EAAE,yDAAyD;KACvE,CAAC;qCAEwC,oCAAgB;GAD7C,mBAAmB,CA6B/B"}
|
|
@@ -3,21 +3,34 @@ import { DatabaseService } from '../database/database.service';
|
|
|
3
3
|
import { HedHogService } from '../hedhog/hedhog.service';
|
|
4
4
|
import { FileSystemService } from '../hedhog/services/file-system.service';
|
|
5
5
|
import { RunnerService } from '../runner/runner.service';
|
|
6
|
+
type DeployConfigOptions = {
|
|
7
|
+
verbose?: boolean;
|
|
8
|
+
repo?: string;
|
|
9
|
+
};
|
|
6
10
|
export declare class DeveloperService {
|
|
7
11
|
private readonly runner;
|
|
8
12
|
private readonly fileSystem;
|
|
9
13
|
private readonly database;
|
|
10
14
|
private readonly hedHogService;
|
|
11
15
|
private verbose;
|
|
16
|
+
private readonly bootstrapIgnoredEntries;
|
|
12
17
|
constructor(runner: RunnerService, fileSystem: FileSystemService, database: DatabaseService, hedHogService: HedHogService);
|
|
13
18
|
log(...args: any[]): void;
|
|
14
19
|
private suppressWarnings;
|
|
15
20
|
private refreshEnvironmentPath;
|
|
16
|
-
deployConfig(path: string, verbose?:
|
|
21
|
+
deployConfig(path: string, { repo, verbose }?: DeployConfigOptions): Promise<void>;
|
|
17
22
|
private checkRequiredTools;
|
|
18
23
|
private checkKubectl;
|
|
19
24
|
private checkDoctl;
|
|
20
25
|
private checkGhCli;
|
|
26
|
+
private ensureGhAuthenticated;
|
|
27
|
+
private generateRandomHexSecret;
|
|
28
|
+
private generateRandomBase64Secret;
|
|
29
|
+
private maskDatabaseUrl;
|
|
30
|
+
private resolveGithubRepository;
|
|
31
|
+
private promptForDatabaseUrl;
|
|
32
|
+
private setGithubSecret;
|
|
33
|
+
private promptToConfigureGithubSecrets;
|
|
21
34
|
private checkHelm;
|
|
22
35
|
private checkPackageManager;
|
|
23
36
|
private getAvailablePackageManager;
|
|
@@ -61,6 +74,12 @@ export declare class DeveloperService {
|
|
|
61
74
|
syncPublish(path: string, verbose?: boolean): Promise<void>;
|
|
62
75
|
showLibraryVersions(path: string, verbose?: boolean): Promise<void>;
|
|
63
76
|
updateBootstrapFiles(path: string, verbose?: boolean): Promise<void>;
|
|
77
|
+
private resolveBootstrapSourceRoot;
|
|
78
|
+
private validateBootstrapDestinationPath;
|
|
79
|
+
private promptForBootstrapDestination;
|
|
80
|
+
private shouldIgnoreBootstrapEntry;
|
|
81
|
+
private syncBootstrapMirror;
|
|
82
|
+
private ensureBootstrapResetState;
|
|
64
83
|
route(path: string, verbose?: boolean): Promise<void>;
|
|
65
84
|
private extractMainModule;
|
|
66
85
|
private extractRoutes;
|
|
@@ -100,3 +119,4 @@ export declare class DeveloperService {
|
|
|
100
119
|
resetAndInstallAll(cwd: string, verbose?: boolean): Promise<void>;
|
|
101
120
|
copyAssetsToLibrary(name: string, cwd: string, verbose?: boolean): Promise<void>;
|
|
102
121
|
}
|
|
122
|
+
export {};
|
|
@@ -35,6 +35,21 @@ let DeveloperService = class DeveloperService {
|
|
|
35
35
|
database;
|
|
36
36
|
hedHogService;
|
|
37
37
|
verbose = false;
|
|
38
|
+
bootstrapIgnoredEntries = new Set([
|
|
39
|
+
'.git',
|
|
40
|
+
'node_modules',
|
|
41
|
+
'dist',
|
|
42
|
+
'.turbo',
|
|
43
|
+
'coverage',
|
|
44
|
+
'.cache',
|
|
45
|
+
'.pnpm-store',
|
|
46
|
+
'.yarn',
|
|
47
|
+
'.next',
|
|
48
|
+
'.nuxt',
|
|
49
|
+
'.svelte-kit',
|
|
50
|
+
'.parcel-cache',
|
|
51
|
+
'.eslintcache',
|
|
52
|
+
]);
|
|
38
53
|
constructor(runner, fileSystem, database, hedHogService) {
|
|
39
54
|
this.runner = runner;
|
|
40
55
|
this.fileSystem = fileSystem;
|
|
@@ -98,7 +113,7 @@ let DeveloperService = class DeveloperService {
|
|
|
98
113
|
this.log('Environment refresh attempt completed');
|
|
99
114
|
}
|
|
100
115
|
}
|
|
101
|
-
async deployConfig(path, verbose = false) {
|
|
116
|
+
async deployConfig(path, { repo, verbose = false } = {}) {
|
|
102
117
|
const restoreWarnings = this.suppressWarnings();
|
|
103
118
|
this.verbose = verbose;
|
|
104
119
|
path = await this.getRootPath(path);
|
|
@@ -172,6 +187,7 @@ let DeveloperService = class DeveloperService {
|
|
|
172
187
|
spinner.succeed(chalk.green('Deployment configuration completed successfully!'));
|
|
173
188
|
// Display summary
|
|
174
189
|
this.displayDeploymentSummary(config);
|
|
190
|
+
await this.promptToConfigureGithubSecrets(path, repo);
|
|
175
191
|
}
|
|
176
192
|
catch (error) {
|
|
177
193
|
spinner.fail('Failed to configure deployment.');
|
|
@@ -217,6 +233,130 @@ let DeveloperService = class DeveloperService {
|
|
|
217
233
|
const versionLine = result.stdout.split('\n')[0];
|
|
218
234
|
return versionLine.trim();
|
|
219
235
|
}
|
|
236
|
+
async ensureGhAuthenticated() {
|
|
237
|
+
await this.checkGhCli();
|
|
238
|
+
await this.runner.executeCommand(runner_service_1.ProgramName.GH, ['auth', 'status'], {}, true);
|
|
239
|
+
}
|
|
240
|
+
generateRandomHexSecret(bytes = 32) {
|
|
241
|
+
return (0, crypto_1.randomBytes)(bytes).toString('hex');
|
|
242
|
+
}
|
|
243
|
+
generateRandomBase64Secret(bytes = 32) {
|
|
244
|
+
return (0, crypto_1.randomBytes)(bytes).toString('base64');
|
|
245
|
+
}
|
|
246
|
+
maskDatabaseUrl(databaseUrl) {
|
|
247
|
+
return databaseUrl.replace(/:\/\/([^:/?#]+):([^@]*)@/, '://$1:***@');
|
|
248
|
+
}
|
|
249
|
+
async resolveGithubRepository(path, repo) {
|
|
250
|
+
if (repo?.trim()) {
|
|
251
|
+
return repo.trim();
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
const result = await this.runner.executeCommand(runner_service_1.ProgramName.GIT, ['remote', 'get-url', 'origin'], { cwd: path }, true);
|
|
255
|
+
const remoteUrl = result.stdout.trim();
|
|
256
|
+
const match = remoteUrl.match(/github\.com[:/]([^/]+\/[^/.]+?)(?:\.git)?$/i);
|
|
257
|
+
if (match?.[1]) {
|
|
258
|
+
return match[1];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// Fall through to prompt.
|
|
263
|
+
}
|
|
264
|
+
while (true) {
|
|
265
|
+
const answers = await inquirer_1.default.prompt([
|
|
266
|
+
{
|
|
267
|
+
type: 'input',
|
|
268
|
+
name: 'repo',
|
|
269
|
+
message: 'Enter the GitHub repository (e.g. owner/name):',
|
|
270
|
+
validate: (input) => {
|
|
271
|
+
const value = String(input || '').trim();
|
|
272
|
+
return /^[^/\s]+\/[^/\s]+$/.test(value)
|
|
273
|
+
? true
|
|
274
|
+
: 'Enter the repository in the format owner/name';
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
]);
|
|
278
|
+
const resolvedRepo = String(answers.repo || '').trim();
|
|
279
|
+
if (resolvedRepo) {
|
|
280
|
+
return resolvedRepo;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async promptForDatabaseUrl() {
|
|
285
|
+
while (true) {
|
|
286
|
+
const answers = await inquirer_1.default.prompt([
|
|
287
|
+
{
|
|
288
|
+
type: 'input',
|
|
289
|
+
name: 'databaseUrl',
|
|
290
|
+
message: 'Enter DATABASE_URL:',
|
|
291
|
+
validate: (input) => String(input || '').trim().length > 0 ||
|
|
292
|
+
'DATABASE_URL is required.',
|
|
293
|
+
},
|
|
294
|
+
]);
|
|
295
|
+
const databaseUrl = String(answers.databaseUrl || '').trim();
|
|
296
|
+
if (databaseUrl) {
|
|
297
|
+
return databaseUrl;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async setGithubSecret(secretName, secretValue, repo) {
|
|
302
|
+
await this.runner.executeCommand(runner_service_1.ProgramName.GH, ['secret', 'set', secretName, '--repo', repo, '--body', secretValue], {}, true);
|
|
303
|
+
}
|
|
304
|
+
async promptToConfigureGithubSecrets(path, repo) {
|
|
305
|
+
const { configureSecrets } = await inquirer_1.default.prompt([
|
|
306
|
+
{
|
|
307
|
+
type: 'confirm',
|
|
308
|
+
name: 'configureSecrets',
|
|
309
|
+
message: 'Would you like to configure the required GitHub secrets now?',
|
|
310
|
+
default: true,
|
|
311
|
+
},
|
|
312
|
+
]);
|
|
313
|
+
if (!configureSecrets) {
|
|
314
|
+
console.log(chalk.yellow('\nGitHub secrets setup skipped. You can configure them later from DEPLOYMENT.md or by rerunning this command.\n'));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
console.log(chalk.blue.bold('\n🔐 GitHub Secrets Setup\n'));
|
|
318
|
+
await this.ensureGhAuthenticated();
|
|
319
|
+
const resolvedRepo = await this.resolveGithubRepository(path, repo);
|
|
320
|
+
const secretsConfig = {
|
|
321
|
+
databaseUrl: await this.promptForDatabaseUrl(),
|
|
322
|
+
jwtSecret: this.generateRandomHexSecret(32),
|
|
323
|
+
encryptionSecret: this.generateRandomBase64Secret(32),
|
|
324
|
+
pepper: this.generateRandomBase64Secret(16),
|
|
325
|
+
};
|
|
326
|
+
const maskedDatabaseUrl = this.maskDatabaseUrl(secretsConfig.databaseUrl);
|
|
327
|
+
console.log(chalk.gray(`Repository: ${chalk.cyan(resolvedRepo)}`));
|
|
328
|
+
console.log(chalk.white(`DATABASE_URL: ${maskedDatabaseUrl}`));
|
|
329
|
+
console.log(chalk.white(`JWT_SECRET: ${secretsConfig.jwtSecret.substring(0, 16)}... (64 chars hex)`));
|
|
330
|
+
console.log(chalk.white(`ENCRYPTION_SECRET: ${secretsConfig.encryptionSecret.substring(0, 16)}... (base64, 32 bytes)`));
|
|
331
|
+
console.log(chalk.white(`PEPPER: ${secretsConfig.pepper.substring(0, 8)}... (base64, 16 bytes)`));
|
|
332
|
+
console.log('');
|
|
333
|
+
const { confirmed } = await inquirer_1.default.prompt([
|
|
334
|
+
{
|
|
335
|
+
type: 'confirm',
|
|
336
|
+
name: 'confirmed',
|
|
337
|
+
message: 'Proceed with overwriting these GitHub secrets?',
|
|
338
|
+
default: true,
|
|
339
|
+
},
|
|
340
|
+
]);
|
|
341
|
+
if (!confirmed) {
|
|
342
|
+
console.log(chalk.yellow('\nGitHub secrets setup cancelled.\n'));
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const spinner = ora('Configuring GitHub secrets...').start();
|
|
346
|
+
try {
|
|
347
|
+
await this.setGithubSecret('DATABASE_URL', secretsConfig.databaseUrl, resolvedRepo);
|
|
348
|
+
await this.setGithubSecret('JWT_SECRET', secretsConfig.jwtSecret, resolvedRepo);
|
|
349
|
+
await this.setGithubSecret('ENCRYPTION_SECRET', secretsConfig.encryptionSecret, resolvedRepo);
|
|
350
|
+
await this.setGithubSecret('PEPPER', secretsConfig.pepper, resolvedRepo);
|
|
351
|
+
spinner.succeed(chalk.green(`GitHub secrets configured successfully for ${resolvedRepo}.`));
|
|
352
|
+
console.log(chalk.gray(`Verify with: gh secret list --repo ${resolvedRepo}`));
|
|
353
|
+
console.log('');
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
spinner.fail('Failed to configure GitHub secrets.');
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
220
360
|
async checkHelm() {
|
|
221
361
|
const result = await this.runner.executeCommand(runner_service_1.ProgramName.HELM, ['version', '--short'], {}, true);
|
|
222
362
|
// Extract version (handles both --short and regular output)
|
|
@@ -1516,7 +1656,7 @@ temp
|
|
|
1516
1656
|
console.log(chalk.blue.bold('\n📖 Next Steps:\n'));
|
|
1517
1657
|
console.log(chalk.white('1. Review the generated files in your project'));
|
|
1518
1658
|
console.log(chalk.white('2. Read DEPLOYMENT.md for setup instructions'));
|
|
1519
|
-
console.log(chalk.white('3. Configure GitHub secrets
|
|
1659
|
+
console.log(chalk.white('3. Configure GitHub secrets now with this CLI or follow DEPLOYMENT.md'));
|
|
1520
1660
|
console.log(chalk.white(`4. Push to ${config.deployBranch || 'production'} branch to trigger deployment`));
|
|
1521
1661
|
if ((config.infraServices || []).length > 0) {
|
|
1522
1662
|
console.log('');
|
|
@@ -1850,116 +1990,21 @@ temp
|
|
|
1850
1990
|
this.verbose = verbose;
|
|
1851
1991
|
const spinner = ora('Updating bootstrap files...').start();
|
|
1852
1992
|
try {
|
|
1853
|
-
|
|
1854
|
-
const
|
|
1855
|
-
|
|
1856
|
-
this.log(chalk.blue(`
|
|
1857
|
-
|
|
1858
|
-
spinner.
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
}
|
|
1869
|
-
const fullPath = pathModule.join(templatePath, entry.name);
|
|
1870
|
-
await this.fileSystem.remove(fullPath);
|
|
1871
|
-
this.log(chalk.gray(`Removed: ${entry.name}`));
|
|
1872
|
-
}
|
|
1873
|
-
this.log(chalk.gray('Cleaned template directory (preserved .git)'));
|
|
1874
|
-
}
|
|
1875
|
-
else {
|
|
1876
|
-
await (0, promises_1.mkdir)(templatePath, { recursive: true });
|
|
1877
|
-
this.log(chalk.blue('Template directory created.'));
|
|
1878
|
-
}
|
|
1879
|
-
spinner.succeed('Template directory cleaned.');
|
|
1880
|
-
// Get all files tracked by git (respects .gitignore)
|
|
1881
|
-
spinner.start('Getting list of files from repository...');
|
|
1882
|
-
const result = await this.runner.executeCommand(runner_service_1.ProgramName.GIT, ['ls-files'], { cwd: path }, true);
|
|
1883
|
-
const allFiles = result.stdout
|
|
1884
|
-
.trim()
|
|
1885
|
-
.split('\n')
|
|
1886
|
-
.filter((f) => f.length > 0);
|
|
1887
|
-
spinner.succeed(`Found ${allFiles.length} files in repository.`);
|
|
1888
|
-
spinner.start('Filtering files...');
|
|
1889
|
-
// Filter files from apps folder - only keep api and admin
|
|
1890
|
-
const allowedAppsFolders = ['api', 'admin'];
|
|
1891
|
-
const ignoredAppsFolders = new Set();
|
|
1892
|
-
const files = allFiles.filter((file) => {
|
|
1893
|
-
// Check if file is in apps directory
|
|
1894
|
-
if (file.startsWith('apps/') || file.startsWith('apps\\')) {
|
|
1895
|
-
const parts = file.split(/[/\\]/);
|
|
1896
|
-
if (parts.length > 1) {
|
|
1897
|
-
const appFolder = parts[1];
|
|
1898
|
-
if (!allowedAppsFolders.includes(appFolder)) {
|
|
1899
|
-
ignoredAppsFolders.add(appFolder);
|
|
1900
|
-
return false;
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
|
-
return true;
|
|
1905
|
-
});
|
|
1906
|
-
spinner.succeed('Files filtered.');
|
|
1907
|
-
// Display ignored folders
|
|
1908
|
-
if (ignoredAppsFolders.size > 0) {
|
|
1909
|
-
spinner.info(chalk.blue(`Ignored apps folders: ${Array.from(ignoredAppsFolders).join(', ')}`));
|
|
1910
|
-
spinner.start('Copying files...');
|
|
1911
|
-
}
|
|
1912
|
-
this.log(chalk.blue(`Found ${files.length} files to copy (${allFiles.length - files.length} files ignored)`));
|
|
1913
|
-
// Copy each file maintaining directory structure
|
|
1914
|
-
spinner.start('Copying files...');
|
|
1915
|
-
let copiedCount = 0;
|
|
1916
|
-
for (const file of files) {
|
|
1917
|
-
const sourcePath = pathModule.join(path, file);
|
|
1918
|
-
const destPath = pathModule.join(templatePath, file);
|
|
1919
|
-
// Create directory if it doesn't exist
|
|
1920
|
-
const destDir = pathModule.dirname(destPath);
|
|
1921
|
-
if (!(0, fs_1.existsSync)(destDir)) {
|
|
1922
|
-
await (0, promises_1.mkdir)(destDir, { recursive: true });
|
|
1923
|
-
}
|
|
1924
|
-
// Copy file
|
|
1925
|
-
await this.fileSystem.copyFile(sourcePath, destPath);
|
|
1926
|
-
copiedCount++;
|
|
1927
|
-
if (verbose && copiedCount % 100 === 0) {
|
|
1928
|
-
spinner.text = `Copying files... (${copiedCount}/${files.length})`;
|
|
1929
|
-
}
|
|
1930
|
-
}
|
|
1931
|
-
// Commit and push changes to git
|
|
1932
|
-
spinner.start('Committing changes to git...');
|
|
1933
|
-
try {
|
|
1934
|
-
await this.runner.executeCommand(runner_service_1.ProgramName.GIT, ['add', '.'], { cwd: templatePath }, true);
|
|
1935
|
-
// Check if there are changes to commit
|
|
1936
|
-
const statusResult = await this.runner.executeCommand(runner_service_1.ProgramName.GIT, ['status', '--porcelain'], { cwd: templatePath }, true);
|
|
1937
|
-
if (statusResult.stdout.trim().length > 0) {
|
|
1938
|
-
const now = new Date();
|
|
1939
|
-
const dateStr = now.toLocaleDateString('en-US', {
|
|
1940
|
-
year: 'numeric',
|
|
1941
|
-
month: 'short',
|
|
1942
|
-
day: 'numeric',
|
|
1943
|
-
});
|
|
1944
|
-
const timeStr = now.toLocaleTimeString('en-US', {
|
|
1945
|
-
hour: '2-digit',
|
|
1946
|
-
minute: '2-digit',
|
|
1947
|
-
});
|
|
1948
|
-
const commitMessage = `chore: update bootstrap files - ${dateStr} at ${timeStr}`;
|
|
1949
|
-
await this.runner.executeCommand(runner_service_1.ProgramName.GIT, ['commit', '-m', commitMessage], { cwd: templatePath }, true);
|
|
1950
|
-
spinner.text = 'Pushing changes to remote repository...';
|
|
1951
|
-
await this.runner.executeCommand(runner_service_1.ProgramName.GIT, ['push'], { cwd: templatePath }, true);
|
|
1952
|
-
spinner.succeed(`Bootstrap files updated successfully. ${copiedCount} files copied and pushed to ${templatePath}`);
|
|
1953
|
-
}
|
|
1954
|
-
else {
|
|
1955
|
-
spinner.succeed(`Bootstrap files updated successfully. ${copiedCount} files copied (no changes to commit)`);
|
|
1956
|
-
}
|
|
1957
|
-
}
|
|
1958
|
-
catch (gitError) {
|
|
1959
|
-
spinner.warn(`Bootstrap files copied (${copiedCount} files) but git operations failed. You may need to commit manually.`);
|
|
1960
|
-
this.log(chalk.yellow('Git error:'), gitError.message);
|
|
1961
|
-
}
|
|
1962
|
-
this.log(chalk.green(`Successfully copied ${copiedCount} files to template directory.`));
|
|
1993
|
+
spinner.text = 'Resolving project root...';
|
|
1994
|
+
const sourcePath = await this.resolveBootstrapSourceRoot(path);
|
|
1995
|
+
const suggestedTemplatePath = pathModule.join(pathModule.dirname(sourcePath), 'template');
|
|
1996
|
+
this.log(chalk.blue(`Source path: ${sourcePath}`));
|
|
1997
|
+
this.log(chalk.blue(`Suggested template path: ${suggestedTemplatePath}`));
|
|
1998
|
+
spinner.stop();
|
|
1999
|
+
const templatePath = await this.promptForBootstrapDestination(suggestedTemplatePath, sourcePath);
|
|
2000
|
+
this.log(chalk.blue(`Confirmed template path: ${templatePath}`));
|
|
2001
|
+
spinner.start('Synchronizing project files to template...');
|
|
2002
|
+
const { copiedCount, deletedCount } = await this.syncBootstrapMirror(sourcePath, templatePath);
|
|
2003
|
+
spinner.succeed(`Template synchronized successfully. ${copiedCount} files copied and ${deletedCount} obsolete entries removed.`);
|
|
2004
|
+
spinner.stop();
|
|
2005
|
+
await this.ensureBootstrapResetState(templatePath);
|
|
2006
|
+
await this.resetDevelopmentEnvironment(templatePath, verbose);
|
|
2007
|
+
console.log(chalk.green(`Bootstrap files updated successfully. Source: ${sourcePath} | Destination: ${templatePath}`));
|
|
1963
2008
|
}
|
|
1964
2009
|
catch (error) {
|
|
1965
2010
|
spinner.fail('Failed to update bootstrap files.');
|
|
@@ -1971,6 +2016,159 @@ temp
|
|
|
1971
2016
|
spinner.stop();
|
|
1972
2017
|
}
|
|
1973
2018
|
}
|
|
2019
|
+
async resolveBootstrapSourceRoot(cwd) {
|
|
2020
|
+
try {
|
|
2021
|
+
const result = await this.runner.executeCommand(runner_service_1.ProgramName.GIT, ['rev-parse', '--show-toplevel'], { cwd }, true);
|
|
2022
|
+
const gitRoot = result.stdout.trim();
|
|
2023
|
+
if (gitRoot) {
|
|
2024
|
+
return pathModule.resolve(gitRoot);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
catch (error) {
|
|
2028
|
+
this.log(chalk.yellow('Could not resolve git root. Falling back to the current working directory.'));
|
|
2029
|
+
}
|
|
2030
|
+
return pathModule.resolve(cwd);
|
|
2031
|
+
}
|
|
2032
|
+
validateBootstrapDestinationPath(templatePath, sourcePath) {
|
|
2033
|
+
const trimmedPath = templatePath.trim();
|
|
2034
|
+
if (!trimmedPath) {
|
|
2035
|
+
return 'Informe o path absoluto do template.';
|
|
2036
|
+
}
|
|
2037
|
+
if (!pathModule.isAbsolute(trimmedPath)) {
|
|
2038
|
+
return 'Informe um caminho absoluto.';
|
|
2039
|
+
}
|
|
2040
|
+
const resolvedPath = pathModule.resolve(trimmedPath);
|
|
2041
|
+
if (!(0, fs_1.existsSync)(resolvedPath)) {
|
|
2042
|
+
return `A pasta "${resolvedPath}" não existe.`;
|
|
2043
|
+
}
|
|
2044
|
+
if (!(0, fs_1.statSync)(resolvedPath).isDirectory()) {
|
|
2045
|
+
return `O caminho "${resolvedPath}" não é uma pasta válida.`;
|
|
2046
|
+
}
|
|
2047
|
+
const relativeToSource = pathModule.relative(sourcePath, resolvedPath);
|
|
2048
|
+
if (relativeToSource === '' ||
|
|
2049
|
+
(!relativeToSource.startsWith('..') &&
|
|
2050
|
+
!pathModule.isAbsolute(relativeToSource))) {
|
|
2051
|
+
return 'O destino não pode ser o mesmo diretório do projeto atual nem ficar dentro dele.';
|
|
2052
|
+
}
|
|
2053
|
+
const relativeFromDestination = pathModule.relative(resolvedPath, sourcePath);
|
|
2054
|
+
if (relativeFromDestination === '' ||
|
|
2055
|
+
(!relativeFromDestination.startsWith('..') &&
|
|
2056
|
+
!pathModule.isAbsolute(relativeFromDestination))) {
|
|
2057
|
+
return 'O destino não pode conter o projeto atual.';
|
|
2058
|
+
}
|
|
2059
|
+
return true;
|
|
2060
|
+
}
|
|
2061
|
+
async promptForBootstrapDestination(suggestedTemplatePath, sourcePath) {
|
|
2062
|
+
const resolvedSuggestion = pathModule.resolve(suggestedTemplatePath);
|
|
2063
|
+
const suggestedPathExists = (0, fs_1.existsSync)(resolvedSuggestion) &&
|
|
2064
|
+
(0, fs_1.statSync)(resolvedSuggestion).isDirectory();
|
|
2065
|
+
if (suggestedPathExists) {
|
|
2066
|
+
const { confirmed } = await inquirer_1.default.prompt([
|
|
2067
|
+
{
|
|
2068
|
+
type: 'confirm',
|
|
2069
|
+
name: 'confirmed',
|
|
2070
|
+
message: `Destino encontrado: ${resolvedSuggestion}. Confirmar?`,
|
|
2071
|
+
default: true,
|
|
2072
|
+
},
|
|
2073
|
+
]);
|
|
2074
|
+
if (confirmed) {
|
|
2075
|
+
return resolvedSuggestion;
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
else {
|
|
2079
|
+
console.log(chalk.yellow(`A pasta template padrão não foi encontrada em: ${resolvedSuggestion}`));
|
|
2080
|
+
}
|
|
2081
|
+
while (true) {
|
|
2082
|
+
const { templatePath } = await inquirer_1.default.prompt([
|
|
2083
|
+
{
|
|
2084
|
+
type: 'input',
|
|
2085
|
+
name: 'templatePath',
|
|
2086
|
+
message: 'Informe o path absoluto correto do template:',
|
|
2087
|
+
validate: (input) => this.validateBootstrapDestinationPath(input, sourcePath),
|
|
2088
|
+
},
|
|
2089
|
+
]);
|
|
2090
|
+
const resolvedTemplatePath = pathModule.resolve(templatePath.trim());
|
|
2091
|
+
const { confirmed } = await inquirer_1.default.prompt([
|
|
2092
|
+
{
|
|
2093
|
+
type: 'confirm',
|
|
2094
|
+
name: 'confirmed',
|
|
2095
|
+
message: `Usar este destino: ${resolvedTemplatePath}?`,
|
|
2096
|
+
default: true,
|
|
2097
|
+
},
|
|
2098
|
+
]);
|
|
2099
|
+
if (confirmed) {
|
|
2100
|
+
return resolvedTemplatePath;
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
shouldIgnoreBootstrapEntry(entryName) {
|
|
2105
|
+
return this.bootstrapIgnoredEntries.has(entryName);
|
|
2106
|
+
}
|
|
2107
|
+
async syncBootstrapMirror(sourcePath, destinationPath) {
|
|
2108
|
+
await (0, promises_1.mkdir)(destinationPath, { recursive: true });
|
|
2109
|
+
const sourceEntries = await (0, promises_1.readdir)(sourcePath, { withFileTypes: true });
|
|
2110
|
+
const destinationEntries = await (0, promises_1.readdir)(destinationPath, {
|
|
2111
|
+
withFileTypes: true,
|
|
2112
|
+
});
|
|
2113
|
+
const sourceEntryMap = new Map(sourceEntries
|
|
2114
|
+
.filter((entry) => !this.shouldIgnoreBootstrapEntry(entry.name))
|
|
2115
|
+
.map((entry) => [entry.name, entry]));
|
|
2116
|
+
let copiedCount = 0;
|
|
2117
|
+
let deletedCount = 0;
|
|
2118
|
+
for (const destinationEntry of destinationEntries) {
|
|
2119
|
+
if (destinationEntry.name === '.git') {
|
|
2120
|
+
continue;
|
|
2121
|
+
}
|
|
2122
|
+
if (!sourceEntryMap.has(destinationEntry.name)) {
|
|
2123
|
+
await this.fileSystem.remove(pathModule.join(destinationPath, destinationEntry.name));
|
|
2124
|
+
deletedCount++;
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
for (const sourceEntry of sourceEntries) {
|
|
2128
|
+
if (this.shouldIgnoreBootstrapEntry(sourceEntry.name)) {
|
|
2129
|
+
continue;
|
|
2130
|
+
}
|
|
2131
|
+
const sourceEntryPath = pathModule.join(sourcePath, sourceEntry.name);
|
|
2132
|
+
const destinationEntryPath = pathModule.join(destinationPath, sourceEntry.name);
|
|
2133
|
+
const destinationExists = (0, fs_1.existsSync)(destinationEntryPath);
|
|
2134
|
+
if (sourceEntry.isDirectory()) {
|
|
2135
|
+
if (destinationExists &&
|
|
2136
|
+
!(0, fs_1.statSync)(destinationEntryPath).isDirectory()) {
|
|
2137
|
+
await this.fileSystem.remove(destinationEntryPath);
|
|
2138
|
+
deletedCount++;
|
|
2139
|
+
}
|
|
2140
|
+
const nestedResult = await this.syncBootstrapMirror(sourceEntryPath, destinationEntryPath);
|
|
2141
|
+
copiedCount += nestedResult.copiedCount;
|
|
2142
|
+
deletedCount += nestedResult.deletedCount;
|
|
2143
|
+
continue;
|
|
2144
|
+
}
|
|
2145
|
+
if (!sourceEntry.isFile()) {
|
|
2146
|
+
this.log(chalk.yellow(`Skipping unsupported entry type: ${sourceEntryPath}`));
|
|
2147
|
+
continue;
|
|
2148
|
+
}
|
|
2149
|
+
if (destinationExists && (0, fs_1.statSync)(destinationEntryPath).isDirectory()) {
|
|
2150
|
+
await this.fileSystem.remove(destinationEntryPath);
|
|
2151
|
+
deletedCount++;
|
|
2152
|
+
}
|
|
2153
|
+
await (0, promises_1.mkdir)(pathModule.dirname(destinationEntryPath), {
|
|
2154
|
+
recursive: true,
|
|
2155
|
+
});
|
|
2156
|
+
await (0, promises_1.copyFile)(sourceEntryPath, destinationEntryPath);
|
|
2157
|
+
copiedCount++;
|
|
2158
|
+
}
|
|
2159
|
+
return { copiedCount, deletedCount };
|
|
2160
|
+
}
|
|
2161
|
+
async ensureBootstrapResetState(templatePath) {
|
|
2162
|
+
const hedhogFilePath = pathModule.join(templatePath, 'hedhog.json');
|
|
2163
|
+
if (!(0, fs_1.existsSync)(hedhogFilePath)) {
|
|
2164
|
+
await this.fileSystem.writeJsonFile(hedhogFilePath, {
|
|
2165
|
+
libraries: [],
|
|
2166
|
+
installed: false,
|
|
2167
|
+
developerMode: false,
|
|
2168
|
+
buildCache: {},
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
1974
2172
|
async route(path, verbose = false) {
|
|
1975
2173
|
const restoreWarnings = this.suppressWarnings();
|
|
1976
2174
|
this.verbose = verbose;
|