mn-rails-cli 0.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,1760 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/index.ts
27
+ var import_commander = require("commander");
28
+
29
+ // src/commands/new.ts
30
+ var fs = __toESM(require("fs-extra"), 1);
31
+ var path = __toESM(require("path"), 1);
32
+ var import_chalk = __toESM(require("chalk"), 1);
33
+ var import_inquirer = __toESM(require("inquirer"), 1);
34
+ async function newCommand(name, options = {}) {
35
+ try {
36
+ let projectName = name;
37
+ if (!projectName) {
38
+ const answer = await import_inquirer.default.prompt([
39
+ {
40
+ type: "input",
41
+ name: "name",
42
+ message: "Project name:",
43
+ validate: (input) => {
44
+ if (!input.trim()) {
45
+ return "Project name cannot be empty";
46
+ }
47
+ if (!/^[a-z0-9-]+$/.test(input)) {
48
+ return "Project name can only contain lowercase letters, numbers, and hyphens";
49
+ }
50
+ return true;
51
+ }
52
+ }
53
+ ]);
54
+ projectName = answer.name;
55
+ }
56
+ if (!projectName || typeof projectName !== "string") {
57
+ console.error(import_chalk.default.red("Error: Project name is required"));
58
+ process.exit(1);
59
+ }
60
+ const projectPath = path.resolve(process.cwd(), projectName);
61
+ if (fs.existsSync(projectPath)) {
62
+ console.error(import_chalk.default.red(`Error: Directory "${projectName}" already exists`));
63
+ process.exit(1);
64
+ }
65
+ console.log(import_chalk.default.blue(`Creating new plugin project: ${projectName}...`));
66
+ fs.ensureDirSync(projectPath);
67
+ const templateName = options.template || "basic";
68
+ const templatePath = path.resolve(__dirname, "../templates", templateName);
69
+ if (!fs.existsSync(templatePath)) {
70
+ console.warn(import_chalk.default.yellow(`Template "${templateName}" not found, using basic template`));
71
+ await createBasicTemplate(projectPath, projectName);
72
+ } else {
73
+ await copyTemplate(templatePath, projectPath, projectName);
74
+ }
75
+ console.log(import_chalk.default.green(`\u2713 Project created successfully!`));
76
+ console.log(import_chalk.default.cyan(`
77
+ Next steps:`));
78
+ console.log(import_chalk.default.cyan(` cd ${projectName}`));
79
+ console.log(import_chalk.default.cyan(` npm install`));
80
+ console.log(import_chalk.default.cyan(` mn-rails build`));
81
+ } catch (error) {
82
+ console.error(import_chalk.default.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
83
+ process.exit(1);
84
+ }
85
+ }
86
+ async function createBasicTemplate(projectPath, projectName) {
87
+ const dirs = ["src", "src/controllers", "src/views"];
88
+ dirs.forEach((dir) => fs.ensureDirSync(path.join(projectPath, dir)));
89
+ const packageJson = {
90
+ name: projectName,
91
+ version: "0.1.0",
92
+ description: "A MarginNote plugin",
93
+ type: "module",
94
+ main: "dist/index.js",
95
+ scripts: {
96
+ build: "mn-rails build",
97
+ dev: "mn-rails dev"
98
+ },
99
+ dependencies: {
100
+ "mn-rails-core": "workspace:*"
101
+ },
102
+ devDependencies: {
103
+ typescript: "^5.3.3"
104
+ }
105
+ };
106
+ fs.writeJSONSync(path.join(projectPath, "package.json"), packageJson, { spaces: 2 });
107
+ const tsconfig = {
108
+ extends: "mn-rails-core/tsconfig.json",
109
+ compilerOptions: {
110
+ outDir: "./dist",
111
+ rootDir: "./src"
112
+ },
113
+ include: ["src/**/*"]
114
+ };
115
+ fs.writeJSONSync(path.join(projectPath, "tsconfig.json"), tsconfig, { spaces: 2 });
116
+ const config = `export default {
117
+ // Plugin configuration
118
+ name: '${projectName}',
119
+ version: '0.1.0',
120
+ };
121
+ `;
122
+ fs.writeFileSync(path.join(projectPath, "mn-rails.config.js"), config);
123
+ const indexContent = `import { App, MN } from 'mn-rails-core';
124
+ import MainController from './controllers/MainController.js';
125
+
126
+ class Plugin extends App {
127
+ private controller?: MainController;
128
+
129
+ onLaunch() {
130
+ MN.log('Plugin launched!');
131
+ this.controller = new MainController();
132
+ }
133
+
134
+ onExit() {
135
+ MN.log('Plugin exiting...');
136
+ this.controller?.onDestroy?.();
137
+ }
138
+ }
139
+
140
+ export default new Plugin();
141
+ `;
142
+ fs.writeFileSync(path.join(projectPath, "src/index.ts"), indexContent);
143
+ const controllerContent = `import { Controller, MN } from 'mn-rails-core';
144
+
145
+ export default class MainController extends Controller {
146
+ onInit() {
147
+ MN.log('MainController initialized - Hello World!');
148
+
149
+ // \u793A\u4F8B\uFF1A\u521B\u5EFA\u539F\u751F\u6309\u94AE
150
+ const button = MN.nativeUI.createButton({
151
+ title: 'Hello World',
152
+ style: 'primary',
153
+ frame: { x: 10, y: 10, width: 150, height: 44 },
154
+ action: async () => {
155
+ // \u663E\u793A\u63D0\u793A\u6846
156
+ MN.alert('Hello World!\\n\u6B22\u8FCE\u4F7F\u7528 MN Rails');
157
+
158
+ // \u6253\u5F00 WebView \u9762\u677F
159
+ const webView = await MN.webUI.render('HelloWorld', {
160
+ message: 'Hello from Controller!'
161
+ }, {
162
+ x: 100,
163
+ y: 100,
164
+ width: 400,
165
+ height: 300
166
+ });
167
+
168
+ if (webView) {
169
+ MN.log('WebView opened:', webView.id);
170
+ }
171
+ }
172
+ });
173
+
174
+ if (button) {
175
+ MN.log('Native button created');
176
+ }
177
+ }
178
+
179
+ // \u5B9A\u4E49\u4F9B WebView \u8C03\u7528\u7684 actions\uFF08\u7C7B\u578B\u5B89\u5168\uFF09
180
+ actions = {
181
+ /**
182
+ * Hello World \u793A\u4F8B\uFF1A\u5411\u67D0\u4EBA\u95EE\u597D
183
+ */
184
+ async hello(name: string): Promise<string> {
185
+ const message = \`Hello, \${name}! Welcome to MN Rails!\`;
186
+ MN.log(message);
187
+ return message;
188
+ },
189
+
190
+ /**
191
+ * \u83B7\u53D6\u5F53\u524D\u65F6\u95F4
192
+ */
193
+ async getCurrentTime(): Promise<string> {
194
+ return new Date().toLocaleString();
195
+ }
196
+ };
197
+ }
198
+ `;
199
+ fs.writeFileSync(path.join(projectPath, "src/controllers/MainController.ts"), controllerContent);
200
+ const helloWorldView = `<!DOCTYPE html>
201
+ <html>
202
+ <head>
203
+ <meta charset="UTF-8">
204
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
205
+ <title>Hello World - MN Rails</title>
206
+ <style>
207
+ body {
208
+ margin: 0;
209
+ padding: 20px;
210
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
211
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
212
+ color: white;
213
+ min-height: 100vh;
214
+ display: flex;
215
+ flex-direction: column;
216
+ align-items: center;
217
+ justify-content: center;
218
+ }
219
+ .container {
220
+ text-align: center;
221
+ max-width: 400px;
222
+ }
223
+ h1 {
224
+ font-size: 2.5em;
225
+ margin: 0 0 20px 0;
226
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
227
+ }
228
+ .message {
229
+ font-size: 1.2em;
230
+ margin: 20px 0;
231
+ padding: 15px;
232
+ background: rgba(255,255,255,0.2);
233
+ border-radius: 10px;
234
+ backdrop-filter: blur(10px);
235
+ }
236
+ button {
237
+ background: white;
238
+ color: #667eea;
239
+ border: none;
240
+ padding: 12px 24px;
241
+ font-size: 1em;
242
+ border-radius: 25px;
243
+ cursor: pointer;
244
+ margin: 10px;
245
+ font-weight: bold;
246
+ box-shadow: 0 4px 6px rgba(0,0,0,0.2);
247
+ transition: transform 0.2s;
248
+ }
249
+ button:hover {
250
+ transform: translateY(-2px);
251
+ box-shadow: 0 6px 8px rgba(0,0,0,0.3);
252
+ }
253
+ button:active {
254
+ transform: translateY(0);
255
+ }
256
+ .result {
257
+ margin-top: 20px;
258
+ padding: 15px;
259
+ background: rgba(255,255,255,0.1);
260
+ border-radius: 10px;
261
+ min-height: 20px;
262
+ }
263
+ </style>
264
+ </head>
265
+ <body>
266
+ <div class="container">
267
+ <h1>\u{1F44B} Hello World!</h1>
268
+ <div class="message" id="message">
269
+ <p>\u6B22\u8FCE\u4F7F\u7528 MN Rails \u6846\u67B6\uFF01</p>
270
+ <p id="props-message"></p>
271
+ </div>
272
+
273
+ <button onclick="handleHello()">\u8C03\u7528 Controller</button>
274
+ <button onclick="handleGetTime()">\u83B7\u53D6\u65F6\u95F4</button>
275
+
276
+ <div class="result" id="result"></div>
277
+ </div>
278
+
279
+ <script>
280
+ // \u663E\u793A\u4ECE Controller \u4F20\u9012\u7684 props
281
+ if (window.__MN_RAILS_PROPS__) {
282
+ const propsMsg = document.getElementById('props-message');
283
+ if (propsMsg && window.__MN_RAILS_PROPS__.message) {
284
+ propsMsg.textContent = window.__MN_RAILS_PROPS__.message;
285
+ }
286
+ }
287
+
288
+ // \u8C03\u7528 Controller \u7684 hello action
289
+ async function handleHello() {
290
+ const resultEl = document.getElementById('result');
291
+ try {
292
+ if (window.bridge) {
293
+ resultEl.textContent = '\u8C03\u7528\u4E2D...';
294
+ const result = await window.bridge.main.hello('MN Rails \u5F00\u53D1\u8005');
295
+ resultEl.textContent = '\u7ED3\u679C: ' + result;
296
+ resultEl.style.color = '#4ade80';
297
+ } else {
298
+ resultEl.textContent = '\u9519\u8BEF: bridge \u5BF9\u8C61\u4E0D\u53EF\u7528';
299
+ resultEl.style.color = '#f87171';
300
+ }
301
+ } catch (error) {
302
+ resultEl.textContent = '\u9519\u8BEF: ' + error.message;
303
+ resultEl.style.color = '#f87171';
304
+ }
305
+ }
306
+
307
+ // \u8C03\u7528 Controller \u7684 getCurrentTime action
308
+ async function handleGetTime() {
309
+ const resultEl = document.getElementById('result');
310
+ try {
311
+ if (window.bridge) {
312
+ resultEl.textContent = '\u83B7\u53D6\u4E2D...';
313
+ const time = await window.bridge.main.getCurrentTime();
314
+ resultEl.textContent = '\u5F53\u524D\u65F6\u95F4: ' + time;
315
+ resultEl.style.color = '#4ade80';
316
+ } else {
317
+ resultEl.textContent = '\u9519\u8BEF: bridge \u5BF9\u8C61\u4E0D\u53EF\u7528';
318
+ resultEl.style.color = '#f87171';
319
+ }
320
+ } catch (error) {
321
+ resultEl.textContent = '\u9519\u8BEF: ' + error.message;
322
+ resultEl.style.color = '#f87171';
323
+ }
324
+ }
325
+
326
+ // \u9875\u9762\u52A0\u8F7D\u5B8C\u6210\u540E\u7684\u63D0\u793A
327
+ window.addEventListener('load', () => {
328
+ console.log('Hello World WebView loaded!');
329
+ if (window.bridge) {
330
+ console.log('\u2713 Bridge is available');
331
+ } else {
332
+ console.warn('\u26A0 Bridge is not available');
333
+ }
334
+ });
335
+ </script>
336
+ </body>
337
+ </html>
338
+ `;
339
+ fs.writeFileSync(path.join(projectPath, "src/views/HelloWorld.html"), helloWorldView);
340
+ const readme = `# ${projectName}
341
+
342
+ A MarginNote plugin built with MN Rails.
343
+
344
+ ## Development
345
+
346
+ \`\`\`bash
347
+ npm install
348
+ mn-rails build
349
+ \`\`\`
350
+
351
+ ## License
352
+
353
+ MIT
354
+ `;
355
+ fs.writeFileSync(path.join(projectPath, "README.md"), readme);
356
+ }
357
+ async function copyTemplate(templatePath, projectPath, projectName) {
358
+ const files = fs.readdirSync(templatePath, { withFileTypes: true });
359
+ for (const file of files) {
360
+ const srcPath = path.join(templatePath, file.name);
361
+ let destPath = path.join(projectPath, file.name);
362
+ if (file.name.endsWith(".template")) {
363
+ destPath = destPath.replace(".template", "");
364
+ }
365
+ if (file.isDirectory()) {
366
+ fs.copySync(srcPath, destPath);
367
+ } else {
368
+ let content = fs.readFileSync(srcPath, "utf-8");
369
+ const projectTitle = projectName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
370
+ content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName).replace(/\{\{PROJECT_TITLE\}\}/g, projectTitle);
371
+ fs.writeFileSync(destPath, content);
372
+ }
373
+ }
374
+ const dirs = ["src", "src/controllers", "src/views"];
375
+ dirs.forEach((dir) => {
376
+ const dirPath = path.join(projectPath, dir);
377
+ if (!fs.existsSync(dirPath)) {
378
+ fs.ensureDirSync(dirPath);
379
+ }
380
+ });
381
+ }
382
+
383
+ // src/commands/build.ts
384
+ var path5 = __toESM(require("path"), 1);
385
+ var fs4 = __toESM(require("fs-extra"), 1);
386
+ var import_chalk3 = __toESM(require("chalk"), 1);
387
+
388
+ // src/build/index.ts
389
+ var path4 = __toESM(require("path"), 1);
390
+ var fs3 = __toESM(require("fs-extra"), 1);
391
+ var import_child_process = require("child_process");
392
+ var import_chalk2 = __toESM(require("chalk"), 1);
393
+
394
+ // src/utils/config.ts
395
+ var path2 = __toESM(require("path"), 1);
396
+ var import_cosmiconfig = require("cosmiconfig");
397
+ async function loadConfig(projectRoot) {
398
+ const defaultConfig = {
399
+ name: path2.basename(projectRoot),
400
+ version: "0.1.0",
401
+ title: path2.basename(projectRoot),
402
+ author: ""
403
+ };
404
+ try {
405
+ const explorer = (0, import_cosmiconfig.cosmiconfig)("mn-rails", {
406
+ searchPlaces: ["mn-rails.config.js", "mn-rails.config.ts", "mn-rails.config.mjs", "mn-rails.config.cjs"]
407
+ });
408
+ const result = await explorer.search(projectRoot);
409
+ if (result && result.config) {
410
+ return { ...defaultConfig, ...result.config };
411
+ }
412
+ } catch (error) {
413
+ console.warn(`Warning: Could not load config file: ${error}`);
414
+ }
415
+ return defaultConfig;
416
+ }
417
+
418
+ // src/dev/companion.ts
419
+ function generateCompanionCode(options) {
420
+ const {
421
+ serverUrl,
422
+ reconnectInterval = 3e3,
423
+ maxReconnectAttempts = 10
424
+ } = options;
425
+ return `
426
+ // MN Rails Debug Companion - Auto-generated, do not edit
427
+ (function() {
428
+ 'use strict';
429
+
430
+ const SERVER_URL = '${serverUrl}';
431
+ const RECONNECT_INTERVAL = ${reconnectInterval};
432
+ const MAX_RECONNECT_ATTEMPTS = ${maxReconnectAttempts};
433
+
434
+ let ws = null;
435
+ let reconnectAttempts = 0;
436
+ let reconnectTimer = null;
437
+ let isConnected = false;
438
+ let messageQueue = [];
439
+
440
+ // \u539F\u59CB console \u65B9\u6CD5
441
+ const originalConsole = {
442
+ log: console.log,
443
+ error: console.error,
444
+ warn: console.warn,
445
+ info: console.info
446
+ };
447
+
448
+ // \u53D1\u9001\u6D88\u606F\u5230\u670D\u52A1\u5668
449
+ function sendMessage(type, payload) {
450
+ const message = {
451
+ type: type,
452
+ payload: payload,
453
+ timestamp: Date.now()
454
+ };
455
+
456
+ if (isConnected && ws && ws.readyState === 1) {
457
+ try {
458
+ ws.send(JSON.stringify(message));
459
+ return true;
460
+ } catch (error) {
461
+ console.error('Failed to send message:', error);
462
+ return false;
463
+ }
464
+ } else {
465
+ // \u5982\u679C\u672A\u8FDE\u63A5\uFF0C\u5C06\u6D88\u606F\u52A0\u5165\u961F\u5217
466
+ messageQueue.push(message);
467
+ return false;
468
+ }
469
+ }
470
+
471
+ // \u5904\u7406\u6D88\u606F\u961F\u5217
472
+ function processMessageQueue() {
473
+ while (messageQueue.length > 0 && isConnected) {
474
+ const message = messageQueue.shift();
475
+ if (message && ws && ws.readyState === 1) {
476
+ try {
477
+ ws.send(JSON.stringify(message));
478
+ } catch (error) {
479
+ // \u53D1\u9001\u5931\u8D25\uFF0C\u91CD\u65B0\u52A0\u5165\u961F\u5217
480
+ messageQueue.unshift(message);
481
+ break;
482
+ }
483
+ }
484
+ }
485
+ }
486
+
487
+ // \u62E6\u622A console.log
488
+ console.log = function(...args) {
489
+ originalConsole.log.apply(console, arguments);
490
+ sendMessage('log', {
491
+ level: 'log',
492
+ args: args.map(arg => {
493
+ if (typeof arg === 'object') {
494
+ try {
495
+ return JSON.stringify(arg);
496
+ } catch (e) {
497
+ return String(arg);
498
+ }
499
+ }
500
+ return String(arg);
501
+ })
502
+ });
503
+ };
504
+
505
+ // \u62E6\u622A console.error
506
+ console.error = function(...args) {
507
+ originalConsole.error.apply(console, arguments);
508
+ sendMessage('error', {
509
+ level: 'error',
510
+ args: args.map(arg => {
511
+ if (typeof arg === 'object') {
512
+ try {
513
+ return JSON.stringify(arg);
514
+ } catch (e) {
515
+ return String(arg);
516
+ }
517
+ }
518
+ return String(arg);
519
+ })
520
+ });
521
+ };
522
+
523
+ // \u62E6\u622A console.warn
524
+ console.warn = function(...args) {
525
+ originalConsole.warn.apply(console, arguments);
526
+ sendMessage('warn', {
527
+ level: 'warn',
528
+ args: args.map(arg => {
529
+ if (typeof arg === 'object') {
530
+ try {
531
+ return JSON.stringify(arg);
532
+ } catch (e) {
533
+ return String(arg);
534
+ }
535
+ }
536
+ return String(arg);
537
+ })
538
+ });
539
+ };
540
+
541
+ // \u8FDE\u63A5 WebSocket \u670D\u52A1\u5668
542
+ function connect() {
543
+ try {
544
+ if (ws) {
545
+ ws.close();
546
+ }
547
+
548
+ ws = new WebSocket(SERVER_URL);
549
+
550
+ ws.onopen = function() {
551
+ isConnected = true;
552
+ reconnectAttempts = 0;
553
+ sendMessage('ready', { message: 'Debug companion connected' });
554
+ processMessageQueue();
555
+ };
556
+
557
+ ws.onmessage = function(event) {
558
+ try {
559
+ const message = JSON.parse(event.data);
560
+ handleServerMessage(message);
561
+ } catch (error) {
562
+ console.error('Failed to parse server message:', error);
563
+ }
564
+ };
565
+
566
+ ws.onerror = function(error) {
567
+ console.error('WebSocket error:', error);
568
+ };
569
+
570
+ ws.onclose = function() {
571
+ isConnected = false;
572
+ ws = null;
573
+
574
+ // \u5C1D\u8BD5\u91CD\u8FDE
575
+ if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
576
+ reconnectAttempts++;
577
+ reconnectTimer = setTimeout(function() {
578
+ connect();
579
+ }, RECONNECT_INTERVAL);
580
+ } else {
581
+ console.error('Max reconnect attempts reached. Debug companion disconnected.');
582
+ }
583
+ };
584
+ } catch (error) {
585
+ console.error('Failed to create WebSocket connection:', error);
586
+ // \u5C1D\u8BD5\u91CD\u8FDE
587
+ if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
588
+ reconnectAttempts++;
589
+ reconnectTimer = setTimeout(function() {
590
+ connect();
591
+ }, RECONNECT_INTERVAL);
592
+ }
593
+ }
594
+ }
595
+
596
+ // \u5B58\u50A8\u63D2\u4EF6\u5B9E\u4F8B\u548C\u6E05\u7406\u51FD\u6570
597
+ let pluginInstance = null;
598
+ let cleanupFunctions = [];
599
+
600
+ // \u6CE8\u518C\u6E05\u7406\u51FD\u6570
601
+ function registerCleanup(fn) {
602
+ cleanupFunctions.push(fn);
603
+ }
604
+
605
+ // \u6267\u884C\u6E05\u7406
606
+ function cleanup() {
607
+ cleanupFunctions.forEach(fn => {
608
+ try {
609
+ fn();
610
+ } catch (error) {
611
+ console.error('Cleanup error:', error);
612
+ }
613
+ });
614
+ cleanupFunctions = [];
615
+ pluginInstance = null;
616
+ }
617
+
618
+ // \u5904\u7406\u670D\u52A1\u5668\u6D88\u606F
619
+ function handleServerMessage(message) {
620
+ switch (message.type) {
621
+ case 'heartbeat':
622
+ // \u54CD\u5E94\u5FC3\u8DF3
623
+ sendMessage('heartbeat', { timestamp: Date.now() });
624
+ break;
625
+
626
+ case 'code-update':
627
+ // \u4EE3\u7801\u66F4\u65B0\u901A\u77E5
628
+ if (message.payload && message.payload.code) {
629
+ try {
630
+ // \u6267\u884C\u6E05\u7406
631
+ cleanup();
632
+
633
+ // \u6267\u884C\u65B0\u4EE3\u7801
634
+ const newCode = message.payload.code;
635
+ const executeCode = new Function(newCode);
636
+ executeCode();
637
+
638
+ console.log('Plugin reloaded successfully');
639
+ } catch (error) {
640
+ console.error('Failed to reload plugin:', error);
641
+ }
642
+ } else {
643
+ // \u5982\u679C\u6CA1\u6709\u4EE3\u7801\uFF0C\u53EA\u662F\u901A\u77E5\u66F4\u65B0
644
+ console.log('Code update detected, please restart the plugin');
645
+ }
646
+ break;
647
+
648
+ default:
649
+ // \u5FFD\u7565\u672A\u77E5\u6D88\u606F\u7C7B\u578B
650
+ break;
651
+ }
652
+ }
653
+
654
+ // \u521D\u59CB\u5316\u8FDE\u63A5
655
+ connect();
656
+
657
+ // \u5BFC\u51FA\u8FDE\u63A5\u72B6\u6001\u548C\u6E05\u7406\u51FD\u6570\uFF08\u7528\u4E8E\u70ED\u91CD\u8F7D\uFF09
658
+ if (typeof window !== 'undefined') {
659
+ window.__MN_RAILS_DEBUG__ = {
660
+ isConnected: function() { return isConnected; },
661
+ reconnect: connect,
662
+ sendMessage: sendMessage,
663
+ registerCleanup: registerCleanup,
664
+ cleanup: cleanup
665
+ };
666
+ }
667
+
668
+ // \u5168\u5C40\u5BFC\u51FA\uFF08\u7528\u4E8E MarginNote \u73AF\u5883\uFF09
669
+ if (typeof global !== 'undefined') {
670
+ global.__MN_RAILS_DEBUG__ = {
671
+ isConnected: function() { return isConnected; },
672
+ reconnect: connect,
673
+ sendMessage: sendMessage,
674
+ registerCleanup: registerCleanup,
675
+ cleanup: cleanup
676
+ };
677
+ }
678
+ })();
679
+ `;
680
+ }
681
+
682
+ // src/build/type-generator.ts
683
+ var fs2 = __toESM(require("fs-extra"), 1);
684
+ var path3 = __toESM(require("path"), 1);
685
+ var import_ts_morph = require("ts-morph");
686
+ function extractActionsType(objectLiteral, sourceFile) {
687
+ const signatures = [];
688
+ const properties = objectLiteral.getProperties();
689
+ for (const prop of properties) {
690
+ if (prop.getKindName() === "MethodDeclaration") {
691
+ const sig = extractMethodSignature(prop, sourceFile);
692
+ if (sig) signatures.push(sig);
693
+ } else if (prop.getKindName() === "PropertyAssignment") {
694
+ const propertyAssignment = prop;
695
+ const initializer = propertyAssignment.getInitializer();
696
+ if (initializer) {
697
+ const kindName = initializer.getKindName();
698
+ if (kindName === "ArrowFunction" || kindName === "FunctionExpression") {
699
+ const name = propertyAssignment.getName();
700
+ const sig = extractFunctionSignature(
701
+ name,
702
+ initializer,
703
+ sourceFile
704
+ );
705
+ if (sig) signatures.push(sig);
706
+ }
707
+ }
708
+ }
709
+ }
710
+ return signatures;
711
+ }
712
+ function extractMethodSignature(method, sourceFile) {
713
+ const name = method.getName();
714
+ if (!name) return null;
715
+ const params = method.getParameters().map((p) => {
716
+ const typeNode = p.getTypeNode();
717
+ return {
718
+ name: p.getName(),
719
+ type: typeNode ? typeNode.getText() : "any",
720
+ optional: p.hasQuestionToken(),
721
+ rest: p.isRestParameter()
722
+ };
723
+ });
724
+ let returnType = "any";
725
+ const returnTypeNode = method.getReturnTypeNode();
726
+ if (returnTypeNode) {
727
+ returnType = resolveReturnType(returnTypeNode.getText());
728
+ } else {
729
+ const returnTypeFromChecker = method.getReturnType();
730
+ if (returnTypeFromChecker) {
731
+ returnType = resolveReturnType(returnTypeFromChecker.getText());
732
+ }
733
+ }
734
+ return { name, params, returnType };
735
+ }
736
+ function extractFunctionSignature(name, fn, sourceFile) {
737
+ const params = fn.getParameters().map((p) => {
738
+ const typeNode = p.getTypeNode();
739
+ return {
740
+ name: p.getName(),
741
+ type: typeNode ? typeNode.getText() : "any",
742
+ optional: p.hasQuestionToken(),
743
+ rest: p.isRestParameter()
744
+ };
745
+ });
746
+ let returnType = "any";
747
+ const returnTypeNode = fn.getReturnTypeNode();
748
+ if (returnTypeNode) {
749
+ returnType = resolveReturnType(returnTypeNode.getText());
750
+ } else {
751
+ const returnTypeFromChecker = fn.getReturnType();
752
+ if (returnTypeFromChecker) {
753
+ returnType = resolveReturnType(returnTypeFromChecker.getText());
754
+ }
755
+ }
756
+ return { name, params, returnType };
757
+ }
758
+ function resolveReturnType(raw) {
759
+ if (raw.startsWith("Promise<")) return raw;
760
+ return `Promise<${raw}>`;
761
+ }
762
+ function formatActionSignature(sig) {
763
+ const params = sig.params.map((p) => {
764
+ const rest = p.rest ? "..." : "";
765
+ const optional = p.optional ? "?" : "";
766
+ return `${rest}${p.name}${optional}: ${p.type}`;
767
+ }).join(", ");
768
+ return ` ${sig.name}: (${params}) => ${sig.returnType};`;
769
+ }
770
+ async function parseAllControllers(projectRoot, onWarn) {
771
+ const srcDir = path3.join(projectRoot, "src");
772
+ const controllersDir = path3.join(srcDir, "controllers");
773
+ if (!fs2.existsSync(controllersDir)) {
774
+ return [];
775
+ }
776
+ const controllerFiles = fs2.readdirSync(controllersDir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
777
+ if (controllerFiles.length === 0) {
778
+ return [];
779
+ }
780
+ const controllerInfos = [];
781
+ const project = new import_ts_morph.Project();
782
+ for (const file of controllerFiles) {
783
+ const filePath = path3.join(controllersDir, file);
784
+ try {
785
+ const sourceFile = project.addSourceFileAtPath(filePath);
786
+ const classes = sourceFile.getClasses();
787
+ for (const classDecl of classes) {
788
+ const className = classDecl.getName();
789
+ if (!className) continue;
790
+ const extendsController = classDecl.getExtends()?.getText().includes("Controller") ?? false;
791
+ if (!extendsController) continue;
792
+ const actionsProperty = classDecl.getProperty("actions");
793
+ if (!actionsProperty) continue;
794
+ const initializer = actionsProperty.getInitializer();
795
+ if (!initializer || !initializer.getKindName().includes("ObjectLiteral")) {
796
+ continue;
797
+ }
798
+ const actionsObject = initializer;
799
+ const actions = extractActionsType(actionsObject, sourceFile);
800
+ if (actions.length === 0) {
801
+ continue;
802
+ }
803
+ controllerInfos.push({
804
+ name: className,
805
+ filePath,
806
+ actions
807
+ });
808
+ break;
809
+ }
810
+ } catch (err) {
811
+ const msg = `Failed to parse Controller in ${filePath}: ${err instanceof Error ? err.message : String(err)}`;
812
+ onWarn?.(msg);
813
+ }
814
+ }
815
+ return controllerInfos;
816
+ }
817
+ async function generateTypesForInjection(projectRoot, onWarn) {
818
+ const controllerInfos = await parseAllControllers(projectRoot, onWarn);
819
+ if (controllerInfos.length === 0) {
820
+ return null;
821
+ }
822
+ const lines = [];
823
+ lines.push("// Auto-generated bridge types - injected by MN Rails");
824
+ lines.push("// Do not edit this section manually");
825
+ lines.push("");
826
+ lines.push("interface BridgeActions {");
827
+ for (const info of controllerInfos) {
828
+ lines.push(` ${info.name}: {`);
829
+ for (const sig of info.actions) {
830
+ lines.push(formatActionSignature(sig));
831
+ }
832
+ lines.push(" };");
833
+ }
834
+ lines.push("}");
835
+ lines.push("");
836
+ lines.push("declare const bridge: BridgeActions;");
837
+ lines.push("");
838
+ return lines.join("\n");
839
+ }
840
+ async function generateTypes(options) {
841
+ const { projectRoot, outputPath, onWarn } = options;
842
+ const controllerInfos = await parseAllControllers(projectRoot, onWarn);
843
+ if (controllerInfos.length === 0) {
844
+ return;
845
+ }
846
+ const lines = [];
847
+ lines.push("// Auto-generated bridge types");
848
+ lines.push("// Do not edit this file manually");
849
+ lines.push("");
850
+ lines.push("export interface BridgeActions {");
851
+ for (const info of controllerInfos) {
852
+ lines.push(` ${info.name}: {`);
853
+ for (const sig of info.actions) {
854
+ lines.push(formatActionSignature(sig));
855
+ }
856
+ lines.push(" };");
857
+ }
858
+ lines.push("}");
859
+ lines.push("");
860
+ lines.push("declare global {");
861
+ lines.push(" const bridge: BridgeActions;");
862
+ lines.push("}");
863
+ lines.push("");
864
+ const output = outputPath || path3.join(projectRoot, "src", "bridge-types.ts");
865
+ await fs2.ensureDir(path3.dirname(output));
866
+ await fs2.writeFile(output, lines.join("\n"), "utf-8");
867
+ }
868
+
869
+ // src/build/index.ts
870
+ async function buildPlugin(projectRoot, options = {}) {
871
+ const srcDir = path4.join(projectRoot, "src");
872
+ const distDir = path4.join(projectRoot, "dist");
873
+ if (!fs3.existsSync(srcDir)) {
874
+ throw new Error("src directory not found");
875
+ }
876
+ if (fs3.existsSync(distDir)) {
877
+ fs3.removeSync(distDir);
878
+ }
879
+ let typeDefinitionsForInjection = null;
880
+ try {
881
+ await generateTypes({
882
+ projectRoot,
883
+ onWarn: (msg) => console.warn(import_chalk2.default.yellow(`Type gen: ${msg}`))
884
+ });
885
+ typeDefinitionsForInjection = await generateTypesForInjection(
886
+ projectRoot,
887
+ (msg) => console.warn(import_chalk2.default.yellow(`Type gen: ${msg}`))
888
+ );
889
+ console.log(import_chalk2.default.blue("\u2713 Bridge types generated"));
890
+ } catch (error) {
891
+ console.warn(import_chalk2.default.yellow(`Warning: Failed to generate types: ${error}`));
892
+ }
893
+ console.log(import_chalk2.default.blue("Compiling TypeScript..."));
894
+ try {
895
+ (0, import_child_process.execSync)("tsc", {
896
+ cwd: projectRoot,
897
+ stdio: "inherit"
898
+ });
899
+ } catch (error) {
900
+ throw new Error("TypeScript compilation failed");
901
+ }
902
+ const config = await loadConfig(projectRoot);
903
+ const addonDir = path4.join(projectRoot, "dist", "addon");
904
+ fs3.ensureDirSync(addonDir);
905
+ const copyFiles = (src, dest) => {
906
+ const entries = fs3.readdirSync(src, { withFileTypes: true });
907
+ for (const entry of entries) {
908
+ const srcPath = path4.join(src, entry.name);
909
+ const destPath = path4.join(dest, entry.name);
910
+ if (entry.isDirectory()) {
911
+ fs3.ensureDirSync(destPath);
912
+ copyFiles(srcPath, destPath);
913
+ } else if (entry.name.endsWith(".js") || entry.name.endsWith(".d.ts")) {
914
+ fs3.copySync(srcPath, destPath);
915
+ }
916
+ }
917
+ };
918
+ const collectFilesByExt = (dir, ext, base = "") => {
919
+ const results = [];
920
+ const entries = fs3.readdirSync(dir, { withFileTypes: true });
921
+ for (const e of entries) {
922
+ const rel = base ? `${base}/${e.name}` : e.name;
923
+ if (e.isDirectory()) {
924
+ results.push(...collectFilesByExt(path4.join(dir, e.name), ext, rel));
925
+ } else if (e.name.endsWith(ext)) {
926
+ results.push(rel);
927
+ }
928
+ }
929
+ return results;
930
+ };
931
+ const copyViewsExcept = (src, dest, skipExts) => {
932
+ const entries = fs3.readdirSync(src, { withFileTypes: true });
933
+ for (const entry of entries) {
934
+ const srcPath = path4.join(src, entry.name);
935
+ const destPath = path4.join(dest, entry.name);
936
+ if (entry.isDirectory()) {
937
+ fs3.ensureDirSync(destPath);
938
+ copyViewsExcept(srcPath, destPath, skipExts);
939
+ } else {
940
+ const skip = skipExts.some((ext) => entry.name.endsWith(ext));
941
+ if (!skip) {
942
+ fs3.copySync(srcPath, destPath);
943
+ }
944
+ }
945
+ }
946
+ };
947
+ copyFiles(distDir, addonDir);
948
+ const srcViewsDir = path4.join(projectRoot, "src", "views");
949
+ if (fs3.existsSync(srcViewsDir)) {
950
+ const addonViewsDir = path4.join(addonDir, "views");
951
+ fs3.ensureDirSync(addonViewsDir);
952
+ let typeDefinitions = null;
953
+ try {
954
+ typeDefinitions = await generateTypesForInjection(
955
+ projectRoot,
956
+ (msg) => console.warn(import_chalk2.default.yellow(`Type gen: ${msg}`))
957
+ );
958
+ } catch (error) {
959
+ console.warn(import_chalk2.default.yellow(`Warning: Failed to generate types for injection: ${error}`));
960
+ }
961
+ const jsxFiles = collectFilesByExt(srcViewsDir, ".jsx");
962
+ if (jsxFiles.length > 0) {
963
+ const esbuild = await import("esbuild");
964
+ for (const rel of jsxFiles) {
965
+ const inputPath = path4.join(srcViewsDir, rel);
966
+ const outRel = rel.replace(/\.jsx$/, ".js");
967
+ const outputPath = path4.join(addonViewsDir, outRel);
968
+ fs3.ensureDirSync(path4.dirname(outputPath));
969
+ await esbuild.build({
970
+ entryPoints: [inputPath],
971
+ bundle: false,
972
+ format: "iife",
973
+ target: "es2015",
974
+ outfile: outputPath,
975
+ jsx: "transform"
976
+ });
977
+ if (typeDefinitions) {
978
+ let content = fs3.readFileSync(outputPath, "utf-8");
979
+ const typeComment = `/**
980
+ * @typedef {Object} BridgeActions
981
+ ${typeDefinitions.split("\n").filter((l) => l.trim() && !l.startsWith("//")).map((l) => ` * ${l}`).join("\n")}
982
+ */
983
+ `;
984
+ content = typeComment + content;
985
+ fs3.writeFileSync(outputPath, content, "utf-8");
986
+ }
987
+ }
988
+ console.log(import_chalk2.default.blue(`\u2713 Compiled ${jsxFiles.length} JSX file(s)`));
989
+ }
990
+ const vueFiles = collectFilesByExt(srcViewsDir, ".vue");
991
+ if (vueFiles.length > 0) {
992
+ const { compileTemplate, compileScript, parse } = await import("@vue/compiler-sfc");
993
+ const esbuild = await import("esbuild");
994
+ for (const rel of vueFiles) {
995
+ const inputPath = path4.join(srcViewsDir, rel);
996
+ const outRel = rel.replace(/\.vue$/, ".js");
997
+ const outputPath = path4.join(addonViewsDir, outRel);
998
+ fs3.ensureDirSync(path4.dirname(outputPath));
999
+ try {
1000
+ const source = fs3.readFileSync(inputPath, "utf-8");
1001
+ const { descriptor, errors } = parse(source, { filename: inputPath });
1002
+ if (errors.length > 0) {
1003
+ console.warn(import_chalk2.default.yellow(`Vue parse warnings for ${rel}:`));
1004
+ errors.forEach((err) => console.warn(import_chalk2.default.yellow(` - ${err.message}`)));
1005
+ }
1006
+ const componentId = `vue-${rel.replace(/[^a-z0-9]/gi, "-")}`;
1007
+ let scriptCode = "";
1008
+ if (descriptor.script || descriptor.scriptSetup) {
1009
+ try {
1010
+ const compiled = compileScript(descriptor, {
1011
+ id: componentId,
1012
+ inlineTemplate: !!descriptor.template
1013
+ });
1014
+ scriptCode = compiled.content;
1015
+ } catch (error) {
1016
+ console.warn(import_chalk2.default.yellow(`Failed to compile script for ${rel}: ${error instanceof Error ? error.message : String(error)}`));
1017
+ scriptCode = "export default {};";
1018
+ }
1019
+ } else {
1020
+ scriptCode = "export default {};";
1021
+ }
1022
+ if (descriptor.template && !descriptor.scriptSetup) {
1023
+ try {
1024
+ const templateCompiled = compileTemplate({
1025
+ source: descriptor.template.content,
1026
+ filename: inputPath,
1027
+ id: componentId,
1028
+ compilerOptions: {
1029
+ mode: "module"
1030
+ }
1031
+ });
1032
+ if (templateCompiled.code) {
1033
+ scriptCode = scriptCode.replace(
1034
+ /export\s+default\s+\{/,
1035
+ `const render = ${templateCompiled.code};
1036
+ export default { render,`
1037
+ );
1038
+ }
1039
+ } catch (error) {
1040
+ console.warn(import_chalk2.default.yellow(`Failed to compile template for ${rel}: ${error instanceof Error ? error.message : String(error)}`));
1041
+ }
1042
+ }
1043
+ if (descriptor.styles && descriptor.styles.length > 0) {
1044
+ const styleContent = descriptor.styles.map((style) => style.content).join("\n");
1045
+ const stylePath = outputPath.replace(/\.js$/, ".css");
1046
+ fs3.writeFileSync(stylePath, styleContent, "utf-8");
1047
+ }
1048
+ const result = await esbuild.transform(scriptCode, {
1049
+ loader: descriptor.script?.lang === "ts" || descriptor.scriptSetup?.lang === "ts" ? "ts" : "js",
1050
+ target: "es2015",
1051
+ format: "iife"
1052
+ });
1053
+ let compiledCode = result.code;
1054
+ if (typeDefinitions) {
1055
+ const typeComment = `/**
1056
+ * @typedef {Object} BridgeActions
1057
+ ${typeDefinitions.split("\n").filter((l) => l.trim() && !l.startsWith("//")).map((l) => ` * ${l}`).join("\n")}
1058
+ */
1059
+ `;
1060
+ compiledCode = typeComment + compiledCode;
1061
+ }
1062
+ fs3.writeFileSync(outputPath, compiledCode, "utf-8");
1063
+ } catch (error) {
1064
+ console.error(import_chalk2.default.red(`Failed to compile Vue file ${rel}: ${error instanceof Error ? error.message : String(error)}`));
1065
+ }
1066
+ }
1067
+ console.log(import_chalk2.default.blue(`\u2713 Compiled ${vueFiles.length} Vue file(s)`));
1068
+ }
1069
+ const injectTypesIntoFile = (srcPath, destPath) => {
1070
+ if (srcPath.endsWith(".vue") || srcPath.endsWith(".jsx")) {
1071
+ throw new Error(
1072
+ `Source file detected: ${srcPath}. Source files (.vue, .jsx) should not be copied to output directory. They must be compiled first. Please check your build configuration.`
1073
+ );
1074
+ }
1075
+ if (srcPath.endsWith(".js") || srcPath.endsWith(".ts")) {
1076
+ let content = fs3.readFileSync(srcPath, "utf-8");
1077
+ if (srcPath.endsWith(".ts") && typeDefinitions) {
1078
+ content = typeDefinitions + "\n\n" + content;
1079
+ } else if (srcPath.endsWith(".js") && typeDefinitions) {
1080
+ const typeComment = `/**
1081
+ * @typedef {Object} BridgeActions
1082
+ ${typeDefinitions.split("\n").filter((l) => l.trim() && !l.startsWith("//")).map((l) => ` * ${l}`).join("\n")}
1083
+ */
1084
+ `;
1085
+ content = typeComment + content;
1086
+ }
1087
+ fs3.writeFileSync(destPath, content, "utf-8");
1088
+ } else {
1089
+ fs3.copySync(srcPath, destPath);
1090
+ }
1091
+ };
1092
+ const copyViewsWithTypeInjection = (src, dest, skipExts) => {
1093
+ const entries = fs3.readdirSync(src, { withFileTypes: true });
1094
+ for (const entry of entries) {
1095
+ const srcPath = path4.join(src, entry.name);
1096
+ const destPath = path4.join(dest, entry.name);
1097
+ if (entry.isDirectory()) {
1098
+ fs3.ensureDirSync(destPath);
1099
+ copyViewsWithTypeInjection(srcPath, destPath, skipExts);
1100
+ } else {
1101
+ const skip = skipExts.some((ext) => entry.name.endsWith(ext));
1102
+ if (!skip) {
1103
+ injectTypesIntoFile(srcPath, destPath);
1104
+ }
1105
+ }
1106
+ }
1107
+ };
1108
+ copyViewsWithTypeInjection(srcViewsDir, addonViewsDir, [".jsx", ".vue"]);
1109
+ console.log(import_chalk2.default.blue("\u2713 Views copied"));
1110
+ }
1111
+ const indexFile = path4.join(addonDir, "index.js");
1112
+ const mainFile = path4.join(addonDir, "main.js");
1113
+ if (fs3.existsSync(indexFile)) {
1114
+ let mainContent = fs3.readFileSync(indexFile, "utf-8");
1115
+ if (options.devMode && options.devServerUrl) {
1116
+ const companionCode = generateCompanionCode({
1117
+ serverUrl: options.devServerUrl
1118
+ });
1119
+ mainContent = companionCode + "\n" + mainContent;
1120
+ console.log(import_chalk2.default.blue("\u2713 Debug companion injected"));
1121
+ }
1122
+ fs3.writeFileSync(mainFile, mainContent);
1123
+ fs3.removeSync(indexFile);
1124
+ }
1125
+ if (typeDefinitionsForInjection) {
1126
+ const typesFile = path4.join(addonDir, "bridge-types.js");
1127
+ const jsdocTypes = typeDefinitionsForInjection.split("\n").filter((line) => line.trim() && !line.startsWith("//")).map((line) => ` * ${line}`).join("\n");
1128
+ const typesCode = `/**
1129
+ * Auto-generated bridge types for runtime injection
1130
+ * @typedef {Object} BridgeActions
1131
+ ${jsdocTypes}
1132
+ * @typedef {BridgeActions} bridge
1133
+ */
1134
+ `;
1135
+ fs3.writeFileSync(typesFile, typesCode, "utf-8");
1136
+ }
1137
+ const addonJson = {
1138
+ key: config.name || path4.basename(projectRoot),
1139
+ title: config.title || config.name || path4.basename(projectRoot),
1140
+ version: config.version || "0.1.0",
1141
+ author: config.author || "",
1142
+ main: "main.js"
1143
+ };
1144
+ fs3.writeJSONSync(path4.join(addonDir, "addon.json"), addonJson, { spaces: 2 });
1145
+ const addonName = `${addonJson.key}-${addonJson.version}.mnaddon`;
1146
+ const addonPath = path4.join(projectRoot, addonName);
1147
+ try {
1148
+ (0, import_child_process.execSync)(`cd ${addonDir} && zip -r ${addonPath} .`, {
1149
+ stdio: "inherit"
1150
+ });
1151
+ console.log(import_chalk2.default.green(`\u2713 Plugin packaged: ${addonName}`));
1152
+ } catch (error) {
1153
+ console.warn(import_chalk2.default.yellow("\u26A0\uFE0F Could not create .mnaddon file (zip command not found)"));
1154
+ console.log(import_chalk2.default.cyan(` Plugin files are in: ${addonDir}`));
1155
+ }
1156
+ }
1157
+
1158
+ // src/commands/build.ts
1159
+ async function buildCommand(options = {}) {
1160
+ try {
1161
+ const projectRoot = process.cwd();
1162
+ const configPath = path5.join(projectRoot, "mn-rails.config.js");
1163
+ if (!fs4.existsSync(configPath)) {
1164
+ console.error(import_chalk3.default.red("Error: mn-rails.config.js not found. Are you in a plugin project?"));
1165
+ process.exit(1);
1166
+ }
1167
+ console.log(import_chalk3.default.blue("Building plugin..."));
1168
+ await buildPlugin(projectRoot, {
1169
+ watch: options.watch || false
1170
+ });
1171
+ if (!options.watch) {
1172
+ console.log(import_chalk3.default.green("\u2713 Build completed successfully!"));
1173
+ }
1174
+ } catch (error) {
1175
+ console.error(import_chalk3.default.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1176
+ process.exit(1);
1177
+ }
1178
+ }
1179
+
1180
+ // src/commands/dev.ts
1181
+ var import_chalk7 = __toESM(require("chalk"), 1);
1182
+ var path8 = __toESM(require("path"), 1);
1183
+
1184
+ // src/dev/server.ts
1185
+ var import_ws = require("ws");
1186
+ var import_chalk4 = __toESM(require("chalk"), 1);
1187
+
1188
+ // src/dev/protocol.ts
1189
+ function createMessage(type, payload, id) {
1190
+ return {
1191
+ type,
1192
+ payload,
1193
+ timestamp: Date.now(),
1194
+ id
1195
+ };
1196
+ }
1197
+ function parseMessage(data) {
1198
+ try {
1199
+ const message = JSON.parse(data);
1200
+ if (message.type && message.payload !== void 0) {
1201
+ return message;
1202
+ }
1203
+ return null;
1204
+ } catch (error) {
1205
+ return null;
1206
+ }
1207
+ }
1208
+ function serializeMessage(message) {
1209
+ return JSON.stringify(message);
1210
+ }
1211
+
1212
+ // src/dev/server.ts
1213
+ var DevServer = class {
1214
+ wss = null;
1215
+ clients = /* @__PURE__ */ new Set();
1216
+ options;
1217
+ heartbeatInterval = null;
1218
+ constructor(options) {
1219
+ this.options = options;
1220
+ }
1221
+ /**
1222
+ * 启动服务器
1223
+ */
1224
+ start() {
1225
+ return new Promise((resolve2, reject) => {
1226
+ try {
1227
+ this.wss = new import_ws.WebSocketServer({ port: this.options.port });
1228
+ this.wss.on("listening", () => {
1229
+ console.log(
1230
+ import_chalk4.default.green(`\u2713 Development server started on ws://localhost:${this.options.port}`)
1231
+ );
1232
+ this.startHeartbeat();
1233
+ resolve2();
1234
+ });
1235
+ this.wss.on("error", (error) => {
1236
+ console.error(import_chalk4.default.red(`WebSocket server error: ${error.message}`));
1237
+ reject(error);
1238
+ });
1239
+ this.wss.on("connection", (ws) => {
1240
+ this.handleConnection(ws);
1241
+ });
1242
+ } catch (error) {
1243
+ reject(error);
1244
+ }
1245
+ });
1246
+ }
1247
+ /**
1248
+ * 停止服务器
1249
+ */
1250
+ stop() {
1251
+ return new Promise((resolve2) => {
1252
+ if (this.heartbeatInterval) {
1253
+ clearInterval(this.heartbeatInterval);
1254
+ this.heartbeatInterval = null;
1255
+ }
1256
+ if (this.wss) {
1257
+ this.clients.forEach((ws) => {
1258
+ ws.close();
1259
+ });
1260
+ this.clients.clear();
1261
+ this.wss.close(() => {
1262
+ console.log(import_chalk4.default.yellow("Development server stopped"));
1263
+ resolve2();
1264
+ });
1265
+ this.wss = null;
1266
+ } else {
1267
+ resolve2();
1268
+ }
1269
+ });
1270
+ }
1271
+ /**
1272
+ * 处理新连接
1273
+ */
1274
+ handleConnection(ws) {
1275
+ this.clients.add(ws);
1276
+ console.log(import_chalk4.default.cyan(`Client connected (${this.clients.size} total)`));
1277
+ this.send(ws, createMessage("connected", { message: "Connected to dev server" }));
1278
+ if (this.options.onConnection) {
1279
+ this.options.onConnection(ws);
1280
+ }
1281
+ ws.on("message", (data) => {
1282
+ const message = parseMessage(data.toString());
1283
+ if (message) {
1284
+ this.handleMessage(ws, message);
1285
+ }
1286
+ });
1287
+ ws.on("close", () => {
1288
+ this.clients.delete(ws);
1289
+ console.log(import_chalk4.default.yellow(`Client disconnected (${this.clients.size} total)`));
1290
+ if (this.options.onDisconnection) {
1291
+ this.options.onDisconnection(ws);
1292
+ }
1293
+ });
1294
+ ws.on("error", (error) => {
1295
+ console.error(import_chalk4.default.red(`WebSocket error: ${error.message}`));
1296
+ });
1297
+ }
1298
+ /**
1299
+ * 处理消息
1300
+ */
1301
+ handleMessage(ws, message) {
1302
+ if (message.type === "heartbeat") {
1303
+ this.send(ws, createMessage("heartbeat", { timestamp: Date.now() }));
1304
+ return;
1305
+ }
1306
+ if (message.type === "ready") {
1307
+ console.log(import_chalk4.default.green("Client ready"));
1308
+ return;
1309
+ }
1310
+ if (this.options.onMessage) {
1311
+ this.options.onMessage(ws, message);
1312
+ }
1313
+ }
1314
+ /**
1315
+ * 发送消息到指定客户端
1316
+ */
1317
+ send(ws, message) {
1318
+ if (ws.readyState === import_ws.WebSocket.OPEN) {
1319
+ try {
1320
+ ws.send(serializeMessage(message));
1321
+ return true;
1322
+ } catch (error) {
1323
+ console.error(import_chalk4.default.red(`Failed to send message: ${error}`));
1324
+ return false;
1325
+ }
1326
+ }
1327
+ return false;
1328
+ }
1329
+ /**
1330
+ * 广播消息到所有客户端
1331
+ */
1332
+ broadcast(message) {
1333
+ let count = 0;
1334
+ this.clients.forEach((ws) => {
1335
+ if (this.send(ws, message)) {
1336
+ count++;
1337
+ }
1338
+ });
1339
+ return count;
1340
+ }
1341
+ /**
1342
+ * 获取客户端数量
1343
+ */
1344
+ getClientCount() {
1345
+ return this.clients.size;
1346
+ }
1347
+ /**
1348
+ * 启动心跳检测
1349
+ */
1350
+ startHeartbeat() {
1351
+ this.heartbeatInterval = setInterval(() => {
1352
+ this.clients.forEach((ws) => {
1353
+ if (ws.readyState === import_ws.WebSocket.OPEN) {
1354
+ this.send(ws, createMessage("heartbeat", { timestamp: Date.now() }));
1355
+ } else {
1356
+ this.clients.delete(ws);
1357
+ }
1358
+ });
1359
+ }, 3e4);
1360
+ }
1361
+ };
1362
+
1363
+ // src/dev/watcher.ts
1364
+ var path6 = __toESM(require("path"), 1);
1365
+ var import_chokidar = __toESM(require("chokidar"), 1);
1366
+ var import_chalk5 = __toESM(require("chalk"), 1);
1367
+ var FileWatcher = class {
1368
+ watcher = null;
1369
+ options;
1370
+ constructor(options) {
1371
+ this.options = options;
1372
+ }
1373
+ /**
1374
+ * 开始监听
1375
+ */
1376
+ start() {
1377
+ return new Promise((resolve2, reject) => {
1378
+ try {
1379
+ const watchPaths = this.options.patterns.map(
1380
+ (pattern) => path6.join(this.options.rootDir, pattern)
1381
+ );
1382
+ const ignored = [
1383
+ "**/node_modules/**",
1384
+ "**/dist/**",
1385
+ "**/.git/**",
1386
+ "**/*.mnaddon",
1387
+ ...this.options.ignored || []
1388
+ ];
1389
+ this.watcher = import_chokidar.default.watch(watchPaths, {
1390
+ ignored,
1391
+ persistent: true,
1392
+ ignoreInitial: true,
1393
+ awaitWriteFinish: {
1394
+ stabilityThreshold: 200,
1395
+ pollInterval: 100
1396
+ }
1397
+ });
1398
+ this.watcher.on("ready", () => {
1399
+ console.log(import_chalk5.default.blue("\u2713 File watcher started"));
1400
+ console.log(import_chalk5.default.gray(`Watching: ${watchPaths.join(", ")}`));
1401
+ resolve2();
1402
+ });
1403
+ this.watcher.on("change", (filePath) => {
1404
+ console.log(import_chalk5.default.cyan(`File changed: ${path6.relative(this.options.rootDir, filePath)}`));
1405
+ if (this.options.onFileChange) {
1406
+ this.options.onFileChange(filePath);
1407
+ }
1408
+ });
1409
+ this.watcher.on("error", (err) => {
1410
+ const error = err instanceof Error ? err : new Error(String(err));
1411
+ console.error(import_chalk5.default.red(`File watcher error: ${error.message}`));
1412
+ if (this.options.onError) {
1413
+ this.options.onError(error);
1414
+ }
1415
+ });
1416
+ this.watcher.on("add", (filePath) => {
1417
+ console.log(import_chalk5.default.gray(`File added: ${path6.relative(this.options.rootDir, filePath)}`));
1418
+ });
1419
+ this.watcher.on("unlink", (filePath) => {
1420
+ console.log(import_chalk5.default.gray(`File removed: ${path6.relative(this.options.rootDir, filePath)}`));
1421
+ });
1422
+ } catch (error) {
1423
+ reject(error);
1424
+ }
1425
+ });
1426
+ }
1427
+ /**
1428
+ * 停止监听
1429
+ */
1430
+ async stop() {
1431
+ if (this.watcher) {
1432
+ await this.watcher.close();
1433
+ this.watcher = null;
1434
+ console.log(import_chalk5.default.yellow("File watcher stopped"));
1435
+ }
1436
+ }
1437
+ };
1438
+
1439
+ // src/dev/builder.ts
1440
+ var import_chalk6 = __toESM(require("chalk"), 1);
1441
+
1442
+ // src/dev/code-loader.ts
1443
+ var path7 = __toESM(require("path"), 1);
1444
+ var fs5 = __toESM(require("fs-extra"), 1);
1445
+ function loadPluginCode(projectRoot) {
1446
+ try {
1447
+ const mainFile = path7.join(projectRoot, "dist", "addon", "main.js");
1448
+ if (!fs5.existsSync(mainFile)) {
1449
+ return null;
1450
+ }
1451
+ let code = fs5.readFileSync(mainFile, "utf-8");
1452
+ const companionEndMarker = "})();";
1453
+ const companionIndex = code.indexOf(companionEndMarker);
1454
+ if (companionIndex !== -1) {
1455
+ code = code.substring(companionIndex + companionEndMarker.length).trim();
1456
+ }
1457
+ return code;
1458
+ } catch (error) {
1459
+ console.error(`Failed to load plugin code: ${error}`);
1460
+ return null;
1461
+ }
1462
+ }
1463
+
1464
+ // src/dev/builder.ts
1465
+ var DevBuilder = class {
1466
+ options;
1467
+ isBuilding = false;
1468
+ buildQueue = [];
1469
+ processingQueue = false;
1470
+ constructor(options) {
1471
+ this.options = options;
1472
+ }
1473
+ /**
1474
+ * 触发构建
1475
+ */
1476
+ async build() {
1477
+ if (this.isBuilding) {
1478
+ return new Promise((resolve2) => {
1479
+ this.buildQueue.push(async () => {
1480
+ await this.doBuild();
1481
+ resolve2();
1482
+ });
1483
+ this.processQueue();
1484
+ });
1485
+ }
1486
+ return this.doBuild();
1487
+ }
1488
+ /**
1489
+ * 执行构建
1490
+ */
1491
+ async doBuild() {
1492
+ if (this.isBuilding) {
1493
+ return;
1494
+ }
1495
+ this.isBuilding = true;
1496
+ if (this.options.onBuildStart) {
1497
+ this.options.onBuildStart();
1498
+ }
1499
+ try {
1500
+ console.log(import_chalk6.default.blue("Building plugin..."));
1501
+ await buildPlugin(this.options.projectRoot, {
1502
+ watch: false,
1503
+ devMode: true,
1504
+ devServerUrl: this.options.devServerUrl
1505
+ });
1506
+ const code = loadPluginCode(this.options.projectRoot);
1507
+ if (this.options.onBuildComplete) {
1508
+ this.options.onBuildComplete(true, void 0, code || void 0);
1509
+ }
1510
+ console.log(import_chalk6.default.green("\u2713 Build completed"));
1511
+ } catch (error) {
1512
+ const err = error instanceof Error ? error : new Error(String(error));
1513
+ console.error(import_chalk6.default.red(`\u2717 Build failed: ${err.message}`));
1514
+ if (this.options.onBuildComplete) {
1515
+ this.options.onBuildComplete(false, err);
1516
+ }
1517
+ } finally {
1518
+ this.isBuilding = false;
1519
+ this.processQueue();
1520
+ }
1521
+ }
1522
+ /**
1523
+ * 处理构建队列
1524
+ */
1525
+ async processQueue() {
1526
+ if (this.processingQueue || this.buildQueue.length === 0) {
1527
+ return;
1528
+ }
1529
+ this.processingQueue = true;
1530
+ while (this.buildQueue.length > 0 && !this.isBuilding) {
1531
+ const task = this.buildQueue.shift();
1532
+ if (task) {
1533
+ await task();
1534
+ }
1535
+ }
1536
+ this.processingQueue = false;
1537
+ }
1538
+ /**
1539
+ * 检查是否正在构建
1540
+ */
1541
+ get building() {
1542
+ return this.isBuilding;
1543
+ }
1544
+ };
1545
+
1546
+ // src/dev/logger.ts
1547
+ var import_consola = require("consola");
1548
+ var logger = (0, import_consola.createConsola)({
1549
+ formatOptions: {
1550
+ date: true,
1551
+ colors: true,
1552
+ compact: false
1553
+ }
1554
+ });
1555
+ function printLogMessage(message) {
1556
+ const { level, args } = message;
1557
+ const parsedArgs = args.map((arg) => {
1558
+ try {
1559
+ return JSON.parse(arg);
1560
+ } catch {
1561
+ return arg;
1562
+ }
1563
+ });
1564
+ switch (level) {
1565
+ case "error":
1566
+ if (parsedArgs.length > 0) {
1567
+ logger.error(...parsedArgs);
1568
+ } else {
1569
+ logger.error("");
1570
+ }
1571
+ break;
1572
+ case "warn":
1573
+ if (parsedArgs.length > 0) {
1574
+ logger.warn(...parsedArgs);
1575
+ } else {
1576
+ logger.warn("");
1577
+ }
1578
+ break;
1579
+ case "info":
1580
+ if (parsedArgs.length > 0) {
1581
+ logger.info(...parsedArgs);
1582
+ } else {
1583
+ logger.info("");
1584
+ }
1585
+ break;
1586
+ case "log":
1587
+ default:
1588
+ if (parsedArgs.length > 0) {
1589
+ logger.log(...parsedArgs);
1590
+ } else {
1591
+ logger.log("");
1592
+ }
1593
+ break;
1594
+ }
1595
+ }
1596
+
1597
+ // src/commands/dev.ts
1598
+ async function devCommand(options = {}) {
1599
+ const port = parseInt(options.port || "3000", 10);
1600
+ const projectRoot = process.cwd();
1601
+ console.log(import_chalk7.default.blue("Starting development server..."));
1602
+ const configPath = path8.join(projectRoot, "mn-rails.config.js");
1603
+ try {
1604
+ const fs6 = await import("fs-extra");
1605
+ if (!fs6.existsSync(configPath)) {
1606
+ console.error(import_chalk7.default.red("Error: mn-rails.config.js not found. Are you in a plugin project?"));
1607
+ process.exit(1);
1608
+ }
1609
+ } catch (error) {
1610
+ console.error(import_chalk7.default.red(`Error checking project: ${error}`));
1611
+ process.exit(1);
1612
+ }
1613
+ let isFirstBuild = true;
1614
+ const devServerUrl = `ws://localhost:${port}`;
1615
+ const builder = new DevBuilder({
1616
+ projectRoot,
1617
+ devServerUrl,
1618
+ onBuildStart: () => {
1619
+ if (!isFirstBuild) {
1620
+ console.log(import_chalk7.default.blue("Rebuilding..."));
1621
+ }
1622
+ },
1623
+ onBuildComplete: async (success, error, code) => {
1624
+ if (success && code) {
1625
+ const updateMessage = {
1626
+ type: "code-update",
1627
+ payload: {
1628
+ message: "Build completed",
1629
+ code
1630
+ },
1631
+ timestamp: Date.now()
1632
+ };
1633
+ const count = server.broadcast(updateMessage);
1634
+ if (count > 0) {
1635
+ console.log(import_chalk7.default.green(`\u2713 Code update sent to ${count} client(s)`));
1636
+ }
1637
+ if (isFirstBuild) {
1638
+ isFirstBuild = false;
1639
+ const fs6 = await import("fs-extra");
1640
+ const addonDir = path8.join(projectRoot, "dist", "addon");
1641
+ const addonJsonPath = path8.join(addonDir, "addon.json");
1642
+ if (fs6.existsSync(addonJsonPath)) {
1643
+ const addonJson = fs6.readJSONSync(addonJsonPath);
1644
+ const addonName = `${addonJson.key}-${addonJson.version}.mnaddon`;
1645
+ const addonPath = path8.join(projectRoot, addonName);
1646
+ console.log(import_chalk7.default.green("\n\u2713 Initial build completed!"));
1647
+ console.log(import_chalk7.default.cyan("\n\u{1F4E6} Plugin Information:"));
1648
+ console.log(import_chalk7.default.white(` Name: ${addonJson.title || addonJson.key}`));
1649
+ console.log(import_chalk7.default.white(` Version: ${addonJson.version}`));
1650
+ if (fs6.existsSync(addonPath)) {
1651
+ console.log(import_chalk7.default.cyan(`
1652
+ \u{1F4C1} Plugin file: ${addonPath}`));
1653
+ } else {
1654
+ console.log(import_chalk7.default.cyan(`
1655
+ \u{1F4C1} Plugin directory: ${addonDir}`));
1656
+ }
1657
+ console.log(import_chalk7.default.cyan("\n\u{1F4CB} Installation Instructions:"));
1658
+ console.log(import_chalk7.default.white(" 1. Open MarginNote app"));
1659
+ console.log(import_chalk7.default.white(" 2. Go to Settings > Add-ons"));
1660
+ console.log(import_chalk7.default.white(' 3. Tap "Import Add-on" or drag the .mnaddon file'));
1661
+ if (fs6.existsSync(addonPath)) {
1662
+ console.log(import_chalk7.default.white(` 4. Select: ${addonName}`));
1663
+ } else {
1664
+ console.log(import_chalk7.default.white(` 4. Select the addon directory: ${addonDir}`));
1665
+ }
1666
+ console.log(import_chalk7.default.cyan("\n\u{1F50C} Connection:"));
1667
+ console.log(import_chalk7.default.white(` WebSocket server: ${devServerUrl}`));
1668
+ console.log(import_chalk7.default.white(" Waiting for plugin to connect..."));
1669
+ console.log(import_chalk7.default.gray("\n The plugin will automatically connect when MarginNote is opened."));
1670
+ console.log(import_chalk7.default.gray(" Hot reload will work once connected.\n"));
1671
+ }
1672
+ }
1673
+ } else if (error) {
1674
+ console.error(import_chalk7.default.red(`
1675
+ \u2717 Build failed: ${error.message}`));
1676
+ if (isFirstBuild) {
1677
+ console.log(import_chalk7.default.yellow("\n\u{1F4A1} Troubleshooting:"));
1678
+ console.log(import_chalk7.default.white(" - Check if TypeScript compilation errors exist"));
1679
+ console.log(import_chalk7.default.white(" - Ensure all dependencies are installed (npm install)"));
1680
+ console.log(import_chalk7.default.white(" - Verify mn-rails.config.js is properly configured\n"));
1681
+ }
1682
+ }
1683
+ }
1684
+ });
1685
+ const watcher = new FileWatcher({
1686
+ rootDir: projectRoot,
1687
+ patterns: ["src/**/*"],
1688
+ onFileChange: async (filePath) => {
1689
+ await builder.build();
1690
+ }
1691
+ });
1692
+ const server = new DevServer({
1693
+ port,
1694
+ onConnection: (ws) => {
1695
+ const count = server.getClientCount();
1696
+ console.log(import_chalk7.default.green(`\u2713 Client connected (${count} total)`));
1697
+ },
1698
+ onDisconnection: (ws) => {
1699
+ const count = server.getClientCount();
1700
+ console.log(import_chalk7.default.yellow(`Client disconnected (${count} remaining)`));
1701
+ },
1702
+ onMessage: (ws, message) => {
1703
+ if (message.type === "log" || message.type === "error" || message.type === "warn") {
1704
+ const logMessage = {
1705
+ level: message.type,
1706
+ args: message.payload.args || [message.payload],
1707
+ timestamp: message.timestamp
1708
+ };
1709
+ printLogMessage(logMessage);
1710
+ } else if (message.type === "ready") {
1711
+ const count = server.getClientCount();
1712
+ console.log(import_chalk7.default.green(`\u2713 Client ready (${count} connected)`));
1713
+ }
1714
+ }
1715
+ });
1716
+ try {
1717
+ await server.start();
1718
+ await watcher.start();
1719
+ console.log(import_chalk7.default.blue("Performing initial build..."));
1720
+ let buildSuccess = false;
1721
+ try {
1722
+ await builder.build();
1723
+ buildSuccess = true;
1724
+ } catch (error) {
1725
+ console.error(import_chalk7.default.red(`
1726
+ \u2717 Initial build failed: ${error instanceof Error ? error.message : String(error)}`));
1727
+ console.log(import_chalk7.default.yellow("\n\u{1F4A1} Troubleshooting:"));
1728
+ console.log(import_chalk7.default.white(" - Check if TypeScript compilation errors exist"));
1729
+ console.log(import_chalk7.default.white(" - Ensure all dependencies are installed (npm install)"));
1730
+ console.log(import_chalk7.default.white(" - Verify mn-rails.config.js is properly configured"));
1731
+ console.log(import_chalk7.default.white(" - Check that src/ directory exists and contains valid code\n"));
1732
+ }
1733
+ if (buildSuccess) {
1734
+ console.log(import_chalk7.default.green("\n\u2713 Development server ready!"));
1735
+ console.log(import_chalk7.default.cyan("\n\u{1F4CA} Status:"));
1736
+ console.log(import_chalk7.default.white(` Server: ws://localhost:${port}`));
1737
+ console.log(import_chalk7.default.white(` Clients: ${server.getClientCount()}`));
1738
+ console.log(import_chalk7.default.cyan("\n\u23F3 Waiting for file changes...\n"));
1739
+ }
1740
+ const shutdown = async () => {
1741
+ console.log(import_chalk7.default.yellow("\nShutting down development server..."));
1742
+ await watcher.stop();
1743
+ await server.stop();
1744
+ process.exit(0);
1745
+ };
1746
+ process.on("SIGINT", shutdown);
1747
+ process.on("SIGTERM", shutdown);
1748
+ } catch (error) {
1749
+ console.error(import_chalk7.default.red(`Failed to start server: ${error}`));
1750
+ process.exit(1);
1751
+ }
1752
+ }
1753
+
1754
+ // src/index.ts
1755
+ var program = new import_commander.Command();
1756
+ program.name("mn-rails").description("A modern plugin development framework for MarginNote").version("0.1.0");
1757
+ program.command("new").description("Create a new MarginNote plugin project").argument("[name]", "Project name").option("-t, --template <template>", "Template to use", "basic").action(newCommand);
1758
+ program.command("build").description("Build the plugin for production").option("-w, --watch", "Watch mode").action(buildCommand);
1759
+ program.command("dev").description("Start development server with hot reload").option("-p, --port <port>", "Server port", "3000").action(devCommand);
1760
+ program.parse(process.argv);