@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 +1 -1
- package/src/commands/create.ts +109 -41
- package/src/commands/save.ts +213 -0
- package/src/commands/sync.ts +145 -79
- package/src/index.ts +49 -35
- package/src/utils/api.ts +65 -32
- package/src/utils/git.ts +28 -26
package/package.json
CHANGED
package/src/commands/create.ts
CHANGED
@@ -1,97 +1,165 @@
|
|
1
|
-
import chalk from
|
2
|
-
import path from
|
3
|
-
import { apiClient, CreateBlockRequest } from
|
4
|
-
import { GitManager } from
|
5
|
-
import { createInterface } from
|
6
|
-
import { requireAuthentication, getAuthenticatedUser } from
|
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(
|
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
|
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(
|
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
|
-
//
|
40
|
-
let
|
41
|
-
|
42
|
-
|
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:
|
48
|
-
title:
|
49
|
-
description:
|
50
|
-
allowedBrickTypes: [
|
51
|
-
scope: [
|
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(
|
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(
|
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.
|
82
|
-
|
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(
|
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(
|
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
|
+
}
|
package/src/commands/sync.ts
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
import chalk from
|
2
|
-
import fs from
|
3
|
-
import path from
|
4
|
-
import { apiClient } from
|
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(
|
29
|
+
console.log(chalk.blue("🔄 Syncing block registry..."));
|
30
30
|
|
31
31
|
// Fetch registry from server
|
32
|
-
console.log(chalk.yellow(
|
33
|
-
const response = await fetch(
|
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(
|
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(
|
52
|
-
console.log(
|
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(
|
63
|
+
console.log(chalk.blue("\n📋 Available components (global namespace):"));
|
58
64
|
for (const [componentName, entry] of Object.entries(registry)) {
|
59
|
-
console.log(
|
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(
|
63
|
-
|
64
|
-
|
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(
|
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(
|
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(
|
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(
|
91
|
-
console.log(chalk.gray(
|
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(
|
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(),
|
109
|
-
path.join(process.cwd(),
|
110
|
-
path.join(process.cwd(),
|
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,
|
139
|
+
if (fs.existsSync(path.join(possiblePath, "package.json"))) {
|
115
140
|
try {
|
116
|
-
const packageJson = JSON.parse(
|
117
|
-
|
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(
|
133
|
-
|
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)
|
146
|
-
|
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(
|
185
|
+
// Tags: ${entry.tags?.join(", ") || "none"}
|
150
186
|
export const ${componentName} = createNamedBlock('${componentName}');`
|
151
|
-
)
|
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)
|
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)
|
169
|
-
|
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
|
-
)
|
218
|
+
)
|
219
|
+
.join("\n")}
|
178
220
|
},
|
179
221
|
authors: {
|
180
|
-
${Object.entries(authorRegistry)
|
181
|
-
|
182
|
-
|
183
|
-
`
|
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
|
-
)
|
236
|
+
)
|
237
|
+
.join("\n")}
|
191
238
|
},`
|
192
|
-
)
|
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,
|
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,
|
255
|
+
const indexPath = path.join(mextBlockPath, "src", "index.ts");
|
208
256
|
if (fs.existsSync(indexPath)) {
|
209
|
-
let indexContent = fs.readFileSync(indexPath,
|
210
|
-
|
257
|
+
let indexContent = fs.readFileSync(indexPath, "utf8");
|
258
|
+
|
211
259
|
// Add export for named exports if not already present
|
212
|
-
if (!indexContent.includes(
|
213
|
-
indexContent +=
|
214
|
-
|
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(
|
223
|
-
|
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,
|
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)
|
248
|
-
|
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(
|
305
|
+
// Tags: ${entry.tags?.join(", ") || "none"}
|
252
306
|
export const ${componentName} = createAuthorBlock('${author}', '${componentName}');`
|
253
|
-
)
|
307
|
+
)
|
308
|
+
.join("\n\n")}
|
254
309
|
|
255
310
|
// Export all components as default for convenience
|
256
311
|
export default {
|
257
|
-
${Object.keys(components)
|
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)
|
267
|
-
|
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
|
-
)
|
333
|
+
)
|
334
|
+
.join("\n")}
|
275
335
|
}
|
276
336
|
};
|
277
337
|
`;
|
278
338
|
|
279
339
|
// Write the author entry file
|
280
|
-
fs.writeFileSync(authorEntryPath, content,
|
281
|
-
|
282
|
-
console.log(
|
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
|
4
|
-
import chalk from
|
5
|
-
import { loginCommand } from
|
6
|
-
import { createCommand } from
|
7
|
-
import { forkCommand } from
|
8
|
-
import { deleteCommand } from
|
9
|
-
import { publishCommand } from
|
10
|
-
import { syncCommand } from
|
11
|
-
import {
|
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(
|
18
|
-
.description(
|
19
|
-
|
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(
|
24
|
-
.description(
|
26
|
+
.command("login")
|
27
|
+
.description("Authenticate with MEXT")
|
25
28
|
.action(loginCommand);
|
26
29
|
|
27
30
|
program
|
28
|
-
.command(
|
29
|
-
.description(
|
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(
|
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(
|
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(
|
46
|
-
.description(
|
47
|
-
.option(
|
48
|
-
.option(
|
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(
|
53
|
-
.description(
|
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(
|
58
|
-
.description(
|
66
|
+
.command("delete <blockId>")
|
67
|
+
.description("Delete a block (requires ownership)")
|
59
68
|
.action(deleteCommand);
|
60
69
|
|
61
70
|
program
|
62
|
-
.command(
|
63
|
-
.description(
|
71
|
+
.command("publish")
|
72
|
+
.description("Publish current block with automatic bundling")
|
64
73
|
.action(publishCommand);
|
65
74
|
|
66
75
|
program
|
67
|
-
.command(
|
68
|
-
.description(
|
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(
|
73
|
-
console.error(chalk.red(`Invalid command: ${program.args.join(
|
74
|
-
console.log(chalk.yellow(
|
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
|
2
|
-
import chalk from
|
3
|
-
import fs from
|
4
|
-
import path from
|
5
|
-
import os from
|
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: (
|
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?:
|
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 =
|
76
|
+
constructor(baseUrl: string = "https://api.mexty.ai") {
|
77
77
|
this.baseUrl = baseUrl;
|
78
|
-
this.tokenPath = path.join(os.homedir(),
|
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
|
-
|
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(
|
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(
|
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(
|
111
|
-
|
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,
|
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(
|
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,
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
2
|
-
import path from
|
3
|
-
import fs from
|
4
|
-
import chalk from
|
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(
|
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(
|
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
|
-
|
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 ===
|
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(
|
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(
|
135
|
+
throw new Error("No current branch found");
|
134
136
|
}
|
135
137
|
|
136
138
|
// Push to remote
|
137
|
-
await git.push(
|
138
|
-
|
139
|
-
spinner.succeed(chalk.green(
|
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 ||
|
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(
|
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
|
+
}
|