@hed-hog/cli 0.0.68 → 0.0.70

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/cli",
3
- "version": "0.0.68",
3
+ "version": "0.0.70",
4
4
  "description": "HedHog CLI tool",
5
5
  "author": "HedHog",
6
6
  "private": false,
@@ -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(), verbose);
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;QACvB,OAAO,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC;IAC7D,CAAC;IAOD,kBAAkB;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AAjBY,kDAAmB;AAc9B;IALC,IAAA,uBAAM,EAAC;QACN,KAAK,EAAE,eAAe;QACtB,WAAW,EAAE,uBAAuB;QACpC,QAAQ,EAAE,KAAK;KAChB,CAAC;;;;6DAGD;8BAhBU,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,CAiB/B"}
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?: boolean): Promise<void>;
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 (see DEPLOYMENT.md)'));
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
- path = await this.getRootPath(path);
1854
- const templatePath = pathModule.join(pathModule.dirname(path), 'template');
1855
- this.log(chalk.blue(`Source path: ${path}`));
1856
- this.log(chalk.blue(`Template path: ${templatePath}`));
1857
- // Clean template directory but keep .git
1858
- spinner.start('Cleaning template directory...');
1859
- if ((0, fs_1.existsSync)(templatePath)) {
1860
- const gitPath = pathModule.join(templatePath, '.git');
1861
- const hasGit = (0, fs_1.existsSync)(gitPath);
1862
- // Remove all items except .git directory
1863
- const entries = (0, fs_1.readdirSync)(templatePath, { withFileTypes: true });
1864
- for (const entry of entries) {
1865
- if (entry.name === '.git') {
1866
- this.log(chalk.gray('Skipping .git directory'));
1867
- continue;
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;