@linyjs/cli 0.0.2

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,383 @@
1
+ /**
2
+ * Smart builder for plugin projects
3
+ *
4
+ * Design Philosophy:
5
+ * - Zero configuration required
6
+ * - CLI handles all build details automatically
7
+ * - Users just write TypeScript/TSX code
8
+ * - Sensible defaults and optimizations out of the box
9
+ */
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import { execSync } from 'child_process';
13
+ import chalk from 'chalk';
14
+ import ora from 'ora';
15
+ /**
16
+ * Detect build system and execute build
17
+ */
18
+ export async function buildProject(options) {
19
+ const startTime = Date.now();
20
+ const { projectRoot, verbose } = options;
21
+ const spinner = ora('Detecting build system...').start();
22
+ // Step 1: Detect build system
23
+ const buildSystem = detectBuildSystem(projectRoot);
24
+ if (!buildSystem) {
25
+ spinner.fail('No build system detected');
26
+ console.error(chalk.red('\nPlease add a build script to package.json:'));
27
+ console.error(chalk.gray(' "scripts": { "build": "tsc" }'));
28
+ process.exit(1);
29
+ }
30
+ spinner.succeed(`Build system detected: ${buildSystem.name}`);
31
+ if (verbose) {
32
+ console.log(chalk.gray(' Type:'), buildSystem.type);
33
+ console.log(chalk.gray(' Config:'), buildSystem.configFile || 'none');
34
+ }
35
+ // Step 2: Execute build
36
+ const buildSpinner = ora('Building project...').start();
37
+ try {
38
+ let outputDir;
39
+ // DEFAULT: Always use esbuild for optimal bundles
40
+ // Users don't need to configure webpack/rollup themselves
41
+ if (buildSystem.type === 'typescript' || buildSystem.type === 'custom') {
42
+ // Even if project has tsc, we use esbuild for better optimization
43
+ if (verbose) {
44
+ console.log(chalk.yellow('\n💡 Using esbuild for optimized bundle (ignoring tsc config)'));
45
+ }
46
+ outputDir = await buildWithEsbuild(projectRoot, buildSystem, options, verbose);
47
+ }
48
+ else if (buildSystem.type === 'esbuild') {
49
+ // Project already uses esbuild, use it directly
50
+ outputDir = await buildWithEsbuild(projectRoot, buildSystem, options, verbose);
51
+ }
52
+ else {
53
+ // For webpack/rollup, still prefer our esbuild approach
54
+ if (verbose) {
55
+ console.log(chalk.yellow('\n💡 Using CLI built-in esbuild (ignoring webpack/rollup config)'));
56
+ }
57
+ outputDir = await buildWithEsbuild(projectRoot, buildSystem, options, verbose);
58
+ }
59
+ // Step 3: Collect output files
60
+ const files = collectOutputFiles(outputDir);
61
+ const size = calculateTotalSize(files);
62
+ const duration = Date.now() - startTime;
63
+ buildSpinner.succeed(`Build completed in ${duration}ms`);
64
+ if (verbose) {
65
+ console.log(chalk.gray(' Output directory:'), outputDir);
66
+ console.log(chalk.gray(' Total files:'), files.length);
67
+ console.log(chalk.gray(' Total size:'), formatFileSize(size));
68
+ }
69
+ return {
70
+ success: true,
71
+ outputDir,
72
+ files,
73
+ size,
74
+ duration
75
+ };
76
+ }
77
+ catch (error) {
78
+ buildSpinner.fail('Build failed');
79
+ if (error instanceof Error) {
80
+ console.error(chalk.red('\nBuild error:'));
81
+ console.error(error.message);
82
+ if (verbose && 'stdout' in error) {
83
+ console.error(chalk.gray('\nDetailed output:'));
84
+ console.error(error.stdout?.toString());
85
+ }
86
+ }
87
+ process.exit(1);
88
+ }
89
+ }
90
+ /**
91
+ * Detect build system from project configuration
92
+ */
93
+ function detectBuildSystem(projectRoot) {
94
+ const packageJsonPath = path.join(projectRoot, 'package.json');
95
+ if (!fs.existsSync(packageJsonPath)) {
96
+ return null;
97
+ }
98
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
99
+ const buildScript = packageJson.scripts?.build || '';
100
+ // Check for TypeScript
101
+ if (fs.existsSync(path.join(projectRoot, 'tsconfig.json'))) {
102
+ if (buildScript.includes('tsc') || buildScript.includes('typescript')) {
103
+ return {
104
+ type: 'typescript',
105
+ name: 'TypeScript Compiler',
106
+ configFile: 'tsconfig.json',
107
+ command: buildScript
108
+ };
109
+ }
110
+ }
111
+ // Check for esbuild
112
+ if (buildScript.includes('esbuild')) {
113
+ return {
114
+ type: 'esbuild',
115
+ name: 'esbuild',
116
+ configFile: null,
117
+ command: buildScript
118
+ };
119
+ }
120
+ // Check for webpack
121
+ if (fs.existsSync(path.join(projectRoot, 'webpack.config.js')) ||
122
+ fs.existsSync(path.join(projectRoot, 'webpack.config.ts'))) {
123
+ return {
124
+ type: 'webpack',
125
+ name: 'Webpack',
126
+ configFile: 'webpack.config.js',
127
+ command: buildScript
128
+ };
129
+ }
130
+ // Check for rollup
131
+ if (fs.existsSync(path.join(projectRoot, 'rollup.config.js'))) {
132
+ return {
133
+ type: 'rollup',
134
+ name: 'Rollup',
135
+ configFile: 'rollup.config.js',
136
+ command: buildScript
137
+ };
138
+ }
139
+ // Default to npm run build
140
+ if (buildScript) {
141
+ return {
142
+ type: 'custom',
143
+ name: 'Custom Build Script',
144
+ configFile: null,
145
+ command: buildScript
146
+ };
147
+ }
148
+ return null;
149
+ }
150
+ /**
151
+ * Build with TypeScript compiler
152
+ */
153
+ async function buildWithTsc(projectRoot, buildSystem, verbose) {
154
+ // Read tsconfig.json to get output directory
155
+ const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
156
+ const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, 'utf-8'));
157
+ const outputDir = tsconfig.compilerOptions?.outDir || 'dist';
158
+ // Execute tsc
159
+ execSync('npx tsc', {
160
+ stdio: verbose ? 'inherit' : 'pipe',
161
+ cwd: projectRoot
162
+ });
163
+ return path.join(projectRoot, outputDir);
164
+ }
165
+ /**
166
+ * Build with esbuild (DEFAULT strategy)
167
+ * This is the recommended approach for production builds
168
+ */
169
+ async function buildWithEsbuild(projectRoot, buildSystem, options, verbose) {
170
+ const outputDir = path.join(projectRoot, 'dist');
171
+ // Ensure output directory exists
172
+ if (!fs.existsSync(outputDir)) {
173
+ fs.mkdirSync(outputDir, { recursive: true });
174
+ }
175
+ // Detect entry points (smart detection)
176
+ const serverEntry = findEntryFile(projectRoot, 'server');
177
+ const clientEntry = findEntryFile(projectRoot, 'client');
178
+ if (!serverEntry && !clientEntry) {
179
+ throw new Error('No entry files found. Expected src/server/index.ts or src/client/index.tsx');
180
+ }
181
+ // Default external dependencies (Ling framework provides these)
182
+ const defaultExternal = [
183
+ 'react',
184
+ 'react-dom',
185
+ 'express',
186
+ '@linyjs/*',
187
+ '@ling/*'
188
+ ];
189
+ const externalDeps = options.external || defaultExternal;
190
+ // Build server bundle (if exists)
191
+ if (serverEntry) {
192
+ const serverOutput = path.join(outputDir, 'server', 'index.js');
193
+ const serverCmd = [
194
+ 'npx esbuild',
195
+ serverEntry,
196
+ '--bundle',
197
+ `--outfile=${serverOutput}`,
198
+ '--format=esm',
199
+ '--platform=node',
200
+ '--target=node18',
201
+ ...externalDeps.map(dep => `--external:${dep}`),
202
+ options.minify !== false ? '--minify' : '', // Default to minify
203
+ '--sourcemap',
204
+ '--log-level=warning'
205
+ ].filter(Boolean).join(' ');
206
+ if (verbose) {
207
+ console.log(chalk.cyan('\n🔨 Building server bundle...'));
208
+ console.log(chalk.gray(' Entry:'), serverEntry);
209
+ console.log(chalk.gray(' Output:'), serverOutput);
210
+ console.log(chalk.gray(' Command:'), serverCmd);
211
+ }
212
+ try {
213
+ execSync(serverCmd, {
214
+ stdio: verbose ? 'inherit' : 'pipe',
215
+ cwd: projectRoot
216
+ });
217
+ if (verbose) {
218
+ const size = fs.statSync(serverOutput).size;
219
+ console.log(chalk.green(' ✓ Server bundle created'), chalk.gray(`(${formatFileSize(size)})`));
220
+ }
221
+ }
222
+ catch (error) {
223
+ throw new Error(`Server build failed: ${error.message}`);
224
+ }
225
+ }
226
+ // Build client bundle (if exists)
227
+ if (clientEntry) {
228
+ const clientOutput = path.join(outputDir, 'client', 'index.js');
229
+ // For client bundle, we need different external dependencies
230
+ // Server-only packages should NOT be bundled in client
231
+ const clientExternal = [
232
+ // ⚠️ IMPORTANT: React and ReactDOM MUST be externalized
233
+ // All plugins must share the SAME React instance to avoid:
234
+ // - Context identity issues (createContext from different React instances are not equal)
235
+ // - Hook ordering problems (different React versions have different internal structures)
236
+ // - Memory leaks (multiple React renderers running simultaneously)
237
+ // - Dependency injection failures (contexts/services created by one React can't be used by another)
238
+ 'react', // Provided by SSR host application
239
+ 'react-dom', // Provided by SSR host application
240
+ 'react-dom/client', // Provided by SSR host application
241
+ // UI slots interface is type-only, safe to external
242
+ '@linyjs/ui-slots-interface',
243
+ // Explicitly exclude server-only packages
244
+ // These should never appear in client code, but just in case:
245
+ 'express',
246
+ '@linyjs/server-module-interface', // Server-only interface (type-only)
247
+ '@linyjs/mongodb', // Server-only service
248
+ // NOTE: @linyjs/client-module-interface is NOT externalized
249
+ // because it contains runtime values (React Context, Hooks)
250
+ // that must be included in the client bundle
251
+ ];
252
+ // Merge with user-provided externals (if any)
253
+ if (options.external) {
254
+ options.external.forEach(dep => {
255
+ if (!clientExternal.includes(dep)) {
256
+ clientExternal.push(dep);
257
+ }
258
+ });
259
+ }
260
+ const clientCmd = [
261
+ 'npx esbuild',
262
+ clientEntry,
263
+ '--bundle',
264
+ `--outfile=${clientOutput}`,
265
+ '--format=esm',
266
+ '--platform=browser',
267
+ '--target=es2020',
268
+ ...clientExternal.map(dep => `--external:${dep}`),
269
+ options.minify !== false ? '--minify' : '',
270
+ '--sourcemap',
271
+ '--log-level=warning'
272
+ ].filter(Boolean).join(' ');
273
+ if (verbose) {
274
+ console.log(chalk.cyan('\n🔨 Building client bundle...'));
275
+ console.log(chalk.gray(' Entry:'), clientEntry);
276
+ console.log(chalk.gray(' Output:'), clientOutput);
277
+ console.log(chalk.gray(' Command:'), clientCmd);
278
+ }
279
+ try {
280
+ execSync(clientCmd, {
281
+ stdio: verbose ? 'inherit' : 'pipe',
282
+ cwd: projectRoot
283
+ });
284
+ if (verbose) {
285
+ const size = fs.statSync(clientOutput).size;
286
+ console.log(chalk.green(' ✓ Client bundle created'), chalk.gray(`(${formatFileSize(size)})`));
287
+ }
288
+ }
289
+ catch (error) {
290
+ throw new Error(`Client build failed: ${error.message}`);
291
+ }
292
+ }
293
+ return outputDir;
294
+ }
295
+ /**
296
+ * Build with npm run build (fallback)
297
+ */
298
+ async function buildWithNpm(projectRoot, verbose) {
299
+ execSync('npm run build', {
300
+ stdio: verbose ? 'inherit' : 'pipe',
301
+ cwd: projectRoot
302
+ });
303
+ // Try to detect output directory
304
+ const possibleDirs = ['dist', 'build', 'lib'];
305
+ for (const dir of possibleDirs) {
306
+ const fullPath = path.join(projectRoot, dir);
307
+ if (fs.existsSync(fullPath)) {
308
+ return fullPath;
309
+ }
310
+ }
311
+ return path.join(projectRoot, 'dist');
312
+ }
313
+ /**
314
+ * Find entry file for server or client
315
+ */
316
+ function findEntryFile(projectRoot, type) {
317
+ const possibleEntries = [
318
+ `src/${type}/index.ts`,
319
+ `src/${type}/index.tsx`,
320
+ `src/${type}.ts`,
321
+ `src/${type}.tsx`,
322
+ `${type}/index.ts`
323
+ ];
324
+ for (const entry of possibleEntries) {
325
+ const fullPath = path.join(projectRoot, entry);
326
+ if (fs.existsSync(fullPath)) {
327
+ return entry;
328
+ }
329
+ }
330
+ return null;
331
+ }
332
+ /**
333
+ * Collect all output files
334
+ */
335
+ function collectOutputFiles(outputDir) {
336
+ const files = [];
337
+ function walk(dir) {
338
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
339
+ for (const entry of entries) {
340
+ const fullPath = path.join(dir, entry.name);
341
+ if (entry.isDirectory()) {
342
+ // Skip node_modules and .git
343
+ if (entry.name === 'node_modules' || entry.name === '.git') {
344
+ continue;
345
+ }
346
+ walk(fullPath);
347
+ }
348
+ else {
349
+ files.push(fullPath);
350
+ }
351
+ }
352
+ }
353
+ walk(outputDir);
354
+ return files;
355
+ }
356
+ /**
357
+ * Calculate total size of files
358
+ */
359
+ function calculateTotalSize(files) {
360
+ return files.reduce((total, file) => {
361
+ try {
362
+ const stats = fs.statSync(file);
363
+ return total + stats.size;
364
+ }
365
+ catch {
366
+ return total;
367
+ }
368
+ }, 0);
369
+ }
370
+ /**
371
+ * Format file size
372
+ */
373
+ function formatFileSize(bytes) {
374
+ const units = ['B', 'KB', 'MB', 'GB'];
375
+ let size = bytes;
376
+ let unitIndex = 0;
377
+ while (size >= 1024 && unitIndex < units.length - 1) {
378
+ size /= 1024;
379
+ unitIndex++;
380
+ }
381
+ return `${size.toFixed(2)} ${units[unitIndex]}`;
382
+ }
383
+ //# sourceMappingURL=builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.js","sourceRoot":"","sources":["../../src/utils/builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AAiBtB;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAqB;IACtD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAEzC,MAAM,OAAO,GAAG,GAAG,CAAC,2BAA2B,CAAC,CAAC,KAAK,EAAE,CAAC;IAEzD,8BAA8B;IAC9B,MAAM,WAAW,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;IAEnD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QACzC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC,CAAC;QACzE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC,CAAC;QAC7D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,OAAO,CAAC,0BAA0B,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC;IAE9D,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;QACrD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,UAAU,IAAI,MAAM,CAAC,CAAC;IACzE,CAAC;IAED,wBAAwB;IACxB,MAAM,YAAY,GAAG,GAAG,CAAC,qBAAqB,CAAC,CAAC,KAAK,EAAE,CAAC;IAExD,IAAI,CAAC;QACH,IAAI,SAAiB,CAAC;QAEtB,kDAAkD;QAClD,0DAA0D;QAC1D,IAAI,WAAW,CAAC,IAAI,KAAK,YAAY,IAAI,WAAW,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACvE,kEAAkE;YAClE,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,+DAA+D,CAAC,CAAC,CAAC;YAC7F,CAAC;YACD,SAAS,GAAG,MAAM,gBAAgB,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QACjF,CAAC;aAAM,IAAI,WAAW,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC1C,gDAAgD;YAChD,SAAS,GAAG,MAAM,gBAAgB,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QACjF,CAAC;aAAM,CAAC;YACN,wDAAwD;YACxD,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,kEAAkE,CAAC,CAAC,CAAC;YAChG,CAAC;YACD,SAAS,GAAG,MAAM,gBAAgB,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QACjF,CAAC;QAED,+BAA+B;QAC/B,MAAM,KAAK,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;QAC5C,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QAExC,YAAY,CAAC,OAAO,CAAC,sBAAsB,QAAQ,IAAI,CAAC,CAAC;QAEzD,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,EAAE,SAAS,CAAC,CAAC;YAC1D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;YACxD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;QACjE,CAAC;QAED,OAAO;YACL,OAAO,EAAE,IAAI;YACb,SAAS;YACT,KAAK;YACL,IAAI;YACJ,QAAQ;SACT,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAElC,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC;YAC3C,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAE7B,IAAI,OAAO,IAAI,QAAQ,IAAI,KAAK,EAAE,CAAC;gBACjC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC;gBAChD,OAAO,CAAC,KAAK,CAAE,KAAa,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB,CAAC,WAAmB;IAC5C,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IAE/D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1E,MAAM,WAAW,GAAG,WAAW,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC;IAErD,uBAAuB;IACvB,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC;QAC3D,IAAI,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACtE,OAAO;gBACL,IAAI,EAAE,YAAY;gBAClB,IAAI,EAAE,qBAAqB;gBAC3B,UAAU,EAAE,eAAe;gBAC3B,OAAO,EAAE,WAAW;aACrB,CAAC;QACJ,CAAC;IACH,CAAC;IAED,oBAAoB;IACpB,IAAI,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACpC,OAAO;YACL,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,SAAS;YACf,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE,WAAW;SACrB,CAAC;IACJ,CAAC;IAED,oBAAoB;IACpB,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;QAC1D,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC,EAAE,CAAC;QAC/D,OAAO;YACL,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,SAAS;YACf,UAAU,EAAE,mBAAmB;YAC/B,OAAO,EAAE,WAAW;SACrB,CAAC;IACJ,CAAC;IAED,mBAAmB;IACnB,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC;QAC9D,OAAO;YACL,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE,kBAAkB;YAC9B,OAAO,EAAE,WAAW;SACrB,CAAC;IACJ,CAAC;IAED,2BAA2B;IAC3B,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO;YACL,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,qBAAqB;YAC3B,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE,WAAW;SACrB,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,YAAY,CACzB,WAAmB,EACnB,WAAwB,EACxB,OAAgB;IAEhB,6CAA6C;IAC7C,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;IACpE,MAAM,SAAS,GAAG,QAAQ,CAAC,eAAe,EAAE,MAAM,IAAI,MAAM,CAAC;IAE7D,cAAc;IACd,QAAQ,CAAC,SAAS,EAAE;QAClB,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;QACnC,GAAG,EAAE,WAAW;KACjB,CAAC,CAAC;IAEH,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;AAC3C,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,gBAAgB,CAC7B,WAAmB,EACnB,WAAwB,EACxB,OAAqB,EACrB,OAAgB;IAEhB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAEjD,iCAAiC;IACjC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC9B,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,wCAAwC;IACxC,MAAM,WAAW,GAAG,aAAa,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACzD,MAAM,WAAW,GAAG,aAAa,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IAEzD,IAAI,CAAC,WAAW,IAAI,CAAC,WAAW,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,4EAA4E,CAAC,CAAC;IAChG,CAAC;IAED,gEAAgE;IAChE,MAAM,eAAe,GAAG;QACtB,OAAO;QACP,WAAW;QACX,SAAS;QACT,WAAW;QACX,SAAS;KACV,CAAC;IAEF,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,IAAI,eAAe,CAAC;IAEzD,kCAAkC;IAClC,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;QAEhE,MAAM,SAAS,GAAG;YAChB,aAAa;YACb,WAAW;YACX,UAAU;YACV,aAAa,YAAY,EAAE;YAC3B,cAAc;YACd,iBAAiB;YACjB,iBAAiB;YACjB,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,cAAc,GAAG,EAAE,CAAC;YAC/C,OAAO,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,oBAAoB;YAChE,aAAa;YACb,qBAAqB;SACtB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAE5B,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC,CAAC;YAC1D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,WAAW,CAAC,CAAC;YACjD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,YAAY,CAAC,CAAC;YACnD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,SAAS,CAAC,CAAC;QACnD,CAAC;QAED,IAAI,CAAC;YACH,QAAQ,CAAC,SAAS,EAAE;gBAClB,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;gBACnC,GAAG,EAAE,WAAW;aACjB,CAAC,CAAC;YAEH,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC;gBAC5C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,2BAA2B,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YACjG,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,wBAAyB,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,kCAAkC;IAClC,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;QAEhE,6DAA6D;QAC7D,uDAAuD;QACvD,MAAM,cAAc,GAAG;YACrB,wDAAwD;YACxD,2DAA2D;YAC3D,yFAAyF;YACzF,yFAAyF;YACzF,mEAAmE;YACnE,oGAAoG;YACpG,OAAO,EAAe,mCAAmC;YACzD,WAAW,EAAW,mCAAmC;YACzD,kBAAkB,EAAI,mCAAmC;YAEzD,oDAAoD;YACpD,4BAA4B;YAE5B,0CAA0C;YAC1C,8DAA8D;YAC9D,SAAS;YACT,iCAAiC,EAAG,oCAAoC;YACxE,iBAAiB,EAAmB,sBAAsB;YAE1D,4DAA4D;YAC5D,4DAA4D;YAC5D,6CAA6C;SAC9C,CAAC;QAEF,8CAA8C;QAC9C,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;gBAC7B,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;oBAClC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC3B,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QAED,MAAM,SAAS,GAAG;YAChB,aAAa;YACb,WAAW;YACX,UAAU;YACV,aAAa,YAAY,EAAE;YAC3B,cAAc;YACd,oBAAoB;YACpB,iBAAiB;YACjB,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,cAAc,GAAG,EAAE,CAAC;YACjD,OAAO,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;YAC1C,aAAa;YACb,qBAAqB;SACtB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAE5B,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC,CAAC;YAC1D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,WAAW,CAAC,CAAC;YACjD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,YAAY,CAAC,CAAC;YACnD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,SAAS,CAAC,CAAC;QACnD,CAAC;QAED,IAAI,CAAC;YACH,QAAQ,CAAC,SAAS,EAAE;gBAClB,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;gBACnC,GAAG,EAAE,WAAW;aACjB,CAAC,CAAC;YAEH,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC;gBAC5C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,2BAA2B,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YACjG,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,wBAAyB,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,YAAY,CAAC,WAAmB,EAAE,OAAgB;IAC/D,QAAQ,CAAC,eAAe,EAAE;QACxB,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;QACnC,GAAG,EAAE,WAAW;KACjB,CAAC,CAAC;IAEH,iCAAiC;IACjC,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IAC9C,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QAC7C,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;AACxC,CAAC;AAED;;GAEG;AACH,SAAS,aAAa,CAAC,WAAmB,EAAE,IAAyB;IACnE,MAAM,eAAe,GAAG;QACtB,OAAO,IAAI,WAAW;QACtB,OAAO,IAAI,YAAY;QACvB,OAAO,IAAI,KAAK;QAChB,OAAO,IAAI,MAAM;QACjB,GAAG,IAAI,WAAW;KACnB,CAAC;IAEF,KAAK,MAAM,KAAK,IAAI,eAAe,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;QAC/C,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CAAC,SAAiB;IAC3C,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,SAAS,IAAI,CAAC,GAAW;QACvB,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAE7D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAE5C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,6BAA6B;gBAC7B,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;oBAC3D,SAAS;gBACX,CAAC;gBACD,IAAI,CAAC,QAAQ,CAAC,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACvB,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,CAAC,SAAS,CAAC,CAAC;IAChB,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CAAC,KAAe;IACzC,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QAClC,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAChC,OAAO,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC,EAAE,CAAC,CAAC,CAAC;AACR,CAAC;AAED;;GAEG;AACH,SAAS,cAAc,CAAC,KAAa;IACnC,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACtC,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,OAAO,IAAI,IAAI,IAAI,IAAI,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpD,IAAI,IAAI,IAAI,CAAC;QACb,SAAS,EAAE,CAAC;IACd,CAAC;IAED,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;AAClD,CAAC"}
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@linyjs/cli",
3
+ "version": "0.0.2",
4
+ "type": "module",
5
+ "bin": {
6
+ "ling": "./dist/index.js"
7
+ },
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "scripts": {
11
+ "build": "tsc -b",
12
+ "clean": "rm -rf dist *.tsbuildinfo"
13
+ },
14
+ "dependencies": {
15
+ "cleye": "^1.3.0",
16
+ "archiver": "^6.0.0",
17
+ "chalk": "^5.0.0",
18
+ "fs-extra": "^11.0.0",
19
+ "ora": "^7.0.0",
20
+ "esbuild": "^0.20.0"
21
+ },
22
+ "devDependencies": {
23
+ "typescript": "^5.7.0",
24
+ "@types/archiver": "^6.0.0",
25
+ "@types/fs-extra": "^11.0.0"
26
+ }
27
+ }
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Dev command - Quick plugin development workflow
3
+ *
4
+ * Packs the plugin, copies to extensions folder, and registers in packages.json
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import chalk from 'chalk';
10
+ import ora from 'ora';
11
+ import { packPlugin } from './pack.js';
12
+
13
+ interface DevOptions {
14
+ target?: string;
15
+ verbose: boolean;
16
+ }
17
+
18
+ /**
19
+ * Execute dev workflow: pack -> copy -> register
20
+ */
21
+ export async function devPlugin(options: DevOptions) {
22
+ const projectRoot = process.cwd();
23
+
24
+ console.log(chalk.blue('🚀 Ling Plugin Dev Mode\n'));
25
+
26
+ // Step 1: Pack the plugin
27
+ const spinner1 = ora('Packing plugin...').start();
28
+
29
+ try {
30
+ // Create temporary output directory
31
+ const tempOutputDir = path.join(projectRoot, '.ling-tmp');
32
+
33
+ await packPlugin({
34
+ output: tempOutputDir,
35
+ build: true,
36
+ verbose: options.verbose
37
+ });
38
+
39
+ // Find the generated .mpk file
40
+ const mpkFiles = fs.readdirSync(tempOutputDir).filter(f => f.endsWith('.mpk'));
41
+
42
+ if (mpkFiles.length === 0) {
43
+ spinner1.fail('No .mpk file generated');
44
+ process.exit(1);
45
+ }
46
+
47
+ const mpkFile = mpkFiles[0];
48
+ const mpkPath = path.join(tempOutputDir, mpkFile);
49
+
50
+ spinner1.succeed(`Plugin packed: ${mpkFile}`);
51
+
52
+ // Step 2: Determine target extensions directory
53
+ const targetDir = options.target || findExtensionsDir(projectRoot);
54
+
55
+ if (!targetDir) {
56
+ spinner1.fail('Cannot find extensions directory');
57
+ console.error(chalk.red('\nPlease specify target directory with --target option:'));
58
+ console.error(chalk.gray(' npx @linyjs/cli dev --target /path/to/ling/packages/ssr/extensions'));
59
+ process.exit(1);
60
+ }
61
+
62
+ // Step 3: Extract and copy to extensions
63
+ const spinner2 = ora('Installing to extensions...').start();
64
+
65
+ const pluginName = mpkFile.replace('.mpk', '').replace(/-\d+\.\d+\.\d+$/, '');
66
+ const installPath = path.join(targetDir, pluginName);
67
+
68
+ // Remove old installation if exists
69
+ if (fs.existsSync(installPath)) {
70
+ fs.rmSync(installPath, { recursive: true, force: true });
71
+ }
72
+
73
+ // Create installation directory
74
+ fs.mkdirSync(installPath, { recursive: true });
75
+
76
+ // Extract .mpk file (it's a ZIP)
77
+ await extractMpk(mpkPath, installPath);
78
+
79
+ spinner2.succeed(`Installed to: ${installPath}`);
80
+
81
+ // Step 4: Register in packages.json
82
+ const spinner3 = ora('Registering plugin...').start();
83
+
84
+ const packagesJsonPath = path.join(targetDir, 'packages.json');
85
+
86
+ let packages: any[] = [];
87
+
88
+ if (fs.existsSync(packagesJsonPath)) {
89
+ const content = fs.readFileSync(packagesJsonPath, 'utf-8');
90
+ packages = JSON.parse(content);
91
+ }
92
+
93
+ // Read manifest from installed plugin
94
+ const manifestPath = path.join(installPath, 'manifest.json');
95
+ let manifest: any = {};
96
+
97
+ if (fs.existsSync(manifestPath)) {
98
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
99
+ }
100
+
101
+ // Check if already registered
102
+ const existingIndex = packages.findIndex(p => p.name === pluginName);
103
+
104
+ const packageInfo = {
105
+ name: pluginName,
106
+ version: manifest.version || '1.0.0',
107
+ installPath: `extensions/${pluginName}`,
108
+ status: 'enabled',
109
+ installTime: Date.now(),
110
+ manifest: {
111
+ name: manifest.name || pluginName,
112
+ version: manifest.version || '1.0.0',
113
+ description: manifest.description || '',
114
+ author: manifest.author || '',
115
+ serverEntry: manifest.serverEntry || 'server/index.js',
116
+ clientEntry: manifest.clientEntry || 'client/index.js'
117
+ }
118
+ };
119
+
120
+ if (existingIndex >= 0) {
121
+ packages[existingIndex] = packageInfo;
122
+ spinner3.succeed(`Plugin updated in packages.json`);
123
+ } else {
124
+ packages.push(packageInfo);
125
+ spinner3.succeed(`Plugin registered in packages.json`);
126
+ }
127
+
128
+ // Write packages.json
129
+ fs.writeFileSync(packagesJsonPath, JSON.stringify(packages, null, 2) + '\n', 'utf-8');
130
+
131
+ // Cleanup temporary files
132
+ if (fs.existsSync(tempOutputDir)) {
133
+ fs.rmSync(tempOutputDir, { recursive: true, force: true });
134
+ }
135
+
136
+ // Show success message
137
+ console.log('\n' + chalk.green('✓ Development setup complete!\n'));
138
+ console.log(chalk.cyan('Plugin installed:'));
139
+ console.log(` Name: ${chalk.bold(pluginName)}`);
140
+ console.log(` Path: ${chalk.gray(installPath)}`);
141
+ console.log(` Status: ${chalk.green('enabled')}\n`);
142
+
143
+ console.log(chalk.cyan('Next steps:'));
144
+ console.log(` 1. Restart SSR server:`);
145
+ console.log(` ${chalk.gray('cd packages/ssr && npx tsx src/demo/dev-server-with-pkg.tsx')}`);
146
+ console.log(` 2. Or use hot reload (if supported)`);
147
+ console.log(` 3. Test your plugin in the browser\n`);
148
+
149
+ } catch (error) {
150
+ spinner1.fail('Dev workflow failed');
151
+ console.error(chalk.red('\nError:'), error instanceof Error ? error.message : error);
152
+ process.exit(1);
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Find extensions directory relative to project root
158
+ */
159
+ function findExtensionsDir(projectRoot: string): string | null {
160
+ // Try common paths
161
+ const candidates = [
162
+ path.join(projectRoot, '..', 'ssr', 'extensions'),
163
+ path.join(projectRoot, '..', '..', 'packages', 'ssr', 'extensions'),
164
+ path.join(projectRoot, 'packages', 'ssr', 'extensions'),
165
+ ];
166
+
167
+ for (const candidate of candidates) {
168
+ if (fs.existsSync(candidate)) {
169
+ return candidate;
170
+ }
171
+ }
172
+
173
+ return null;
174
+ }
175
+
176
+ /**
177
+ * Extract .mpk file (ZIP format) to target directory
178
+ */
179
+ async function extractMpk(mpkPath: string, targetDir: string): Promise<void> {
180
+ // Use unzipper or similar library
181
+ // For now, use system unzip command
182
+ const { execSync } = await import('child_process');
183
+
184
+ try {
185
+ execSync(`unzip -o "${mpkPath}" -d "${targetDir}"`, {
186
+ stdio: 'pipe'
187
+ });
188
+ } catch (error) {
189
+ throw new Error('Failed to extract .mpk file. Make sure unzip is installed.');
190
+ }
191
+ }