@mexty/cli 1.0.2 → 1.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mexty/cli",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
4
  "description": "MEXT CLI for managing blocks and repositories",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -1,97 +1,165 @@
1
- import chalk from 'chalk';
2
- import path from 'path';
3
- import { apiClient, CreateBlockRequest } from '../utils/api';
4
- import { GitManager } from '../utils/git';
5
- import { createInterface } from 'readline';
6
- import { requireAuthentication, getAuthenticatedUser } from '../utils/auth';
1
+ import chalk from "chalk";
2
+ import path from "path";
3
+ import { apiClient, CreateBlockRequest } from "../utils/api";
4
+ import { GitManager } from "../utils/git";
5
+ import { createInterface } from "readline";
6
+ import { requireAuthentication, getAuthenticatedUser } from "../utils/auth";
7
7
 
8
8
  interface CreateOptions {
9
9
  description?: string;
10
10
  type?: string;
11
+ name?: string;
12
+ category?: string;
11
13
  }
12
14
 
13
15
  // Simple prompt function to replace inquirer
14
- async function prompt(question: string, defaultValue?: string): Promise<string> {
16
+ async function prompt(
17
+ question: string,
18
+ defaultValue?: string
19
+ ): Promise<string> {
15
20
  return new Promise((resolve) => {
16
21
  const rl = createInterface({
17
22
  input: process.stdin,
18
- output: process.stdout
23
+ output: process.stdout,
19
24
  });
20
25
 
21
- const promptText = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
22
-
26
+ const promptText = defaultValue
27
+ ? `${question} (${defaultValue}): `
28
+ : `${question}: `;
29
+
23
30
  rl.question(promptText, (answer) => {
24
31
  rl.close();
25
- resolve(answer.trim() || defaultValue || '');
32
+ resolve(answer.trim() || defaultValue || "");
26
33
  });
27
34
  });
28
35
  }
29
36
 
30
- export async function createCommand(name: string, options: CreateOptions): Promise<void> {
37
+ export async function createCommand(
38
+ subcommand?: string,
39
+ options: CreateOptions = {}
40
+ ): Promise<void> {
31
41
  try {
32
42
  // Check authentication first
33
43
  requireAuthentication();
34
-
44
+
35
45
  const user = getAuthenticatedUser();
36
- console.log(chalk.blue(`🚀 Creating new block: ${name}`));
37
- console.log(chalk.gray(` User: ${user?.fullName || user?.email || 'Unknown'}`));
38
46
 
39
- // Get description if not provided
40
- let description = options.description;
41
- if (!description) {
42
- description = await prompt('Enter a description for the block', `Custom block: ${name}`);
47
+ // Handle both old and new syntax
48
+ let blockName: string;
49
+ let blockDescription: string;
50
+ let blockType: string;
51
+
52
+ if (subcommand === "block") {
53
+ // New syntax: mexty create block --name "..." --description "..." --category "..."
54
+ if (!options.name) {
55
+ console.error(
56
+ chalk.red('❌ --name is required when using "mexty create block"')
57
+ );
58
+ console.log(
59
+ chalk.yellow(
60
+ ' Usage: mexty create block --name "Block Name" --description "Description" --category "category"'
61
+ )
62
+ );
63
+ process.exit(1);
64
+ }
65
+
66
+ blockName = options.name;
67
+ blockDescription = options.description || `Custom block: ${blockName}`;
68
+ blockType = options.category || options.type || "custom";
69
+ } else {
70
+ // Old syntax: mexty create "Block Name" --description "..." --type "..."
71
+ if (!subcommand) {
72
+ console.error(chalk.red("❌ Block name is required"));
73
+ console.log(
74
+ chalk.yellow(' Usage: mexty create "Block Name" [options]')
75
+ );
76
+ console.log(
77
+ chalk.yellow(
78
+ ' Or: mexty create block --name "Block Name" [options]'
79
+ )
80
+ );
81
+ process.exit(1);
82
+ }
83
+
84
+ blockName = subcommand;
85
+ blockDescription = options.description || `Custom block: ${blockName}`;
86
+ blockType = options.type || "custom";
43
87
  }
44
88
 
89
+ console.log(chalk.blue(`🚀 Creating new block: ${blockName}`));
90
+ console.log(
91
+ chalk.gray(` User: ${user?.fullName || user?.email || "Unknown"}`)
92
+ );
93
+ console.log(chalk.gray(` Category: ${blockType}`));
94
+
45
95
  // Prepare block data
46
96
  const blockData: CreateBlockRequest = {
47
- blockType: options.type || 'custom',
48
- title: name,
49
- description: description,
50
- allowedBrickTypes: ['text', 'image', 'video', 'code', 'quiz'], // Default allowed types
51
- scope: ['user-store'], // Default scope for CLI-created blocks
52
- content: []
97
+ blockType: blockType,
98
+ title: blockName,
99
+ description: blockDescription,
100
+ allowedBrickTypes: ["text", "image", "video", "code", "quiz"], // Default allowed types
101
+ scope: ["user-store"], // Default scope for CLI-created blocks
102
+ content: [],
53
103
  };
54
104
 
55
- console.log(chalk.yellow('📡 Creating block on server...'));
56
-
105
+ console.log(chalk.yellow("📡 Creating block on server..."));
106
+
57
107
  // Create the block
58
108
  const block = await apiClient.createBlock(blockData);
59
-
109
+
60
110
  console.log(chalk.green(`✅ Block created successfully!`));
61
111
  console.log(chalk.gray(` Block ID: ${block._id}`));
62
112
  console.log(chalk.gray(` Block Type: ${block.blockType}`));
63
-
113
+
64
114
  if (block.gitUrl) {
65
115
  console.log(chalk.gray(` GitHub URL: ${block.gitUrl}`));
66
-
116
+
67
117
  // Clone the repository
68
118
  const repoName = GitManager.extractRepoName(block.gitUrl);
69
119
  const targetDir = path.join(process.cwd(), repoName);
70
-
120
+
71
121
  console.log(chalk.yellow(`📦 Cloning repository to ./${repoName}...`));
72
-
122
+
73
123
  try {
74
124
  const gitManager = new GitManager();
75
125
  await gitManager.cloneRepository(block.gitUrl, targetDir);
76
-
77
- console.log(chalk.green(`🎉 Block created and repository cloned successfully!`));
126
+
127
+ console.log(
128
+ chalk.green(`🎉 Block created and repository cloned successfully!`)
129
+ );
78
130
  console.log(chalk.blue(`\nNext steps:`));
79
131
  console.log(chalk.gray(` 1. cd ${repoName}`));
80
132
  console.log(chalk.gray(` 2. Make your changes`));
81
- console.log(chalk.gray(` 3. git add . && git commit -m "Your changes"`));
82
- console.log(chalk.gray(` 4. mexty publish`));
83
-
133
+ console.log(chalk.gray(` 3. mexty save`));
134
+
135
+ // Change to the cloned directory
136
+ try {
137
+ process.chdir(targetDir);
138
+ console.log(chalk.green(`📁 Changed to directory: ${repoName}`));
139
+ } catch (chdirError: any) {
140
+ console.warn(
141
+ chalk.yellow(
142
+ `⚠️ Could not change to directory: ${chdirError.message}`
143
+ )
144
+ );
145
+ console.log(chalk.gray(` Please manually run: cd ${repoName}`));
146
+ }
84
147
  } catch (cloneError: any) {
85
- console.error(chalk.red(`❌ Failed to clone repository: ${cloneError.message}`));
148
+ console.error(
149
+ chalk.red(`❌ Failed to clone repository: ${cloneError.message}`)
150
+ );
86
151
  console.log(chalk.yellow(`You can manually clone it later:`));
87
152
  console.log(chalk.gray(` git clone ${block.gitUrl}`));
88
153
  }
89
154
  } else {
90
- console.log(chalk.yellow('⚠️ No GitHub repository was created (GitHub not configured)'));
155
+ console.log(
156
+ chalk.yellow(
157
+ "⚠️ No GitHub repository was created (GitHub not configured)"
158
+ )
159
+ );
91
160
  }
92
-
93
161
  } catch (error: any) {
94
162
  console.error(chalk.red(`❌ Failed to create block: ${error.message}`));
95
163
  process.exit(1);
96
164
  }
97
- }
165
+ }
@@ -0,0 +1,213 @@
1
+ import chalk from "chalk";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { apiClient } from "../utils/api";
5
+ import { GitManager } from "../utils/git";
6
+ import { createInterface } from "readline";
7
+ import { requireAuthentication, getAuthenticatedUser } from "../utils/auth";
8
+
9
+ // Simple prompt function
10
+ async function prompt(
11
+ question: string,
12
+ defaultValue?: string
13
+ ): Promise<string> {
14
+ return new Promise((resolve) => {
15
+ const rl = createInterface({
16
+ input: process.stdin,
17
+ output: process.stdout,
18
+ });
19
+
20
+ const promptText = defaultValue
21
+ ? `${question} (${defaultValue}): `
22
+ : `${question}: `;
23
+
24
+ rl.question(promptText, (answer) => {
25
+ rl.close();
26
+ resolve(answer.trim() || defaultValue || "");
27
+ });
28
+ });
29
+ }
30
+
31
+ // Extract block ID from package.json or git URL
32
+ async function findBlockId(): Promise<string | null> {
33
+ const packageJsonPath = path.join(process.cwd(), "package.json");
34
+
35
+ if (fs.existsSync(packageJsonPath)) {
36
+ try {
37
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
38
+
39
+ // Look for block ID in package.json name or description
40
+ if (packageJson.name && packageJson.name.startsWith("block-")) {
41
+ return packageJson.name.replace("block-", "");
42
+ }
43
+
44
+ // Look in description for block ID pattern
45
+ if (packageJson.description) {
46
+ const match = packageJson.description.match(
47
+ /block[:\s]+([a-f0-9]{24})/i
48
+ );
49
+ if (match) {
50
+ return match[1];
51
+ }
52
+ }
53
+ } catch (error) {
54
+ console.warn(chalk.yellow("⚠️ Could not parse package.json"));
55
+ }
56
+ }
57
+
58
+ // Try to extract from git remote URL
59
+ try {
60
+ const gitManager = new GitManager();
61
+ const remoteUrl = await gitManager.getRemoteUrl();
62
+
63
+ if (remoteUrl) {
64
+ const match = remoteUrl.match(/block-([a-f0-9]{24})/);
65
+ if (match) {
66
+ return match[1];
67
+ }
68
+ }
69
+ } catch (error) {
70
+ // Ignore git errors
71
+ }
72
+
73
+ return null;
74
+ }
75
+
76
+ export async function saveCommand(): Promise<void> {
77
+ try {
78
+ // Check authentication first
79
+ requireAuthentication();
80
+
81
+ const user = getAuthenticatedUser();
82
+ console.log(chalk.blue("💾 Saving and publishing block..."));
83
+ console.log(
84
+ chalk.gray(` User: ${user?.fullName || user?.email || "Unknown"}`)
85
+ );
86
+
87
+ // Check if we're in a git repository
88
+ const gitManager = new GitManager();
89
+ const isGitRepo = await gitManager.isGitRepository();
90
+
91
+ if (!isGitRepo) {
92
+ console.error(
93
+ chalk.red(
94
+ "❌ Not a git repository. Please run this command from a block repository."
95
+ )
96
+ );
97
+ process.exit(1);
98
+ }
99
+
100
+ // Get repository information
101
+ const repoInfo = await gitManager.getRepositoryInfo();
102
+ console.log(chalk.gray(` Current branch: ${repoInfo.branch}`));
103
+ console.log(chalk.gray(` Remote URL: ${repoInfo.remoteUrl}`));
104
+
105
+ // Find block ID
106
+ const blockId = await findBlockId();
107
+ if (!blockId) {
108
+ console.error(
109
+ chalk.red("❌ Could not determine block ID from repository.")
110
+ );
111
+ console.error(
112
+ chalk.yellow(
113
+ " Make sure you are in a block repository created with mexty"
114
+ )
115
+ );
116
+ process.exit(1);
117
+ }
118
+
119
+ console.log(chalk.gray(` Block ID: ${blockId}`));
120
+
121
+ // Check if there are changes to commit
122
+ if (repoInfo.hasChanges) {
123
+ console.log(
124
+ chalk.yellow("📝 Found uncommitted changes, preparing to commit...")
125
+ );
126
+
127
+ // Get commit message from user
128
+ const commitMessage = await prompt(
129
+ "Enter commit message",
130
+ "Update block content"
131
+ );
132
+
133
+ if (!commitMessage.trim()) {
134
+ console.error(chalk.red("❌ Commit message cannot be empty"));
135
+ process.exit(1);
136
+ }
137
+
138
+ // Stage all changes
139
+ console.log(chalk.yellow("📋 Staging changes (git add .)..."));
140
+ try {
141
+ await gitManager.git.add(".");
142
+ console.log(chalk.green("✅ Changes staged successfully"));
143
+ } catch (addError: any) {
144
+ console.error(
145
+ chalk.red(`❌ Failed to stage changes: ${addError.message}`)
146
+ );
147
+ process.exit(1);
148
+ }
149
+
150
+ // Commit changes
151
+ console.log(chalk.yellow(`💬 Committing changes: "${commitMessage}"...`));
152
+ try {
153
+ await gitManager.git.commit(commitMessage);
154
+ console.log(chalk.green("✅ Changes committed successfully"));
155
+ } catch (commitError: any) {
156
+ console.error(
157
+ chalk.red(`❌ Failed to commit changes: ${commitError.message}`)
158
+ );
159
+ process.exit(1);
160
+ }
161
+ } else {
162
+ console.log(chalk.green("✅ No uncommitted changes found"));
163
+ }
164
+
165
+ // Push changes to remote
166
+ console.log(
167
+ chalk.yellow(`📤 Pushing changes to remote (${repoInfo.branch})...`)
168
+ );
169
+ try {
170
+ await gitManager.pushToRemote();
171
+ console.log(chalk.green("✅ Changes pushed successfully"));
172
+ } catch (pushError: any) {
173
+ console.error(
174
+ chalk.red(`❌ Failed to push changes: ${pushError.message}`)
175
+ );
176
+ console.log(
177
+ chalk.yellow(
178
+ " Please check your network connection and GitHub permissions"
179
+ )
180
+ );
181
+ process.exit(1);
182
+ }
183
+
184
+ // Trigger save and bundle
185
+ console.log(chalk.yellow("🏗️ Triggering build and bundle process..."));
186
+
187
+ try {
188
+ const result = await apiClient.saveAndBundle({ blockId });
189
+
190
+ console.log(chalk.green("🎉 Block saved and published successfully!"));
191
+ console.log(chalk.gray(` Bundle Path: ${result.bundlePath}`));
192
+ console.log(chalk.gray(` Federation URL: ${result.federationUrl}`));
193
+
194
+ if (result.message) {
195
+ console.log(chalk.blue(` ${result.message}`));
196
+ }
197
+
198
+ console.log(
199
+ chalk.blue("\n📋 Your block is now building in the background.")
200
+ );
201
+ console.log(
202
+ chalk.gray(" You can check the build status in the web interface.")
203
+ );
204
+ } catch (buildError: any) {
205
+ console.error(chalk.red(`❌ Build failed: ${buildError.message}`));
206
+ console.log(chalk.yellow(" Check the server logs for more details."));
207
+ process.exit(1);
208
+ }
209
+ } catch (error: any) {
210
+ console.error(chalk.red(`❌ Failed to save block: ${error.message}`));
211
+ process.exit(1);
212
+ }
213
+ }
@@ -1,7 +1,7 @@
1
- import chalk from 'chalk';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { apiClient } from '../utils/api';
1
+ import chalk from "chalk";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { apiClient } from "../utils/api";
5
5
 
6
6
  interface BlockRegistryEntry {
7
7
  blockId: string;
@@ -26,14 +26,16 @@ interface AuthorNamespaceRegistry {
26
26
 
27
27
  export async function syncCommand(): Promise<void> {
28
28
  try {
29
- console.log(chalk.blue('🔄 Syncing block registry...'));
29
+ console.log(chalk.blue("🔄 Syncing block registry..."));
30
30
 
31
31
  // Fetch registry from server
32
- console.log(chalk.yellow('📡 Fetching registry from server...'));
33
- const response = await fetch('https://api.v2.mext.app/api/blocks/registry');
34
-
32
+ console.log(chalk.yellow("📡 Fetching registry from server..."));
33
+ const response = await fetch("https://api.mexty.ai/api/blocks/registry");
34
+
35
35
  if (!response.ok) {
36
- throw new Error(`Server responded with ${response.status}: ${response.statusText}`);
36
+ throw new Error(
37
+ `Server responded with ${response.status}: ${response.statusText}`
38
+ );
37
39
  }
38
40
 
39
41
  const data = await response.json();
@@ -48,25 +50,43 @@ export async function syncCommand(): Promise<void> {
48
50
  console.log(chalk.gray(` Last updated: ${meta.lastUpdated}`));
49
51
 
50
52
  if (Object.keys(registry).length === 0) {
51
- console.log(chalk.yellow('⚠️ No components found in registry.'));
52
- console.log(chalk.gray(' Make sure you have built some blocks first using "mexty publish"'));
53
+ console.log(chalk.yellow("⚠️ No components found in registry."));
54
+ console.log(
55
+ chalk.gray(
56
+ ' Make sure you have built some blocks first using "mexty publish"'
57
+ )
58
+ );
53
59
  return;
54
60
  }
55
61
 
56
62
  // Display available components
57
- console.log(chalk.blue('\n📋 Available components (global namespace):'));
63
+ console.log(chalk.blue("\n📋 Available components (global namespace):"));
58
64
  for (const [componentName, entry] of Object.entries(registry)) {
59
- console.log(chalk.green(` ${componentName}${entry.author ? chalk.gray(` (by ${entry.author})`) : ''}`));
65
+ console.log(
66
+ chalk.green(
67
+ ` ${componentName}${
68
+ entry.author ? chalk.gray(` (by ${entry.author})`) : ""
69
+ }`
70
+ )
71
+ );
60
72
  console.log(chalk.gray(` Block ID: ${entry.blockId}`));
61
73
  console.log(chalk.gray(` Title: ${entry.title}`));
62
- console.log(chalk.gray(` Description: ${entry.description.substring(0, 60)}${entry.description.length > 60 ? '...' : ''}`));
63
- console.log(chalk.gray(` Tags: ${entry.tags?.join(', ') || 'none'}`));
64
- console.log('');
74
+ console.log(
75
+ chalk.gray(
76
+ ` Description: ${entry.description.substring(0, 60)}${
77
+ entry.description.length > 60 ? "..." : ""
78
+ }`
79
+ )
80
+ );
81
+ console.log(
82
+ chalk.gray(` Tags: ${entry.tags?.join(", ") || "none"}`)
83
+ );
84
+ console.log("");
65
85
  }
66
86
 
67
87
  // Display author namespaces
68
88
  if (Object.keys(authorRegistry).length > 0) {
69
- console.log(chalk.blue('\n👤 Author namespaces:'));
89
+ console.log(chalk.blue("\n👤 Author namespaces:"));
70
90
  for (const [author, components] of Object.entries(authorRegistry)) {
71
91
  console.log(chalk.cyan(` @${author}:`));
72
92
  for (const [componentName, entry] of Object.entries(components)) {
@@ -74,25 +94,30 @@ export async function syncCommand(): Promise<void> {
74
94
  console.log(chalk.gray(` Block ID: ${entry.blockId}`));
75
95
  console.log(chalk.gray(` Title: ${entry.title}`));
76
96
  }
77
- console.log('');
97
+ console.log("");
78
98
  }
79
99
  }
80
100
 
81
101
  // Generate named exports file and author entry files
82
102
  const mextBlockPath = findMextBlockPath();
83
103
  if (mextBlockPath) {
84
- console.log(chalk.blue('🔧 Updating mext-block exports...'));
104
+ console.log(chalk.blue("🔧 Updating mext-block exports..."));
85
105
  await generateNamedExports(mextBlockPath, registry, authorRegistry);
86
106
  await generateAuthorEntryFiles(mextBlockPath, authorRegistry);
87
107
  console.log(chalk.green(`✅ Exports updated in ${mextBlockPath}`));
88
- console.log(chalk.gray(` Generated ${Object.keys(authorRegistry).length} author entry files`));
108
+ console.log(
109
+ chalk.gray(
110
+ ` Generated ${
111
+ Object.keys(authorRegistry).length
112
+ } author entry files`
113
+ )
114
+ );
89
115
  } else {
90
- console.log(chalk.yellow('⚠️ mext-block package not found locally.'));
91
- console.log(chalk.gray(' Named exports file not generated.'));
116
+ console.log(chalk.yellow("⚠️ mext-block package not found locally."));
117
+ console.log(chalk.gray(" Named exports file not generated."));
92
118
  }
93
119
 
94
- console.log(chalk.green('\n🎉 Registry sync completed!'));
95
-
120
+ console.log(chalk.green("\n🎉 Registry sync completed!"));
96
121
  } catch (error: any) {
97
122
  console.error(chalk.red(`❌ Failed to sync registry: ${error.message}`));
98
123
  process.exit(1);
@@ -105,16 +130,21 @@ export async function syncCommand(): Promise<void> {
105
130
  function findMextBlockPath(): string | null {
106
131
  // Look for mext-block in common locations
107
132
  const possiblePaths = [
108
- path.join(process.cwd(), '..', 'mext-block'),
109
- path.join(process.cwd(), 'mext-block'),
110
- path.join(process.cwd(), '..', '..', 'mext-block'),
133
+ path.join(process.cwd(), "..", "mext-block"),
134
+ path.join(process.cwd(), "mext-block"),
135
+ path.join(process.cwd(), "..", "..", "mext-block"),
111
136
  ];
112
137
 
113
138
  for (const possiblePath of possiblePaths) {
114
- if (fs.existsSync(path.join(possiblePath, 'package.json'))) {
139
+ if (fs.existsSync(path.join(possiblePath, "package.json"))) {
115
140
  try {
116
- const packageJson = JSON.parse(fs.readFileSync(path.join(possiblePath, 'package.json'), 'utf8'));
117
- if (packageJson.name === '@mexty/block' || packageJson.name === '@mexty/block') {
141
+ const packageJson = JSON.parse(
142
+ fs.readFileSync(path.join(possiblePath, "package.json"), "utf8")
143
+ );
144
+ if (
145
+ packageJson.name === "@mexty/block" ||
146
+ packageJson.name === "@mexty/block"
147
+ ) {
118
148
  return possiblePath;
119
149
  }
120
150
  } catch (error) {
@@ -129,9 +159,13 @@ function findMextBlockPath(): string | null {
129
159
  /**
130
160
  * Generate named exports file for the mext-block package
131
161
  */
132
- async function generateNamedExports(mextBlockPath: string, registry: BlockRegistry, authorRegistry: AuthorNamespaceRegistry): Promise<void> {
133
- const namedExportsPath = path.join(mextBlockPath, 'src', 'namedExports.ts');
134
-
162
+ async function generateNamedExports(
163
+ mextBlockPath: string,
164
+ registry: BlockRegistry,
165
+ authorRegistry: AuthorNamespaceRegistry
166
+ ): Promise<void> {
167
+ const namedExportsPath = path.join(mextBlockPath, "src", "namedExports.ts");
168
+
135
169
  // Generate the file content
136
170
  const content = `// Auto-generated file - DO NOT EDIT MANUALLY
137
171
  // Generated on: ${new Date().toISOString()}
@@ -142,22 +176,27 @@ import { createNamedBlock } from './components/NamedBlock';
142
176
  import { createAuthorBlock } from './components/AuthorBlock';
143
177
 
144
178
  // ===== GLOBAL NAMESPACE COMPONENTS =====
145
- ${Object.entries(registry).map(([componentName, entry]) =>
146
- `// ${entry.title}${entry.author ? ` (by ${entry.author})` : ''}
179
+ ${Object.entries(registry)
180
+ .map(
181
+ ([componentName, entry]) =>
182
+ `// ${entry.title}${entry.author ? ` (by ${entry.author})` : ""}
147
183
  // Block ID: ${entry.blockId}
148
184
  // Description: ${entry.description}
149
- // Tags: ${entry.tags?.join(', ') || 'none'}
185
+ // Tags: ${entry.tags?.join(", ") || "none"}
150
186
  export const ${componentName} = createNamedBlock('${componentName}');`
151
- ).join('\n\n')}
187
+ )
188
+ .join("\n\n")}
152
189
 
153
190
  // Export all global components as an object for convenience
154
191
  export const NamedComponents = {
155
- ${Object.keys(registry).map(componentName => ` ${componentName},`).join('\n')}
192
+ ${Object.keys(registry)
193
+ .map((componentName) => ` ${componentName},`)
194
+ .join("\n")}
156
195
  };
157
196
 
158
197
  // Note: Author-specific components are now available via direct imports:
159
198
  // import { ComponentName } from '@mexty/block/authorname'
160
- // Available authors: ${Object.keys(authorRegistry).join(', ')}
199
+ // Available authors: ${Object.keys(authorRegistry).join(", ")}
161
200
 
162
201
  // Registry metadata
163
202
  export const registryMetadata = {
@@ -165,31 +204,40 @@ export const registryMetadata = {
165
204
  totalAuthors: ${Object.keys(authorRegistry).length},
166
205
  lastGenerated: '${new Date().toISOString()}',
167
206
  components: {
168
- ${Object.entries(registry).map(([componentName, entry]) =>
169
- ` ${componentName}: {
207
+ ${Object.entries(registry)
208
+ .map(
209
+ ([componentName, entry]) =>
210
+ ` ${componentName}: {
170
211
  blockId: '${entry.blockId}',
171
212
  title: '${entry.title}',
172
213
  description: '${entry.description.replace(/'/g, "\\'")}',
173
- author: '${entry.author || ''}',
174
- tags: [${entry.tags?.map(tag => `'${tag}'`).join(', ') || ''}],
214
+ author: '${entry.author || ""}',
215
+ tags: [${entry.tags?.map((tag) => `'${tag}'`).join(", ") || ""}],
175
216
  lastUpdated: '${entry.lastUpdated}'
176
217
  },`
177
- ).join('\n')}
218
+ )
219
+ .join("\n")}
178
220
  },
179
221
  authors: {
180
- ${Object.entries(authorRegistry).map(([author, components]) =>
181
- ` ${author}: {
182
- ${Object.entries(components).map(([componentName, entry]) =>
183
- ` ${componentName}: {
222
+ ${Object.entries(authorRegistry)
223
+ .map(
224
+ ([author, components]) =>
225
+ ` ${author}: {
226
+ ${Object.entries(components)
227
+ .map(
228
+ ([componentName, entry]) =>
229
+ ` ${componentName}: {
184
230
  blockId: '${entry.blockId}',
185
231
  title: '${entry.title}',
186
232
  description: '${entry.description.replace(/'/g, "\\'")}',
187
- tags: [${entry.tags?.map(tag => `'${tag}'`).join(', ') || ''}],
233
+ tags: [${entry.tags?.map((tag) => `'${tag}'`).join(", ") || ""}],
188
234
  lastUpdated: '${entry.lastUpdated}'
189
235
  },`
190
- ).join('\n')}
236
+ )
237
+ .join("\n")}
191
238
  },`
192
- ).join('\n')}
239
+ )
240
+ .join("\n")}
193
241
  }
194
242
  };
195
243
  `;
@@ -201,17 +249,18 @@ ${Object.entries(components).map(([componentName, entry]) =>
201
249
  }
202
250
 
203
251
  // Write the file
204
- fs.writeFileSync(namedExportsPath, content, 'utf8');
252
+ fs.writeFileSync(namedExportsPath, content, "utf8");
205
253
 
206
254
  // Update the main index.ts to include these exports
207
- const indexPath = path.join(mextBlockPath, 'src', 'index.ts');
255
+ const indexPath = path.join(mextBlockPath, "src", "index.ts");
208
256
  if (fs.existsSync(indexPath)) {
209
- let indexContent = fs.readFileSync(indexPath, 'utf8');
210
-
257
+ let indexContent = fs.readFileSync(indexPath, "utf8");
258
+
211
259
  // Add export for named exports if not already present
212
- if (!indexContent.includes('export * from \'./namedExports\'')) {
213
- indexContent += '\n// Auto-generated named exports\nexport * from \'./namedExports\';\n';
214
- fs.writeFileSync(indexPath, indexContent, 'utf8');
260
+ if (!indexContent.includes("export * from './namedExports'")) {
261
+ indexContent +=
262
+ "\n// Auto-generated named exports\nexport * from './namedExports';\n";
263
+ fs.writeFileSync(indexPath, indexContent, "utf8");
215
264
  }
216
265
  }
217
266
  }
@@ -219,24 +268,27 @@ ${Object.entries(components).map(([componentName, entry]) =>
219
268
  /**
220
269
  * Generate author-specific entry files for direct imports
221
270
  */
222
- async function generateAuthorEntryFiles(mextBlockPath: string, authorRegistry: AuthorNamespaceRegistry): Promise<void> {
223
- const authorsDir = path.join(mextBlockPath, 'src', 'authors');
224
-
271
+ async function generateAuthorEntryFiles(
272
+ mextBlockPath: string,
273
+ authorRegistry: AuthorNamespaceRegistry
274
+ ): Promise<void> {
275
+ const authorsDir = path.join(mextBlockPath, "src", "authors");
276
+
225
277
  // Clean up existing author files
226
278
  if (fs.existsSync(authorsDir)) {
227
279
  fs.rmSync(authorsDir, { recursive: true, force: true });
228
280
  }
229
-
281
+
230
282
  // Create authors directory
231
283
  fs.mkdirSync(authorsDir, { recursive: true });
232
-
284
+
233
285
  // Generate an entry file for each author
234
286
  for (const [author, components] of Object.entries(authorRegistry)) {
235
287
  const authorDir = path.join(authorsDir, author);
236
288
  fs.mkdirSync(authorDir, { recursive: true });
237
-
238
- const authorEntryPath = path.join(authorDir, 'index.ts');
239
-
289
+
290
+ const authorEntryPath = path.join(authorDir, "index.ts");
291
+
240
292
  // Generate the author's entry file content
241
293
  const content = `// Auto-generated author entry file for ${author}
242
294
  // Generated on: ${new Date().toISOString()}
@@ -244,17 +296,22 @@ async function generateAuthorEntryFiles(mextBlockPath: string, authorRegistry: A
244
296
 
245
297
  import { createAuthorBlock } from '../../components/AuthorBlock';
246
298
 
247
- ${Object.entries(components).map(([componentName, entry]) =>
248
- `// ${entry.title}
299
+ ${Object.entries(components)
300
+ .map(
301
+ ([componentName, entry]) =>
302
+ `// ${entry.title}
249
303
  // Block ID: ${entry.blockId}
250
304
  // Description: ${entry.description}
251
- // Tags: ${entry.tags?.join(', ') || 'none'}
305
+ // Tags: ${entry.tags?.join(", ") || "none"}
252
306
  export const ${componentName} = createAuthorBlock('${author}', '${componentName}');`
253
- ).join('\n\n')}
307
+ )
308
+ .join("\n\n")}
254
309
 
255
310
  // Export all components as default for convenience
256
311
  export default {
257
- ${Object.keys(components).map(componentName => ` ${componentName},`).join('\n')}
312
+ ${Object.keys(components)
313
+ .map((componentName) => ` ${componentName},`)
314
+ .join("\n")}
258
315
  };
259
316
 
260
317
  // Author metadata
@@ -263,22 +320,31 @@ export const authorMetadata = {
263
320
  totalComponents: ${Object.keys(components).length},
264
321
  lastGenerated: '${new Date().toISOString()}',
265
322
  components: {
266
- ${Object.entries(components).map(([componentName, entry]) =>
267
- ` ${componentName}: {
323
+ ${Object.entries(components)
324
+ .map(
325
+ ([componentName, entry]) =>
326
+ ` ${componentName}: {
268
327
  blockId: '${entry.blockId}',
269
328
  title: '${entry.title}',
270
329
  description: '${entry.description.replace(/'/g, "\\'")}',
271
- tags: [${entry.tags?.map(tag => `'${tag}'`).join(', ') || ''}],
330
+ tags: [${entry.tags?.map((tag) => `'${tag}'`).join(", ") || ""}],
272
331
  lastUpdated: '${entry.lastUpdated}'
273
332
  },`
274
- ).join('\n')}
333
+ )
334
+ .join("\n")}
275
335
  }
276
336
  };
277
337
  `;
278
338
 
279
339
  // Write the author entry file
280
- fs.writeFileSync(authorEntryPath, content, 'utf8');
281
-
282
- console.log(chalk.gray(` Created entry file for ${author} (${Object.keys(components).length} components)`));
340
+ fs.writeFileSync(authorEntryPath, content, "utf8");
341
+
342
+ console.log(
343
+ chalk.gray(
344
+ ` Created entry file for ${author} (${
345
+ Object.keys(components).length
346
+ } components)`
347
+ )
348
+ );
283
349
  }
284
- }
350
+ }
package/src/index.ts CHANGED
@@ -1,77 +1,91 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { Command } from 'commander';
4
- import chalk from 'chalk';
5
- import { loginCommand } from './commands/login';
6
- import { createCommand } from './commands/create';
7
- import { forkCommand } from './commands/fork';
8
- import { deleteCommand } from './commands/delete';
9
- import { publishCommand } from './commands/publish';
10
- import { syncCommand } from './commands/sync';
11
- import { apiClient } from './utils/api';
3
+ import { Command } from "commander";
4
+ import chalk from "chalk";
5
+ import { loginCommand } from "./commands/login";
6
+ import { createCommand } from "./commands/create";
7
+ import { forkCommand } from "./commands/fork";
8
+ import { deleteCommand } from "./commands/delete";
9
+ import { publishCommand } from "./commands/publish";
10
+ import { syncCommand } from "./commands/sync";
11
+ import { saveCommand } from "./commands/save";
12
+ import { apiClient } from "./utils/api";
12
13
 
13
14
  const program = new Command();
14
15
 
15
16
  // CLI Configuration
16
17
  program
17
- .name('mexty')
18
- .description('MEXT CLI for managing React microfrontend blocks and components')
19
- .version('1.0.0');
18
+ .name("mexty")
19
+ .description(
20
+ "MEXT CLI for managing React microfrontend blocks and components"
21
+ )
22
+ .version("1.0.0");
20
23
 
21
24
  // Add commands
22
25
  program
23
- .command('login')
24
- .description('Authenticate with MEXT')
26
+ .command("login")
27
+ .description("Authenticate with MEXT")
25
28
  .action(loginCommand);
26
29
 
27
30
  program
28
- .command('logout')
29
- .description('Logout from MEXT')
31
+ .command("logout")
32
+ .description("Logout from MEXT")
30
33
  .action(async () => {
31
34
  try {
32
35
  if (!apiClient.isAuthenticated()) {
33
- console.log(chalk.yellow('⚠️ You are not logged in'));
36
+ console.log(chalk.yellow("⚠️ You are not logged in"));
34
37
  return;
35
38
  }
36
-
39
+
37
40
  await apiClient.logout();
38
- console.log(chalk.green('✅ Logged out successfully'));
41
+ console.log(chalk.green("✅ Logged out successfully"));
39
42
  } catch (error: any) {
40
43
  console.error(chalk.red(`❌ Logout failed: ${error.message}`));
41
44
  }
42
45
  });
43
46
 
47
+ // Support both old and new create syntax
44
48
  program
45
- .command('create <name>')
46
- .description('Create a new React microfrontend block')
47
- .option('-d, --description <description>', 'Block description')
48
- .option('-t, --type <type>', 'Block type', 'custom')
49
+ .command("create [subcommand]")
50
+ .description("Create a new React microfrontend block")
51
+ .option("-d, --description <description>", "Block description")
52
+ .option("-t, --type <type>", "Block type", "custom")
53
+ .option("-n, --name <name>", 'Block name (for "create block" syntax)')
54
+ .option(
55
+ "-c, --category <category>",
56
+ 'Block category (for "create block" syntax)'
57
+ )
49
58
  .action(createCommand);
50
59
 
51
60
  program
52
- .command('fork <blockId>')
53
- .description('Fork an existing block and clone its repository')
61
+ .command("fork <blockId>")
62
+ .description("Fork an existing block and clone its repository")
54
63
  .action(forkCommand);
55
64
 
56
65
  program
57
- .command('delete <blockId>')
58
- .description('Delete a block (requires ownership)')
66
+ .command("delete <blockId>")
67
+ .description("Delete a block (requires ownership)")
59
68
  .action(deleteCommand);
60
69
 
61
70
  program
62
- .command('publish')
63
- .description('Publish current block with automatic bundling')
71
+ .command("publish")
72
+ .description("Publish current block with automatic bundling")
64
73
  .action(publishCommand);
65
74
 
66
75
  program
67
- .command('sync')
68
- .description('Sync block registry and update typed exports')
76
+ .command("sync")
77
+ .description("Sync block registry and update typed exports")
69
78
  .action(syncCommand);
70
79
 
80
+ program
81
+ .command("save")
82
+ .description("Save current block (git add, commit, push, and trigger build)")
83
+ .action(saveCommand);
84
+
71
85
  // Error handling
72
- program.on('command:*', () => {
73
- console.error(chalk.red(`Invalid command: ${program.args.join(' ')}`));
74
- console.log(chalk.yellow('See --help for a list of available commands.'));
86
+ program.on("command:*", () => {
87
+ console.error(chalk.red(`Invalid command: ${program.args.join(" ")}`));
88
+ console.log(chalk.yellow("See --help for a list of available commands."));
75
89
  process.exit(1);
76
90
  });
77
91
 
@@ -81,4 +95,4 @@ program.parse(process.argv);
81
95
  // Show help if no command provided
82
96
  if (!process.argv.slice(2).length) {
83
97
  program.outputHelp();
84
- }
98
+ }
package/src/utils/api.ts CHANGED
@@ -1,8 +1,8 @@
1
- import axios, { AxiosInstance, AxiosResponse } from 'axios';
2
- import chalk from 'chalk';
3
- import fs from 'fs';
4
- import path from 'path';
5
- import os from 'os';
1
+ import axios, { AxiosInstance, AxiosResponse } from "axios";
2
+ import chalk from "chalk";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import os from "os";
6
6
 
7
7
  export interface Block {
8
8
  _id: string;
@@ -14,13 +14,13 @@ export interface Block {
14
14
  courseName?: string;
15
15
  allowedBrickTypes: string[];
16
16
  allowedBlockTypes?: string[];
17
- scope: ('library' | 'user-store' | 'published-store')[];
17
+ scope: ("library" | "user-store" | "published-store")[];
18
18
  content: any[];
19
19
  // Fork tracking
20
20
  forkedId?: string;
21
21
  bundlePath?: string;
22
22
  federationUrl?: string;
23
- buildStatus?: 'pending' | 'building' | 'success' | 'failed';
23
+ buildStatus?: "pending" | "building" | "success" | "failed";
24
24
  buildError?: string;
25
25
  lastBuilt?: Date;
26
26
  createdAt: Date;
@@ -73,15 +73,15 @@ class ApiClient {
73
73
  private baseUrl: string;
74
74
  private tokenPath: string;
75
75
 
76
- constructor(baseUrl: string = 'https://api.v2.mext.app') {
76
+ constructor(baseUrl: string = "https://api.mexty.ai") {
77
77
  this.baseUrl = baseUrl;
78
- this.tokenPath = path.join(os.homedir(), '.mext', 'auth.json');
79
-
78
+ this.tokenPath = path.join(os.homedir(), ".mext", "auth.json");
79
+
80
80
  this.client = axios.create({
81
81
  baseURL: baseUrl,
82
82
  timeout: 30000,
83
83
  headers: {
84
- 'Content-Type': 'application/json',
84
+ "Content-Type": "application/json",
85
85
  },
86
86
  });
87
87
 
@@ -102,13 +102,27 @@ class ApiClient {
102
102
  (response) => response,
103
103
  (error) => {
104
104
  if (error.response?.status === 401) {
105
- console.error(chalk.red('Authentication required. Please login first: mexty login'));
105
+ console.error(
106
+ chalk.red(
107
+ "Authentication required. Please login first: mexty login"
108
+ )
109
+ );
106
110
  this.clearStoredToken();
107
111
  } else if (error.response) {
108
- console.error(chalk.red(`API Error ${error.response.status}: ${error.response.data?.error || error.message}`));
112
+ console.error(
113
+ chalk.red(
114
+ `API Error ${error.response.status}: ${
115
+ error.response.data?.error || error.message
116
+ }`
117
+ )
118
+ );
109
119
  } else if (error.request) {
110
- console.error(chalk.red('Network Error: Could not reach MEXT server'));
111
- console.error(chalk.yellow(`Make sure the server is running at ${this.baseUrl}`));
120
+ console.error(
121
+ chalk.red("Network Error: Could not reach MEXT server")
122
+ );
123
+ console.error(
124
+ chalk.yellow(`Make sure the server is running at ${this.baseUrl}`)
125
+ );
112
126
  } else {
113
127
  console.error(chalk.red(`Request Error: ${error.message}`));
114
128
  }
@@ -120,7 +134,7 @@ class ApiClient {
120
134
  private getStoredToken(): string | null {
121
135
  try {
122
136
  if (fs.existsSync(this.tokenPath)) {
123
- const authData = JSON.parse(fs.readFileSync(this.tokenPath, 'utf8'));
137
+ const authData = JSON.parse(fs.readFileSync(this.tokenPath, "utf8"));
124
138
  return authData.token || null;
125
139
  }
126
140
  } catch (error) {
@@ -135,16 +149,18 @@ class ApiClient {
135
149
  if (!fs.existsSync(authDir)) {
136
150
  fs.mkdirSync(authDir, { recursive: true });
137
151
  }
138
-
152
+
139
153
  const authData = {
140
154
  token,
141
155
  user,
142
- timestamp: new Date().toISOString()
156
+ timestamp: new Date().toISOString(),
143
157
  };
144
-
158
+
145
159
  fs.writeFileSync(this.tokenPath, JSON.stringify(authData, null, 2));
146
160
  } catch (error: any) {
147
- console.warn(chalk.yellow(`Warning: Could not store auth token: ${error.message}`));
161
+ console.warn(
162
+ chalk.yellow(`Warning: Could not store auth token: ${error.message}`)
163
+ );
148
164
  }
149
165
  }
150
166
 
@@ -165,7 +181,7 @@ class ApiClient {
165
181
  public getStoredUser(): any {
166
182
  try {
167
183
  if (fs.existsSync(this.tokenPath)) {
168
- const authData = JSON.parse(fs.readFileSync(this.tokenPath, 'utf8'));
184
+ const authData = JSON.parse(fs.readFileSync(this.tokenPath, "utf8"));
169
185
  return authData.user || null;
170
186
  }
171
187
  } catch (error) {
@@ -175,12 +191,18 @@ class ApiClient {
175
191
  }
176
192
 
177
193
  async createBlock(data: CreateBlockRequest): Promise<Block> {
178
- const response: AxiosResponse<Block> = await this.client.post('/api/blocks', data);
194
+ const response: AxiosResponse<Block> = await this.client.post(
195
+ "/api/blocks",
196
+ data
197
+ );
179
198
  return response.data;
180
199
  }
181
200
 
182
201
  async forkBlock(data: ForkBlockRequest): Promise<Block> {
183
- const response: AxiosResponse<Block> = await this.client.post('/api/blocks/fork', data);
202
+ const response: AxiosResponse<Block> = await this.client.post(
203
+ "/api/blocks/fork",
204
+ data
205
+ );
184
206
  return response.data;
185
207
  }
186
208
 
@@ -189,18 +211,23 @@ class ApiClient {
189
211
  }
190
212
 
191
213
  async getBlock(blockId: string): Promise<Block> {
192
- const response: AxiosResponse<Block> = await this.client.get(`/api/blocks/${blockId}`);
214
+ const response: AxiosResponse<Block> = await this.client.get(
215
+ `/api/blocks/${blockId}`
216
+ );
193
217
  return response.data;
194
218
  }
195
219
 
196
220
  async saveAndBundle(data: SaveAndBundleRequest): Promise<any> {
197
- const response = await this.client.post('/api/blocks/save-and-bundle', data);
221
+ const response = await this.client.post(
222
+ "/api/blocks/save-and-bundle",
223
+ data
224
+ );
198
225
  return response.data;
199
226
  }
200
227
 
201
228
  async healthCheck(): Promise<boolean> {
202
229
  try {
203
- await this.client.get('/api/health');
230
+ await this.client.get("/api/health");
204
231
  return true;
205
232
  } catch (error) {
206
233
  return false;
@@ -208,24 +235,30 @@ class ApiClient {
208
235
  }
209
236
 
210
237
  async requestOTP(email: string): Promise<AuthResponse> {
211
- const response: AxiosResponse<AuthResponse> = await this.client.post('/api/auth/request-otp', { email });
238
+ const response: AxiosResponse<AuthResponse> = await this.client.post(
239
+ "/api/auth/request-otp",
240
+ { email }
241
+ );
212
242
  return response.data;
213
243
  }
214
244
 
215
245
  async verifyOTP(email: string, otp: string): Promise<AuthResponse> {
216
- const response: AxiosResponse<AuthResponse> = await this.client.post('/api/auth/verify-otp', { email, otp });
246
+ const response: AxiosResponse<AuthResponse> = await this.client.post(
247
+ "/api/auth/verify-otp",
248
+ { email, otp }
249
+ );
217
250
  const data = response.data;
218
-
251
+
219
252
  if (data.success && data.token && data.user) {
220
253
  this.storeToken(data.token, data.user);
221
254
  }
222
-
255
+
223
256
  return data;
224
257
  }
225
258
 
226
259
  async logout(): Promise<void> {
227
260
  try {
228
- await this.client.post('/api/auth/logout');
261
+ await this.client.post("/api/auth/logout");
229
262
  } catch (error) {
230
263
  // Ignore logout errors
231
264
  } finally {
@@ -239,4 +272,4 @@ class ApiClient {
239
272
  }
240
273
  }
241
274
 
242
- export const apiClient = new ApiClient();
275
+ export const apiClient = new ApiClient();
package/src/utils/git.ts CHANGED
@@ -1,13 +1,13 @@
1
- import simpleGit, { SimpleGit } from 'simple-git';
2
- import path from 'path';
3
- import fs from 'fs';
4
- import chalk from 'chalk';
1
+ import simpleGit, { SimpleGit } from "simple-git";
2
+ import path from "path";
3
+ import fs from "fs";
4
+ import chalk from "chalk";
5
5
 
6
6
  // Simple spinner implementation since ora v5 has import issues
7
7
  class SimpleSpinner {
8
8
  private message: string;
9
9
  private interval: NodeJS.Timeout | null = null;
10
- private frames = ['', '', '', '', '', '', '', '', '', ''];
10
+ private frames = ["", "", "", "", "", "", "", "", "", ""];
11
11
  private currentFrame = 0;
12
12
 
13
13
  constructor(message: string) {
@@ -17,7 +17,9 @@ class SimpleSpinner {
17
17
  start(): this {
18
18
  process.stdout.write(this.message);
19
19
  this.interval = setInterval(() => {
20
- process.stdout.write(`\r${this.frames[this.currentFrame]} ${this.message}`);
20
+ process.stdout.write(
21
+ `\r${this.frames[this.currentFrame]} ${this.message}`
22
+ );
21
23
  this.currentFrame = (this.currentFrame + 1) % this.frames.length;
22
24
  }, 80);
23
25
  return this;
@@ -38,7 +40,7 @@ class SimpleSpinner {
38
40
  clearInterval(this.interval);
39
41
  this.interval = null;
40
42
  }
41
- process.stdout.write('\r');
43
+ process.stdout.write("\r");
42
44
  }
43
45
  }
44
46
 
@@ -47,7 +49,7 @@ function ora(message: string): SimpleSpinner {
47
49
  }
48
50
 
49
51
  export class GitManager {
50
- private git: SimpleGit;
52
+ public git: SimpleGit;
51
53
 
52
54
  constructor(cwd?: string) {
53
55
  this.git = simpleGit(cwd);
@@ -68,7 +70,7 @@ export class GitManager {
68
70
 
69
71
  // Clone the repository
70
72
  await this.git.clone(repoUrl, targetDir);
71
-
73
+
72
74
  spinner.succeed(chalk.green(`Repository cloned to ${targetDir}`));
73
75
  } catch (error: any) {
74
76
  spinner.fail(chalk.red(`Failed to clone repository: ${error.message}`));
@@ -96,7 +98,7 @@ export class GitManager {
96
98
  try {
97
99
  const git = dir ? simpleGit(dir) : this.git;
98
100
  const remotes = await git.getRemotes(true);
99
- const origin = remotes.find(remote => remote.name === 'origin');
101
+ const origin = remotes.find((remote) => remote.name === "origin");
100
102
  return origin?.refs?.fetch || null;
101
103
  } catch (error) {
102
104
  return null;
@@ -120,23 +122,23 @@ export class GitManager {
120
122
  * Push current branch to remote
121
123
  */
122
124
  async pushToRemote(dir?: string): Promise<void> {
123
- const spinner = ora('Pushing changes to remote repository...').start();
125
+ const spinner = ora("Pushing changes to remote repository...").start();
124
126
 
125
127
  try {
126
128
  const git = dir ? simpleGit(dir) : this.git;
127
-
129
+
128
130
  // Get current branch
129
131
  const status = await git.status();
130
132
  const currentBranch = status.current;
131
133
 
132
134
  if (!currentBranch) {
133
- throw new Error('No current branch found');
135
+ throw new Error("No current branch found");
134
136
  }
135
137
 
136
138
  // Push to remote
137
- await git.push('origin', currentBranch);
138
-
139
- spinner.succeed(chalk.green('Changes pushed to remote repository'));
139
+ await git.push("origin", currentBranch);
140
+
141
+ spinner.succeed(chalk.green("Changes pushed to remote repository"));
140
142
  } catch (error: any) {
141
143
  spinner.fail(chalk.red(`Failed to push changes: ${error.message}`));
142
144
  throw error;
@@ -152,14 +154,14 @@ export class GitManager {
152
154
  hasChanges: boolean;
153
155
  }> {
154
156
  const git = dir ? simpleGit(dir) : this.git;
155
-
157
+
156
158
  const status = await git.status();
157
159
  const remoteUrl = await this.getRemoteUrl(dir);
158
-
160
+
159
161
  return {
160
- branch: status.current || 'unknown',
162
+ branch: status.current || "unknown",
161
163
  remoteUrl,
162
- hasChanges: !status.isClean()
164
+ hasChanges: !status.isClean(),
163
165
  };
164
166
  }
165
167
 
@@ -172,10 +174,10 @@ export class GitManager {
172
174
  if (match) {
173
175
  return match[1];
174
176
  }
175
-
177
+
176
178
  // Fallback: use the last part of the URL
177
- const parts = gitUrl.split('/');
178
- return parts[parts.length - 1].replace('.git', '');
179
+ const parts = gitUrl.split("/");
180
+ return parts[parts.length - 1].replace(".git", "");
179
181
  }
180
182
 
181
183
  /**
@@ -186,9 +188,9 @@ export class GitManager {
186
188
  /^https:\/\/github\.com\/[\w\-\.]+\/[\w\-\.]+(?:\.git)?$/,
187
189
  /^git@github\.com:[\w\-\.]+\/[\w\-\.]+(?:\.git)?$/,
188
190
  /^https:\/\/gitlab\.com\/[\w\-\.]+\/[\w\-\.]+(?:\.git)?$/,
189
- /^git@gitlab\.com:[\w\-\.]+\/[\w\-\.]+(?:\.git)?$/
191
+ /^git@gitlab\.com:[\w\-\.]+\/[\w\-\.]+(?:\.git)?$/,
190
192
  ];
191
193
 
192
- return patterns.some(pattern => pattern.test(url));
194
+ return patterns.some((pattern) => pattern.test(url));
193
195
  }
194
- }
196
+ }