index.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. #!/usr/bin/env node
  2. import fs from 'fs-extra';
  3. import path from 'node:path';
  4. import { fileURLToPath } from 'node:url';
  5. import * as p from '@clack/prompts';
  6. import { bold, cyan, grey, red, blue } from 'kleur/colors';
  7. import minimist from 'minimist';
  8. import * as langAll from './lang';
  9. // 获取版本号
  10. // Get version
  11. // @ts-ignore
  12. const { version } = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
  13. // 显示版本号
  14. // Display version number
  15. console.log(`
  16. ${grey(`create-stdf@${version}
  17. `)}`);
  18. const spinner = p.spinner();
  19. p.intro('Welcome to use STDF!');
  20. let lang = langAll.en_US;
  21. // 获取命令行参数
  22. // Get command line parameters
  23. const argv = minimist(process.argv.slice(2));
  24. // 获取项目名称和模板名称和语言
  25. // Get project name and template name
  26. const argvProjectName = argv._[0];
  27. const argvTemplate = argv.template || argv.t;
  28. const argvLanguage = argv.language || argv.l;
  29. // 语言列表
  30. // Language list
  31. const languages = [];
  32. // 循环 langAll 对象,将语言列表中的语言名字替换为对应的语言名字,且按照 sort 排序
  33. // Loop through the langAll object and replace the language name in the language list with the corresponding language name, and sort by sort
  34. for (const key in langAll) {
  35. languages.push({
  36. value: key,
  37. label: langAll[key].name,
  38. sort: langAll[key].sort,
  39. });
  40. }
  41. // 按照 sort 排序
  42. // Sort by sort
  43. languages.sort((a, b) => a.sort - b.sort);
  44. // 如果命令行参数中有语言,且语言列表中有该语言,使用该语言,否则使用英语
  45. // If there is a language in the command line parameters and the language list has the language, use the language, otherwise use English
  46. lang = argvLanguage && languages.find(item => item.value === argvLanguage) ? langAll[argvLanguage] : langAll.en_US;
  47. // 模板列表
  48. // Template list
  49. const templateOptions = [
  50. { value: 'skt', label: 'SvelteKit + Tailwind', template: 'sveltekit-tailwind', finish: true },
  51. { value: 'vt', label: 'Vite + Tailwind', template: 'vite-tailwind', finish: true },
  52. { value: 'vu', label: 'Vite + UnoCSS', template: 'vite-uno', finish: true },
  53. { value: 'sku', label: 'SvelteKit + UnoCSS', template: 'sveltekit-uno', finish: true },
  54. { value: 'vtt', label: 'Vite + Tailwind + TypeScript', template: 'vite-tailwind-typescript', finish: false },
  55. { value: 'vut', label: 'Vite + UnoCSS + TypeScript', template: 'vite-uno-typescript', finish: false },
  56. { value: 'sktt', label: 'SvelteKit + Tailwind + TypeScript', template: 'sveltekit-tailwind-typescript', finish: false },
  57. { value: 'skut', label: 'SvelteKit + UnoCSS + TypeScript', template: 'sveltekit-uno-typescript', finish: false },
  58. ];
  59. // 如果命令行参数中有项目名称,但没有模板名称,直接使用默认模板 skt
  60. // If there is a project name in the command line parameters, but no template name, use the default template skt directly
  61. if (argvProjectName && !argvTemplate) {
  62. createFunc(argvProjectName, templateOptions[0]);
  63. }
  64. // 如果命令行参数中有项目名称和模板名称,直接使用命令行参数中的值
  65. // If there is a project name and template name in the command line parameters, use the value in the command line parameters directly
  66. else if (argvProjectName && argvTemplate) {
  67. const item = templateOptions.find(item => item.value === argvTemplate);
  68. if (!item) {
  69. p.intro(red(lang.pectn));
  70. } else if (!item.finish) {
  71. p.intro(red(item.label + ' ' + lang.hnay));
  72. } else {
  73. createFunc(argvProjectName, item);
  74. }
  75. } else {
  76. (async () => {
  77. // 选择一种语言
  78. // Select a language
  79. const languageType = await p.select({
  80. message: bold('Please select your preferred language'),
  81. options: languages,
  82. });
  83. if (p.isCancel(languageType)) {
  84. p.cancel(red('⛔ ') + lang.oc);
  85. process.exit(0);
  86. }
  87. lang = langAll[languageType];
  88. // 选择一个模板
  89. // Select a template
  90. let template = await p.select({
  91. message: bold(lang.psat),
  92. options: templateOptions,
  93. });
  94. if (p.isCancel(template)) {
  95. p.cancel(red('⛔ ') + lang.oc);
  96. process.exit(0);
  97. }
  98. // 直到选择的 template 所在项 的 finish 为 true 为止,否则一直重新选择
  99. // Until the finish of the selected template is true, otherwise keep reselecting
  100. while (!templateOptions.find(item => item.value === template)?.finish) {
  101. if (p.isCancel(template)) {
  102. p.cancel(red('⛔ ') + lang.oc);
  103. process.exit(0);
  104. }
  105. p.intro(red(templateOptions.find(item => item.value === template).label + ' ' + lang.hnay + ' ' + lang.pca));
  106. template = await p.select({
  107. message: bold(lang.psat),
  108. options: templateOptions,
  109. });
  110. }
  111. // 是否启用类型检查
  112. // Whether to enable type checking
  113. const isTypeCheck = await p.confirm({
  114. message: bold(lang.wtetcfjsf),
  115. });
  116. if (p.isCancel(isTypeCheck)) {
  117. p.cancel(red('⛔ ') + lang.oc);
  118. process.exit(0);
  119. }
  120. // 输入项目名称
  121. // Enter the project name
  122. const projectName = await p.text({
  123. message: bold(lang.pn),
  124. placeholder: 'stdf-project',
  125. validate: value => {
  126. if (!value) {
  127. // 判断是否为空,提示 “项目名称不能为空”
  128. // Determine whether it is empty, prompt "Project name cannot be empty"
  129. return lang.pncbne;
  130. }
  131. if (fs.existsSync(value)) {
  132. // 判断是否已存在,提示 “项目名称已存在”
  133. // Determine whether it already exists, prompt "Project name already exists"
  134. return lang.pane;
  135. }
  136. },
  137. });
  138. if (p.isCancel(projectName)) {
  139. p.cancel(red('⛔ ') + lang.oc);
  140. process.exit(0);
  141. }
  142. // 根据 template 的值,复制对应目录下的所有文件到当前目录
  143. // According to the value of template, copy all files under the corresponding directory to the current directory
  144. templateOptions.forEach(async item => {
  145. if (item.value === template) {
  146. createFunc(projectName, item, isTypeCheck);
  147. }
  148. });
  149. })();
  150. }
  151. function createFunc(projectName, item, isTypeCheck = true) {
  152. // 如果 projectName 是数字,转为字符串
  153. // If projectName is a number, convert it to a string
  154. if (typeof projectName === 'number') {
  155. projectName = projectName.toString();
  156. }
  157. // 项目目录
  158. // Project directory
  159. const projectDir = path.join(path.resolve(), projectName);
  160. spinner.start('🚀 ' + lang.cfsing);
  161. fs.mkdirSync(projectDir);
  162. // 获取模板目录的绝对路径,考虑到 Windows 系统的兼容性, 使用 path.join
  163. // Get the absolute path of the template directory, considering the compatibility of the Windows system, use path.join
  164. // @ts-ignore
  165. const templatePath = path.resolve(fileURLToPath(import.meta.url), '../..', `templates/${item.template}`);
  166. // 将 templatePath 目录下的所有文件复制到 projectDir 目录下
  167. // Copy all files under the templatePath directory to the projectDir directory\
  168. fs.copy(templatePath, projectDir)
  169. .then(() => {
  170. spinner.stop();
  171. p.outro(`${projectName} - ${lang.pcsucc} 🎉`);
  172. // 如果 isTypeCheck 为 true,将 common 目录下的 jsconfig.json 文件复制到项目目录下
  173. // If isTypeCheck is true, copy the jsconfig.json file under the common directory to the project directory
  174. if (isTypeCheck) {
  175. // @ts-ignore
  176. const commonPath = path.resolve(fileURLToPath(import.meta.url), '../..', 'common');
  177. fs.copySync(path.join(commonPath, '/jsconfig.json'), path.join(projectDir, 'jsconfig.json'));
  178. }
  179. // 读取 package.json 文件
  180. // Read the package.json file
  181. const packageJson = JSON.parse(fs.readFileSync(`${projectDir}/package.json`, 'utf-8'));
  182. // 将项目内的 package.json 中的 name 属性修改为 projectName
  183. // Modify the name attribute in package.json in the project to projectName
  184. packageJson.name = projectName;
  185. // 将修改后的 packageJson 写入到项目内的 package.json 文件中
  186. // Write the modified packageJson to the package.json file in the project
  187. fs.writeFileSync(`${projectDir}/package.json`, JSON.stringify(packageJson, null, 4), 'utf-8');
  188. // 根据 item.value 的值,判断是否使用了 @sveltejs/kit
  189. // According to the value of item.value, determine whether @sveltejs/kit is used
  190. const isHasKit = item.value === 'skt' || item.value === 'sku' || item.value === 'sktt' || item.value === 'skut';
  191. // 根据 item.value 的值,判断使用 Tailwind 还是 UnoCSS
  192. // According to the value of item.value, determine whether to use Tailwind or UnoCSS
  193. const isHasUno = item.value === 'vu' || item.value === 'sku' || item.value === 'vut' || item.value === 'skut';
  194. // 获得依赖的版本号
  195. // get the version number of the dependency
  196. const versions = {
  197. vite: packageJson.devDependencies.vite.replace('^', ''),
  198. svelte: packageJson.devDependencies.svelte.replace('^', ''),
  199. stdf: packageJson.devDependencies.stdf.replace('^', ''),
  200. };
  201. if (isHasKit) {
  202. versions['@sveltejs/kit'] = packageJson.devDependencies['@sveltejs/kit'].replace('^', '');
  203. }
  204. if (isHasUno) {
  205. versions['unocss'] = packageJson.devDependencies.unocss.replace('^', '');
  206. } else {
  207. versions['tailwindcss'] = packageJson.devDependencies.tailwindcss.replace('^', '');
  208. }
  209. // 将 versions 的键值拼接为 bold('Vite:') cyan(versions.vite) bold('Svelte:') cyan(versions.svelte) 的形式
  210. // Splice the key value of versions into the form of bold('Vite:') cyan(versions.vite) bold('Svelte:') cyan(versions.svelte)
  211. let versionsString = '';
  212. for (const key in versions) {
  213. versionsString += bold(key) + ': ' + cyan(versions[key]) + ' ';
  214. }
  215. // 显示版本号
  216. // Display version number
  217. console.log(`📦 ${versionsString}
  218. `);
  219. // 显示提示信息
  220. // Display prompt information
  221. console.log(
  222. `👉 ${bold(lang.tgs)}
  223. ${blue(`cd ${projectName}`)}
  224. ${blue('pnpm i / npm i / yarn / bun i')}
  225. ${blue('npm dev / bun dev')}
  226. `,
  227. );
  228. // 显示配置主题色
  229. // Display configuration theme color
  230. console.log(
  231. `🎨 ${grey(isHasUno ? lang.pcyt_vu : lang.pcyt_vt)}
  232. `,
  233. );
  234. })
  235. .catch(err => {
  236. spinner.stop();
  237. console.error(red(lang.cferror + '--' + err));
  238. });
  239. }