push-guardian 1.0.0

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.
Files changed (96) hide show
  1. package/.dockerignore +15 -0
  2. package/.pushguardian-plugins.json +10 -0
  3. package/Dockerfile +41 -0
  4. package/Dockerfile.dev +20 -0
  5. package/README.md +386 -0
  6. package/TECHNO.md +139 -0
  7. package/babel.config.js +1 -0
  8. package/developper_utils.md +119 -0
  9. package/docker-compose.yml +41 -0
  10. package/docs/PLUGINS.md +223 -0
  11. package/docs/technical/architecture.md +298 -0
  12. package/docs/technical/performance-guide.md +390 -0
  13. package/docs/technical/plugin-persistence.md +169 -0
  14. package/docs/technical/plugins-guide.md +409 -0
  15. package/jest.config.js +22 -0
  16. package/package.json +53 -0
  17. package/plugins/example-plugin/index.js +55 -0
  18. package/plugins/example-plugin/plugin.json +8 -0
  19. package/scripts/coverage-report.js +75 -0
  20. package/src/cli/command/config.js +33 -0
  21. package/src/cli/command/install.js +137 -0
  22. package/src/cli/command/mirror.js +90 -0
  23. package/src/cli/command/performance.js +160 -0
  24. package/src/cli/command/plugin.js +171 -0
  25. package/src/cli/command/security.js +152 -0
  26. package/src/cli/command/shell.js +238 -0
  27. package/src/cli/command/validate.js +54 -0
  28. package/src/cli/index.js +23 -0
  29. package/src/cli/install/codeQualityTools.js +156 -0
  30. package/src/cli/install/hooks.js +89 -0
  31. package/src/cli/install/mirroring.js +299 -0
  32. package/src/core/codeQualityTools/configAnalyzer.js +216 -0
  33. package/src/core/codeQualityTools/configGenerator.js +381 -0
  34. package/src/core/codeQualityTools/configManager.js +65 -0
  35. package/src/core/codeQualityTools/fileDetector.js +62 -0
  36. package/src/core/codeQualityTools/languageTools.js +104 -0
  37. package/src/core/codeQualityTools/toolInstaller.js +53 -0
  38. package/src/core/configManager.js +43 -0
  39. package/src/core/errorCMD.js +9 -0
  40. package/src/core/interactiveMenu/interactiveMenu.js +73 -0
  41. package/src/core/mirroring/branchSynchronizer.js +59 -0
  42. package/src/core/mirroring/generate.js +114 -0
  43. package/src/core/mirroring/repoManager.js +112 -0
  44. package/src/core/mirroring/syncManager.js +176 -0
  45. package/src/core/module/env-loader.js +109 -0
  46. package/src/core/performance/metricsCollector.js +217 -0
  47. package/src/core/performance/performanceAnalyzer.js +182 -0
  48. package/src/core/plugins/basePlugin.js +89 -0
  49. package/src/core/plugins/pluginManager.js +123 -0
  50. package/src/core/plugins/pluginRegistry.js +215 -0
  51. package/src/core/validator.js +53 -0
  52. package/src/hooks/constrains/constrains.js +174 -0
  53. package/src/hooks/constrains/constraintEngine.js +140 -0
  54. package/src/utils/chalk-wrapper.js +26 -0
  55. package/src/utils/exec-wrapper.js +6 -0
  56. package/tests/fixtures/mock-eslint-config-array.js +8 -0
  57. package/tests/fixtures/mock-eslint-config-single.js +6 -0
  58. package/tests/fixtures/mockLoadedPlugin.js +11 -0
  59. package/tests/setup.js +28 -0
  60. package/tests/unit/basePlugin.test.js +355 -0
  61. package/tests/unit/branchSynchronizer.test.js +308 -0
  62. package/tests/unit/cli-commands.test.js +144 -0
  63. package/tests/unit/codeQualityConfigManager.test.js +233 -0
  64. package/tests/unit/codeQualityTools.test.js +36 -0
  65. package/tests/unit/command-install.test.js +247 -0
  66. package/tests/unit/command-mirror.test.js +179 -0
  67. package/tests/unit/command-performance.test.js +169 -0
  68. package/tests/unit/command-plugin.test.js +288 -0
  69. package/tests/unit/command-security.test.js +277 -0
  70. package/tests/unit/command-shell.test.js +325 -0
  71. package/tests/unit/configAnalyzer.test.js +593 -0
  72. package/tests/unit/configGenerator.test.js +808 -0
  73. package/tests/unit/configManager.test.js +195 -0
  74. package/tests/unit/constrains.test.js +463 -0
  75. package/tests/unit/constraint.test.js +554 -0
  76. package/tests/unit/env-loader.test.js +279 -0
  77. package/tests/unit/fileDetector.test.js +171 -0
  78. package/tests/unit/install-codeQualityTools.test.js +343 -0
  79. package/tests/unit/install-hooks.test.js +280 -0
  80. package/tests/unit/install-mirroring.test.js +731 -0
  81. package/tests/unit/install-modules.test.js +81 -0
  82. package/tests/unit/interactiveMenu.test.js +426 -0
  83. package/tests/unit/languageTools.test.js +244 -0
  84. package/tests/unit/metricsCollector.test.js +354 -0
  85. package/tests/unit/mirroring-generate.test.js +96 -0
  86. package/tests/unit/modules-exist.test.js +96 -0
  87. package/tests/unit/performanceAnalyzer.test.js +473 -0
  88. package/tests/unit/pluginManager.test.js +427 -0
  89. package/tests/unit/pluginRegistry.test.js +592 -0
  90. package/tests/unit/repoManager.test.js +469 -0
  91. package/tests/unit/reviewAppManager.test.js +5 -0
  92. package/tests/unit/security-command.test.js +43 -0
  93. package/tests/unit/syncManager.test.js +494 -0
  94. package/tests/unit/toolInstaller.test.js +240 -0
  95. package/tests/unit/utils.test.js +144 -0
  96. package/tests/unit/validator.test.js +215 -0
@@ -0,0 +1,9 @@
1
+ const { getChalk } = require('../utils/chalk-wrapper');
2
+ const chalk = getChalk();
3
+
4
+ module.exports = (error) => {
5
+ const stackLines = error.stack.split('\n');
6
+ const location = stackLines[1] ? stackLines[1].trim() : 'Emplacement inconnu';
7
+ console.log(chalk.red('💥 Error during validation:'), error.message, location);
8
+ process.exit(1);
9
+ };
@@ -0,0 +1,73 @@
1
+ const { getChalk } = require('../../utils/chalk-wrapper');
2
+ const chalk = getChalk();
3
+
4
+ module.exports = (message, choices, preselected = []) => {
5
+ return new Promise((resolve) => {
6
+ let currentIndex = 0;
7
+ let choiseMap = preselected;
8
+
9
+ const renderMenu = () => {
10
+ console.clear();
11
+ console.log(chalk.blue(message));
12
+ choices.forEach((choise, index) => {
13
+ if (index == currentIndex) console.log(chalk.yellow(`> ${choise}`));
14
+ else if (index != currentIndex && choiseMap.includes(index)) console.log(chalk.green(`* ${choise}`));
15
+ else console.log(` ${choise}`);
16
+ });
17
+ console.log('\n▲: Up\t▼: Down\t◀: Unselect\t►: Select (Enter to validate, Ctrl+C to quit)');
18
+ };
19
+
20
+ const handleInput = (key) => {
21
+ if (key == '\u0003') {
22
+ // QUIT with Ctrl+C
23
+ process.exit();
24
+ return;
25
+ }
26
+
27
+ if (key == '\u001B[A') {
28
+ // UP Arrow
29
+ currentIndex = (currentIndex - 1 + choices.length) % choices.length;
30
+ renderMenu();
31
+ } else if (key == '\u001B[B') {
32
+ // DOWN Arrow
33
+ currentIndex = (currentIndex + 1) % choices.length;
34
+ renderMenu();
35
+ } else if (key == '\r') {
36
+ // ENTER
37
+ process.stdin.setRawMode(false);
38
+ process.stdin.pause();
39
+ executeChoise(choiseMap);
40
+ } else if (key == '\u001B[D') {
41
+ // Left Arrow
42
+ const index = choiseMap.indexOf(currentIndex);
43
+ if (index !== -1) {
44
+ choiseMap.splice(index, 1);
45
+ }
46
+ renderMenu();
47
+ } else if (key == '\u001B[C') {
48
+ // Rigth Arrow
49
+ choiseMap.push(currentIndex);
50
+ renderMenu();
51
+ }
52
+ };
53
+
54
+ const executeChoise = (choiseMap) => {
55
+ if (!choiseMap.length) {
56
+ resolve([choices[currentIndex]]);
57
+ } else {
58
+ const selectedChoices = choiseMap.map((index) => choices[index]);
59
+ resolve(selectedChoices);
60
+ }
61
+ process.stdin.removeListener('data', handleInput);
62
+ process.stdin.setRawMode(false);
63
+ process.stdin.pause();
64
+ };
65
+
66
+ process.stdin.setRawMode(true);
67
+ process.stdin.resume();
68
+ process.stdin.setEncoding('utf8');
69
+ process.stdin.on('data', handleInput);
70
+
71
+ renderMenu();
72
+ });
73
+ };
@@ -0,0 +1,59 @@
1
+ class BranchSynchronizer {
2
+ constructor(clients) {
3
+ this.clients = clients;
4
+ }
5
+
6
+ async syncBranches(srcPlatform, targetPlatform, sourceRepo, targetRepo, sourceOwner, targetOwner) {
7
+ try {
8
+ const branches = await this.getBranches(srcPlatform, sourceRepo, sourceOwner);
9
+
10
+ for (const branch of branches) {
11
+ try {
12
+ await this.createBranch(targetPlatform, targetRepo, branch, targetOwner);
13
+ } catch (error) {
14
+ console.warn(`⚠️ Impossible de créer la branche ${branch.name}: ${error.message}`);
15
+ }
16
+ }
17
+ } catch (error) {
18
+ throw new Error(`La synchronisation des branches a échoué: ${error.message}`);
19
+ }
20
+ }
21
+ async getBranches(platform, repoName, owner) {
22
+ const client = this.clients[platform];
23
+
24
+ if (!client) throw new Error(`Plateforme non prise en charge: ${platform}`);
25
+ if (platform === 'github') {
26
+ const response = await client.repos.listBranches({ owner, repo: repoName });
27
+ return response.data;
28
+ }
29
+ if (platform === 'gitlab') return await client.Branches.all(repoName);
30
+ if (platform === 'bitbucket')
31
+ return await client.repositories.listBranches({ workspace: owner || 'workspace', repo_slug: repoName });
32
+ throw new Error(`Liste des branches non implémentée pour ${platform}`);
33
+ }
34
+
35
+ async createBranch(platform, repoName, branchData, owner) {
36
+ const client = this.clients[platform];
37
+
38
+ if (!client) throw new Error(`Plateforme non prise en charge: ${platform}`);
39
+ if (platform === 'github')
40
+ return await client.git.createRef({
41
+ owner,
42
+ repo: repoName,
43
+ ref: `refs/heads/${branchData.name}`,
44
+ sha: branchData.commit.sha
45
+ });
46
+ if (platform === 'gitlab')
47
+ return await client.Branches.create(repoName, branchData.name, { ref: branchData.commit.sha });
48
+ if (platform === 'bitbucket')
49
+ return await client.repositories.createBranch({
50
+ workspace: owner || 'workspace',
51
+ repo_slug: repoName,
52
+ name: branchData.name,
53
+ target: { hash: branchData.commit.sha }
54
+ });
55
+ throw new Error(`La création de branche n'est pas implémentée pour ${platform}`);
56
+ }
57
+ }
58
+
59
+ module.exports = { BranchSynchronizer };
@@ -0,0 +1,114 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { getChalk } = require('../../utils/chalk-wrapper');
4
+ const chalk = getChalk();
5
+
6
+ function generateWorkflow() {
7
+ try {
8
+ const workflowsDir = path.join(process.cwd(), '.github', 'workflows');
9
+ if (!fs.existsSync(workflowsDir)) {
10
+ fs.mkdirSync(workflowsDir, { recursive: true });
11
+ console.log(chalk.green('✅ Dossier .github/workflows créé'));
12
+ }
13
+ const workflowContent = `name: Mirror Repository
14
+
15
+ on:
16
+ workflow_dispatch:
17
+ schedule:
18
+ - cron: '0 2 * * *' # Every day at 2 AM UTC
19
+ push:
20
+ branches: [ main, master ]
21
+
22
+ jobs:
23
+ mirror:
24
+ runs-on: ubuntu-latest
25
+
26
+ steps:
27
+ - name: Checkout current repository
28
+ uses: actions/checkout@v4
29
+ with:
30
+ path: .
31
+
32
+ steps:
33
+ - name: Checkout push-guardian
34
+ uses: actions/checkout@v4
35
+ with:
36
+ repository: lagie-marin/push-guardian
37
+ path: push-guardian
38
+
39
+ - name: Setup Node.js
40
+ uses: actions/setup-node@v4
41
+ with:
42
+ node-version: '22.20.0'
43
+ cache: 'npm'
44
+ cache-dependency-path: push-guardian/package-lock.json
45
+
46
+ - name: Install specific npm version
47
+ run: |
48
+ echo "Current npm version:"
49
+ npm --version
50
+ echo ""
51
+ echo "Installing npm 11.6.0..."
52
+ # Désinstaller la version actuelle et installer la version spécifique
53
+ npm install -g npm@11.6.0 --force
54
+ echo ""
55
+ echo "New npm version:"
56
+ npm --version
57
+
58
+ - name: Install dependencies
59
+ run: |
60
+ cd push-guardian
61
+ npm ci
62
+
63
+ - name: Link push-guardian globally
64
+ run: |
65
+ cd push-guardian
66
+ npm link
67
+
68
+ - name: Execute mirror command
69
+ run: |
70
+ CURRENT_REPO="\${{ github.event.repository.name }}"
71
+ CURRENT_OWNER="\${{ github.event.repository.owner.login }}"
72
+
73
+ mirror_cmd="npx push-guardian mirror"
74
+ mirror_cmd="$mirror_cmd --source \${{ vars.SOURCE_PLATFORM }}"
75
+ mirror_cmd="$mirror_cmd --target \${{ vars.TARGET_PLATFORM }}"
76
+ mirror_cmd="$mirror_cmd --source-repo $CURRENT_REPO"
77
+ mirror_cmd="$mirror_cmd --repo \${{ vars.TARGET_REPO }}"
78
+ mirror_cmd="$mirror_cmd --source-owner $CURRENT_OWNER"
79
+ mirror_cmd="$mirror_cmd --target-owner \${{ vars.TARGET_OWNER }}"
80
+
81
+ if [ "\${{ vars.SYNC_BRANCHES }}" = "true" ]; then
82
+ mirror_cmd="$mirror_cmd --sync-branches"
83
+ fi
84
+
85
+ if [ "\${{ vars.PUBLIC_REPO }}" = "true" ]; then
86
+ mirror_cmd="$mirror_cmd --public-repo"
87
+ fi
88
+
89
+ echo "Executing: $mirror_cmd"
90
+ eval $mirror_cmd
91
+ env:
92
+ GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
93
+ GIT_TOKEN: \${{ secrets.GITHUB_TOKEN }}
94
+ GITLAB_TOKEN: \${{ secrets.GITLAB_TOKEN }}
95
+ BIT_BUCKET: \${{ secrets.BIT_BUCKET }}
96
+ BB_WORKSPACE: \${{ vars.BB_WORKSPACE }}
97
+ AZURE_DEVOPS_URL: \${{ secrets.AZURE_DEVOPS_URL }}
98
+ AZURE_DEVOPS_TOKEN: \${{ secrets.AZURE_DEVOPS_TOKEN }}
99
+ `;
100
+
101
+ const workflowPath = path.join(workflowsDir, 'mirror.yml');
102
+ fs.writeFileSync(workflowPath, workflowContent, 'utf8');
103
+
104
+ console.log(chalk.green('✅ Workflow GitHub Actions généré : .github/workflows/mirror.yml'));
105
+ console.log(chalk.blue('ℹ️ Pensez à configurer les variables et secrets dans GitHub Actions'));
106
+ } catch (error) {
107
+ console.error(chalk.red(`❌ Erreur lors de la génération du workflow : ${error.message}`));
108
+ throw error;
109
+ }
110
+ }
111
+
112
+ module.exports = {
113
+ generateWorkflow
114
+ };
@@ -0,0 +1,112 @@
1
+ class RepoManager {
2
+ constructor(clients) {
3
+ this.clients = clients;
4
+ }
5
+
6
+ async createOrUpdateRepo(
7
+ srcPlatform,
8
+ targetPlatform,
9
+ sourceRepo,
10
+ targetRepo,
11
+ sourceOwner,
12
+ targetOwner,
13
+ public_repo = false
14
+ ) {
15
+ try {
16
+ const srcRepo = await this.getRepo(srcPlatform, sourceRepo, sourceOwner);
17
+ const normalizedSrcRepo = srcRepo && srcRepo.data ? srcRepo.data : srcRepo;
18
+ const repoData = {
19
+ name: targetRepo,
20
+ description: normalizedSrcRepo.description || '',
21
+ private: normalizedSrcRepo.private || false
22
+ };
23
+ await this.createRepo(targetPlatform, repoData, targetOwner, public_repo);
24
+ } catch (error) {
25
+ throw new Error(`Échec de la mise en miroir du dépôt: ${error.message}`);
26
+ }
27
+ }
28
+
29
+ async getRepo(platform, repoName, owner) {
30
+ const client = this.clients[platform];
31
+ if (!client) throw new Error(`Plateforme non prise en charge: ${platform}`);
32
+ if (platform === 'github') return await client.repos.get({ owner, repo: repoName });
33
+ if (platform === 'gitlab') return await client.Projects.show(repoName);
34
+ if (platform === 'bitbucket')
35
+ return await client.repositories.get({ workspace: owner || 'workspace', repo_slug: repoName });
36
+ throw new Error(`La récupération du dépôt n'est pas implémentée pour ${platform}`);
37
+ }
38
+
39
+ async createRepo(platform, repoData, owner, public_repo = false) {
40
+ const client = this.clients[platform];
41
+ if (!client) throw new Error(`Plateforme non prise en charge: ${platform}`);
42
+ if (platform === 'github') {
43
+ try {
44
+ const existingRepo = await client.repos.get({ owner, repo: repoData.name }).catch(() => null);
45
+ if (existingRepo) {
46
+ console.log(
47
+ `📁 Le dépôt ${repoData.name} existe déjà chez ${owner}, utilisation du dépôt existant.`
48
+ );
49
+ return existingRepo.data;
50
+ }
51
+ return await client.repos.createForAuthenticatedUser({ ...repoData, private: !public_repo });
52
+ } catch {
53
+ try {
54
+ return await client.repos.createInOrg({ org: owner, ...repoData, private: !public_repo });
55
+ } catch (orgError) {
56
+ throw new Error(`Impossible de créer le dépôt: ${orgError.message}`);
57
+ }
58
+ }
59
+ }
60
+ if (platform === 'gitlab') {
61
+ const projectPath = `${owner}/${repoData.name}`;
62
+
63
+ try {
64
+ const existingByPath = await client.Projects.show(projectPath);
65
+ if (existingByPath) {
66
+ console.log(
67
+ `📁 Le dépôt ${repoData.name} existe déjà chez ${owner}, utilisation du dépôt existant.`
68
+ );
69
+ return existingByPath;
70
+ }
71
+ } catch {
72
+ // Le projet n'existe pas (ou n'est pas accessible), on tente la création juste après.
73
+ }
74
+
75
+ const existing = await client.Projects.search(repoData.name).catch(() => []);
76
+ const existingRepo = existing.find((p) => p.name === repoData.name && p.namespace.name === owner);
77
+ if (existingRepo) {
78
+ console.log(`📁 Le dépôt ${repoData.name} existe déjà chez ${owner}, utilisation du dépôt existant.`);
79
+ return existingRepo;
80
+ }
81
+
82
+ try {
83
+ return await client.Projects.create({
84
+ ...repoData,
85
+ visibility: public_repo ? 'public' : 'private'
86
+ });
87
+ } catch (error) {
88
+ if (error && (error.message || '').toLowerCase().includes('forbidden')) {
89
+ throw new Error(
90
+ "GitLab a refusé l'opération (Forbidden). Vérifiez que GITLAB_TOKEN a le scope 'api' et les droits de création dans le namespace cible."
91
+ );
92
+ }
93
+ throw error;
94
+ }
95
+ }
96
+ if (platform === 'bitbucket') {
97
+ const existing = await client.repositories.list({ workspace: owner || 'workspace' });
98
+ const existingRepo = existing.data.values.find((r) => r.name === repoData.name);
99
+ if (existingRepo) {
100
+ console.log(`📁 Le dépôt ${repoData.name} existe déjà chez ${owner}, utilisation du dépôt existant.`);
101
+ return existingRepo;
102
+ }
103
+ return await client.repositories.create({
104
+ workspace: owner || 'workspace',
105
+ repository: { ...repoData, is_private: !public_repo }
106
+ });
107
+ }
108
+ throw new Error(`La création de dépôt n'est pas implémentée pour ${platform}`);
109
+ }
110
+ }
111
+
112
+ module.exports = { RepoManager };
@@ -0,0 +1,176 @@
1
+ const { Octokit } = require('@octokit/rest');
2
+ const { WebApi } = require('azure-devops-node-api');
3
+ const { Bitbucket } = require('bitbucket');
4
+ const { Gitlab } = require('gitlab');
5
+ const simpleGit = require('simple-git');
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+ const { RepoManager } = require('./repoManager');
9
+ const { BranchSynchronizer } = require('./branchSynchronizer');
10
+ const { getEnv } = require('../module/env-loader');
11
+
12
+ class SyncManager {
13
+ constructor(config) {
14
+ this.config = config;
15
+ this.clients = this.initClients();
16
+ this.repoManager = new RepoManager(this.clients);
17
+ this.branchSynchronizer = new BranchSynchronizer(this.clients);
18
+ }
19
+
20
+ initClients() {
21
+ const clients = {};
22
+
23
+ if (this.config.github && this.config.github.enabled) {
24
+ try {
25
+ const token = getEnv('GITHUB_TOKEN');
26
+ clients.github = new Octokit({ auth: token });
27
+ } catch (error) {
28
+ console.warn(`⚠️ Impossible d'initialiser le client GitHub: ${error.message}`);
29
+ }
30
+ }
31
+
32
+ if (this.config.gitlab && this.config.gitlab.enabled) {
33
+ try {
34
+ const token = getEnv('GITLAB_TOKEN');
35
+ clients.gitlab = new Gitlab({ token: token });
36
+ } catch (error) {
37
+ console.warn(`⚠️ Impossible d'initialiser le client GitLab: ${error.message}`);
38
+ }
39
+ }
40
+
41
+ if (this.config.bitbucket && this.config.bitbucket.enabled) {
42
+ try {
43
+ const username = getEnv('BITBUCKET_USERNAME');
44
+ const password = getEnv('BITBUCKET_PASSWORD');
45
+ clients.bitbucket = new Bitbucket({
46
+ auth: { username: username, password: password }
47
+ });
48
+ } catch (error) {
49
+ console.warn(`⚠️ Impossible d'initialiser le client BitBucket: ${error.message}`);
50
+ }
51
+ }
52
+
53
+ if (this.config.azure && this.config.azure.enabled) {
54
+ try {
55
+ const url = getEnv('AZURE_DEVOPS_URL');
56
+ const token = getEnv('AZURE_DEVOPS_TOKEN');
57
+ clients.azure = new WebApi(url, token);
58
+ } catch (error) {
59
+ console.warn(`⚠️ Impossible d'initialiser le client Azure DevOps: ${error.message}`);
60
+ }
61
+ }
62
+
63
+ return clients;
64
+ }
65
+
66
+ async mirror(
67
+ sourcePlatform,
68
+ targetPlatform,
69
+ sourceRepo,
70
+ targetRepo,
71
+ sourceOwner,
72
+ targetOwner,
73
+ syncBranches = false,
74
+ public_repo = false
75
+ ) {
76
+ try {
77
+ await this.repoManager.createOrUpdateRepo(
78
+ sourcePlatform,
79
+ targetPlatform,
80
+ sourceRepo,
81
+ targetRepo,
82
+ sourceOwner,
83
+ targetOwner,
84
+ public_repo
85
+ );
86
+
87
+ await this.pushCodeToTarget(
88
+ sourcePlatform,
89
+ targetPlatform,
90
+ sourceRepo,
91
+ targetRepo,
92
+ sourceOwner,
93
+ targetOwner
94
+ );
95
+
96
+ if (syncBranches) {
97
+ await this.branchSynchronizer.syncBranches(
98
+ sourcePlatform,
99
+ targetPlatform,
100
+ sourceRepo,
101
+ targetRepo,
102
+ sourceOwner,
103
+ targetOwner
104
+ );
105
+ }
106
+ } catch (error) {
107
+ console.error(`❌ Échec de la mise en miroir: ${error.message}`);
108
+ throw error;
109
+ }
110
+ }
111
+
112
+ async pushCodeToTarget(sourcePlatform, targetPlatform, sourceRepo, targetRepo, sourceOwner, targetOwner) {
113
+ if (sourcePlatform !== 'github' || targetPlatform !== 'github') {
114
+ console.log("⚠️ Le push du code n'est actuellement supporté que pour GitHub vers GitHub");
115
+ return;
116
+ }
117
+
118
+ const sourceToken = getEnv('GITHUB_TOKEN');
119
+ const targetToken = sourceToken;
120
+
121
+ if (!sourceToken || !targetToken) {
122
+ console.log('⚠️ Tokens manquants pour pousser le code');
123
+ return;
124
+ }
125
+
126
+ const tempDir = path.join(process.cwd(), 'temp-mirror-' + Date.now());
127
+
128
+ try {
129
+ fs.mkdirSync(tempDir, { recursive: true });
130
+
131
+ const git = simpleGit(tempDir);
132
+
133
+ const sourceUrl = `https://${sourceToken}@github.com/${sourceOwner}/${sourceRepo}.git`;
134
+ console.log('📥 Clonage du dépôt source...');
135
+ await git.clone(sourceUrl, '.');
136
+
137
+ const targetUrl = `https://${targetToken}@github.com/${targetOwner}/${targetRepo}.git`;
138
+ console.log('📤 Configuration du remote cible...');
139
+ await git.removeRemote('origin');
140
+ await git.addRemote('origin', targetUrl);
141
+
142
+ console.log('🚀 Push du code vers le dépôt cible...');
143
+ await git.push('origin', 'main', ['--force']);
144
+
145
+ const branches = await git.branch();
146
+ for (const branch of branches.all) {
147
+ if (branch !== 'main' && branch !== 'master') {
148
+ try {
149
+ await git.push('origin', branch, ['--force']);
150
+ } catch (error) {
151
+ console.warn(`⚠️ Impossible de pousser la branche ${branch}: ${error.message}`);
152
+ }
153
+ }
154
+ }
155
+
156
+ try {
157
+ await git.pushTags('origin');
158
+ } catch (error) {
159
+ console.warn(`⚠️ Impossible de pousser les tags: ${error.message}`);
160
+ }
161
+
162
+ console.log('✅ Code poussé avec succès vers le dépôt cible');
163
+ } catch (error) {
164
+ console.error(`❌ Échec du push du code: ${error.message}`);
165
+ throw error;
166
+ } finally {
167
+ try {
168
+ fs.rmSync(tempDir, { recursive: true, force: true });
169
+ } catch (cleanupError) {
170
+ console.warn(`⚠️ Impossible de nettoyer le dossier temporaire: ${cleanupError.message}`);
171
+ }
172
+ }
173
+ }
174
+ }
175
+
176
+ module.exports = { SyncManager };
@@ -0,0 +1,109 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Charge les variables d'environnement depuis un fichier .env
6
+ * @param {string} envPath - Chemin vers le fichier .env (par défaut: '.env')
7
+ */
8
+ function loadEnv(envPath = '.env') {
9
+ try {
10
+ const absolutePath = path.resolve(process.cwd(), envPath);
11
+
12
+ if (!fs.existsSync(absolutePath)) {
13
+ console.warn('⚠️ Fichier .env non trouvé, utilisation des variables système');
14
+ return;
15
+ }
16
+
17
+ const content = fs.readFileSync(absolutePath, 'utf8');
18
+ let lines = content.split('\n');
19
+
20
+ lines.forEach((line, index) => {
21
+ line = line.trim();
22
+ if (!line || line.startsWith('#')) return;
23
+
24
+ const match = line.match(/^\s*([\w.-]+)\s*=\s*(.*)?\s*$/);
25
+ if (!match) {
26
+ console.warn(`⚠️ Ligne ${index + 1} ignorée (format invalide): ${line}`);
27
+ return;
28
+ }
29
+
30
+ const key = match[1];
31
+ let value = match[2] || '';
32
+
33
+ if (value.startsWith('"') && value.endsWith('"')) {
34
+ value = value.slice(1, -1).replace(/\\n/g, '\n');
35
+ } else if (value.startsWith("'") && value.endsWith("'")) {
36
+ value = value.slice(1, -1);
37
+ }
38
+
39
+ if (process.env[key] === undefined) {
40
+ process.env[key] = value;
41
+ }
42
+ });
43
+
44
+ console.log("✅ Variables d'environnement chargées depuis .env");
45
+ } catch (error) {
46
+ console.error('❌ Erreur lors du chargement du fichier .env:', error.message);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Récupère une variable d'environnement avec validation
52
+ * @param {string} key - Clé de la variable
53
+ * @param {*} defaultValue - Valeur par défaut si non trouvée
54
+ * @returns {string}
55
+ */
56
+ function getEnv(key, defaultValue = null, werror = false) {
57
+ const value = process.env[key];
58
+ if (value === undefined || value === '') {
59
+ if (defaultValue === null) {
60
+ if (werror) {
61
+ throw new Error(`Variable d'environnement manquante: ${key}`);
62
+ } else {
63
+ return '';
64
+ }
65
+ }
66
+ return defaultValue;
67
+ }
68
+ return value;
69
+ }
70
+
71
+ /**
72
+ * Sauvegarde une variable d'environnement dans le fichier .env
73
+ * @param {string} key - Clé de la variable
74
+ * @param {string} value - Valeur de la variable
75
+ * @param {string} envPath - Chemin vers le fichier .env (par défaut: '.env')
76
+ */
77
+ function saveEnv(key, value, envPath = '.env') {
78
+ try {
79
+ const absolutePath = path.resolve(process.cwd(), envPath);
80
+ let content = '';
81
+
82
+ if (fs.existsSync(absolutePath)) {
83
+ content = fs.readFileSync(absolutePath, 'utf8');
84
+ }
85
+
86
+ const lines = content.split('\n');
87
+ let updated = false;
88
+
89
+ for (let i = 0; i < lines.length; i++) {
90
+ const line = lines[i].trim();
91
+ if (line.startsWith(`${key}=`) || line.startsWith(`${key} =`)) {
92
+ lines[i] = `${key}=${value}`;
93
+ updated = true;
94
+ break;
95
+ }
96
+ }
97
+
98
+ if (!updated) {
99
+ lines.push(`${key}=${value}`);
100
+ }
101
+
102
+ fs.writeFileSync(absolutePath, lines.join('\n'), 'utf8');
103
+ console.log(`✅ Variable ${key} sauvegardée dans .env`);
104
+ } catch (error) {
105
+ console.error('❌ Erreur lors de la sauvegarde de la variable:', error.message);
106
+ }
107
+ }
108
+
109
+ module.exports = { loadEnv, getEnv, saveEnv };