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