@bigtyphoon/melo 1.7.6 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Add a new server type to the project
5
+ * Creates templates and updates all necessary configuration files
6
+ */
7
+
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import { Command } from 'commander';
11
+ import { ADD_SERVER_TYPE_INFO, ADD_SERVER_TYPE_EXISTS, DEFAULT_USERNAME, DEFAULT_PWD } from '../utils/constants';
12
+
13
+ interface ServerOptions {
14
+ username?: string;
15
+ password?: string;
16
+ frontend?: boolean;
17
+ args?: string[];
18
+ }
19
+
20
+ export default function (program: Command) {
21
+ program.command('add-server <server-name>')
22
+ .description('Add a new server type with templates and configurations')
23
+ .option('-f, --frontend', 'mark as frontend server', false)
24
+ .option('-p, --port <port>', 'default port number', '3050')
25
+ .option('-c, --client-port <port>', 'client port (for frontend)', '3010')
26
+ .action(function (serverName: string, opts: ServerOptions) {
27
+ addServerType(serverName, opts);
28
+ });
29
+ }
30
+
31
+ function addServerType(serverName: string, opts: ServerOptions) {
32
+ const cwd = process.cwd();
33
+
34
+ // Check if we're in a valid melo project
35
+ if (!isMeloProject(cwd)) {
36
+ console.error('Error: Not a valid melo project directory.');
37
+ console.info('Please run this command in a melo project root directory.');
38
+ process.exit(1);
39
+ }
40
+
41
+ // Check if server type already exists
42
+ if (serverTypeExists(cwd, serverName)) {
43
+ console.warn(`Server type '${serverName}' already exists.`);
44
+ console.info(ADD_SERVER_TYPE_EXISTS);
45
+
46
+ // Ask if user wants to overwrite or update
47
+ const readline = require('readline');
48
+ const rl = readline.createInterface({
49
+ input: process.stdin,
50
+ output: process.stdout
51
+ });
52
+
53
+ rl.question(`Do you want to update existing '${serverName}' configuration? (y/N): `, (answer: string) => {
54
+ if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
55
+ updateServerType(cwd, serverName, opts);
56
+ } else {
57
+ console.info('Operation cancelled.');
58
+ }
59
+ rl.close();
60
+ });
61
+ return;
62
+ }
63
+
64
+ // Create new server type
65
+ createServerType(cwd, serverName, opts);
66
+ console.info(ADD_SERVER_TYPE_INFO.replace('${serverName}', serverName));
67
+ }
68
+
69
+ function isMeloProject(cwd: string): boolean {
70
+ // Check for key files/directories that indicate a melo project
71
+ const requiredPaths = [
72
+ 'app.ts',
73
+ 'config',
74
+ 'app/servers'
75
+ ];
76
+
77
+ return requiredPaths.every(p => fs.existsSync(path.join(cwd, p)));
78
+ }
79
+
80
+ function serverTypeExists(cwd: string, serverName: string): boolean {
81
+ const serverDir = path.join(cwd, 'app', 'servers', serverName);
82
+ return fs.existsSync(serverDir);
83
+ }
84
+
85
+ function createServerType(cwd: string, serverName: string, opts: ServerOptions) {
86
+ // 1. Create directory structure
87
+ const serverDir = path.join(cwd, 'app', 'servers', serverName);
88
+ const handlerDir = path.join(serverDir, 'handler');
89
+ const remoteDir = path.join(serverDir, 'remote');
90
+
91
+ fs.mkdirSync(handlerDir, { recursive: true });
92
+ fs.mkdirSync(remoteDir, { recursive: true });
93
+
94
+ // 2. Create handler template
95
+ createHandlerTemplate(handlerDir, serverName);
96
+
97
+ // 3. Create remote template
98
+ createRemoteTemplate(remoteDir, serverName);
99
+
100
+ // 4. Update adminServer.json
101
+ updateAdminServerJson(cwd, serverName);
102
+
103
+ // 5. Update servers.json
104
+ updateServersJson(cwd, serverName, opts);
105
+
106
+ // 6. Update app.ts (add configuration block if it doesn't exist)
107
+ updateAppTs(cwd, serverName, opts);
108
+ }
109
+
110
+ function updateServerType(cwd: string, serverName: string, opts: ServerOptions) {
111
+ // Only update configuration files, don't overwrite handler/remote files
112
+ updateServersJson(cwd, serverName, opts);
113
+ updateAppTs(cwd, serverName, opts);
114
+ console.info(`Updated '${serverName}' configuration.`);
115
+ }
116
+
117
+ function createHandlerTemplate(handlerDir: string, serverName: string) {
118
+ const content = `import { Application, BackendSession, FrontendSession } from '@bigtyphoon/melo';
119
+
120
+ export default function (app: Application) {
121
+ return new Handler(app);
122
+ }
123
+
124
+ export class Handler {
125
+ constructor(private app: Application) {
126
+
127
+ }
128
+
129
+ /**
130
+ * Example handler method
131
+ *
132
+ * @param {Object} msg request message
133
+ * @param {Object} session current session object
134
+ */
135
+ async example(msg: any, session: BackendSession | FrontendSession) {
136
+ return { code: 200, msg: '${serverName} handler is working.' };
137
+ }
138
+ }
139
+ `;
140
+ fs.writeFileSync(path.join(handlerDir, `${serverName}Handler.ts`), content);
141
+ }
142
+
143
+ function createRemoteTemplate(remoteDir: string, serverName: string) {
144
+ const content = `import { Application, BackendSession, RemoterClass } from '@bigtyphoon/melo';
145
+
146
+ export default function (app: Application) {
147
+ return new Remoter(app);
148
+ }
149
+
150
+ export class Remoter implements RemoterClass {
151
+ constructor(private app: Application) {
152
+
153
+ }
154
+
155
+ /**
156
+ * Example remote method
157
+ *
158
+ * @param {Object} msg request message
159
+ * @param {Object} session current session object
160
+ */
161
+ async example(msg: any, session: BackendSession): Promise<any> {
162
+ return { code: 200, msg: '${serverName} remote is working.' };
163
+ }
164
+ }
165
+ `;
166
+ fs.writeFileSync(path.join(remoteDir, `${serverName}Remote.ts`), content);
167
+ }
168
+
169
+ function updateAdminServerJson(cwd: string, serverName: string) {
170
+ const adminServerPath = path.join(cwd, 'config', 'adminServer.json');
171
+
172
+ let adminServers: any[] = [];
173
+ if (fs.existsSync(adminServerPath)) {
174
+ try {
175
+ adminServers = JSON.parse(fs.readFileSync(adminServerPath, 'utf8'));
176
+ } catch (e) {
177
+ adminServers = [];
178
+ }
179
+ }
180
+
181
+ // Check if server type already exists
182
+ const exists = adminServers.some(s => s.type === serverName);
183
+ if (!exists) {
184
+ adminServers.push({
185
+ type: serverName,
186
+ token: generateToken()
187
+ });
188
+ fs.writeFileSync(adminServerPath, JSON.stringify(adminServers, null, 2));
189
+ }
190
+ }
191
+
192
+ function updateServersJson(cwd: string, serverName: string, opts: ServerOptions) {
193
+ const serversPath = path.join(cwd, 'config', 'servers.json');
194
+
195
+ let servers: any = {};
196
+ if (fs.existsSync(serversPath)) {
197
+ try {
198
+ servers = JSON.parse(fs.readFileSync(serversPath, 'utf8'));
199
+ } catch (e) {
200
+ servers = {};
201
+ }
202
+ }
203
+
204
+ // Ensure development and production exist
205
+ if (!servers.development) servers.development = {};
206
+ if (!servers.production) servers.production = {};
207
+
208
+ const port = parseInt(opts.args?.find(a => a.startsWith('--port'))?.split('=')[1] || '3050');
209
+ const clientPort = parseInt(opts.args?.find(a => a.startsWith('--client-port'))?.split('=')[1] || '3010');
210
+
211
+ const serverConfig = {
212
+ id: `${serverName}-server-1`,
213
+ host: '127.0.0.1',
214
+ port: port,
215
+ frontend: opts.frontend || false,
216
+ args: ' --inspect=10003'
217
+ };
218
+
219
+ if (opts.frontend) {
220
+ (serverConfig as any).clientHost = '127.0.0.1';
221
+ (serverConfig as any).clientPort = clientPort;
222
+ }
223
+
224
+ // Add to both environments
225
+ servers.development[serverName] = [serverConfig];
226
+
227
+ // Production uses slightly different inspect port
228
+ const prodConfig = { ...serverConfig };
229
+ prodConfig.args = ' --inspect=10004';
230
+ servers.production[serverName] = [prodConfig];
231
+
232
+ fs.writeFileSync(serversPath, JSON.stringify(servers, null, 2));
233
+ }
234
+
235
+ function updateAppTs(cwd: string, serverName: string, opts: ServerOptions) {
236
+ const appTsPath = path.join(cwd, 'app.ts');
237
+
238
+ if (!fs.existsSync(appTsPath)) {
239
+ console.warn('Warning: app.ts not found, skipping app.ts update.');
240
+ return;
241
+ }
242
+
243
+ let content = fs.readFileSync(appTsPath, 'utf8');
244
+
245
+ // Check if configuration block already exists
246
+ const configPattern = new RegExp(`app\\.configure\\(['"].*['"],\\s*['"]${serverName}['"]`);
247
+ if (configPattern.test(content)) {
248
+ console.info(`Configuration for '${serverName}' already exists in app.ts`);
249
+ return;
250
+ }
251
+
252
+ // Find the last app.configure block and add after it
253
+ const configurePattern = /app\.configure\(['"].*['"],\s*['"].*['"],\s*function\s*\(\)\s*\{[\s\S]*?\}\);/g;
254
+ const matches = content.match(configurePattern);
255
+
256
+ let insertPosition = content.length;
257
+ if (matches && matches.length > 0) {
258
+ const lastMatch = matches[matches.length - 1];
259
+ insertPosition = content.lastIndexOf(lastMatch) + lastMatch.length;
260
+ } else {
261
+ // Find position before // start app comment
262
+ const startAppMatch = content.match(/\/\/\s*start app/);
263
+ if (startAppMatch) {
264
+ insertPosition = content.indexOf(startAppMatch[0]);
265
+ }
266
+ }
267
+
268
+ const newConfig = `
269
+
270
+ // ${serverName} server configuration
271
+ app.configure('production|development', '${serverName}', function () {
272
+ // Add ${serverName} specific configuration here
273
+ // Example: app.set('${serverName}Config', { /* config */ });
274
+ });
275
+ `;
276
+
277
+ content = content.slice(0, insertPosition) + newConfig + content.slice(insertPosition);
278
+ fs.writeFileSync(appTsPath, content);
279
+ }
280
+
281
+ function generateToken(): string {
282
+ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
283
+ let token = '';
284
+ for (let i = 0; i < 40; i++) {
285
+ token += chars.charAt(Math.floor(Math.random() * chars.length));
286
+ }
287
+ return token;
288
+ }