@linyjs/cli 0.0.4 → 0.0.5
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/package.json +5 -1
- package/DESIGN.md +0 -901
- package/ZERO_CONFIG_GUIDE.md +0 -462
- package/src/commands/dev.ts +0 -251
- package/src/commands/init.ts +0 -138
- package/src/commands/pack.ts +0 -315
- package/src/index.ts +0 -84
- package/src/utils/builder.ts +0 -472
- package/tsconfig.json +0 -9
package/ZERO_CONFIG_GUIDE.md
DELETED
|
@@ -1,462 +0,0 @@
|
|
|
1
|
-
# 零配置插件开发指南
|
|
2
|
-
|
|
3
|
-
## 🎯 核心理念
|
|
4
|
-
|
|
5
|
-
**你只需要:**
|
|
6
|
-
1. 编写 TypeScript/TSX 代码
|
|
7
|
-
2. 运行 `npx @linyjs/cli pack`
|
|
8
|
-
3. 得到优化后的 `.mpk` 文件
|
|
9
|
-
|
|
10
|
-
**不需要:**
|
|
11
|
-
- ❌ 配置 webpack
|
|
12
|
-
- ❌ 配置 rollup
|
|
13
|
-
- ❌ 配置 babel
|
|
14
|
-
- ❌ 配置 esbuild
|
|
15
|
-
- ❌ 处理 bundle 优化
|
|
16
|
-
- ❌ 管理外部依赖
|
|
17
|
-
|
|
18
|
-
CLI 会自动处理所有构建细节!
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## 🚀 5分钟快速开始
|
|
23
|
-
|
|
24
|
-
### 步骤 1: 创建项目结构
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
mkdir my-plugin && cd my-plugin
|
|
28
|
-
npm init -y
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### 步骤 2: 安装依赖
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
npm install @linyjs/server-module-interface @linyjs/client-module-interface react
|
|
35
|
-
npm install -D typescript @types/react
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### 步骤 3: 创建 tsconfig.json
|
|
39
|
-
|
|
40
|
-
```json
|
|
41
|
-
{
|
|
42
|
-
"compilerOptions": {
|
|
43
|
-
"target": "ES2020",
|
|
44
|
-
"module": "ESNext",
|
|
45
|
-
"lib": ["ES2020", "DOM"],
|
|
46
|
-
"jsx": "react-jsx",
|
|
47
|
-
"strict": true,
|
|
48
|
-
"esModuleInterop": true,
|
|
49
|
-
"skipLibCheck": true,
|
|
50
|
-
"moduleResolution": "node"
|
|
51
|
-
},
|
|
52
|
-
"include": ["src/**/*"]
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
**注意:** 不需要配置 `outDir`,CLI 会处理输出!
|
|
57
|
-
|
|
58
|
-
### 步骤 4: 编写代码
|
|
59
|
-
|
|
60
|
-
**src/server/index.ts**
|
|
61
|
-
```typescript
|
|
62
|
-
import type { IModule } from '@linyjs/server-module-interface';
|
|
63
|
-
|
|
64
|
-
class HelloController {
|
|
65
|
-
getRoutes() {
|
|
66
|
-
return [{
|
|
67
|
-
method: 'GET' as const,
|
|
68
|
-
path: '/api/hello',
|
|
69
|
-
middlewares: [],
|
|
70
|
-
responseType: 'json' as const,
|
|
71
|
-
handler: async () => {
|
|
72
|
-
return { message: 'Hello from plugin!' };
|
|
73
|
-
}
|
|
74
|
-
}];
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export const helloServerModule: IModule = {
|
|
79
|
-
name: 'helloServer',
|
|
80
|
-
serviceDefs: [{
|
|
81
|
-
serviceTag: 'helloController',
|
|
82
|
-
serviceImpl: HelloController,
|
|
83
|
-
meta: { usageType: 'controller' }
|
|
84
|
-
}]
|
|
85
|
-
};
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
**src/client/index.tsx**
|
|
89
|
-
```typescript
|
|
90
|
-
import type { IModule } from '@linyjs/client-module-interface';
|
|
91
|
-
import React from 'react';
|
|
92
|
-
|
|
93
|
-
function HelloPage() {
|
|
94
|
-
return <div>Hello from plugin!</div>;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export const helloClientModule: IModule = {
|
|
98
|
-
name: 'helloClient',
|
|
99
|
-
serviceDefs: [{
|
|
100
|
-
serviceTag: 'helloRoute',
|
|
101
|
-
serviceImpl: class {
|
|
102
|
-
basePath = '/hello';
|
|
103
|
-
getRoutes() {
|
|
104
|
-
return [{
|
|
105
|
-
path: '/',
|
|
106
|
-
component: HelloPage,
|
|
107
|
-
key: 'hello-page',
|
|
108
|
-
meta: { title: 'Hello' }
|
|
109
|
-
}];
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
meta: { usageType: 'route' }
|
|
113
|
-
}]
|
|
114
|
-
};
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### 步骤 5: 一键打包
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
npx @linyjs/cli pack
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
**就这么简单!** 🎉
|
|
124
|
-
|
|
125
|
-
CLI 会自动:
|
|
126
|
-
1. ✅ 检测入口文件(`src/server/index.ts` 和 `src/client/index.tsx`)
|
|
127
|
-
2. ✅ 使用 esbuild 进行优化构建
|
|
128
|
-
3. ✅ Bundle 所有依赖
|
|
129
|
-
4. ✅ Minify 代码(减小体积)
|
|
130
|
-
5. ✅ Tree shaking(移除未使用代码)
|
|
131
|
-
6. ✅ 处理外部依赖(react、express 等)
|
|
132
|
-
7. ✅ 生成 manifest.json
|
|
133
|
-
8. ✅ 验证插件结构
|
|
134
|
-
9. ✅ 打包为 `.mpk` 文件
|
|
135
|
-
|
|
136
|
-
### 步骤 6: 查看结果
|
|
137
|
-
|
|
138
|
-
```
|
|
139
|
-
✓ Plugin ready!
|
|
140
|
-
File: /path/to/my-plugin-1.0.0.mpk
|
|
141
|
-
Size: 45.23 KB
|
|
142
|
-
Version: 1.0.0
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## 📂 标准项目结构
|
|
148
|
-
|
|
149
|
-
CLI 期望以下目录结构(约定优于配置):
|
|
150
|
-
|
|
151
|
-
```
|
|
152
|
-
my-plugin/
|
|
153
|
-
├── package.json
|
|
154
|
-
├── tsconfig.json # 只需要基本的 TS 配置
|
|
155
|
-
├── src/
|
|
156
|
-
│ ├── server/
|
|
157
|
-
│ │ └── index.ts # 服务端入口(必需)
|
|
158
|
-
│ ├── client/
|
|
159
|
-
│ │ └── index.tsx # 客户端入口(可选)
|
|
160
|
-
│ └── shared/
|
|
161
|
-
│ └── types.ts # 共享类型(可选)
|
|
162
|
-
└── README.md
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
**入口文件命名约定:**
|
|
166
|
-
|
|
167
|
-
| 类型 | 路径 | 说明 |
|
|
168
|
-
|------|------|------|
|
|
169
|
-
| 服务端 | `src/server/index.ts` | 主入口文件 |
|
|
170
|
-
| 客户端 | `src/client/index.tsx` | 主入口文件 |
|
|
171
|
-
|
|
172
|
-
如果找不到这些文件,CLI 会尝试其他常见模式:
|
|
173
|
-
- `src/server.ts`
|
|
174
|
-
- `src/client.tsx`
|
|
175
|
-
- `server/index.ts`
|
|
176
|
-
- `client/index.tsx`
|
|
177
|
-
|
|
178
|
-
---
|
|
179
|
-
|
|
180
|
-
## ⚙️ 默认行为详解
|
|
181
|
-
|
|
182
|
-
### 1. 自动使用 esbuild
|
|
183
|
-
|
|
184
|
-
无论你项目中是否有 webpack、rollup 或 tsc 配置,CLI **始终使用 esbuild** 进行构建。
|
|
185
|
-
|
|
186
|
-
**为什么?**
|
|
187
|
-
- ⚡ **速度** - 比 webpack 快 10-100 倍
|
|
188
|
-
- 📦 **体积小** - 更好的 tree shaking
|
|
189
|
-
- 🔧 **零配置** - 无需复杂的配置文件
|
|
190
|
-
- 🎯 **优化** - 内置 minify、bundle、code splitting
|
|
191
|
-
|
|
192
|
-
### 2. 智能外部依赖处理
|
|
193
|
-
|
|
194
|
-
CLI 自动将以下依赖标记为 external(不打包):
|
|
195
|
-
|
|
196
|
-
```typescript
|
|
197
|
-
const defaultExternal = [
|
|
198
|
-
'react', // 由 SSR 宿主提供
|
|
199
|
-
'react-dom', // 由 SSR 宿主提供
|
|
200
|
-
'express', // 由 Node.js 环境提供
|
|
201
|
-
'@linyjs/*', // Ling 框架核心
|
|
202
|
-
'@ling/*' // 兼容旧版本
|
|
203
|
-
];
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
**好处:**
|
|
207
|
-
- 📉 **减小体积** - 避免重复打包共享库
|
|
208
|
-
- ⚡ **加载更快** - 利用浏览器/Node.js 缓存
|
|
209
|
-
- 🔄 **版本一致** - 确保使用宿主环境的版本
|
|
210
|
-
|
|
211
|
-
### 3. 自动优化
|
|
212
|
-
|
|
213
|
-
默认启用的优化:
|
|
214
|
-
|
|
215
|
-
```bash
|
|
216
|
-
# Server Bundle
|
|
217
|
-
--bundle # 打包所有依赖
|
|
218
|
-
--minify # 代码压缩
|
|
219
|
-
--sourcemap # 生成 sourcemap
|
|
220
|
-
--platform=node # Node.js 优化
|
|
221
|
-
--target=node18 # 目标平台
|
|
222
|
-
|
|
223
|
-
# Client Bundle
|
|
224
|
-
--bundle # 打包所有依赖
|
|
225
|
-
--minify # 代码压缩
|
|
226
|
-
--sourcemap # 生成 sourcemap
|
|
227
|
-
--platform=browser # 浏览器优化
|
|
228
|
-
--target=es2020 # 目标平台
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### 4. 输出结构
|
|
232
|
-
|
|
233
|
-
CLI 生成的标准输出:
|
|
234
|
-
|
|
235
|
-
```
|
|
236
|
-
dist/
|
|
237
|
-
├── server/
|
|
238
|
-
│ └── index.js # 服务端 bundle
|
|
239
|
-
├── client/
|
|
240
|
-
│ └── index.js # 客户端 bundle
|
|
241
|
-
└── manifest.json # 插件清单
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
---
|
|
245
|
-
|
|
246
|
-
## 🎨 自定义(可选)
|
|
247
|
-
|
|
248
|
-
虽然零配置是默认行为,但你仍然可以自定义:
|
|
249
|
-
|
|
250
|
-
### 方式 1: CLI 选项
|
|
251
|
-
|
|
252
|
-
```bash
|
|
253
|
-
# 跳过构建(使用已有 dist/)
|
|
254
|
-
npx @linyjs/cli pack --no-build
|
|
255
|
-
|
|
256
|
-
# 详细模式(查看构建详情)
|
|
257
|
-
npx @linyjs/cli pack --verbose
|
|
258
|
-
|
|
259
|
-
# 指定输出目录
|
|
260
|
-
npx @linyjs/cli pack -o ./releases
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
### 方式 2: .lingrc.json(高级)
|
|
264
|
-
|
|
265
|
-
```json
|
|
266
|
-
{
|
|
267
|
-
"build": {
|
|
268
|
-
"external": [
|
|
269
|
-
"react",
|
|
270
|
-
"lodash", // 添加额外的 external 依赖
|
|
271
|
-
"moment"
|
|
272
|
-
],
|
|
273
|
-
"exclude": [
|
|
274
|
-
"*.test.ts", // 排除测试文件
|
|
275
|
-
"**/__tests__/**"
|
|
276
|
-
]
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
### 方式 3: package.json 元数据
|
|
282
|
-
|
|
283
|
-
```json
|
|
284
|
-
{
|
|
285
|
-
"name": "@my-org/my-plugin",
|
|
286
|
-
"version": "1.0.0",
|
|
287
|
-
"description": "My awesome plugin",
|
|
288
|
-
"author": "Your Name",
|
|
289
|
-
"ling": {
|
|
290
|
-
"permissions": [
|
|
291
|
-
"article:read",
|
|
292
|
-
"article:write"
|
|
293
|
-
]
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
---
|
|
299
|
-
|
|
300
|
-
## 💡 最佳实践
|
|
301
|
-
|
|
302
|
-
### 1. 保持简单
|
|
303
|
-
|
|
304
|
-
```typescript
|
|
305
|
-
// ✅ 推荐:简单的入口文件
|
|
306
|
-
export const myServerModule: IModule = {
|
|
307
|
-
name: 'myServer',
|
|
308
|
-
serviceDefs: [...]
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
// ❌ 避免:复杂的构建逻辑
|
|
312
|
-
// 不需要 webpack.config.js
|
|
313
|
-
// 不需要 rollup.config.js
|
|
314
|
-
// 不需要 babel.config.js
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
### 2. 使用 TypeScript
|
|
318
|
-
|
|
319
|
-
```json
|
|
320
|
-
// tsconfig.json - 保持简洁
|
|
321
|
-
{
|
|
322
|
-
"compilerOptions": {
|
|
323
|
-
"strict": true,
|
|
324
|
-
"esModuleInterop": true
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
### 3. 合理的依赖管理
|
|
330
|
-
|
|
331
|
-
```json
|
|
332
|
-
{
|
|
333
|
-
"dependencies": {
|
|
334
|
-
"@linyjs/server-module-interface": "^0.0.1",
|
|
335
|
-
"@linyjs/client-module-interface": "^0.0.1",
|
|
336
|
-
"react": "^19.0.0"
|
|
337
|
-
// ❌ 不要添加 express、webpack 等构建工具
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
### 4. 清晰的目录结构
|
|
343
|
-
|
|
344
|
-
```
|
|
345
|
-
src/
|
|
346
|
-
├── server/ # 服务端代码
|
|
347
|
-
│ ├── index.ts # 入口
|
|
348
|
-
│ ├── controllers/ # 控制器
|
|
349
|
-
│ └── services/ # 服务
|
|
350
|
-
├── client/ # 客户端代码
|
|
351
|
-
│ ├── index.tsx # 入口
|
|
352
|
-
│ ├── components/ # 组件
|
|
353
|
-
│ └── pages/ # 页面
|
|
354
|
-
└── shared/ # 共享代码
|
|
355
|
-
└── types.ts # 类型定义
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
---
|
|
359
|
-
|
|
360
|
-
## 🐛 常见问题
|
|
361
|
-
|
|
362
|
-
### Q1: 我需要配置 webpack 吗?
|
|
363
|
-
|
|
364
|
-
**不需要!** CLI 内置了 esbuild,自动处理所有构建细节。
|
|
365
|
-
|
|
366
|
-
```bash
|
|
367
|
-
# 只需运行
|
|
368
|
-
npx @linyjs/cli pack
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
### Q2: 如何添加第三方库?
|
|
372
|
-
|
|
373
|
-
直接安装即可,CLI 会自动处理:
|
|
374
|
-
|
|
375
|
-
```bash
|
|
376
|
-
npm install lodash
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
在代码中使用:
|
|
380
|
-
|
|
381
|
-
```typescript
|
|
382
|
-
import _ from 'lodash';
|
|
383
|
-
|
|
384
|
-
// CLI 会自动判断是否打包或 external
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
如果需要 external(减小体积),在 `.lingrc.json` 中配置:
|
|
388
|
-
|
|
389
|
-
```json
|
|
390
|
-
{
|
|
391
|
-
"build": {
|
|
392
|
-
"external": ["lodash"]
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
### Q3: 如何调试构建问题?
|
|
398
|
-
|
|
399
|
-
使用 `--verbose` 模式:
|
|
400
|
-
|
|
401
|
-
```bash
|
|
402
|
-
npx @linyjs/cli pack --verbose
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
会显示:
|
|
406
|
-
- 检测到的构建系统
|
|
407
|
-
- 使用的构建命令
|
|
408
|
-
- 每个 bundle 的大小
|
|
409
|
-
- 详细的错误信息
|
|
410
|
-
|
|
411
|
-
### Q4: 支持 JSX/TSX 吗?
|
|
412
|
-
|
|
413
|
-
**完全支持!** CLI 自动处理 JSX 转换。
|
|
414
|
-
|
|
415
|
-
```tsx
|
|
416
|
-
// src/client/index.tsx
|
|
417
|
-
function MyComponent() {
|
|
418
|
-
return <div>Hello</div>; // ✅ 正常工作
|
|
419
|
-
}
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
### Q5: 如何处理环境变量?
|
|
423
|
-
|
|
424
|
-
在代码中直接使用:
|
|
425
|
-
|
|
426
|
-
```typescript
|
|
427
|
-
const apiKey = process.env.MY_API_KEY;
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
CLI 不会处理环境变量,它们会在运行时从宿主环境读取。
|
|
431
|
-
|
|
432
|
-
---
|
|
433
|
-
|
|
434
|
-
## 📊 性能对比
|
|
435
|
-
|
|
436
|
-
| 构建方式 | 配置复杂度 | 构建速度 | 输出体积 | 推荐度 |
|
|
437
|
-
|---------|-----------|---------|---------|--------|
|
|
438
|
-
| **CLI (esbuild)** | ⭐ 零配置 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ✅✅✅ |
|
|
439
|
-
| Webpack | ⭐⭐⭐⭐ 复杂 | ⭐⭐ | ⭐⭐⭐ | ❌ |
|
|
440
|
-
| Rollup | ⭐⭐⭐ 中等 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ❌ |
|
|
441
|
-
| tsc only | ⭐⭐ 简单 | ⭐⭐⭐⭐ | ⭐⭐ | ❌ |
|
|
442
|
-
|
|
443
|
-
---
|
|
444
|
-
|
|
445
|
-
## 🎓 总结
|
|
446
|
-
|
|
447
|
-
**零配置插件开发的三个原则:**
|
|
448
|
-
|
|
449
|
-
1. **约定优于配置** - 遵循标准目录结构
|
|
450
|
-
2. **智能默认值** - CLI 自动选择最优策略
|
|
451
|
-
3. **渐进式增强** - 需要时可以自定义
|
|
452
|
-
|
|
453
|
-
**你只需要:**
|
|
454
|
-
```bash
|
|
455
|
-
# 1. 写代码
|
|
456
|
-
# 2. 打包
|
|
457
|
-
npx @linyjs/cli pack
|
|
458
|
-
|
|
459
|
-
# 完成!✨
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
享受简单、高效的插件开发体验吧!🚀
|
package/src/commands/dev.ts
DELETED
|
@@ -1,251 +0,0 @@
|
|
|
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
|
-
* Load .env file and return environment variables
|
|
20
|
-
*/
|
|
21
|
-
function loadEnvConfig(projectRoot: string): Record<string, string> {
|
|
22
|
-
const envPath = path.join(projectRoot, '.env');
|
|
23
|
-
|
|
24
|
-
if (!fs.existsSync(envPath)) {
|
|
25
|
-
// Try parent directories (monorepo structure)
|
|
26
|
-
const parentEnvPath = path.join(projectRoot, '..', '.env');
|
|
27
|
-
if (fs.existsSync(parentEnvPath)) {
|
|
28
|
-
return parseEnvFile(parentEnvPath);
|
|
29
|
-
}
|
|
30
|
-
return {};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return parseEnvFile(envPath);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Parse .env file content
|
|
38
|
-
*/
|
|
39
|
-
function parseEnvFile(envPath: string): Record<string, string> {
|
|
40
|
-
const content = fs.readFileSync(envPath, 'utf-8');
|
|
41
|
-
const config: Record<string, string> = {};
|
|
42
|
-
|
|
43
|
-
content.split('\n').forEach(line => {
|
|
44
|
-
line = line.trim();
|
|
45
|
-
if (line && !line.startsWith('#')) {
|
|
46
|
-
const [key, ...valueParts] = line.split('=');
|
|
47
|
-
if (key && valueParts.length > 0) {
|
|
48
|
-
config[key.trim()] = valueParts.join('=').trim();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
return config;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Execute dev workflow: pack -> copy -> register
|
|
58
|
-
*/
|
|
59
|
-
export async function devPlugin(options: DevOptions) {
|
|
60
|
-
const projectRoot = process.cwd();
|
|
61
|
-
|
|
62
|
-
console.log(chalk.blue('🚀 Ling Plugin Dev Mode\n'));
|
|
63
|
-
|
|
64
|
-
// Load environment configuration
|
|
65
|
-
const envConfig = loadEnvConfig(projectRoot);
|
|
66
|
-
|
|
67
|
-
// Step 1: Pack the plugin
|
|
68
|
-
const spinner1 = ora('Packing plugin...').start();
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
// Create temporary output directory
|
|
72
|
-
const tempOutputDir = path.join(projectRoot, '.ling-tmp');
|
|
73
|
-
|
|
74
|
-
await packPlugin({
|
|
75
|
-
output: tempOutputDir,
|
|
76
|
-
build: true,
|
|
77
|
-
verbose: options.verbose
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// Find the generated .mpk file
|
|
81
|
-
const mpkFiles = fs.readdirSync(tempOutputDir).filter(f => f.endsWith('.mpk'));
|
|
82
|
-
|
|
83
|
-
if (mpkFiles.length === 0) {
|
|
84
|
-
spinner1.fail('No .mpk file generated');
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const mpkFile = mpkFiles[0];
|
|
89
|
-
const mpkPath = path.join(tempOutputDir, mpkFile);
|
|
90
|
-
|
|
91
|
-
spinner1.succeed(`Plugin packed: ${mpkFile}`);
|
|
92
|
-
|
|
93
|
-
// Step 2: Determine target extensions directory
|
|
94
|
-
const targetDir = options.target || findExtensionsDir(projectRoot, envConfig);
|
|
95
|
-
|
|
96
|
-
if (!targetDir) {
|
|
97
|
-
spinner1.fail('Cannot find extensions directory');
|
|
98
|
-
console.error(chalk.red('\nPlease specify target directory:'));
|
|
99
|
-
console.error(chalk.gray('\nOption 1: Use --target flag'));
|
|
100
|
-
console.error(chalk.gray(' npx @linyjs/cli dev --target /path/to/extensions'));
|
|
101
|
-
console.error(chalk.gray('\nOption 2: Add to .env file'));
|
|
102
|
-
console.error(chalk.gray(' EXTENSIONS_DIR=/path/to/ling/packages/ssr/extensions'));
|
|
103
|
-
process.exit(1);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Step 3: Extract and copy to extensions
|
|
107
|
-
const spinner2 = ora('Installing to extensions...').start();
|
|
108
|
-
|
|
109
|
-
const pluginName = mpkFile.replace('.mpk', '').replace(/-\d+\.\d+\.\d+$/, '');
|
|
110
|
-
const installPath = path.join(targetDir, pluginName);
|
|
111
|
-
|
|
112
|
-
// Remove old installation if exists
|
|
113
|
-
if (fs.existsSync(installPath)) {
|
|
114
|
-
fs.rmSync(installPath, { recursive: true, force: true });
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Create installation directory
|
|
118
|
-
fs.mkdirSync(installPath, { recursive: true });
|
|
119
|
-
|
|
120
|
-
// Extract .mpk file (it's a ZIP)
|
|
121
|
-
await extractMpk(mpkPath, installPath);
|
|
122
|
-
|
|
123
|
-
spinner2.succeed(`Installed to: ${installPath}`);
|
|
124
|
-
|
|
125
|
-
// Step 4: Register in packages.json
|
|
126
|
-
const spinner3 = ora('Registering plugin...').start();
|
|
127
|
-
|
|
128
|
-
const packagesJsonPath = path.join(targetDir, 'packages.json');
|
|
129
|
-
|
|
130
|
-
let packages: any[] = [];
|
|
131
|
-
|
|
132
|
-
if (fs.existsSync(packagesJsonPath)) {
|
|
133
|
-
const content = fs.readFileSync(packagesJsonPath, 'utf-8');
|
|
134
|
-
packages = JSON.parse(content);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Read manifest from installed plugin
|
|
138
|
-
const manifestPath = path.join(installPath, 'manifest.json');
|
|
139
|
-
let manifest: any = {};
|
|
140
|
-
|
|
141
|
-
if (fs.existsSync(manifestPath)) {
|
|
142
|
-
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Check if already registered
|
|
146
|
-
const existingIndex = packages.findIndex(p => p.name === pluginName);
|
|
147
|
-
|
|
148
|
-
const packageInfo = {
|
|
149
|
-
name: pluginName,
|
|
150
|
-
version: manifest.version || '1.0.0',
|
|
151
|
-
installPath: `extensions/${pluginName}`,
|
|
152
|
-
status: 'enabled',
|
|
153
|
-
installTime: Date.now(),
|
|
154
|
-
manifest: {
|
|
155
|
-
name: manifest.name || pluginName,
|
|
156
|
-
version: manifest.version || '1.0.0',
|
|
157
|
-
description: manifest.description || '',
|
|
158
|
-
author: manifest.author || '',
|
|
159
|
-
serverEntry: manifest.serverEntry || 'server/index.js',
|
|
160
|
-
clientEntry: manifest.clientEntry || 'client/index.js'
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
if (existingIndex >= 0) {
|
|
165
|
-
packages[existingIndex] = packageInfo;
|
|
166
|
-
spinner3.succeed(`Plugin updated in packages.json`);
|
|
167
|
-
} else {
|
|
168
|
-
packages.push(packageInfo);
|
|
169
|
-
spinner3.succeed(`Plugin registered in packages.json`);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Write packages.json
|
|
173
|
-
fs.writeFileSync(packagesJsonPath, JSON.stringify(packages, null, 2) + '\n', 'utf-8');
|
|
174
|
-
|
|
175
|
-
// Cleanup temporary files
|
|
176
|
-
if (fs.existsSync(tempOutputDir)) {
|
|
177
|
-
fs.rmSync(tempOutputDir, { recursive: true, force: true });
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Show success message
|
|
181
|
-
console.log('\n' + chalk.green('✓ Development setup complete!\n'));
|
|
182
|
-
console.log(chalk.cyan('Plugin installed:'));
|
|
183
|
-
console.log(` Name: ${chalk.bold(pluginName)}`);
|
|
184
|
-
console.log(` Path: ${chalk.gray(installPath)}`);
|
|
185
|
-
console.log(` Status: ${chalk.green('enabled')}\n`);
|
|
186
|
-
|
|
187
|
-
console.log(chalk.cyan('Next steps:'));
|
|
188
|
-
console.log(` 1. Restart SSR server:`);
|
|
189
|
-
console.log(` ${chalk.gray('cd packages/ssr && npx tsx src/demo/dev-server-with-pkg.tsx')}`);
|
|
190
|
-
console.log(` 2. Or use hot reload (if supported)`);
|
|
191
|
-
console.log(` 3. Test your plugin in the browser\n`);
|
|
192
|
-
|
|
193
|
-
} catch (error) {
|
|
194
|
-
spinner1.fail('Dev workflow failed');
|
|
195
|
-
console.error(chalk.red('\nError:'), error instanceof Error ? error.message : error);
|
|
196
|
-
process.exit(1);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Find extensions directory with priority:
|
|
202
|
-
* 1. --target command line option
|
|
203
|
-
* 2. EXTENSIONS_DIR from .env file
|
|
204
|
-
* 3. Auto-detect common paths
|
|
205
|
-
*/
|
|
206
|
-
function findExtensionsDir(projectRoot: string, envConfig: Record<string, string>): string | null {
|
|
207
|
-
// Priority 1: Environment variable from .env
|
|
208
|
-
const envExtensionsDir = envConfig.EXTENSIONS_DIR || envConfig.EXTENSIONS_PATH;
|
|
209
|
-
if (envExtensionsDir) {
|
|
210
|
-
// Resolve relative path from project root
|
|
211
|
-
const resolvedPath = path.isAbsolute(envExtensionsDir)
|
|
212
|
-
? envExtensionsDir
|
|
213
|
-
: path.join(projectRoot, envExtensionsDir);
|
|
214
|
-
|
|
215
|
-
if (fs.existsSync(resolvedPath)) {
|
|
216
|
-
return resolvedPath;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Priority 2: Auto-detect common paths (monorepo structure)
|
|
221
|
-
const candidates = [
|
|
222
|
-
path.join(projectRoot, '..', 'ssr', 'extensions'),
|
|
223
|
-
path.join(projectRoot, '..', '..', 'packages', 'ssr', 'extensions'),
|
|
224
|
-
path.join(projectRoot, 'packages', 'ssr', 'extensions'),
|
|
225
|
-
];
|
|
226
|
-
|
|
227
|
-
for (const candidate of candidates) {
|
|
228
|
-
if (fs.existsSync(candidate)) {
|
|
229
|
-
return candidate;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Extract .mpk file (ZIP format) to target directory
|
|
238
|
-
*/
|
|
239
|
-
async function extractMpk(mpkPath: string, targetDir: string): Promise<void> {
|
|
240
|
-
// Use unzipper or similar library
|
|
241
|
-
// For now, use system unzip command
|
|
242
|
-
const { execSync } = await import('child_process');
|
|
243
|
-
|
|
244
|
-
try {
|
|
245
|
-
execSync(`unzip -o "${mpkPath}" -d "${targetDir}"`, {
|
|
246
|
-
stdio: 'pipe'
|
|
247
|
-
});
|
|
248
|
-
} catch (error) {
|
|
249
|
-
throw new Error('Failed to extract .mpk file. Make sure unzip is installed.');
|
|
250
|
-
}
|
|
251
|
-
}
|