@mexty/cli 1.8.2 → 1.9.1
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/block-68f6a9dd608928af6fbea9ba/.eslintrc +14 -0
- package/dist/block-68f6a9dd608928af6fbea9ba/.prettierrc +10 -0
- package/dist/block-68f6a9dd608928af6fbea9ba/README.md +63 -0
- package/dist/block-68f6a9dd608928af6fbea9ba/index.html +13 -0
- package/dist/block-68f6a9dd608928af6fbea9ba/package-lock.json +6657 -0
- package/dist/block-68f6a9dd608928af6fbea9ba/package.json +34 -0
- package/dist/block-68f6a9dd608928af6fbea9ba/src/App.tsx +45 -0
- package/dist/block-68f6a9dd608928af6fbea9ba/src/block.tsx +111 -0
- package/dist/block-68f6a9dd608928af6fbea9ba/src/index.tsx +6 -0
- package/dist/block-68f6a9dd608928af6fbea9ba/src/styles.css +8 -0
- package/dist/block-68f6a9dd608928af6fbea9ba/tsconfig.json +27 -0
- package/dist/block-68f6a9dd608928af6fbea9ba/vite.config.ts +20 -0
- package/dist/block-68f6a9dd608928af6fbea9ba/webpack.config.js +59 -0
- package/dist/block-68f6aa43608928af6fbea9c9/.eslintrc +14 -0
- package/dist/block-68f6aa43608928af6fbea9c9/.prettierrc +10 -0
- package/dist/block-68f6aa43608928af6fbea9c9/README.md +63 -0
- package/dist/block-68f6aa43608928af6fbea9c9/coco +0 -0
- package/dist/block-68f6aa43608928af6fbea9c9/index.html +13 -0
- package/dist/block-68f6aa43608928af6fbea9c9/package-lock.json +6657 -0
- package/dist/block-68f6aa43608928af6fbea9c9/package.json +34 -0
- package/dist/block-68f6aa43608928af6fbea9c9/src/App.tsx +45 -0
- package/dist/block-68f6aa43608928af6fbea9c9/src/block.tsx +111 -0
- package/dist/block-68f6aa43608928af6fbea9c9/src/index.tsx +6 -0
- package/dist/block-68f6aa43608928af6fbea9c9/src/styles.css +8 -0
- package/dist/block-68f6aa43608928af6fbea9c9/tsconfig.json +27 -0
- package/dist/block-68f6aa43608928af6fbea9c9/vite.config.ts +20 -0
- package/dist/block-68f6aa43608928af6fbea9c9/webpack.config.js +59 -0
- package/dist/coco +0 -0
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +61 -1
- package/dist/commands/create.js.map +1 -1
- package/dist/utils/api.d.ts +7 -0
- package/dist/utils/api.d.ts.map +1 -1
- package/dist/utils/api.js +4 -0
- package/dist/utils/api.js.map +1 -1
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +55 -9
- package/dist/utils/git.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/create.ts +69 -1
- package/src/commands/github-login.ts +107 -107
- package/src/utils/api.ts +5 -0
- package/src/utils/git.ts +269 -254
package/src/utils/git.ts
CHANGED
|
@@ -1,254 +1,269 @@
|
|
|
1
|
-
import simpleGit, { SimpleGit } from "simple-git";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import chalk from "chalk";
|
|
5
|
-
import { apiClient } from "./api";
|
|
6
|
-
|
|
7
|
-
// Simple spinner implementation since ora v5 has import issues
|
|
8
|
-
class SimpleSpinner {
|
|
9
|
-
private message: string;
|
|
10
|
-
private interval: NodeJS.Timeout | null = null;
|
|
11
|
-
private frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
12
|
-
private currentFrame = 0;
|
|
13
|
-
|
|
14
|
-
constructor(message: string) {
|
|
15
|
-
this.message = message;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Add setter for message text
|
|
19
|
-
set text(newMessage: string) {
|
|
20
|
-
this.message = newMessage;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
start(): this {
|
|
24
|
-
process.stdout.write(this.message);
|
|
25
|
-
this.interval = setInterval(() => {
|
|
26
|
-
process.stdout.write(
|
|
27
|
-
`\r${this.frames[this.currentFrame]} ${this.message}`
|
|
28
|
-
);
|
|
29
|
-
this.currentFrame = (this.currentFrame + 1) % this.frames.length;
|
|
30
|
-
}, 80);
|
|
31
|
-
return this;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
succeed(message: string): void {
|
|
35
|
-
this.stop();
|
|
36
|
-
console.log(`\r✅ ${message}`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
fail(message: string): void {
|
|
40
|
-
this.stop();
|
|
41
|
-
console.log(`\r❌ ${message}`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
private stop(): void {
|
|
45
|
-
if (this.interval) {
|
|
46
|
-
clearInterval(this.interval);
|
|
47
|
-
this.interval = null;
|
|
48
|
-
}
|
|
49
|
-
process.stdout.write("\r");
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function ora(message: string): SimpleSpinner {
|
|
54
|
-
return new SimpleSpinner(message);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export class GitManager {
|
|
58
|
-
public git: SimpleGit;
|
|
59
|
-
|
|
60
|
-
constructor(cwd?: string) {
|
|
61
|
-
this.git = simpleGit(cwd);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Clone a repository to a local directory
|
|
66
|
-
* Supports private repositories if GitHub is connected
|
|
67
|
-
*/
|
|
68
|
-
async cloneRepository(repoUrl: string, targetDir: string): Promise<void> {
|
|
69
|
-
const spinner = ora(`Cloning repository from ${repoUrl}...`).start();
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
// Ensure target directory doesn't exist
|
|
73
|
-
if (fs.existsSync(targetDir)) {
|
|
74
|
-
spinner.fail(chalk.red(`Directory ${targetDir} already exists`));
|
|
75
|
-
throw new Error(`Directory ${targetDir} already exists`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Check if this is a GitHub URL that might need authentication
|
|
79
|
-
const isGitHub = repoUrl.includes('github.com');
|
|
80
|
-
let authenticatedUrl = repoUrl;
|
|
81
|
-
|
|
82
|
-
if (isGitHub) {
|
|
83
|
-
try {
|
|
84
|
-
// Try to get GitHub token for private repo access
|
|
85
|
-
const tokenData = await apiClient.getGitHubToken();
|
|
86
|
-
|
|
87
|
-
if (tokenData.success && tokenData.token) {
|
|
88
|
-
// Inject token into URL for authenticated cloning
|
|
89
|
-
authenticatedUrl = this.injectTokenIntoUrl(repoUrl, tokenData.token);
|
|
90
|
-
spinner.text = `Cloning repository (authenticated)...`;
|
|
91
|
-
}
|
|
92
|
-
} catch (error: any) {
|
|
93
|
-
// If token retrieval fails (e.g., not connected), try without auth
|
|
94
|
-
// This is fine for public repositories
|
|
95
|
-
if (error.response?.status === 404) {
|
|
96
|
-
spinner.text = `Cloning repository (public)...`;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Clone the repository
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
spinner.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
try {
|
|
186
|
-
const git = dir ? simpleGit(dir) : this.git;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
*
|
|
243
|
-
*/
|
|
244
|
-
static
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
1
|
+
import simpleGit, { SimpleGit } from "simple-git";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { apiClient } from "./api";
|
|
6
|
+
|
|
7
|
+
// Simple spinner implementation since ora v5 has import issues
|
|
8
|
+
class SimpleSpinner {
|
|
9
|
+
private message: string;
|
|
10
|
+
private interval: NodeJS.Timeout | null = null;
|
|
11
|
+
private frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
12
|
+
private currentFrame = 0;
|
|
13
|
+
|
|
14
|
+
constructor(message: string) {
|
|
15
|
+
this.message = message;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Add setter for message text
|
|
19
|
+
set text(newMessage: string) {
|
|
20
|
+
this.message = newMessage;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
start(): this {
|
|
24
|
+
process.stdout.write(this.message);
|
|
25
|
+
this.interval = setInterval(() => {
|
|
26
|
+
process.stdout.write(
|
|
27
|
+
`\r${this.frames[this.currentFrame]} ${this.message}`
|
|
28
|
+
);
|
|
29
|
+
this.currentFrame = (this.currentFrame + 1) % this.frames.length;
|
|
30
|
+
}, 80);
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
succeed(message: string): void {
|
|
35
|
+
this.stop();
|
|
36
|
+
console.log(`\r✅ ${message}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fail(message: string): void {
|
|
40
|
+
this.stop();
|
|
41
|
+
console.log(`\r❌ ${message}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private stop(): void {
|
|
45
|
+
if (this.interval) {
|
|
46
|
+
clearInterval(this.interval);
|
|
47
|
+
this.interval = null;
|
|
48
|
+
}
|
|
49
|
+
process.stdout.write("\r");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function ora(message: string): SimpleSpinner {
|
|
54
|
+
return new SimpleSpinner(message);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class GitManager {
|
|
58
|
+
public git: SimpleGit;
|
|
59
|
+
|
|
60
|
+
constructor(cwd?: string) {
|
|
61
|
+
this.git = simpleGit(cwd);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Clone a repository to a local directory
|
|
66
|
+
* Supports private repositories if GitHub is connected
|
|
67
|
+
*/
|
|
68
|
+
async cloneRepository(repoUrl: string, targetDir: string): Promise<void> {
|
|
69
|
+
const spinner = ora(`Cloning repository from ${repoUrl}...`).start();
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
// Ensure target directory doesn't exist
|
|
73
|
+
if (fs.existsSync(targetDir)) {
|
|
74
|
+
spinner.fail(chalk.red(`Directory ${targetDir} already exists`));
|
|
75
|
+
throw new Error(`Directory ${targetDir} already exists`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check if this is a GitHub URL that might need authentication
|
|
79
|
+
const isGitHub = repoUrl.includes('github.com');
|
|
80
|
+
let authenticatedUrl = repoUrl;
|
|
81
|
+
|
|
82
|
+
if (isGitHub) {
|
|
83
|
+
try {
|
|
84
|
+
// Try to get GitHub token for private repo access
|
|
85
|
+
const tokenData = await apiClient.getGitHubToken();
|
|
86
|
+
|
|
87
|
+
if (tokenData.success && tokenData.token) {
|
|
88
|
+
// Inject token into URL for authenticated cloning
|
|
89
|
+
authenticatedUrl = this.injectTokenIntoUrl(repoUrl, tokenData.token);
|
|
90
|
+
spinner.text = `Cloning repository (authenticated)...`;
|
|
91
|
+
}
|
|
92
|
+
} catch (error: any) {
|
|
93
|
+
// If token retrieval fails (e.g., not connected), try without auth
|
|
94
|
+
// This is fine for public repositories
|
|
95
|
+
if (error.response?.status === 404) {
|
|
96
|
+
spinner.text = `Cloning repository (public)...`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Clone the repository using native git command
|
|
102
|
+
try {
|
|
103
|
+
const { exec } = await import('child_process');
|
|
104
|
+
const { promisify } = await import('util');
|
|
105
|
+
const execAsync = promisify(exec);
|
|
106
|
+
|
|
107
|
+
const cloneCommand = `git clone "${authenticatedUrl}" "${targetDir}"`;
|
|
108
|
+
await execAsync(cloneCommand);
|
|
109
|
+
|
|
110
|
+
spinner.succeed(chalk.green(`Repository cloned to ${targetDir}`));
|
|
111
|
+
} catch (cloneErr: any) {
|
|
112
|
+
throw cloneErr;
|
|
113
|
+
}
|
|
114
|
+
} catch (error: any) {
|
|
115
|
+
// Check if error is due to authentication
|
|
116
|
+
if (error.message.includes('Authentication failed') ||
|
|
117
|
+
error.message.includes('could not read Username') ||
|
|
118
|
+
error.message.includes('Repository not found')) {
|
|
119
|
+
spinner.fail(chalk.red(`Failed to clone repository: Authentication required`));
|
|
120
|
+
console.log(chalk.yellow('\n💡 This might be a private repository.'));
|
|
121
|
+
console.log(chalk.blue(' Connect your GitHub account: mexty github-login\n'));
|
|
122
|
+
} else {
|
|
123
|
+
spinner.fail(chalk.red(`Failed to clone repository: ${error.message}`));
|
|
124
|
+
}
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Inject GitHub token into repository URL for authenticated access
|
|
131
|
+
*/
|
|
132
|
+
private injectTokenIntoUrl(repoUrl: string, token: string): string {
|
|
133
|
+
// GitHub recommends using 'x-access-token' as the username with the token as password
|
|
134
|
+
// Format: https://x-access-token:{token}@github.com/...
|
|
135
|
+
|
|
136
|
+
// Remove trailing slash if present (can cause issues with git clone)
|
|
137
|
+
const cleanUrl = repoUrl.replace(/\/$/, '');
|
|
138
|
+
|
|
139
|
+
// Handle HTTPS URLs
|
|
140
|
+
if (cleanUrl.startsWith('https://github.com/')) {
|
|
141
|
+
return cleanUrl.replace('https://github.com/', `https://x-access-token:${token}@github.com/`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Handle SSH URLs (convert to HTTPS with token)
|
|
145
|
+
if (cleanUrl.startsWith('git@github.com:')) {
|
|
146
|
+
const path = cleanUrl.replace('git@github.com:', '');
|
|
147
|
+
return `https://x-access-token:${token}@github.com/${path}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Return original URL if format not recognized
|
|
151
|
+
return cleanUrl;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Check if current directory is a Git repository
|
|
156
|
+
*/
|
|
157
|
+
async isGitRepository(dir?: string): Promise<boolean> {
|
|
158
|
+
try {
|
|
159
|
+
const git = dir ? simpleGit(dir) : this.git;
|
|
160
|
+
await git.status();
|
|
161
|
+
return true;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get the current repository's remote URL
|
|
169
|
+
*/
|
|
170
|
+
async getRemoteUrl(dir?: string): Promise<string | null> {
|
|
171
|
+
try {
|
|
172
|
+
const git = dir ? simpleGit(dir) : this.git;
|
|
173
|
+
const remotes = await git.getRemotes(true);
|
|
174
|
+
const origin = remotes.find((remote) => remote.name === "origin");
|
|
175
|
+
return origin?.refs?.fetch || null;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Check if there are uncommitted changes
|
|
183
|
+
*/
|
|
184
|
+
async hasUncommittedChanges(dir?: string): Promise<boolean> {
|
|
185
|
+
try {
|
|
186
|
+
const git = dir ? simpleGit(dir) : this.git;
|
|
187
|
+
const status = await git.status();
|
|
188
|
+
return !status.isClean();
|
|
189
|
+
} catch (error) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Push current branch to remote
|
|
196
|
+
*/
|
|
197
|
+
async pushToRemote(dir?: string): Promise<void> {
|
|
198
|
+
const spinner = ora("Pushing changes to remote repository...").start();
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
const git = dir ? simpleGit(dir) : this.git;
|
|
202
|
+
|
|
203
|
+
// Get current branch
|
|
204
|
+
const status = await git.status();
|
|
205
|
+
const currentBranch = status.current;
|
|
206
|
+
|
|
207
|
+
if (!currentBranch) {
|
|
208
|
+
throw new Error("No current branch found");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Push to remote
|
|
212
|
+
await git.push("origin", currentBranch);
|
|
213
|
+
|
|
214
|
+
spinner.succeed(chalk.green("Changes pushed to remote repository"));
|
|
215
|
+
} catch (error: any) {
|
|
216
|
+
spinner.fail(chalk.red(`Failed to push changes: ${error.message}`));
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get repository information
|
|
223
|
+
*/
|
|
224
|
+
async getRepositoryInfo(dir?: string): Promise<{
|
|
225
|
+
branch: string;
|
|
226
|
+
remoteUrl: string | null;
|
|
227
|
+
hasChanges: boolean;
|
|
228
|
+
}> {
|
|
229
|
+
const git = dir ? simpleGit(dir) : this.git;
|
|
230
|
+
|
|
231
|
+
const status = await git.status();
|
|
232
|
+
const remoteUrl = await this.getRemoteUrl(dir);
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
branch: status.current || "unknown",
|
|
236
|
+
remoteUrl,
|
|
237
|
+
hasChanges: !status.isClean(),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Extract repository name from URL
|
|
243
|
+
*/
|
|
244
|
+
static extractRepoName(gitUrl: string): string {
|
|
245
|
+
// Handle both SSH and HTTPS URLs
|
|
246
|
+
const match = gitUrl.match(/\/([^\/]+?)(?:\.git)?$/);
|
|
247
|
+
if (match) {
|
|
248
|
+
return match[1];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Fallback: use the last part of the URL
|
|
252
|
+
const parts = gitUrl.split("/");
|
|
253
|
+
return parts[parts.length - 1].replace(".git", "");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Validate Git URL format
|
|
258
|
+
*/
|
|
259
|
+
static isValidGitUrl(url: string): boolean {
|
|
260
|
+
const patterns = [
|
|
261
|
+
/^https:\/\/github\.com\/[\w\-\.]+\/[\w\-\.]+(?:\.git)?$/,
|
|
262
|
+
/^git@github\.com:[\w\-\.]+\/[\w\-\.]+(?:\.git)?$/,
|
|
263
|
+
/^https:\/\/gitlab\.com\/[\w\-\.]+\/[\w\-\.]+(?:\.git)?$/,
|
|
264
|
+
/^git@gitlab\.com:[\w\-\.]+\/[\w\-\.]+(?:\.git)?$/,
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
return patterns.some((pattern) => pattern.test(url));
|
|
268
|
+
}
|
|
269
|
+
}
|