@mulanjs/mulanjs 1.0.1-dev.20260212143840

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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/dist/compiler/compiler.js +90 -0
  4. package/dist/compiler/script-compiler.js +314 -0
  5. package/dist/compiler/sfc-parser.js +93 -0
  6. package/dist/compiler/style-compiler.js +56 -0
  7. package/dist/compiler/template-compiler.js +442 -0
  8. package/dist/components/bloch-sphere.js +252 -0
  9. package/dist/core/component.js +145 -0
  10. package/dist/core/hooks.js +229 -0
  11. package/dist/core/quantum.js +284 -0
  12. package/dist/core/query.js +63 -0
  13. package/dist/core/reactive.js +105 -0
  14. package/dist/core/renderer.js +70 -0
  15. package/dist/core/vault.js +81 -0
  16. package/dist/index.js +52 -0
  17. package/dist/mulan.esm.js +1948 -0
  18. package/dist/mulan.js +215 -0
  19. package/dist/router/index.js +210 -0
  20. package/dist/security/sanitizer.js +47 -0
  21. package/dist/store/index.js +42 -0
  22. package/dist/types/compiler/compiler.d.ts +7 -0
  23. package/dist/types/compiler/script-compiler.d.ts +8 -0
  24. package/dist/types/compiler/sfc-parser.d.ts +21 -0
  25. package/dist/types/compiler/style-compiler.d.ts +7 -0
  26. package/dist/types/compiler/template-compiler.d.ts +7 -0
  27. package/dist/types/compiler.d.ts +7 -0
  28. package/dist/types/components/bloch-sphere.d.ts +16 -0
  29. package/dist/types/core/component.d.ts +54 -0
  30. package/dist/types/core/hooks.d.ts +49 -0
  31. package/dist/types/core/quantum.d.ts +50 -0
  32. package/dist/types/core/query.d.ts +14 -0
  33. package/dist/types/core/reactive.d.ts +21 -0
  34. package/dist/types/core/renderer.d.ts +4 -0
  35. package/dist/types/core/vault.d.ts +12 -0
  36. package/dist/types/index.d.ts +70 -0
  37. package/dist/types/router/index.d.ts +24 -0
  38. package/dist/types/script-compiler.d.ts +8 -0
  39. package/dist/types/security/sanitizer.d.ts +17 -0
  40. package/dist/types/sfc-parser.d.ts +21 -0
  41. package/dist/types/store/index.d.ts +10 -0
  42. package/dist/types/style-compiler.d.ts +7 -0
  43. package/dist/types/template-compiler.d.ts +7 -0
  44. package/package.json +64 -0
  45. package/src/cli/extensions/mulanjs-vscode-1.0.0.vsix +0 -0
  46. package/src/cli/index.js +600 -0
  47. package/src/compiler/compiler.ts +102 -0
  48. package/src/compiler/script-compiler.ts +336 -0
  49. package/src/compiler/sfc-parser.ts +118 -0
  50. package/src/compiler/style-compiler.ts +66 -0
  51. package/src/compiler/template-compiler.ts +519 -0
  52. package/src/compiler/tsconfig.json +13 -0
  53. package/src/loader/index.js +81 -0
@@ -0,0 +1,600 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander');
4
+ const fs = require('fs-extra');
5
+ const path = require('path');
6
+ const { execSync } = require('child_process');
7
+ const readline = require('readline');
8
+
9
+ const program = new Command();
10
+
11
+ program
12
+ .name('mulan')
13
+ .description('MulanJS CLI - The World\'s Most Powerful ASTR-Q Framework CLI')
14
+ .version('1.0.1-dev.0');
15
+
16
+ const LOGO_SVG = `<svg width="50" height="50" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg" class="logo">
17
+ <circle cx="60" cy="60" r="40" fill="#FF4081" opacity="0.1"/>
18
+ <g transform="rotate(0 60 60)">
19
+ <ellipse cx="60" cy="30" rx="15" ry="25" fill="#E91E63" opacity="0.8"/>
20
+ <ellipse cx="60" cy="90" rx="15" ry="25" fill="#E91E63" opacity="0.8"/>
21
+ <ellipse cx="30" cy="60" rx="25" ry="15" fill="#9C27B0" opacity="0.8"/>
22
+ <ellipse cx="90" cy="60" rx="25" ry="15" fill="#9C27B0" opacity="0.8"/>
23
+ </g>
24
+ <g transform="rotate(45 60 60)">
25
+ <ellipse cx="60" cy="30" rx="15" ry="25" fill="#2196F3" opacity="0.8"/>
26
+ <ellipse cx="60" cy="90" rx="15" ry="25" fill="#2196F3" opacity="0.8"/>
27
+ <ellipse cx="30" cy="60" rx="25" ry="15" fill="#3F51B5" opacity="0.8"/>
28
+ <ellipse cx="90" cy="60" rx="25" ry="15" fill="#3F51B5" opacity="0.8"/>
29
+ </g>
30
+ <circle cx="60" cy="60" r="15" fill="#FFC107"/>
31
+ <rect x="55" y="55" width="10" height="3" fill="#000"/>
32
+ <rect x="55" y="60" width="5" height="3" fill="#000"/>
33
+ <rect x="55" y="65" width="10" height="3" fill="#000"/>
34
+ <circle cx="45" cy="45" r="2" fill="#FF9800"/>
35
+ <circle cx="75" cy="45" r="2" fill="#FF9800"/>
36
+ <circle cx="45" cy="75" r="2" fill="#FF9800"/>
37
+ <circle cx="75" cy="75" r="2" fill="#FF9800"/>
38
+ </svg>`;
39
+
40
+ function printBanner() {
41
+ console.log(`
42
+ \x1b[35m█▀▄▀█ █░█ █░░ ▄▀█ █▄░█ \x1b[36m░░█ █▀\x1b[0m
43
+ \x1b[35m█░▀░█ █▄█ █▄▄ █▀█ █░▀█ \x1b[36m█▄█ ▄█\x1b[0m
44
+
45
+ \x1b[33m⚡ THE WORLD'S MOST POWERFUL ASTR-Q FRAMEWORK ⚡\x1b[0m
46
+ `);
47
+ }
48
+
49
+ function askQuestion(query) {
50
+ const rl = readline.createInterface({
51
+ input: process.stdin,
52
+ output: process.stdout,
53
+ });
54
+ return new Promise(resolve => rl.question(query, ans => {
55
+ rl.close();
56
+ resolve(ans);
57
+ }));
58
+ }
59
+
60
+ program
61
+ .command('init <project-name>')
62
+ .description('Initialize a new MulanJS project')
63
+ .option('--ts', 'Initialize as TypeScript project')
64
+ .option('--js', 'Initialize as JavaScript project')
65
+ .action(async (projectName, options) => {
66
+ printBanner();
67
+ console.log(`\n🚀 Unleashing MulanJS Power: Preparing ${projectName}...\n`);
68
+
69
+ const projectPath = path.resolve(process.cwd(), projectName);
70
+ const publicPath = path.join(projectPath, 'public');
71
+ const srcPath = path.join(projectPath, 'src');
72
+ const compPath = path.join(srcPath, 'components');
73
+ const pagesPath = path.join(srcPath, 'pages');
74
+
75
+ if (fs.existsSync(projectPath)) {
76
+ console.error(`❌ Error: Directory ${projectName} already exists.`);
77
+ process.exit(1);
78
+ }
79
+
80
+ // Language/Mode Selection
81
+ let mode = 0;
82
+
83
+ if (options.ts) {
84
+ mode = 1;
85
+ } else if (options.js) {
86
+ mode = 2;
87
+ } else {
88
+ console.log("Select your Power Level:");
89
+ console.log("1) MulanJS (.mujs + TypeScript) [Recommended]");
90
+ console.log("2) TypeScript (Classic)");
91
+ console.log("3) JavaScript (Legacy)");
92
+
93
+ try {
94
+ const answer = await askQuestion("Enter choice (1/2/3) [1]: ");
95
+ const choice = answer.trim();
96
+ if (choice === '2') mode = 1;
97
+ else if (choice === '3') mode = 2;
98
+ else mode = 0;
99
+ } catch (e) {
100
+ console.warn("Input failed, defaulting to MulanJS.");
101
+ }
102
+ }
103
+
104
+ const isMuJS = mode === 0;
105
+ const isTS = mode === 0 || mode === 1;
106
+ const fileExt = isMuJS ? 'mujs' : (isTS ? 'ts' : 'js');
107
+ const mainExt = isTS ? 'ts' : 'js';
108
+
109
+ console.log(`\n⚡ Setting up Environment: ${isMuJS ? 'MulanJS' : (isTS ? 'TypeScript' : 'JavaScript')}...`);
110
+
111
+ try {
112
+ // 1. Create Directory Structure (Ensure for ALL modes)
113
+ await fs.ensureDir(publicPath);
114
+ await fs.ensureDir(srcPath);
115
+ await fs.ensureDir(compPath);
116
+ await fs.ensureDir(pagesPath);
117
+
118
+ // 2. config files
119
+ const packageJson = {
120
+ name: projectName,
121
+ version: "1.0.0",
122
+ description: "Powered by MulanJS",
123
+ dependencies: {},
124
+ devDependencies: {
125
+ "webpack": "^5.104.1",
126
+ "webpack-cli": "^6.0.1",
127
+ "webpack-dev-server": "^5.2.0",
128
+ "css-loader": "^6.8.1",
129
+ "style-loader": "^3.3.3",
130
+ "sass": "^1.83.0",
131
+ "sass-loader": "^16.0.0"
132
+ },
133
+ scripts: {
134
+ "start": "mulan dev",
135
+ "dev": "mulan dev",
136
+ "build": "mulan build",
137
+ "generate": "mulan generate"
138
+ }
139
+ };
140
+
141
+ if (isTS) {
142
+ packageJson.devDependencies["typescript"] = "^5.0.0";
143
+ packageJson.devDependencies["ts-loader"] = "^9.5.4";
144
+ }
145
+
146
+ await fs.outputJson(path.join(projectPath, 'package.json'), packageJson, { spaces: 2 });
147
+
148
+ // VS Code Support
149
+ const vscodePath = path.join(projectPath, '.vscode');
150
+ await fs.ensureDir(vscodePath);
151
+ await fs.outputJson(path.join(vscodePath, 'extensions.json'), {
152
+ "recommendations": [
153
+ "mulanjs.mulanjs-vscode"
154
+ ]
155
+ }, { spaces: 2 });
156
+
157
+
158
+ // tsconfig
159
+ if (isTS) {
160
+ const tsConfig = {
161
+ "compilerOptions": {
162
+ "target": "es2016",
163
+ "module": "esnext",
164
+ "moduleResolution": "node",
165
+ "strict": true,
166
+ "esModuleInterop": true,
167
+ "skipLibCheck": true,
168
+ "forceConsistentCasingInFileNames": true,
169
+ "outDir": "./dist"
170
+ },
171
+ "include": ["src/**/*"]
172
+ };
173
+ await fs.outputJson(path.join(projectPath, 'tsconfig.json'), tsConfig, { spaces: 2 });
174
+
175
+ // d.ts for .mujs
176
+ const dts = `declare module '*.mujs' {
177
+ import { MuComponent } from '@mulanjs/mulanjs';
178
+ const component: new (...args: any[]) => MuComponent;
179
+ export default component;
180
+ }`;
181
+ await fs.writeFile(path.join(srcPath, 'mulan-env.d.ts'), dts);
182
+ }
183
+
184
+ // mulan.config.js
185
+ const mulanConfig = `module.exports = {
186
+ entry: 'src/main.${mainExt}',
187
+ outputDir: 'dist',
188
+ port: 1016
189
+ };`;
190
+ await fs.writeFile(path.join(projectPath, 'mulan.config.js'), mulanConfig);
191
+
192
+ // webpack.config.js
193
+ const ruleTS = isTS ? `{ test: /\\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ },` : '';
194
+ const ruleMuJS = `{ test: /\\.mujs$/, use: '@mulanjs/mulanjs/loader' },`;
195
+
196
+ const webpackConfig = `const path = require('path');
197
+
198
+ module.exports = {
199
+ mode: 'development',
200
+ entry: './src/main.${mainExt}',
201
+ output: {
202
+ filename: 'bundle.js',
203
+ path: path.resolve(__dirname, 'dist'),
204
+ clean: true,
205
+ },
206
+ resolve: {
207
+ extensions: ['.ts', '.js', '.mujs']
208
+ },
209
+ devServer: {
210
+ static: {
211
+ directory: path.join(__dirname, 'public'),
212
+ },
213
+ port: 1016,
214
+ open: true,
215
+ hot: true,
216
+ historyApiFallback: true,
217
+ },
218
+ module: {
219
+ rules: [
220
+ ${ruleTS}
221
+ ${ruleMuJS}
222
+ { test: /\.s[ac]ss$/i, use: ["style-loader", "css-loader", "sass-loader"] },
223
+ { test: /\\.css$/i, use: ["style-loader", "css-loader"] },
224
+ ],
225
+ },
226
+ };`;
227
+ await fs.writeFile(path.join(projectPath, 'webpack.config.js'), webpackConfig);
228
+
229
+ // Index HTML
230
+ const indexHtml = `<!DOCTYPE html>
231
+ <html lang="en">
232
+ <head>
233
+ <meta charset="UTF-8">
234
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
235
+ <title>${projectName} - MulanJS</title>
236
+ </head>
237
+ <body>
238
+ <div id="app"></div>
239
+ <div id="router-outlet"></div>
240
+ <script src="/bundle.js"></script>
241
+ </body>
242
+ </html>`;
243
+ await fs.writeFile(path.join(publicPath, 'index.html'), indexHtml);
244
+
245
+ // Styles
246
+ const styleCss = `
247
+ body { font-family: 'Inter', sans-serif; background: #0a0a0a; color: #fff; margin: 0; }
248
+ .mu-btn {
249
+ background: linear-gradient(45deg, #e94560, #9c27b0);
250
+ border: none; color: white; padding: 10px 20px;
251
+ border-radius: 6px; cursor: pointer; font-size: 1rem;
252
+ transition: transform 0.1s;
253
+ }
254
+ .mu-btn:active { transform: scale(0.95); }
255
+ `;
256
+ await fs.writeFile(path.join(srcPath, 'style.css'), styleCss);
257
+
258
+
259
+ // --- COMPONENT GENERATION (Unified Templates) ---
260
+
261
+ const headerTemplate = (isMuJS) ?
262
+ `<script setup lang="ts">
263
+ </script>
264
+ <mu-template>
265
+ <header>
266
+ <div class="logo-container">
267
+ ${LOGO_SVG}
268
+ <div class="brand">MulanJS</div>
269
+ </div>
270
+ <nav>
271
+ <mu-link to="/">Home</mu-link>
272
+ <mu-link to="/about">About</mu-link>
273
+ </nav>
274
+ </header>
275
+ </mu-template>
276
+ <style scoped>
277
+ header {
278
+ display: flex; justify-content: space-between; align-items: center;
279
+ padding: 15px 30px; background: rgba(20, 20, 20, 0.95);
280
+ border-bottom: 1px solid #333; backdrop-filter: blur(10px);
281
+ position: sticky; top: 0; z-index: 100;
282
+ }
283
+ .logo-container { display: flex; align-items: center; gap: 15px; }
284
+ .brand { font-weight: bold; font-size: 1.5rem; letter-spacing: -0.5px; }
285
+ nav mu-link { color: #aaa; margin-left: 20px; text-decoration: none; font-weight: 500; transition: color 0.2s; display: inline-block; }
286
+ nav mu-link:hover { color: #e94560; }
287
+ .logo { height: 40px; width: 40px; animation: spin 20s linear infinite; }
288
+ @keyframes spin { 100% { transform: rotate(360deg); } }
289
+ </style>` : `import { Component } from '@mulanjs/mulanjs';
290
+
291
+ export class Header extends Component {
292
+ template${isTS ? '(): string' : ''} {
293
+ return \`
294
+ <header>
295
+ <div class="logo-container">
296
+ ${LOGO_SVG}
297
+ <div class="brand">MulanJS ${isTS ? 'TS' : 'JS'}</div>
298
+ </div>
299
+ <nav>
300
+ <mu-link to="/">Home</mu-link>
301
+ <mu-link to="/about">About</mu-link>
302
+ </nav>
303
+ </header>
304
+ <style>
305
+ header {
306
+ display: flex; justify-content: space-between; align-items: center;
307
+ padding: 15px 30px; background: rgba(20, 20, 20, 0.95);
308
+ border-bottom: 1px solid #333; backdrop-filter: blur(10px);
309
+ position: sticky; top: 0; z-index: 100;
310
+ }
311
+ .logo-container { display: flex; align-items: center; gap: 15px; }
312
+ .brand { font-weight: bold; font-size: 1.5rem; letter-spacing: -0.5px; }
313
+ nav mu-link { color: #aaa; margin-left: 20px; text-decoration: none; font-weight: 500; transition: color 0.2s; display: inline-block; }
314
+ nav mu-link:hover { color: #e94560; }
315
+ .logo { height: 40px; width: 40px; animation: spin 20s linear infinite; }
316
+ @keyframes spin { 100% { transform: rotate(360deg); } }
317
+ </style>
318
+ \`;
319
+ }
320
+ }`;
321
+
322
+ // Home Page with Counter
323
+ const homeTemplate = (isMuJS) ?
324
+ `<script setup lang="ts">
325
+ import { muState } from '@mulanjs/mulanjs';
326
+ const state = muState({ count: 0 });
327
+ </script>
328
+ <mu-template>
329
+ <div class="page">
330
+ <div class="hero">
331
+ <h1>Powerful. Fast. MulanJS.</h1>
332
+ <p>The next generation framework for modern web applications.</p>
333
+
334
+ <div class="counter-box">
335
+ <p>Interactive Counter:</p>
336
+ <button class="mu-btn" @click="state.count++">
337
+ Count is: \${state.count}
338
+ </button>
339
+ </div>
340
+ </div>
341
+ </div>
342
+ </mu-template>
343
+ <style scoped>
344
+ .page { padding: 60px 20px; text-align: center; }
345
+ h1 { font-size: 4rem; background: linear-gradient(to right, #e94560, #9c27b0); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 20px; }
346
+ p { font-size: 1.2rem; color: #888; max-width: 600px; margin: 0 auto 40px; }
347
+ .counter-box { background: #1a1a1a; padding: 30px; border-radius: 12px; display: inline-block; border: 1px solid #333; }
348
+ </style>` : `import { Component, muState } from '@mulanjs/mulanjs';
349
+
350
+ export class Home extends Component {
351
+ constructor(container${isTS ? ': HTMLElement' : ''}) {
352
+ super(container);
353
+ this.state = muState({ count: 0 });
354
+ }
355
+
356
+ template${isTS ? '(): string' : ''} {
357
+ return \`
358
+ <div class="page">
359
+ <div class="hero">
360
+ <h1>Powerful. Fast. MulanJS.</h1>
361
+ <p>Welcome to the Classic ${isTS ? 'TypeScript' : 'JavaScript'} Era.</p>
362
+
363
+ <div class="counter-box">
364
+ <p>Interactive Counter:</p>
365
+ <button class="mu-btn" onclick="\${this.bind(() => this.state.count++)}">
366
+ Count is: \${this.state.count}
367
+ </button>
368
+ </div>
369
+ </div>
370
+ </div>
371
+ <style>
372
+ .page { padding: 60px 20px; text-align: center; }
373
+ h1 { font-size: 4rem; background: linear-gradient(to right, #e94560, #9c27b0); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 20px; }
374
+ p { font-size: 1.2rem; color: #888; max-width: 600px; margin: 0 auto 40px; }
375
+ .counter-box { background: #1a1a1a; padding: 30px; border-radius: 12px; display: inline-block; border: 1px solid #333; }
376
+ </style>
377
+ \`;
378
+ }
379
+ }`;
380
+
381
+ // About Page
382
+ const aboutTemplate = (isMuJS) ?
383
+ `<mu-template>
384
+ <div class="page">
385
+ <h1>About Us</h1>
386
+ <p>Building the future with MulanJS.</p>
387
+ </div>
388
+ </mu-template>
389
+ <style scoped src="./About.css"></style>
390
+ ` : `import { Component } from '@mulanjs/mulanjs';
391
+
392
+ export class About extends Component {
393
+ template${isTS ? '(): string' : ''} {
394
+ return \`
395
+ <div class="page">
396
+ <div style="text-align:center; padding: 40px;">
397
+ <h1>About Us</h1>
398
+ <p>Building the future with MulanJS Classic.</p>
399
+ </div>
400
+ </div>
401
+ \`;
402
+ }
403
+ }`;
404
+
405
+ // Write Component Files
406
+ await fs.writeFile(path.join(compPath, `Header.${fileExt}`), headerTemplate);
407
+ await fs.writeFile(path.join(pagesPath, `Home.${fileExt}`), homeTemplate);
408
+ await fs.writeFile(path.join(pagesPath, `About.${fileExt}`), aboutTemplate);
409
+
410
+
411
+ if (isMuJS) {
412
+ const aboutCss = `.page { padding: 40px; text-align: center; }`;
413
+ await fs.writeFile(path.join(pagesPath, 'About.css'), aboutCss);
414
+ }
415
+
416
+ // Main Entry Point (Unified)
417
+ const mainContent = (isMuJS) ?
418
+ `import { Router } from '@mulanjs/mulanjs';
419
+ import Home from './pages/Home.mujs';
420
+ import About from './pages/About.mujs';
421
+ import Header from './components/Header.mujs';
422
+ import './style.css';
423
+
424
+ const app = document.getElementById('app');
425
+ if (app) {
426
+ app.innerHTML = '<div id="header-mount"></div><div id="router-mount"></div>';
427
+
428
+ const headerMount = document.getElementById('header-mount');
429
+ if (headerMount) new Header(headerMount).mount();
430
+
431
+ const routerMount = document.getElementById('router-mount');
432
+ if (routerMount) {
433
+ new Router([
434
+ { path: '/', component: Home },
435
+ { path: '/about', component: About }
436
+ ], routerMount);
437
+ }
438
+ }` :
439
+ `import { Router } from '@mulanjs/mulanjs';
440
+ import { Home } from './pages/Home';
441
+ import { About } from './pages/About';
442
+ import { Header } from './components/Header';
443
+ import './style.css';
444
+
445
+ const app = document.getElementById('app');
446
+ if (app) {
447
+ app.innerHTML = '<div id="header-mount"></div><div id="router-mount"></div>';
448
+
449
+ // Mount Header
450
+ const headerMount = document.getElementById('header-mount');
451
+ if (headerMount) new Header(headerMount).mount();
452
+
453
+ const routerMount = document.getElementById('router-mount');
454
+ if (routerMount) {
455
+ new Router([
456
+ { path: '/', component: Home },
457
+ { path: '/about', component: About }
458
+ ], routerMount);
459
+ }
460
+ }`;
461
+ await fs.writeFile(path.join(srcPath, `main.${mainExt}`), mainContent);
462
+
463
+
464
+ console.log(`\n✅ Project ${projectName} created successfully!`);
465
+ console.log(`\n👉 Next Steps:`);
466
+ console.log(` 1. cd ${projectName}`);
467
+ console.log(` 2. npm install`);
468
+ console.log(` 3. npm link @mulanjs/mulanjs`);
469
+ console.log(` 4. npm run dev`);
470
+
471
+ // Auto-install VS Code Extension
472
+ try {
473
+ const vsixPath = path.join(__dirname, 'extensions', 'mulanjs-vscode-1.0.0.vsix');
474
+ if (fs.existsSync(vsixPath)) {
475
+ console.log('\n📦 Installing MulanJS VS Code Extension...');
476
+ execSync(`code --install-extension "${vsixPath}" --force`, { stdio: 'inherit' });
477
+ console.log('✅ Extension installed successfully!');
478
+ }
479
+ } catch (e) {
480
+ console.warn('⚠️ Could not install VS Code extension (VS Code CLI not found or failed).');
481
+ }
482
+
483
+
484
+ } catch (error) {
485
+ console.error('Failed to create project:', error);
486
+ }
487
+ });
488
+
489
+ const net = require('net');
490
+
491
+ async function findAvailablePort(startPort) {
492
+ const isPortAvailable = (port) => {
493
+ return new Promise((resolve) => {
494
+ const server = net.createServer();
495
+ server.once('error', () => resolve(false));
496
+ server.once('listening', () => {
497
+ server.close();
498
+ resolve(true);
499
+ });
500
+ server.listen(port);
501
+ });
502
+ };
503
+
504
+ let port = startPort;
505
+ while (!(await isPortAvailable(port))) {
506
+ port++;
507
+ }
508
+ return port;
509
+ }
510
+
511
+ program
512
+ .command('dev [args...]')
513
+ .description('Start the Mulan Development Server')
514
+ .action(async () => {
515
+ try {
516
+ const port = await findAvailablePort(1016);
517
+ console.log(`🚀 Starting Mulan Power Server on port ${port}...`);
518
+
519
+ if (!fs.existsSync(path.join(process.cwd(), 'webpack.config.js'))) {
520
+ console.warn("⚠️ webpack.config.js not found.");
521
+ }
522
+
523
+ // Pass the port to webpack-dev-server via --port flag.
524
+ // Note: This overrides the port in webpack.config.js if set there.
525
+ execSync(`npx webpack serve --port ${port}`, { stdio: 'inherit' });
526
+ } catch (e) {
527
+ console.error('❌ Failed to start server:', e.message);
528
+ }
529
+ });
530
+
531
+ program
532
+ .command('build [args...]')
533
+ .description('Build for production')
534
+ .action(() => {
535
+ try {
536
+ execSync('npx webpack --mode production', { stdio: 'inherit' });
537
+ } catch (e) {
538
+ console.error('❌ Build failed:', e.message);
539
+ }
540
+ });
541
+
542
+ program
543
+ .command('generate <type> <name>')
544
+ .alias('g')
545
+ .description('Generate a new MulanJS resource')
546
+ .action(async (type, name) => {
547
+ if (type === 'component') {
548
+ const compName = name.charAt(0).toUpperCase() + name.slice(1);
549
+ console.log(`⚡ Generating Component: ${compName}...`);
550
+ const ext = 'mujs';
551
+ const content = `<script setup lang="ts">
552
+ import { muState } from '@mulanjs/mulanjs';
553
+ const state = muState({ count: 0 });
554
+ </script>
555
+ <mu-template>
556
+ <div class="mu-comp">
557
+ <h2>${compName} Component</h2>
558
+ </div>
559
+ </mu-template>
560
+ <style scoped>
561
+ .mu-comp { padding: 20px; }
562
+ </style>`;
563
+ const filePath = path.join(process.cwd(), 'src', 'components', `${compName}.${ext}`);
564
+ try {
565
+ await fs.ensureDir(path.join(process.cwd(), 'src', 'components'));
566
+ if (await fs.pathExists(filePath)) return console.error('Exists!');
567
+ await fs.writeFile(filePath, content);
568
+ console.log(`✅ Created: ${filePath}`);
569
+ } catch (e) {
570
+ console.error('❌ Generation failed:', e);
571
+ }
572
+ }
573
+ });
574
+
575
+ program
576
+ .command('install-extension')
577
+ .description('Install the MulanJS VS Code Extension for icons and syntax highlighting')
578
+ .action(async () => {
579
+ console.log('📦 Locating MulanJS VS Code Extension...');
580
+ try {
581
+ // Find where mulanjs is installed. If run via npx, __dirname is the cli folder.
582
+ const vsixPath = path.join(__dirname, 'extensions', 'mulanjs-vscode-1.0.0.vsix');
583
+
584
+ if (!fs.existsSync(vsixPath)) {
585
+ console.error('❌ Error: Extension file not found at:', vsixPath);
586
+ console.log('Ensure you have a full installation of the mulanjs package.');
587
+ return;
588
+ }
589
+
590
+ console.log('🚀 Installing extension via VS Code CLI...');
591
+ execSync(`code --install-extension "${vsixPath}" --force`, { stdio: 'inherit' });
592
+ console.log('✅ Success! Restart VS Code to see your new .mujs icons.');
593
+ } catch (e) {
594
+ console.error('\n❌ Failed to install extension.');
595
+ console.log('Make sure "code" (VS Code CLI) is in your PATH.');
596
+ console.log('Manual Install: You can also drag the .vsix file from your node_modules/mulanjs/src/cli/extensions into VS Code.');
597
+ }
598
+ });
599
+
600
+ program.parse(process.argv);
@@ -0,0 +1,102 @@
1
+
2
+ import { parseMUJS } from './sfc-parser';
3
+ import { compileScript } from './script-compiler';
4
+ import { compileTemplate } from './template-compiler';
5
+ import { compileStyle } from './style-compiler';
6
+
7
+ export interface CompileResult {
8
+ code: string;
9
+ css: string;
10
+ errors: string[];
11
+ map?: string; // Source Map
12
+ }
13
+
14
+ export function compileSFC(source: string, filename: string): CompileResult {
15
+ // 1. Parse
16
+ const descriptor = parseMUJS(source, filename);
17
+
18
+ // 2. Script
19
+ const scriptResult = compileScript(descriptor);
20
+ // Replace export default to capture the component
21
+ let scriptCode = scriptResult.code.replace('export default', 'const __component__ =');
22
+ if (!scriptCode.includes('const __component__ =')) {
23
+ scriptCode = scriptCode.replace(/export\s+default/, 'const __component__ =');
24
+ }
25
+
26
+ // 3. Style
27
+ const styleResult = compileStyle(descriptor, filename);
28
+
29
+ // 4. Template
30
+ const templateResult = compileTemplate(descriptor, scriptResult, styleResult.scopedId);
31
+
32
+ // 5. Assembly
33
+ const cssInjection = styleResult.css ? `
34
+ if (typeof document !== 'undefined') {
35
+ const style = document.createElement('style');
36
+ style.textContent = \`${styleResult.css}\`;
37
+ document.head.appendChild(style);
38
+ }
39
+ ` : '';
40
+
41
+ const finalCode = `${scriptCode}
42
+
43
+ ${templateResult.code}
44
+
45
+ // Attach template to component
46
+ if (__component__.prototype) {
47
+ // Class component
48
+ __component__.prototype.template = render;
49
+ } else if (typeof __component__ === 'object') {
50
+ // Options object
51
+ __component__.template = render;
52
+ }
53
+
54
+ ${cssInjection}
55
+
56
+ // --- MulanJS HMR Engine ---
57
+ if (typeof module !== 'undefined' && module.hot) {
58
+ const hmrId = "${filename.replace(/\\/g, '/')}";
59
+ window.__MULAN_HMR__ = window.__MULAN_HMR__ || {
60
+ records: new Map(),
61
+ notify: (id, newComp) => {
62
+ const oldRecord = window.__MULAN_HMR__.records.get(id);
63
+ if (oldRecord) {
64
+ console.log('[MulanJS HMR] Patching:', id);
65
+ if (oldRecord.prototype && newComp.prototype) {
66
+ oldRecord.prototype.template = newComp.prototype.template;
67
+ // Also patch setup if available (for Functional components)
68
+ if (newComp._setup) oldRecord._setup = newComp._setup;
69
+ } else {
70
+ Object.assign(oldRecord, newComp);
71
+ }
72
+ if (window.__MULAN_REFRESH__) {
73
+ console.log('[MulanJS HMR] Triggering Deep Refresh');
74
+ window.__MULAN_REFRESH__();
75
+ }
76
+ }
77
+ }
78
+ };
79
+
80
+ if (window.__MULAN_HMR__.records.has(hmrId)) {
81
+ window.__MULAN_HMR__.notify(hmrId, __component__);
82
+ } else {
83
+ window.__MULAN_HMR__.records.set(hmrId, __component__);
84
+ }
85
+ module.hot.accept();
86
+ }
87
+
88
+ // console.log('[MulanJS] Component created:', __component__);
89
+ export default __component__;
90
+
91
+ // Helper to get relative path for sourceURL
92
+ // This ensures the generated file appears in a readable folder structure in DevTools
93
+ // SourceURL removed. Using standard Source Maps only.
94
+ `;
95
+
96
+ return {
97
+ code: finalCode,
98
+ css: styleResult.css,
99
+ errors: [...scriptResult.errors, ...templateResult.errors],
100
+ map: scriptResult.map // Pass the map
101
+ };
102
+ }