cf-yoyo 1.0.0
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/.eslintrc.json +28 -0
- package/.github/workflows/ci.yml +96 -0
- package/.prettierrc.json +10 -0
- package/CHANGELOG.md +55 -0
- package/README.md +138 -0
- package/__tests__/cli-e2e.test.ts +145 -0
- package/__tests__/config.test.ts +268 -0
- package/__tests__/filesystem.test.ts +453 -0
- package/__tests__/logger.test.ts +274 -0
- package/__tests__/template-engine.test.ts +450 -0
- package/__tests__/types.test.ts +25 -0
- package/deep_todos.md +766 -0
- package/dist/cli/commands/create.d.ts +26 -0
- package/dist/cli/commands/create.d.ts.map +1 -0
- package/dist/cli/commands/create.js +308 -0
- package/dist/cli/commands/create.js.map +1 -0
- package/dist/cli/commands/git.d.ts +10 -0
- package/dist/cli/commands/git.d.ts.map +1 -0
- package/dist/cli/commands/git.js +887 -0
- package/dist/cli/commands/git.js.map +1 -0
- package/dist/cli/commands/list.d.ts +10 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +90 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/index.d.ts +15 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +62 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/config.d.ts +35 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +260 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/filesystem.d.ts +84 -0
- package/dist/core/filesystem.d.ts.map +1 -0
- package/dist/core/filesystem.js +417 -0
- package/dist/core/filesystem.js.map +1 -0
- package/dist/core/git-token.d.ts +81 -0
- package/dist/core/git-token.d.ts.map +1 -0
- package/dist/core/git-token.js +244 -0
- package/dist/core/git-token.js.map +1 -0
- package/dist/core/git.d.ts +70 -0
- package/dist/core/git.d.ts.map +1 -0
- package/dist/core/git.js +367 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/prompt.d.ts +28 -0
- package/dist/core/prompt.d.ts.map +1 -0
- package/dist/core/prompt.js +253 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/template-engine.d.ts +52 -0
- package/dist/core/template-engine.d.ts.map +1 -0
- package/dist/core/template-engine.js +308 -0
- package/dist/core/template-engine.js.map +1 -0
- package/dist/core/template-manager.d.ts +54 -0
- package/dist/core/template-manager.d.ts.map +1 -0
- package/dist/core/template-manager.js +330 -0
- package/dist/core/template-manager.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +244 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +51 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/logger.d.ts +68 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +140 -0
- package/dist/utils/logger.js.map +1 -0
- package/memory.md +241 -0
- package/need-debug.md +395 -0
- package/package.json +42 -0
- package/src/cli/commands/create.ts +326 -0
- package/src/cli/commands/git.ts +1001 -0
- package/src/cli/commands/list.ts +97 -0
- package/src/cli/index.ts +71 -0
- package/src/core/config.ts +262 -0
- package/src/core/filesystem.ts +408 -0
- package/src/core/git-token.ts +248 -0
- package/src/core/git.ts +384 -0
- package/src/core/prompt.ts +345 -0
- package/src/core/template-engine.ts +324 -0
- package/src/core/template-manager.ts +338 -0
- package/src/index.ts +19 -0
- package/src/types/index.ts +259 -0
- package/src/utils/logger.ts +150 -0
- package/templates/pages/basic/README.md.mustache +63 -0
- package/templates/pages/basic/package.json.mustache +23 -0
- package/templates/pages/basic/public/css/style.css +199 -0
- package/templates/pages/basic/public/index.html.mustache +72 -0
- package/templates/pages/basic/public/js/main.js +103 -0
- package/templates/pages/basic/template.json +38 -0
- package/templates/pages/basic/tsconfig.json +21 -0
- package/templates/pages/basic/wrangler.toml.mustache +14 -0
- package/templates/pages/basic-js/README.md.mustache +62 -0
- package/templates/pages/basic-js/package.json.mustache +25 -0
- package/templates/pages/basic-js/public/css/style.css +212 -0
- package/templates/pages/basic-js/public/index.html.mustache +53 -0
- package/templates/pages/basic-js/public/js/main.js +134 -0
- package/templates/pages/basic-js/template.json +35 -0
- package/templates/pages/basic-js/wrangler.toml.mustache +14 -0
- package/templates/pages/react/README.md.mustache +97 -0
- package/templates/pages/react/index.html.mustache +14 -0
- package/templates/pages/react/package.json.mustache +34 -0
- package/templates/pages/react/src/App.css +168 -0
- package/templates/pages/react/src/App.tsx.mustache +62 -0
- package/templates/pages/react/src/index.css +53 -0
- package/templates/pages/react/src/main.tsx.mustache +10 -0
- package/templates/pages/react/src/vite-env.d.ts +1 -0
- package/templates/pages/react/template.json +54 -0
- package/templates/pages/react/tsconfig.json +21 -0
- package/templates/pages/react/tsconfig.node.json +10 -0
- package/templates/pages/react/vite.config.ts +16 -0
- package/templates/worker/basic/README.md.mustache +56 -0
- package/templates/worker/basic/package.json.mustache +29 -0
- package/templates/worker/basic/src/index.ts.mustache +125 -0
- package/templates/worker/basic/template.json +30 -0
- package/templates/worker/basic/tsconfig.json +24 -0
- package/templates/worker/basic/wrangler.toml.mustache +33 -0
- package/templates/worker/basic-js/README.md.mustache +55 -0
- package/templates/worker/basic-js/package.json.mustache +25 -0
- package/templates/worker/basic-js/src/index.js.mustache +146 -0
- package/templates/worker/basic-js/template.json +27 -0
- package/templates/worker/basic-js/wrangler.toml.mustache +33 -0
- package/templates/worker/hono/README.md.mustache +79 -0
- package/templates/worker/hono/package.json.mustache +33 -0
- package/templates/worker/hono/src/index.ts.mustache +64 -0
- package/templates/worker/hono/src/routes/index.ts.mustache +165 -0
- package/templates/worker/hono/template.json +34 -0
- package/templates/worker/hono/tsconfig.json +24 -0
- package/templates/worker/hono/wrangler.toml.mustache +36 -0
- package/templates/worker/hono-js/README.md.mustache +67 -0
- package/templates/worker/hono-js/package.json.mustache +29 -0
- package/templates/worker/hono-js/src/index.js.mustache +55 -0
- package/templates/worker/hono-js/src/routes/index.js.mustache +127 -0
- package/templates/worker/hono-js/template.json +31 -0
- package/templates/worker/hono-js/wrangler.toml.mustache +36 -0
- package/thoughts/ledgers/CONTINUITY_ses_287e.md +74 -0
- package/thoughts/ledgers/CONTINUITY_ses_28b5.md +85 -0
- package/tsconfig.json +30 -0
- package/vitest.config.ts +20 -0
- package//351/240/205/347/233/256/350/241/250.md +140 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* list 命令實現
|
|
3
|
+
* 負責列出可用的專案模板
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
import { logger } from '../../utils/logger';
|
|
8
|
+
import { ProjectType, TemplateInfo, Language } from '../../types';
|
|
9
|
+
import { getAllTemplates } from '../../core/template-manager';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 建立 list 命令
|
|
13
|
+
*/
|
|
14
|
+
export function createListCommand(program: Command): void {
|
|
15
|
+
program
|
|
16
|
+
.command('list')
|
|
17
|
+
.description('列出可用的專案模板')
|
|
18
|
+
.option('--type <type>', '根據專案類型過濾')
|
|
19
|
+
.option('--language <language>', '根據語言過濾 (typescript, javascript)')
|
|
20
|
+
.action((options: { type?: string; language?: string }) => {
|
|
21
|
+
logger.empty();
|
|
22
|
+
logger.icon('📋', '可用的專案模板');
|
|
23
|
+
logger.divider();
|
|
24
|
+
logger.empty();
|
|
25
|
+
|
|
26
|
+
// 從 template-manager 獲取實際可用的模板
|
|
27
|
+
const allTemplates = getAllTemplates();
|
|
28
|
+
let templates: TemplateInfo[];
|
|
29
|
+
|
|
30
|
+
if (options.type) {
|
|
31
|
+
const normalizedType = options.type.toLowerCase();
|
|
32
|
+
// 驗證專案類型
|
|
33
|
+
const validTypes = Object.values(ProjectType);
|
|
34
|
+
if (!validTypes.includes(normalizedType as ProjectType)) {
|
|
35
|
+
logger.warn(`找不到符合類型 "${options.type}" 的模板`);
|
|
36
|
+
logger.empty();
|
|
37
|
+
logger.info(`可用的專案類型:${validTypes.join(', ')}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
templates = allTemplates.filter((t) => t.projectType === normalizedType);
|
|
41
|
+
} else {
|
|
42
|
+
templates = allTemplates;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 根據語言過濾
|
|
46
|
+
if (options.language) {
|
|
47
|
+
const lang =
|
|
48
|
+
options.language.toLowerCase() === 'javascript'
|
|
49
|
+
? Language.JAVASCRIPT
|
|
50
|
+
: Language.TYPESCRIPT;
|
|
51
|
+
templates = templates.filter((t) => t.language === lang);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (templates.length === 0) {
|
|
55
|
+
logger.warn(`找不到符合條件的模板`);
|
|
56
|
+
logger.empty();
|
|
57
|
+
logger.info(`可用的專案類型:${Object.values(ProjectType).join(', ')}`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 按照專案類型分組
|
|
62
|
+
const grouped: Record<string, TemplateInfo[]> = {};
|
|
63
|
+
for (const template of templates) {
|
|
64
|
+
const key = template.projectType;
|
|
65
|
+
if (!grouped[key]) {
|
|
66
|
+
grouped[key] = [];
|
|
67
|
+
}
|
|
68
|
+
grouped[key].push(template);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 輸出分組信息
|
|
72
|
+
const icons: Record<string, string> = {
|
|
73
|
+
[ProjectType.WORKER]: '☁️',
|
|
74
|
+
[ProjectType.PAGES]: '📄',
|
|
75
|
+
[ProjectType.D1]: '🗄️',
|
|
76
|
+
[ProjectType.KV]: '📦',
|
|
77
|
+
[ProjectType.R2]: '🪣',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
for (const [type, typeTemplates] of Object.entries(grouped)) {
|
|
81
|
+
logger.icon('🏷️', `專案類型:${type}`);
|
|
82
|
+
logger.divider('─', 30);
|
|
83
|
+
typeTemplates.forEach((template) => {
|
|
84
|
+
const icon = icons[type] || '📁';
|
|
85
|
+
const langTag = template.language === Language.JAVASCRIPT ? ' (JS)' : ' (TS)';
|
|
86
|
+
logger.info(` ${icon} ${template.name.padEnd(20)}${langTag} - ${template.description}`);
|
|
87
|
+
});
|
|
88
|
+
logger.empty();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
logger.divider();
|
|
92
|
+
logger.empty();
|
|
93
|
+
logger.icon('💡', '使用方式:');
|
|
94
|
+
logger.info(' npx cf-yoyo create my-project --type <type>');
|
|
95
|
+
logger.empty();
|
|
96
|
+
});
|
|
97
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 入口模組
|
|
3
|
+
* 負責初始化 Commander.js 並註冊所有命令
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
import { createCommand } from './commands/create';
|
|
8
|
+
import { createListCommand } from './commands/list';
|
|
9
|
+
import { createGitCommand } from './commands/git';
|
|
10
|
+
import { logger } from '../utils/logger';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 初始化 CLI 應用
|
|
14
|
+
*/
|
|
15
|
+
export function initializeCLI(): Command {
|
|
16
|
+
const program = new Command();
|
|
17
|
+
|
|
18
|
+
// 設定 CLI 基本資訊
|
|
19
|
+
program
|
|
20
|
+
.name('cf-yoyo')
|
|
21
|
+
.description('Cloudflare 專案腳手架 CLI 工具 - 快速建立 Cloudflare Workers、Pages 等專案')
|
|
22
|
+
.version('1.0.0')
|
|
23
|
+
.helpOption('-h, --help', '顯示幫助信息')
|
|
24
|
+
.addHelpText(
|
|
25
|
+
'after',
|
|
26
|
+
`
|
|
27
|
+
📚 使用範例:
|
|
28
|
+
$ cf-yoyo create # 互動式建立專案
|
|
29
|
+
$ cf-yoyo create my-app # 建立名為 my-app 的專案
|
|
30
|
+
$ cf-yoyo list # 列出所有模板
|
|
31
|
+
$ cf-yoyo list --type worker # 列出 Worker 相關模板
|
|
32
|
+
$ cf-yoyo git # 開啟 Git 操作互動選單
|
|
33
|
+
|
|
34
|
+
💡 提示:
|
|
35
|
+
使用 create 命令建立新專案,使用 list 命令查看可用模板
|
|
36
|
+
使用 git 命令開啟 Git 操作互動選單(初始化、推送、克隆)
|
|
37
|
+
`
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// 註冊命令
|
|
41
|
+
createCommand(program);
|
|
42
|
+
createListCommand(program);
|
|
43
|
+
createGitCommand(program);
|
|
44
|
+
|
|
45
|
+
// 錯誤處理
|
|
46
|
+
program.showHelpAfterError();
|
|
47
|
+
program.showSuggestionAfterError();
|
|
48
|
+
|
|
49
|
+
return program;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 執行 CLI
|
|
54
|
+
*/
|
|
55
|
+
export async function runCLI(argv?: string[]): Promise<void> {
|
|
56
|
+
const program = initializeCLI();
|
|
57
|
+
|
|
58
|
+
// 如果沒有提供任何參數,顯示幫助
|
|
59
|
+
if (!argv || argv.length === 0) {
|
|
60
|
+
const args = process.argv.slice(2);
|
|
61
|
+
if (args.length === 0) {
|
|
62
|
+
program.help();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 解析命令行參數
|
|
67
|
+
program.parse(argv || process.argv);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 預設匯出
|
|
71
|
+
export default runCLI;
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 專案配置解析模組
|
|
3
|
+
* 負責讀取用戶輸入、合併預設配置、生成最終配置物件
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { ProjectConfig, ProjectType, TemplateType, UserInput, Language } from '../types';
|
|
8
|
+
import { logger } from '../utils/logger';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 預設配置
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_CONFIG: Omit<ProjectConfig, 'projectName' | 'targetDir'> = {
|
|
14
|
+
language: Language.TYPESCRIPT,
|
|
15
|
+
projectType: ProjectType.WORKER,
|
|
16
|
+
template: TemplateType.BASIC,
|
|
17
|
+
initGit: true,
|
|
18
|
+
installDeps: true,
|
|
19
|
+
nodeVersion: '18.0.0',
|
|
20
|
+
tsVersion: '5.0.0',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 專案類型映射表
|
|
25
|
+
*/
|
|
26
|
+
const PROJECT_TYPE_MAP: Record<string, ProjectType> = {
|
|
27
|
+
worker: ProjectType.WORKER,
|
|
28
|
+
pages: ProjectType.PAGES,
|
|
29
|
+
d1: ProjectType.D1,
|
|
30
|
+
kv: ProjectType.KV,
|
|
31
|
+
r2: ProjectType.R2,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 模板類型映射表
|
|
36
|
+
*/
|
|
37
|
+
const TEMPLATE_TYPE_MAP: Record<string, TemplateType> = {
|
|
38
|
+
basic: TemplateType.BASIC,
|
|
39
|
+
hono: TemplateType.HONO,
|
|
40
|
+
'itty-router': TemplateType.ITTY_ROUTER,
|
|
41
|
+
static: TemplateType.STATIC,
|
|
42
|
+
react: TemplateType.REACT,
|
|
43
|
+
vue: TemplateType.VUE,
|
|
44
|
+
nextjs: TemplateType.NEXTJS,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 驗證專案類型
|
|
49
|
+
*/
|
|
50
|
+
function validateProjectType(type: string): ProjectType {
|
|
51
|
+
const normalizedType = type.toLowerCase();
|
|
52
|
+
if (PROJECT_TYPE_MAP[normalizedType]) {
|
|
53
|
+
return PROJECT_TYPE_MAP[normalizedType];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 如果無效,使用預設值
|
|
57
|
+
logger.warn(`無效的專案類型「${type}」,使用預設值「${DEFAULT_CONFIG.projectType}」`);
|
|
58
|
+
return DEFAULT_CONFIG.projectType;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 驗證模板類型
|
|
63
|
+
*/
|
|
64
|
+
function validateTemplateType(template: string, projectType: ProjectType): TemplateType {
|
|
65
|
+
const normalizedTemplate = template.toLowerCase();
|
|
66
|
+
|
|
67
|
+
// 檢查模板是否有效
|
|
68
|
+
if (TEMPLATE_TYPE_MAP[normalizedTemplate]) {
|
|
69
|
+
const templateType = TEMPLATE_TYPE_MAP[normalizedTemplate];
|
|
70
|
+
|
|
71
|
+
// 檢查模板是否與專案類型相容
|
|
72
|
+
if (isTemplateCompatible(templateType, projectType)) {
|
|
73
|
+
return templateType;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 根據專案類型返回預設模板
|
|
78
|
+
return getDefaultTemplateForProjectType(projectType);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 檢查模板是否與專案類型相容
|
|
83
|
+
*/
|
|
84
|
+
function isTemplateCompatible(template: TemplateType, projectType: ProjectType): boolean {
|
|
85
|
+
switch (projectType) {
|
|
86
|
+
case ProjectType.WORKER:
|
|
87
|
+
return [TemplateType.BASIC, TemplateType.HONO, TemplateType.ITTY_ROUTER].includes(template);
|
|
88
|
+
case ProjectType.PAGES:
|
|
89
|
+
return [
|
|
90
|
+
TemplateType.STATIC,
|
|
91
|
+
TemplateType.REACT,
|
|
92
|
+
TemplateType.VUE,
|
|
93
|
+
TemplateType.NEXTJS,
|
|
94
|
+
].includes(template);
|
|
95
|
+
case ProjectType.D1:
|
|
96
|
+
case ProjectType.KV:
|
|
97
|
+
case ProjectType.R2:
|
|
98
|
+
return template === TemplateType.BASIC;
|
|
99
|
+
default:
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 獲取專案類型的預設模板
|
|
106
|
+
*/
|
|
107
|
+
function getDefaultTemplateForProjectType(projectType: ProjectType): TemplateType {
|
|
108
|
+
switch (projectType) {
|
|
109
|
+
case ProjectType.WORKER:
|
|
110
|
+
return TemplateType.BASIC;
|
|
111
|
+
case ProjectType.PAGES:
|
|
112
|
+
return TemplateType.STATIC;
|
|
113
|
+
case ProjectType.D1:
|
|
114
|
+
case ProjectType.KV:
|
|
115
|
+
case ProjectType.R2:
|
|
116
|
+
return TemplateType.BASIC;
|
|
117
|
+
default:
|
|
118
|
+
return TemplateType.BASIC;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 生成目標目錄路徑
|
|
124
|
+
*/
|
|
125
|
+
function generateTargetDir(projectName: string, cwd: string = process.cwd()): string {
|
|
126
|
+
return path.join(cwd, projectName);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 解析並合併用戶輸入與預設配置
|
|
131
|
+
*/
|
|
132
|
+
export function parseConfig(userInput: UserInput, options: { cwd?: string } = {}): ProjectConfig {
|
|
133
|
+
const { cwd = process.cwd() } = options;
|
|
134
|
+
|
|
135
|
+
// 驗證必填欄位
|
|
136
|
+
if (!userInput.projectName) {
|
|
137
|
+
throw new Error('專案名稱為必填欄位');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 解析專案類型
|
|
141
|
+
const projectType = userInput.projectType || DEFAULT_CONFIG.projectType;
|
|
142
|
+
|
|
143
|
+
// 解析模板類型
|
|
144
|
+
const template = userInput.template
|
|
145
|
+
? validateTemplateType(String(userInput.template), projectType)
|
|
146
|
+
: getDefaultTemplateForProjectType(projectType);
|
|
147
|
+
|
|
148
|
+
// 建立最終配置
|
|
149
|
+
const config: ProjectConfig = {
|
|
150
|
+
projectName: userInput.projectName.trim(),
|
|
151
|
+
language: userInput.language ?? DEFAULT_CONFIG.language,
|
|
152
|
+
projectType,
|
|
153
|
+
template,
|
|
154
|
+
initGit: userInput.initGit ?? DEFAULT_CONFIG.initGit,
|
|
155
|
+
installDeps: userInput.installDeps ?? DEFAULT_CONFIG.installDeps,
|
|
156
|
+
targetDir: generateTargetDir(userInput.projectName, cwd),
|
|
157
|
+
nodeVersion: DEFAULT_CONFIG.nodeVersion,
|
|
158
|
+
tsVersion: DEFAULT_CONFIG.tsVersion,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
logger.debug(`配置解析完成:${JSON.stringify(config, null, 2)}`);
|
|
162
|
+
|
|
163
|
+
return config;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 語言類型映射表
|
|
168
|
+
*/
|
|
169
|
+
const LANGUAGE_MAP: Record<string, Language> = {
|
|
170
|
+
typescript: Language.TYPESCRIPT,
|
|
171
|
+
ts: Language.TYPESCRIPT,
|
|
172
|
+
javascript: Language.JAVASCRIPT,
|
|
173
|
+
js: Language.JAVASCRIPT,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 驗證語言類型
|
|
178
|
+
*/
|
|
179
|
+
function validateLanguage(lang: string): Language {
|
|
180
|
+
const normalizedLang = lang.toLowerCase();
|
|
181
|
+
if (LANGUAGE_MAP[normalizedLang]) {
|
|
182
|
+
return LANGUAGE_MAP[normalizedLang];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 如果無效,使用預設值
|
|
186
|
+
logger.warn(`無效的語言「${lang}」,使用預設值「${DEFAULT_CONFIG.language}」`);
|
|
187
|
+
return DEFAULT_CONFIG.language;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 從命令行選項解析配置
|
|
192
|
+
*/
|
|
193
|
+
export function parseFromOptions(
|
|
194
|
+
projectName: string | undefined,
|
|
195
|
+
options: {
|
|
196
|
+
template?: string;
|
|
197
|
+
git?: boolean;
|
|
198
|
+
install?: boolean;
|
|
199
|
+
type?: string;
|
|
200
|
+
language?: string;
|
|
201
|
+
}
|
|
202
|
+
): UserInput {
|
|
203
|
+
const userInput: UserInput = {};
|
|
204
|
+
|
|
205
|
+
// 專案名稱
|
|
206
|
+
if (projectName) {
|
|
207
|
+
userInput.projectName = projectName;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 語言選擇
|
|
211
|
+
if (options.language) {
|
|
212
|
+
userInput.language = validateLanguage(options.language);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 專案類型
|
|
216
|
+
if (options.type) {
|
|
217
|
+
userInput.projectType = validateProjectType(options.type);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 模板
|
|
221
|
+
if (options.template) {
|
|
222
|
+
const projectType = userInput.projectType || DEFAULT_CONFIG.projectType;
|
|
223
|
+
userInput.template = validateTemplateType(options.template, projectType);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Git 初始化
|
|
227
|
+
if (options.git !== undefined) {
|
|
228
|
+
userInput.initGit = options.git;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 依賴安裝
|
|
232
|
+
if (options.install !== undefined) {
|
|
233
|
+
userInput.installDeps = options.install;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return userInput;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* 獲取當前 Node.js 版本
|
|
241
|
+
*/
|
|
242
|
+
export function getCurrentNodeVersion(): string {
|
|
243
|
+
return process.version.substring(1); // 去掉開頭的 'v'
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* 檢查 Node.js 版本是否符合要求
|
|
248
|
+
*/
|
|
249
|
+
export function checkNodeVersion(requiredVersion: string): boolean {
|
|
250
|
+
const current = process.version;
|
|
251
|
+
const required = `v${requiredVersion}`;
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
const semver = require('semver');
|
|
255
|
+
return semver.gte(current, required);
|
|
256
|
+
} catch {
|
|
257
|
+
// 如果沒有安裝 semver,使用簡單版本比較
|
|
258
|
+
return current >= required;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export { DEFAULT_CONFIG };
|