create-golden 1.4.9 → 1.4.11
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/README.md +69 -69
- package/index.js +424 -380
- package/package.json +22 -22
package/README.md
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
# create-golden
|
|
2
|
-
|
|
3
|
-
用于从 GitHub 仓库创建 golden-template 项目的命令行工具。
|
|
4
|
-
|
|
5
|
-
## 使用前准备
|
|
6
|
-
|
|
7
|
-
由于仓库是私有的,使用前需要 GitHub Personal Access Token。
|
|
8
|
-
|
|
9
|
-
**方式一:交互式输入(推荐,最简单)**
|
|
10
|
-
直接运行命令,脚本会提示你输入 token。**首次输入后会自动保存到本地,后续使用无需重复输入。**
|
|
11
|
-
|
|
12
|
-
**方式二:环境变量(可选)**
|
|
13
|
-
如果已设置环境变量 `GITHUB_TOKEN`,会优先使用环境变量,不会读取本地保存的 token。
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
# Windows (PowerShell)
|
|
17
|
-
$env:GITHUB_TOKEN="your_token_here"
|
|
18
|
-
|
|
19
|
-
# Windows (CMD)
|
|
20
|
-
set GITHUB_TOKEN=your_token_here
|
|
21
|
-
|
|
22
|
-
# Linux/Mac
|
|
23
|
-
export GITHUB_TOKEN=your_token_here
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
**如何创建 Token:**
|
|
27
|
-
1. 访问 https://github.com/settings/tokens
|
|
28
|
-
2. 点击 "Generate new token" -> "Generate new token (classic)"
|
|
29
|
-
3. 输入 token 名称,选择 `repo` 权限
|
|
30
|
-
4. 点击 "Generate token" 并复制 token
|
|
31
|
-
|
|
32
|
-
**Token 存储位置:**
|
|
33
|
-
- Windows: `C:\Users\你的用户名\.create-golden-token`
|
|
34
|
-
- Linux/Mac: `~/.create-golden-token`
|
|
35
|
-
|
|
36
|
-
## 使用方法
|
|
37
|
-
|
|
38
|
-
### 初始化项目
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
# 在当前目录初始化项目
|
|
42
|
-
yarn create golden
|
|
43
|
-
|
|
44
|
-
# 或显式指定 init 命令
|
|
45
|
-
yarn create golden init
|
|
46
|
-
|
|
47
|
-
# 在指定目录初始化项目
|
|
48
|
-
yarn create golden init [project-name]
|
|
49
|
-
|
|
50
|
-
# 兼容旧用法(直接指定项目名)
|
|
51
|
-
yarn create golden [project-name]
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
如果不指定项目名称,会在当前目录初始化项目。
|
|
55
|
-
|
|
56
|
-
### 添加可选内容
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
# 添加 modal 组件(待实现)
|
|
60
|
-
yarn create golden modal
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## 注意事项
|
|
64
|
-
|
|
65
|
-
- 需要拥有 `kuidream/golden-base-specs` 仓库的访问权限
|
|
66
|
-
- 首次使用需要提供有效的 GitHub Personal Access Token(交互式输入或环境变量)
|
|
67
|
-
- Token 会自动保存到本地,后续使用无需重复输入
|
|
68
|
-
- GitHub 已不支持密码认证,必须使用 Personal Access Token
|
|
69
|
-
- 会自动排除 `node_modules` 和 `dist` 文件夹
|
|
1
|
+
# create-golden
|
|
2
|
+
|
|
3
|
+
用于从 GitHub 仓库创建 golden-template 项目的命令行工具。
|
|
4
|
+
|
|
5
|
+
## 使用前准备
|
|
6
|
+
|
|
7
|
+
由于仓库是私有的,使用前需要 GitHub Personal Access Token。
|
|
8
|
+
|
|
9
|
+
**方式一:交互式输入(推荐,最简单)**
|
|
10
|
+
直接运行命令,脚本会提示你输入 token。**首次输入后会自动保存到本地,后续使用无需重复输入。**
|
|
11
|
+
|
|
12
|
+
**方式二:环境变量(可选)**
|
|
13
|
+
如果已设置环境变量 `GITHUB_TOKEN`,会优先使用环境变量,不会读取本地保存的 token。
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Windows (PowerShell)
|
|
17
|
+
$env:GITHUB_TOKEN="your_token_here"
|
|
18
|
+
|
|
19
|
+
# Windows (CMD)
|
|
20
|
+
set GITHUB_TOKEN=your_token_here
|
|
21
|
+
|
|
22
|
+
# Linux/Mac
|
|
23
|
+
export GITHUB_TOKEN=your_token_here
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**如何创建 Token:**
|
|
27
|
+
1. 访问 https://github.com/settings/tokens
|
|
28
|
+
2. 点击 "Generate new token" -> "Generate new token (classic)"
|
|
29
|
+
3. 输入 token 名称,选择 `repo` 权限
|
|
30
|
+
4. 点击 "Generate token" 并复制 token
|
|
31
|
+
|
|
32
|
+
**Token 存储位置:**
|
|
33
|
+
- Windows: `C:\Users\你的用户名\.create-golden-token`
|
|
34
|
+
- Linux/Mac: `~/.create-golden-token`
|
|
35
|
+
|
|
36
|
+
## 使用方法
|
|
37
|
+
|
|
38
|
+
### 初始化项目
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# 在当前目录初始化项目
|
|
42
|
+
yarn create golden
|
|
43
|
+
|
|
44
|
+
# 或显式指定 init 命令
|
|
45
|
+
yarn create golden init
|
|
46
|
+
|
|
47
|
+
# 在指定目录初始化项目
|
|
48
|
+
yarn create golden init [project-name]
|
|
49
|
+
|
|
50
|
+
# 兼容旧用法(直接指定项目名)
|
|
51
|
+
yarn create golden [project-name]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
如果不指定项目名称,会在当前目录初始化项目。
|
|
55
|
+
|
|
56
|
+
### 添加可选内容
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# 添加 modal 组件(待实现)
|
|
60
|
+
yarn create golden modal
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 注意事项
|
|
64
|
+
|
|
65
|
+
- 需要拥有 `kuidream/golden-base-specs` 仓库的访问权限
|
|
66
|
+
- 首次使用需要提供有效的 GitHub Personal Access Token(交互式输入或环境变量)
|
|
67
|
+
- Token 会自动保存到本地,后续使用无需重复输入
|
|
68
|
+
- GitHub 已不支持密码认证,必须使用 Personal Access Token
|
|
69
|
+
- 会自动排除 `node_modules` 和 `dist` 文件夹
|
package/index.js
CHANGED
|
@@ -1,380 +1,424 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs-extra');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const https = require('https');
|
|
6
|
-
const yauzl = require('yauzl');
|
|
7
|
-
const readline = require('readline');
|
|
8
|
-
const os = require('os');
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
for (const ignoreRule of ignoreRules) {
|
|
157
|
-
const hasRule = lines.some(line => line.trim() === ignoreRule);
|
|
158
|
-
|
|
159
|
-
if (!hasRule) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const https = require('https');
|
|
6
|
+
const yauzl = require('yauzl');
|
|
7
|
+
const readline = require('readline');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const { execSync } = require('child_process');
|
|
10
|
+
|
|
11
|
+
const GITHUB_REPO = 'kuidream/golden-base-specs';
|
|
12
|
+
const GITHUB_BRANCH = 'main';
|
|
13
|
+
const GITHUB_API_URL = `https://api.github.com/repos/${GITHUB_REPO}/zipball/${GITHUB_BRANCH}`;
|
|
14
|
+
const EXCLUDE_DIRS = ['node_modules', 'dist', '.ray', '.DS_Store', 'VI spec'];
|
|
15
|
+
const TOKEN_FILE = path.join(os.homedir(), '.create-golden-token');
|
|
16
|
+
|
|
17
|
+
async function downloadFile(url, dest, token) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const file = fs.createWriteStream(dest);
|
|
20
|
+
const headers = {
|
|
21
|
+
'User-Agent': 'create-golden'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
if (token) {
|
|
25
|
+
headers['Authorization'] = `token ${token}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
https.get(url, {
|
|
29
|
+
headers: headers,
|
|
30
|
+
followRedirect: true
|
|
31
|
+
}, (response) => {
|
|
32
|
+
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
33
|
+
return downloadFile(response.headers.location, dest, token).then(resolve).catch(reject);
|
|
34
|
+
}
|
|
35
|
+
if (response.statusCode === 401 || response.statusCode === 403) {
|
|
36
|
+
reject(new Error('Authentication failed. Please set GITHUB_TOKEN environment variable with a valid token that has access to the repository.'));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (response.statusCode !== 200) {
|
|
40
|
+
reject(new Error(`Failed to download: ${response.statusCode} ${response.statusMessage}`));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
response.pipe(file);
|
|
44
|
+
file.on('finish', () => {
|
|
45
|
+
file.close();
|
|
46
|
+
resolve();
|
|
47
|
+
});
|
|
48
|
+
}).on('error', (err) => {
|
|
49
|
+
fs.unlink(dest, () => {});
|
|
50
|
+
reject(err);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function shouldExclude(filePath) {
|
|
56
|
+
const parts = filePath.split(path.sep);
|
|
57
|
+
return EXCLUDE_DIRS.some(dir => parts.includes(dir));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function extractZip(zipPath, extractTo) {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
|
|
63
|
+
if (err) {
|
|
64
|
+
reject(err);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
zipfile.readEntry();
|
|
69
|
+
let rootDir = null;
|
|
70
|
+
|
|
71
|
+
zipfile.on('entry', (entry) => {
|
|
72
|
+
if (/\/$/.test(entry.fileName)) {
|
|
73
|
+
if (!rootDir) {
|
|
74
|
+
rootDir = entry.fileName;
|
|
75
|
+
}
|
|
76
|
+
zipfile.readEntry();
|
|
77
|
+
} else {
|
|
78
|
+
if (shouldExclude(entry.fileName)) {
|
|
79
|
+
zipfile.readEntry();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const relativePath = rootDir ? entry.fileName.replace(rootDir, '') : entry.fileName;
|
|
84
|
+
|
|
85
|
+
if (!relativePath.startsWith('golden-template/')) {
|
|
86
|
+
zipfile.readEntry();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const targetPath = path.join(extractTo, relativePath.replace('golden-template/', ''));
|
|
91
|
+
const targetDir = path.dirname(targetPath);
|
|
92
|
+
|
|
93
|
+
fs.ensureDirSync(targetDir);
|
|
94
|
+
|
|
95
|
+
zipfile.openReadStream(entry, (err, readStream) => {
|
|
96
|
+
if (err) {
|
|
97
|
+
reject(err);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const writeStream = fs.createWriteStream(targetPath);
|
|
102
|
+
readStream.pipe(writeStream);
|
|
103
|
+
writeStream.on('close', () => {
|
|
104
|
+
zipfile.readEntry();
|
|
105
|
+
});
|
|
106
|
+
writeStream.on('error', (err) => {
|
|
107
|
+
reject(err);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
zipfile.on('end', () => {
|
|
114
|
+
resolve();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
zipfile.on('error', (err) => {
|
|
118
|
+
reject(err);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getSavedToken() {
|
|
125
|
+
try {
|
|
126
|
+
if (fs.existsSync(TOKEN_FILE)) {
|
|
127
|
+
return fs.readFileSync(TOKEN_FILE, 'utf8').trim();
|
|
128
|
+
}
|
|
129
|
+
} catch (err) {
|
|
130
|
+
// Ignore errors
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function saveToken(token) {
|
|
136
|
+
try {
|
|
137
|
+
fs.writeFileSync(TOKEN_FILE, token, { mode: 0o600 });
|
|
138
|
+
} catch (err) {
|
|
139
|
+
console.warn('Warning: Could not save token to file:', err.message);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function updateGitignore(projectRoot) {
|
|
144
|
+
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
145
|
+
const ignoreRules = ['src/pages/demo'];
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
let gitignoreContent = '';
|
|
149
|
+
if (fs.existsSync(gitignorePath)) {
|
|
150
|
+
gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const lines = gitignoreContent.split('\n');
|
|
154
|
+
let hasChanges = false;
|
|
155
|
+
|
|
156
|
+
for (const ignoreRule of ignoreRules) {
|
|
157
|
+
const hasRule = lines.some(line => line.trim() === ignoreRule);
|
|
158
|
+
|
|
159
|
+
if (!hasRule) {
|
|
160
|
+
if (gitignoreContent && !gitignoreContent.endsWith('\n')) {
|
|
161
|
+
gitignoreContent += '\n';
|
|
162
|
+
}
|
|
163
|
+
gitignoreContent += ignoreRule + '\n';
|
|
164
|
+
hasChanges = true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (hasChanges) {
|
|
169
|
+
fs.writeFileSync(gitignorePath, gitignoreContent, 'utf8');
|
|
170
|
+
}
|
|
171
|
+
} catch (err) {
|
|
172
|
+
console.warn('Warning: Could not update .gitignore:', err.message);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function updateGitExclude(projectRoot) {
|
|
177
|
+
const gitDir = path.join(projectRoot, '.git');
|
|
178
|
+
const excludeRules = ['.cursorrules', 'AGENTS.md', '代码规范.md', '项目架构说明.md', '组件和功能速查地图.md'];
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
if (!fs.existsSync(gitDir)) {
|
|
182
|
+
try {
|
|
183
|
+
execSync('git init', { cwd: projectRoot, stdio: 'ignore' });
|
|
184
|
+
} catch (err) {
|
|
185
|
+
console.warn('Warning: Could not initialize git repository:', err.message);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const infoDir = path.join(gitDir, 'info');
|
|
191
|
+
const excludePath = path.join(infoDir, 'exclude');
|
|
192
|
+
|
|
193
|
+
fs.ensureDirSync(infoDir);
|
|
194
|
+
|
|
195
|
+
let excludeContent = '';
|
|
196
|
+
if (fs.existsSync(excludePath)) {
|
|
197
|
+
excludeContent = fs.readFileSync(excludePath, 'utf8');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const lines = excludeContent.split('\n');
|
|
201
|
+
let hasChanges = false;
|
|
202
|
+
|
|
203
|
+
for (const rule of excludeRules) {
|
|
204
|
+
const hasRule = lines.some(line => line.trim() === rule);
|
|
205
|
+
|
|
206
|
+
if (!hasRule) {
|
|
207
|
+
if (excludeContent && !excludeContent.endsWith('\n')) {
|
|
208
|
+
excludeContent += '\n';
|
|
209
|
+
}
|
|
210
|
+
excludeContent += rule + '\n';
|
|
211
|
+
hasChanges = true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (hasChanges) {
|
|
216
|
+
fs.writeFileSync(excludePath, excludeContent, 'utf8');
|
|
217
|
+
}
|
|
218
|
+
} catch (err) {
|
|
219
|
+
console.warn('Warning: Could not update git exclude:', err.message);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function promptToken() {
|
|
224
|
+
return new Promise((resolve) => {
|
|
225
|
+
const rl = readline.createInterface({
|
|
226
|
+
input: process.stdin,
|
|
227
|
+
output: process.stdout
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
console.log('\nGitHub Personal Access Token is required to access the private repository.');
|
|
231
|
+
console.log('You can create a token at: https://github.com/settings/tokens');
|
|
232
|
+
console.log('The token needs "repo" scope to access private repositories.');
|
|
233
|
+
console.log('The token will be saved locally for future use.\n');
|
|
234
|
+
|
|
235
|
+
rl.question('Please enter your GitHub token: ', (token) => {
|
|
236
|
+
rl.close();
|
|
237
|
+
const trimmedToken = token.trim();
|
|
238
|
+
if (trimmedToken) {
|
|
239
|
+
saveToken(trimmedToken);
|
|
240
|
+
}
|
|
241
|
+
resolve(trimmedToken);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const COMMANDS = ['init', 'modal'];
|
|
247
|
+
|
|
248
|
+
async function handleInit(projectName) {
|
|
249
|
+
const targetDir = projectName ? path.join(process.cwd(), projectName) : process.cwd();
|
|
250
|
+
|
|
251
|
+
if (projectName && fs.existsSync(targetDir)) {
|
|
252
|
+
console.error(`Error: Directory ${projectName} already exists.`);
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 检查当前目录是否为空(如果没有指定项目名称)
|
|
257
|
+
if (!projectName) {
|
|
258
|
+
const files = fs.readdirSync(process.cwd());
|
|
259
|
+
const ignoreFiles = ['.git', '.gitignore', 'node_modules', '.DS_Store'];
|
|
260
|
+
const visibleFiles = files.filter(f => !ignoreFiles.includes(f));
|
|
261
|
+
if (visibleFiles.length > 0) {
|
|
262
|
+
console.error('Error: Current directory is not empty. Please specify a project name or use an empty directory.');
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let githubToken = process.env.GITHUB_TOKEN;
|
|
268
|
+
if (!githubToken) {
|
|
269
|
+
githubToken = getSavedToken();
|
|
270
|
+
}
|
|
271
|
+
if (!githubToken) {
|
|
272
|
+
githubToken = await promptToken();
|
|
273
|
+
if (!githubToken) {
|
|
274
|
+
console.error('Error: GitHub token is required.');
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
console.log(`Downloading latest golden-template from GitHub (${GITHUB_REPO}/${GITHUB_BRANCH})...`);
|
|
280
|
+
|
|
281
|
+
// 使用系统临时目录存储 zip 文件
|
|
282
|
+
const zipPath = path.join(os.tmpdir(), `create-golden-${Date.now()}.zip`);
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
await downloadFile(GITHUB_API_URL, zipPath, githubToken);
|
|
286
|
+
console.log('Extracting files...');
|
|
287
|
+
|
|
288
|
+
// 如果指定了项目名,确保目标目录存在
|
|
289
|
+
if (projectName) {
|
|
290
|
+
fs.ensureDirSync(targetDir);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// 直接解压到目标目录
|
|
294
|
+
await extractZip(zipPath, targetDir);
|
|
295
|
+
|
|
296
|
+
// 解压时已经去掉了 golden-template/ 前缀,文件直接解压到 targetDir
|
|
297
|
+
// 但 GitHub zipball 可能还有一个根目录,需要找到实际的项目根目录
|
|
298
|
+
let projectRootPath = targetDir;
|
|
299
|
+
|
|
300
|
+
// 检查 targetDir 下是否直接有项目文件(如 package.json)
|
|
301
|
+
if (!fs.existsSync(path.join(targetDir, 'package.json'))) {
|
|
302
|
+
// 如果没有,可能在子目录中(GitHub zipball 通常会有一个根目录)
|
|
303
|
+
const entries = fs.readdirSync(targetDir, { withFileTypes: true });
|
|
304
|
+
for (const entry of entries) {
|
|
305
|
+
if (entry.isDirectory()) {
|
|
306
|
+
const potentialPath = path.join(targetDir, entry.name);
|
|
307
|
+
if (fs.existsSync(path.join(potentialPath, 'package.json'))) {
|
|
308
|
+
// 将子目录内容移动到目标目录
|
|
309
|
+
const files = fs.readdirSync(potentialPath);
|
|
310
|
+
files.forEach(file => {
|
|
311
|
+
const srcPath = path.join(potentialPath, file);
|
|
312
|
+
const destPath = path.join(targetDir, file);
|
|
313
|
+
fs.moveSync(srcPath, destPath);
|
|
314
|
+
});
|
|
315
|
+
fs.removeSync(potentialPath);
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (fs.existsSync(path.join(projectRootPath, 'package.json'))) {
|
|
323
|
+
updateGitignore(projectRootPath);
|
|
324
|
+
updateGitExclude(projectRootPath);
|
|
325
|
+
|
|
326
|
+
if (projectName) {
|
|
327
|
+
console.log(`Successfully created ${projectName}!`);
|
|
328
|
+
console.log(`\nNext steps:`);
|
|
329
|
+
console.log(` cd ${projectName}`);
|
|
330
|
+
console.log(` yarn`);
|
|
331
|
+
} else {
|
|
332
|
+
console.log(`Successfully initialized golden-template in current directory!`);
|
|
333
|
+
console.log(`\nNext steps:`);
|
|
334
|
+
console.log(` yarn`);
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
// 列出解压后的目录结构以便调试
|
|
338
|
+
console.error('Error: Could not find project files (package.json) in repository');
|
|
339
|
+
console.error('\nExtracted directory structure:');
|
|
340
|
+
try {
|
|
341
|
+
const listDir = (dir, prefix = '') => {
|
|
342
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
343
|
+
entries.forEach((entry, index) => {
|
|
344
|
+
const isLast = index === entries.length - 1;
|
|
345
|
+
const currentPrefix = isLast ? '└── ' : '├── ';
|
|
346
|
+
console.error(prefix + currentPrefix + entry.name);
|
|
347
|
+
if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== 'dist') {
|
|
348
|
+
listDir(path.join(dir, entry.name), prefix + (isLast ? ' ' : '│ '));
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
};
|
|
352
|
+
listDir(targetDir);
|
|
353
|
+
} catch (err) {
|
|
354
|
+
console.error('Could not list directory structure:', err.message);
|
|
355
|
+
}
|
|
356
|
+
throw new Error('Could not find project files in repository');
|
|
357
|
+
}
|
|
358
|
+
} catch (error) {
|
|
359
|
+
console.error('Error:', error.message);
|
|
360
|
+
process.exit(1);
|
|
361
|
+
} finally {
|
|
362
|
+
// 只清理临时 zip 文件
|
|
363
|
+
if (fs.existsSync(zipPath)) {
|
|
364
|
+
try {
|
|
365
|
+
fs.unlinkSync(zipPath);
|
|
366
|
+
} catch (err) {
|
|
367
|
+
// 忽略删除临时文件的错误
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async function handleModal() {
|
|
374
|
+
console.log('Modal command is not implemented yet.');
|
|
375
|
+
console.log('This command will be used to add modal components to your project.');
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function showHelp() {
|
|
380
|
+
console.log('Usage:');
|
|
381
|
+
console.log(' yarn create golden [command] [options]');
|
|
382
|
+
console.log('');
|
|
383
|
+
console.log('Commands:');
|
|
384
|
+
console.log(' init [project-name] Initialize a new golden-template project');
|
|
385
|
+
console.log(' modal Add modal components to the project');
|
|
386
|
+
console.log('');
|
|
387
|
+
console.log('Examples:');
|
|
388
|
+
console.log(' yarn create golden # Initialize in current directory');
|
|
389
|
+
console.log(' yarn create golden init # Initialize in current directory');
|
|
390
|
+
console.log(' yarn create golden init my-project # Initialize in my-project directory');
|
|
391
|
+
console.log(' yarn create golden modal # Add modal components');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async function main() {
|
|
395
|
+
const args = process.argv.slice(2);
|
|
396
|
+
|
|
397
|
+
if (args.length === 0) {
|
|
398
|
+
// yarn create golden -> 默认执行 init
|
|
399
|
+
await handleInit();
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const firstArg = args[0];
|
|
404
|
+
|
|
405
|
+
// 检查第一个参数是否是命令
|
|
406
|
+
if (COMMANDS.includes(firstArg)) {
|
|
407
|
+
const command = firstArg;
|
|
408
|
+
const restArgs = args.slice(1);
|
|
409
|
+
|
|
410
|
+
if (command === 'init') {
|
|
411
|
+
const projectName = restArgs[0];
|
|
412
|
+
await handleInit(projectName);
|
|
413
|
+
} else if (command === 'modal') {
|
|
414
|
+
await handleModal();
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
// 第一个参数不是命令,当作项目名处理(兼容旧用法)
|
|
418
|
+
// yarn create golden my-project -> 执行 init my-project
|
|
419
|
+
await handleInit(firstArg);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
main();
|
|
424
|
+
|
package/package.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "create-golden",
|
|
3
|
-
"version": "1.4.
|
|
4
|
-
"description": "Create a new golden-template project from the latest GitHub repository (requires GITHUB_TOKEN)",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"create-golden": "index.js"
|
|
8
|
-
},
|
|
9
|
-
"keywords": [
|
|
10
|
-
"golden-template",
|
|
11
|
-
"template"
|
|
12
|
-
],
|
|
13
|
-
"author": "Tuya.inc",
|
|
14
|
-
"license": "MIT",
|
|
15
|
-
"dependencies": {
|
|
16
|
-
"fs-extra": "^11.1.1",
|
|
17
|
-
"yauzl": "^2.10.0"
|
|
18
|
-
},
|
|
19
|
-
"engines": {
|
|
20
|
-
"node": ">=12.0.0"
|
|
21
|
-
}
|
|
22
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "create-golden",
|
|
3
|
+
"version": "1.4.11",
|
|
4
|
+
"description": "Create a new golden-template project from the latest GitHub repository (requires GITHUB_TOKEN)",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-golden": "index.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"golden-template",
|
|
11
|
+
"template"
|
|
12
|
+
],
|
|
13
|
+
"author": "Tuya.inc",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"fs-extra": "^11.1.1",
|
|
17
|
+
"yauzl": "^2.10.0"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=12.0.0"
|
|
21
|
+
}
|
|
22
|
+
}
|