@mexty/cli 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +5 -5
  2. package/package.json +4 -2
  3. package/src/commands/create.ts +109 -41
  4. package/src/commands/save.ts +213 -0
  5. package/src/commands/sync.ts +2 -2
  6. package/src/index.ts +98 -84
  7. package/src/utils/api.ts +241 -239
  8. package/src/utils/git.ts +28 -26
  9. package/dist/commands/create.d.ts +0 -7
  10. package/dist/commands/create.d.ts.map +0 -1
  11. package/dist/commands/create.js +0 -80
  12. package/dist/commands/create.js.map +0 -1
  13. package/dist/commands/delete.d.ts +0 -2
  14. package/dist/commands/delete.d.ts.map +0 -1
  15. package/dist/commands/delete.js +0 -54
  16. package/dist/commands/delete.js.map +0 -1
  17. package/dist/commands/fork.d.ts +0 -2
  18. package/dist/commands/fork.d.ts.map +0 -1
  19. package/dist/commands/fork.js +0 -52
  20. package/dist/commands/fork.js.map +0 -1
  21. package/dist/commands/login.d.ts +0 -2
  22. package/dist/commands/login.d.ts.map +0 -1
  23. package/dist/commands/login.js +0 -12
  24. package/dist/commands/login.js.map +0 -1
  25. package/dist/commands/publish.d.ts +0 -2
  26. package/dist/commands/publish.d.ts.map +0 -1
  27. package/dist/commands/publish.js +0 -139
  28. package/dist/commands/publish.js.map +0 -1
  29. package/dist/commands/sync.d.ts +0 -2
  30. package/dist/commands/sync.d.ts.map +0 -1
  31. package/dist/commands/sync.js +0 -140
  32. package/dist/commands/sync.js.map +0 -1
  33. package/dist/index.d.ts +0 -3
  34. package/dist/index.d.ts.map +0 -1
  35. package/dist/index.js +0 -60
  36. package/dist/index.js.map +0 -1
  37. package/dist/utils/api.d.ts +0 -55
  38. package/dist/utils/api.d.ts.map +0 -1
  39. package/dist/utils/api.js +0 -68
  40. package/dist/utils/api.js.map +0 -1
  41. package/dist/utils/git.d.ts +0 -41
  42. package/dist/utils/git.d.ts.map +0 -1
  43. package/dist/utils/git.js +0 -171
  44. package/dist/utils/git.js.map +0 -1
package/src/utils/api.ts CHANGED
@@ -1,240 +1,242 @@
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
-
7
- export interface Block {
8
- _id: string;
9
- blockType: string;
10
- title: string;
11
- description: string;
12
- gitUrl?: string;
13
- courseId?: string;
14
- courseName?: string;
15
- allowedBrickTypes: string[];
16
- allowedBlockTypes?: string[];
17
- scope: ('library' | 'user-store' | 'published-store')[];
18
- content: any[];
19
- bundlePath?: string;
20
- federationUrl?: string;
21
- buildStatus?: 'pending' | 'building' | 'success' | 'failed';
22
- buildError?: string;
23
- lastBuilt?: Date;
24
- createdAt: Date;
25
- updatedAt: Date;
26
- }
27
-
28
- export interface CreateBlockRequest {
29
- blockType: string;
30
- title: string;
31
- description: string;
32
- gitUrl?: string;
33
- courseId?: string;
34
- courseName?: string;
35
- allowedBrickTypes: string[];
36
- allowedBlockTypes?: string[];
37
- scope: string[];
38
- content?: any[];
39
- }
40
-
41
- export interface ForkBlockRequest {
42
- blockId: string;
43
- title?: string;
44
- description?: string;
45
- }
46
-
47
- export interface SaveAndBundleRequest {
48
- blockId: string;
49
- }
50
-
51
- export interface AuthResponse {
52
- success: boolean;
53
- message: string;
54
- token?: string;
55
- user?: {
56
- id: string;
57
- email: string;
58
- fullName?: string;
59
- occupation?: string;
60
- isProfileComplete: boolean;
61
- };
62
- }
63
-
64
- export interface LoginRequest {
65
- email: string;
66
- otp: string;
67
- }
68
-
69
- class ApiClient {
70
- private client: AxiosInstance;
71
- private baseUrl: string;
72
- private tokenPath: string;
73
-
74
- constructor(baseUrl: string = 'http://localhost:3001') {
75
- this.baseUrl = baseUrl;
76
- this.tokenPath = path.join(os.homedir(), '.mext', 'auth.json');
77
-
78
- this.client = axios.create({
79
- baseURL: baseUrl,
80
- timeout: 30000,
81
- headers: {
82
- 'Content-Type': 'application/json',
83
- },
84
- });
85
-
86
- // Add request interceptor to include auth token
87
- this.client.interceptors.request.use(
88
- (config) => {
89
- const token = this.getStoredToken();
90
- if (token) {
91
- config.headers.Authorization = `Bearer ${token}`;
92
- }
93
- return config;
94
- },
95
- (error) => Promise.reject(error)
96
- );
97
-
98
- // Add response interceptor for error handling
99
- this.client.interceptors.response.use(
100
- (response) => response,
101
- (error) => {
102
- if (error.response?.status === 401) {
103
- console.error(chalk.red('Authentication required. Please login first: mexty login'));
104
- this.clearStoredToken();
105
- } else if (error.response) {
106
- console.error(chalk.red(`API Error ${error.response.status}: ${error.response.data?.error || error.message}`));
107
- } else if (error.request) {
108
- console.error(chalk.red('Network Error: Could not reach MEXT server'));
109
- console.error(chalk.yellow(`Make sure the server is running at ${this.baseUrl}`));
110
- } else {
111
- console.error(chalk.red(`Request Error: ${error.message}`));
112
- }
113
- throw error;
114
- }
115
- );
116
- }
117
-
118
- private getStoredToken(): string | null {
119
- try {
120
- if (fs.existsSync(this.tokenPath)) {
121
- const authData = JSON.parse(fs.readFileSync(this.tokenPath, 'utf8'));
122
- return authData.token || null;
123
- }
124
- } catch (error) {
125
- // Ignore errors reading token file
126
- }
127
- return null;
128
- }
129
-
130
- private storeToken(token: string, user: any): void {
131
- try {
132
- const authDir = path.dirname(this.tokenPath);
133
- if (!fs.existsSync(authDir)) {
134
- fs.mkdirSync(authDir, { recursive: true });
135
- }
136
-
137
- const authData = {
138
- token,
139
- user,
140
- timestamp: new Date().toISOString()
141
- };
142
-
143
- fs.writeFileSync(this.tokenPath, JSON.stringify(authData, null, 2));
144
- } catch (error: any) {
145
- console.warn(chalk.yellow(`Warning: Could not store auth token: ${error.message}`));
146
- }
147
- }
148
-
149
- private clearStoredToken(): void {
150
- try {
151
- if (fs.existsSync(this.tokenPath)) {
152
- fs.unlinkSync(this.tokenPath);
153
- }
154
- } catch (error) {
155
- // Ignore errors clearing token file
156
- }
157
- }
158
-
159
- public isAuthenticated(): boolean {
160
- return this.getStoredToken() !== null;
161
- }
162
-
163
- public getStoredUser(): any {
164
- try {
165
- if (fs.existsSync(this.tokenPath)) {
166
- const authData = JSON.parse(fs.readFileSync(this.tokenPath, 'utf8'));
167
- return authData.user || null;
168
- }
169
- } catch (error) {
170
- // Ignore errors reading user data
171
- }
172
- return null;
173
- }
174
-
175
- async createBlock(data: CreateBlockRequest): Promise<Block> {
176
- const response: AxiosResponse<Block> = await this.client.post('/api/blocks', data);
177
- return response.data;
178
- }
179
-
180
- async forkBlock(data: ForkBlockRequest): Promise<Block> {
181
- const response: AxiosResponse<Block> = await this.client.post('/api/blocks/fork', data);
182
- return response.data;
183
- }
184
-
185
- async deleteBlock(blockId: string): Promise<void> {
186
- await this.client.delete(`/api/blocks/${blockId}`);
187
- }
188
-
189
- async getBlock(blockId: string): Promise<Block> {
190
- const response: AxiosResponse<Block> = await this.client.get(`/api/blocks/${blockId}`);
191
- return response.data;
192
- }
193
-
194
- async saveAndBundle(data: SaveAndBundleRequest): Promise<any> {
195
- const response = await this.client.post('/api/blocks/save-and-bundle', data);
196
- return response.data;
197
- }
198
-
199
- async healthCheck(): Promise<boolean> {
200
- try {
201
- await this.client.get('/api/health');
202
- return true;
203
- } catch (error) {
204
- return false;
205
- }
206
- }
207
-
208
- async requestOTP(email: string): Promise<AuthResponse> {
209
- const response: AxiosResponse<AuthResponse> = await this.client.post('/api/auth/request-otp', { email });
210
- return response.data;
211
- }
212
-
213
- async verifyOTP(email: string, otp: string): Promise<AuthResponse> {
214
- const response: AxiosResponse<AuthResponse> = await this.client.post('/api/auth/verify-otp', { email, otp });
215
- const data = response.data;
216
-
217
- if (data.success && data.token && data.user) {
218
- this.storeToken(data.token, data.user);
219
- }
220
-
221
- return data;
222
- }
223
-
224
- async logout(): Promise<void> {
225
- try {
226
- await this.client.post('/api/auth/logout');
227
- } catch (error) {
228
- // Ignore logout errors
229
- } finally {
230
- this.clearStoredToken();
231
- }
232
- }
233
-
234
- setBaseUrl(url: string): void {
235
- this.baseUrl = url;
236
- this.client.defaults.baseURL = url;
237
- }
238
- }
239
-
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
+
7
+ export interface Block {
8
+ _id: string;
9
+ blockType: string;
10
+ title: string;
11
+ description: string;
12
+ gitUrl?: string;
13
+ courseId?: string;
14
+ courseName?: string;
15
+ allowedBrickTypes: string[];
16
+ allowedBlockTypes?: string[];
17
+ scope: ('library' | 'user-store' | 'published-store')[];
18
+ content: any[];
19
+ // Fork tracking
20
+ forkedId?: string;
21
+ bundlePath?: string;
22
+ federationUrl?: string;
23
+ buildStatus?: 'pending' | 'building' | 'success' | 'failed';
24
+ buildError?: string;
25
+ lastBuilt?: Date;
26
+ createdAt: Date;
27
+ updatedAt: Date;
28
+ }
29
+
30
+ export interface CreateBlockRequest {
31
+ blockType: string;
32
+ title: string;
33
+ description: string;
34
+ gitUrl?: string;
35
+ courseId?: string;
36
+ courseName?: string;
37
+ allowedBrickTypes: string[];
38
+ allowedBlockTypes?: string[];
39
+ scope: string[];
40
+ content?: any[];
41
+ }
42
+
43
+ export interface ForkBlockRequest {
44
+ blockId: string;
45
+ title?: string;
46
+ description?: string;
47
+ }
48
+
49
+ export interface SaveAndBundleRequest {
50
+ blockId: string;
51
+ }
52
+
53
+ export interface AuthResponse {
54
+ success: boolean;
55
+ message: string;
56
+ token?: string;
57
+ user?: {
58
+ id: string;
59
+ email: string;
60
+ fullName?: string;
61
+ occupation?: string;
62
+ isProfileComplete: boolean;
63
+ };
64
+ }
65
+
66
+ export interface LoginRequest {
67
+ email: string;
68
+ otp: string;
69
+ }
70
+
71
+ class ApiClient {
72
+ private client: AxiosInstance;
73
+ private baseUrl: string;
74
+ private tokenPath: string;
75
+
76
+ constructor(baseUrl: string = 'https://api.v2.mext.app') {
77
+ this.baseUrl = baseUrl;
78
+ this.tokenPath = path.join(os.homedir(), '.mext', 'auth.json');
79
+
80
+ this.client = axios.create({
81
+ baseURL: baseUrl,
82
+ timeout: 30000,
83
+ headers: {
84
+ 'Content-Type': 'application/json',
85
+ },
86
+ });
87
+
88
+ // Add request interceptor to include auth token
89
+ this.client.interceptors.request.use(
90
+ (config) => {
91
+ const token = this.getStoredToken();
92
+ if (token) {
93
+ config.headers.Authorization = `Bearer ${token}`;
94
+ }
95
+ return config;
96
+ },
97
+ (error) => Promise.reject(error)
98
+ );
99
+
100
+ // Add response interceptor for error handling
101
+ this.client.interceptors.response.use(
102
+ (response) => response,
103
+ (error) => {
104
+ if (error.response?.status === 401) {
105
+ console.error(chalk.red('Authentication required. Please login first: mexty login'));
106
+ this.clearStoredToken();
107
+ } else if (error.response) {
108
+ console.error(chalk.red(`API Error ${error.response.status}: ${error.response.data?.error || error.message}`));
109
+ } 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}`));
112
+ } else {
113
+ console.error(chalk.red(`Request Error: ${error.message}`));
114
+ }
115
+ throw error;
116
+ }
117
+ );
118
+ }
119
+
120
+ private getStoredToken(): string | null {
121
+ try {
122
+ if (fs.existsSync(this.tokenPath)) {
123
+ const authData = JSON.parse(fs.readFileSync(this.tokenPath, 'utf8'));
124
+ return authData.token || null;
125
+ }
126
+ } catch (error) {
127
+ // Ignore errors reading token file
128
+ }
129
+ return null;
130
+ }
131
+
132
+ private storeToken(token: string, user: any): void {
133
+ try {
134
+ const authDir = path.dirname(this.tokenPath);
135
+ if (!fs.existsSync(authDir)) {
136
+ fs.mkdirSync(authDir, { recursive: true });
137
+ }
138
+
139
+ const authData = {
140
+ token,
141
+ user,
142
+ timestamp: new Date().toISOString()
143
+ };
144
+
145
+ fs.writeFileSync(this.tokenPath, JSON.stringify(authData, null, 2));
146
+ } catch (error: any) {
147
+ console.warn(chalk.yellow(`Warning: Could not store auth token: ${error.message}`));
148
+ }
149
+ }
150
+
151
+ private clearStoredToken(): void {
152
+ try {
153
+ if (fs.existsSync(this.tokenPath)) {
154
+ fs.unlinkSync(this.tokenPath);
155
+ }
156
+ } catch (error) {
157
+ // Ignore errors clearing token file
158
+ }
159
+ }
160
+
161
+ public isAuthenticated(): boolean {
162
+ return this.getStoredToken() !== null;
163
+ }
164
+
165
+ public getStoredUser(): any {
166
+ try {
167
+ if (fs.existsSync(this.tokenPath)) {
168
+ const authData = JSON.parse(fs.readFileSync(this.tokenPath, 'utf8'));
169
+ return authData.user || null;
170
+ }
171
+ } catch (error) {
172
+ // Ignore errors reading user data
173
+ }
174
+ return null;
175
+ }
176
+
177
+ async createBlock(data: CreateBlockRequest): Promise<Block> {
178
+ const response: AxiosResponse<Block> = await this.client.post('/api/blocks', data);
179
+ return response.data;
180
+ }
181
+
182
+ async forkBlock(data: ForkBlockRequest): Promise<Block> {
183
+ const response: AxiosResponse<Block> = await this.client.post('/api/blocks/fork', data);
184
+ return response.data;
185
+ }
186
+
187
+ async deleteBlock(blockId: string): Promise<void> {
188
+ await this.client.delete(`/api/blocks/${blockId}`);
189
+ }
190
+
191
+ async getBlock(blockId: string): Promise<Block> {
192
+ const response: AxiosResponse<Block> = await this.client.get(`/api/blocks/${blockId}`);
193
+ return response.data;
194
+ }
195
+
196
+ async saveAndBundle(data: SaveAndBundleRequest): Promise<any> {
197
+ const response = await this.client.post('/api/blocks/save-and-bundle', data);
198
+ return response.data;
199
+ }
200
+
201
+ async healthCheck(): Promise<boolean> {
202
+ try {
203
+ await this.client.get('/api/health');
204
+ return true;
205
+ } catch (error) {
206
+ return false;
207
+ }
208
+ }
209
+
210
+ async requestOTP(email: string): Promise<AuthResponse> {
211
+ const response: AxiosResponse<AuthResponse> = await this.client.post('/api/auth/request-otp', { email });
212
+ return response.data;
213
+ }
214
+
215
+ async verifyOTP(email: string, otp: string): Promise<AuthResponse> {
216
+ const response: AxiosResponse<AuthResponse> = await this.client.post('/api/auth/verify-otp', { email, otp });
217
+ const data = response.data;
218
+
219
+ if (data.success && data.token && data.user) {
220
+ this.storeToken(data.token, data.user);
221
+ }
222
+
223
+ return data;
224
+ }
225
+
226
+ async logout(): Promise<void> {
227
+ try {
228
+ await this.client.post('/api/auth/logout');
229
+ } catch (error) {
230
+ // Ignore logout errors
231
+ } finally {
232
+ this.clearStoredToken();
233
+ }
234
+ }
235
+
236
+ setBaseUrl(url: string): void {
237
+ this.baseUrl = url;
238
+ this.client.defaults.baseURL = url;
239
+ }
240
+ }
241
+
240
242
  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
+ }
@@ -1,7 +0,0 @@
1
- interface CreateOptions {
2
- description?: string;
3
- type?: string;
4
- }
5
- export declare function createCommand(name: string, options: CreateOptions): Promise<void>;
6
- export {};
7
- //# sourceMappingURL=create.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAMA,UAAU,aAAa;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAmBD,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA8DvF"}