create-bubbles 0.1.24 → 0.1.25
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/dist/index.js +21 -0
- package/package.json +1 -2
- package/dist/index.mjs +0 -422
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import e from"node:fs";import t from"node:path";import{fileURLToPath as n}from"node:url";import*as r from"@clack/prompts";import i from"cross-spawn";import a from"gradient-string";import o from"mri";const s={vue:a([`#42B883`,`#42B883`]),react:a([`#087EA4`,`#087EA4`]),taro:a([`#0000C2`,`#fff`]),nextjs:a([`#000`,`#fff`]),others:a([`#8B5CF6`,`#A855F7`])},c=o(process.argv.slice(2),{alias:{h:`help`,t:`template`},boolean:[`help`,`overwrite`],string:[`template`]}),l=process.cwd(),u=`\
|
|
2
|
+
Usage: create-bubbles [OPTION]... [DIRECTORY]
|
|
3
|
+
|
|
4
|
+
Create a new Bubbles project in JavaScript or TypeScript.
|
|
5
|
+
With no arguments, start the CLI in interactive mode.
|
|
6
|
+
|
|
7
|
+
Options:
|
|
8
|
+
-t, --template NAME use a specific template
|
|
9
|
+
|
|
10
|
+
Available templates:
|
|
11
|
+
${s.vue(`vue-vp-eslint vue`)}
|
|
12
|
+
${s.vue(`vp-vue-eslint-vapor vue`)}
|
|
13
|
+
${s.react(`react-rsbuild-biome react`)}
|
|
14
|
+
${s.react(`vp-react react`)}
|
|
15
|
+
${s.react(`vp-react-shadcn react`)}
|
|
16
|
+
${s.react(`vp-monorepo-react-nestjs react`)}
|
|
17
|
+
${s.taro(`taro-vue-eslint taro`)}
|
|
18
|
+
${s.nextjs(`nextjs-vinext-eslint nextjs`)}`,d=e=>e.trim().replace(/\/+$/g,``),f=e=>{if(!e)return;let t=e.split(` `)[0].split(`/`);return{name:t[0],version:t[1]}},p=t=>{let n=e.readdirSync(t);return n.length===0||n.length===1&&n[0]===`.git`},m=async n=>{await Promise.all(e.readdirSync(n).filter(e=>e!==`.git`).map(r=>e.promises.rm(t.resolve(n,r),{recursive:!0,force:!0})))},h=e=>/^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(e),g=e=>e.trim().toLowerCase().replace(/\s+/g,`-`).replace(/^[._]/,``).replace(/[^a-z\d\-~]+g/,`-`),_=[{name:`vue`,display:`Vue`,color:s.vue,variants:[{name:`vue-vp-eslint`,display:`vp + eslint`,color:s.vue},{name:`vp-vue-eslint-vapor`,display:`vp + eslint + vapor`,color:s.vue}]},{name:`react`,display:`React`,color:s.react,variants:[{name:`react-rsbuild-biome`,display:`rsbuild-biome`,color:s.react},{name:`vp-react`,display:`vp-react`,color:s.react},{name:`vp-react-shadcn`,display:`vp-react-shadcn`,color:s.react},{name:`vp-monorepo-react-nestjs`,display:`vp-monorepo-react-nestjs`,color:s.react}]},{name:`taro`,display:`Taro`,color:s.taro,variants:[{name:`taro-vue-eslint`,display:`taro-vue + eslint`,color:s.taro},{name:`taro-react-oxc`,display:`taro-react + oxc`,color:s.taro}]},{name:`nextjs`,display:`NextJS`,color:s.nextjs,variants:[{name:`nextjs-vinext-eslint`,display:`nextjs + vinext + eslint`,color:s.nextjs}]},{name:`others`,display:`Others`,color:s.others,variants:[{name:`create-eletron-vite`,display:`Electron ↗`,color:s.others,customCommand:`pnpm create electron-vite@latest TARGET_DIR`}]}],v=_.map(e=>e.variants.map(e=>`${e.name}`)).reduce((e,t)=>e.concat(t),[]),y=(e,t)=>{console.log(`💦customCommand`,e,t);let n=t?t.name:`npm`,r=n===`yarn`&&t?.version.startsWith(`1.`);return e.replace(/^npm create (?:-- )?/,()=>n===`bun`?`bun x create-`:n===`pnpm`?`pnpm create `:e.startsWith(`npm create -- `)?`${n} create -- `:`${n} create `).replace(`@latest`,()=>r?``:`@latest`).replace(/^npm exec/,()=>n===`pnpm`?`pnpm dlx`:n===`yarn`&&!r?`yarn dlx`:n===`bun`?`bun x`:`npm exec`)},b={_gitignore:`.gitignore`},x=(n,r)=>{e.mkdirSync(r,{recursive:!0});for(let i of e.readdirSync(n)){let e=t.resolve(n,i),a=t.resolve(r,i);S(e,a)}},S=(t,n)=>{e.statSync(t).isDirectory()?x(t,n):e.copyFileSync(t,n)},C=(t,n)=>{let r=e.readFileSync(t,`utf-8`);e.writeFileSync(t,n(r),`utf-8`)},w=(e,n)=>{C(t.resolve(e,`package.json`),e=>e.replace(/"@vitejs\/plugin-react": ".+?"/,`"@vitejs/plugin-react-swc": "^4.0.1"`)),C(t.resolve(e,`vite.config.${n?`ts`:`js`}`),e=>e.replace(`@vitejs/plugin-react`,`@vitejs/plugin-react-swc`))};(async()=>{console.log(c);let o=c._[0]?d(c._[0]):void 0,x=c.template,C=c.overwrite,T=`bubbles-project`;if(c.help){console.log(u);return}let E=f(process.env.npm_config_user_agent),D=()=>r.cancel(`Operation cancelled`),O=o;if(!O){let e=await r.text({message:`Project name`,defaultValue:T,placeholder:T,validate:e=>(e||[]).length===0||d(e||``).length>0?void 0:`Invalid project name`});if(r.isCancel(e))return D();O=d(e)}if(e.existsSync(O)&&!p(O)){let e=C?`yes`:await r.select({message:O===`.`?`Current directory`:`Target directory "${O}" is not empty. Please choose how to proceed:`,options:[{label:`${s.vue(`Cancel operation`)}`,value:`no`},{label:`Remove existing files and continue`,value:`yes`},{label:`Ignore files and continue`,value:`ignore`}]});if(r.isCancel(e))return D();switch(e){case`yes`:m(O);break;case`no`:D();return}}let k=t.basename(t.resolve(O));if(!h(k)){let e=await r.text({message:`Package name is invalid. please input again:`,defaultValue:g(k),placeholder:g(k),validate(e){if(!h(e))return`Invalid package.json name`}});if(r.isCancel(e))return D();k=e}let A=x,j=!1;if(x&&!v.includes(x)&&(A=void 0,j=!0),!A){let e=await r.select({message:j?`"${x}" isn't a valia template. please choose from below:`:`Select a framework`,options:_.map(e=>{let t=e.color;return{label:t(e.display),value:e}})});if(r.isCancel(e))return D();console.log(`💦pkgInfo`,E);let t=await r.select({message:`Select a variant:`,options:e.variants.map(e=>{let t=e.color,n=e.customCommand?y(e.customCommand,E).replace(/ TARGET_DIR$/,``):void 0;return{label:t(e.display||e.name),value:e.name,hint:n}})});if(r.isCancel(t))return D();A=t}let M=t.join(l,O);e.mkdirSync(M,{recursive:!0});let N=!1;A.includes(`-swc`)&&(N=!0,A=A.replace(`-swc`,``));let P=E?E.name:`npm`,{customCommand:F}=_.flatMap(e=>e.variants).find(e=>e.name===A)??{};if(F){let[e,...t]=y(F,E).split(` `),n=t.map(e=>e.replace(`TARGET_DIR`,()=>O));console.log(`💦replacedArgs`,n);let{status:r}=i.sync(e,n,{stdio:`inherit`});process.exit(r??0)}r.log.step(`scaffolding project in ${M}...`);let I=t.resolve(n(import.meta.url),`../..`,`template-${A}`),L=(n,r)=>{let i=t.join(M,b[n]??n);r?e.writeFileSync(i,r):S(t.join(I,n),i)},R=e.readdirSync(I);for(let e of R)e!==`package.json`&&L(e);let z=JSON.parse(e.readFileSync(t.join(I,`package.json`),`utf-8`));z.name=k,L(`package.json`,`${JSON.stringify(z,null,2)}\n`),N&&w(M,A.endsWith(`-ts`));let B=``,V=t.relative(l,M);switch(console.log(`💦cwd`,l),console.log(`💦root`,M),console.log(`💦cdProjectName`,V),B+=`Done. Now run:
|
|
19
|
+
`,M!==l&&(B+=`\n cd ${V.includes(` `)?`"${V}"`:V} `),P){case`yarn`:B+=`
|
|
20
|
+
yarn`,B+=`
|
|
21
|
+
yarn dev`;break;default:B+=a([`pink`,`white`])(`\n ${P} install`),B+=a([`pink`,`white`])(`\n ${P} run dev`);break}r.outro(B)})().catch(e=>{console.error(`💦`,e)});export{};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-bubbles",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
4
4
|
"homepage": "https://github.com/435012357/bubblesjs/tree/master/packages/create-bubbles#readme",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/435012357/bubblesjs/issues"
|
|
@@ -36,7 +36,6 @@
|
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^24.3.0",
|
|
39
|
-
"tsdown": "^0.22.14",
|
|
40
39
|
"vite": "npm:@voidzero-dev/vite-plus-core@0.2.4",
|
|
41
40
|
"vite-plus": "0.2.4"
|
|
42
41
|
},
|
package/dist/index.mjs
DELETED
|
@@ -1,422 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import * as prompts from "@clack/prompts";
|
|
5
|
-
import spawn from "cross-spawn";
|
|
6
|
-
import gradient from "gradient-string";
|
|
7
|
-
import mri from "mri";
|
|
8
|
-
//#region src/index.ts
|
|
9
|
-
const colorMap = {
|
|
10
|
-
vue: gradient(["#42B883", "#42B883"]),
|
|
11
|
-
react: gradient(["#087EA4", "#087EA4"]),
|
|
12
|
-
taro: gradient(["#0000C2", "#fff"]),
|
|
13
|
-
nextjs: gradient(["#000", "#fff"]),
|
|
14
|
-
others: gradient(["#8B5CF6", "#A855F7"])
|
|
15
|
-
};
|
|
16
|
-
/**
|
|
17
|
-
* process.argv 是 node index.js 前两个参数
|
|
18
|
-
* 第一个参数是 node 可执行文件的路径
|
|
19
|
-
* 第二个参数是当前执行的脚本文件的路径
|
|
20
|
-
* 虽然最终使用的是 pnpm create bubbles 但最后都会指向bin 执行 node index.js
|
|
21
|
-
*/
|
|
22
|
-
const argv = mri(process.argv.slice(2), {
|
|
23
|
-
alias: {
|
|
24
|
-
h: "help",
|
|
25
|
-
t: "template"
|
|
26
|
-
},
|
|
27
|
-
boolean: ["help", "overwrite"],
|
|
28
|
-
string: ["template"]
|
|
29
|
-
});
|
|
30
|
-
/** 执行命令的绝对路径 代指执行命令的地方 */
|
|
31
|
-
const cwd = process.cwd();
|
|
32
|
-
const helpMessage = `\
|
|
33
|
-
Usage: create-bubbles [OPTION]... [DIRECTORY]
|
|
34
|
-
|
|
35
|
-
Create a new Bubbles project in JavaScript or TypeScript.
|
|
36
|
-
With no arguments, start the CLI in interactive mode.
|
|
37
|
-
|
|
38
|
-
Options:
|
|
39
|
-
-t, --template NAME use a specific template
|
|
40
|
-
|
|
41
|
-
Available templates:
|
|
42
|
-
${colorMap.vue("vue-vp-eslint vue")}
|
|
43
|
-
${colorMap.vue("vp-vue-eslint-vapor vue")}
|
|
44
|
-
${colorMap.react("react-rsbuild-biome react")}
|
|
45
|
-
${colorMap.react("vp-react react")}
|
|
46
|
-
${colorMap.react("vp-react-shadcn react")}
|
|
47
|
-
${colorMap.react("vp-monorepo-react-nestjs react")}
|
|
48
|
-
${colorMap.taro("taro-vue-eslint taro")}
|
|
49
|
-
${colorMap.nextjs("nextjs-vinext-eslint nextjs")}`;
|
|
50
|
-
/**
|
|
51
|
-
* 去除空格并且替换掉末尾一个或者多个“/”
|
|
52
|
-
* @param targetDir
|
|
53
|
-
* @returns
|
|
54
|
-
*/
|
|
55
|
-
const formatTargetDir = (targetDir) => {
|
|
56
|
-
return targetDir.trim().replace(/\/+$/g, "");
|
|
57
|
-
};
|
|
58
|
-
const pkgFromUserAgent = (userAgent) => {
|
|
59
|
-
if (!userAgent) return;
|
|
60
|
-
const pkgSpecArr = userAgent.split(" ")[0].split("/");
|
|
61
|
-
return {
|
|
62
|
-
name: pkgSpecArr[0],
|
|
63
|
-
version: pkgSpecArr[1]
|
|
64
|
-
};
|
|
65
|
-
};
|
|
66
|
-
/**
|
|
67
|
-
* 要么没有文件要么只有一个.git 文件夹
|
|
68
|
-
* @param path
|
|
69
|
-
* @returns
|
|
70
|
-
*/
|
|
71
|
-
const isEmpty = (path) => {
|
|
72
|
-
const files = fs.readdirSync(path);
|
|
73
|
-
return files.length === 0 || files.length === 1 && files[0] === ".git";
|
|
74
|
-
};
|
|
75
|
-
const emptyDir = async (dir) => {
|
|
76
|
-
await Promise.all(fs.readdirSync(dir).filter((file) => file !== ".git").map((file) => fs.promises.rm(path.resolve(dir, file), {
|
|
77
|
-
recursive: true,
|
|
78
|
-
force: true
|
|
79
|
-
})));
|
|
80
|
-
};
|
|
81
|
-
/**
|
|
82
|
-
* 验证包名是否满足以 @ - * ~ 字母 数字 开头 并且
|
|
83
|
-
* 第一个?: 代表非捕获模式 第二个代表 可有可无
|
|
84
|
-
* @scope/abc abc
|
|
85
|
-
* (?:@[a-z\d\-*~][a-z\d\-*._~]*\/)? => 匹配 @scope/
|
|
86
|
-
* [a-z\d\-~][a-z\d\-._~]* 匹配 / 后面
|
|
87
|
-
*
|
|
88
|
-
* @param packageName
|
|
89
|
-
* @returns
|
|
90
|
-
*/
|
|
91
|
-
const isValidPackageName = (packageName) => {
|
|
92
|
-
return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(packageName);
|
|
93
|
-
};
|
|
94
|
-
/**
|
|
95
|
-
* 把不合法的包名元素替换掉
|
|
96
|
-
* @param packageName
|
|
97
|
-
* @returns
|
|
98
|
-
*/
|
|
99
|
-
const toValidPackageName = (packageName) => {
|
|
100
|
-
return packageName.trim().toLowerCase().replace(/\s+/g, "-").replace(/^[._]/, "").replace(/[^a-z\d\-~]+g/, "-");
|
|
101
|
-
};
|
|
102
|
-
const FRAMEWORKS = [
|
|
103
|
-
{
|
|
104
|
-
name: "vue",
|
|
105
|
-
display: "Vue",
|
|
106
|
-
color: colorMap.vue,
|
|
107
|
-
variants: [{
|
|
108
|
-
name: "vue-vp-eslint",
|
|
109
|
-
display: "vp + eslint",
|
|
110
|
-
color: colorMap.vue
|
|
111
|
-
}, {
|
|
112
|
-
name: "vp-vue-eslint-vapor",
|
|
113
|
-
display: "vp + eslint + vapor",
|
|
114
|
-
color: colorMap.vue
|
|
115
|
-
}]
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
name: "react",
|
|
119
|
-
display: "React",
|
|
120
|
-
color: colorMap.react,
|
|
121
|
-
variants: [
|
|
122
|
-
{
|
|
123
|
-
name: "react-rsbuild-biome",
|
|
124
|
-
display: "rsbuild-biome",
|
|
125
|
-
color: colorMap.react
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
name: "vp-react",
|
|
129
|
-
display: "vp-react",
|
|
130
|
-
color: colorMap.react
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
name: "vp-react-shadcn",
|
|
134
|
-
display: "vp-react-shadcn",
|
|
135
|
-
color: colorMap.react
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
name: "vp-monorepo-react-nestjs",
|
|
139
|
-
display: "vp-monorepo-react-nestjs",
|
|
140
|
-
color: colorMap.react
|
|
141
|
-
}
|
|
142
|
-
]
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
name: "taro",
|
|
146
|
-
display: "Taro",
|
|
147
|
-
color: colorMap.taro,
|
|
148
|
-
variants: [{
|
|
149
|
-
name: "taro-vue-eslint",
|
|
150
|
-
display: "taro-vue + eslint",
|
|
151
|
-
color: colorMap.taro
|
|
152
|
-
}, {
|
|
153
|
-
name: "taro-react-oxc",
|
|
154
|
-
display: "taro-react + oxc",
|
|
155
|
-
color: colorMap.taro
|
|
156
|
-
}]
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
name: "nextjs",
|
|
160
|
-
display: "NextJS",
|
|
161
|
-
color: colorMap.nextjs,
|
|
162
|
-
variants: [{
|
|
163
|
-
name: "nextjs-vinext-eslint",
|
|
164
|
-
display: "nextjs + vinext + eslint",
|
|
165
|
-
color: colorMap.nextjs
|
|
166
|
-
}]
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
name: "others",
|
|
170
|
-
display: "Others",
|
|
171
|
-
color: colorMap.others,
|
|
172
|
-
variants: [{
|
|
173
|
-
name: "create-eletron-vite",
|
|
174
|
-
display: "Electron ↗",
|
|
175
|
-
color: colorMap.others,
|
|
176
|
-
customCommand: "pnpm create electron-vite@latest TARGET_DIR"
|
|
177
|
-
}]
|
|
178
|
-
}
|
|
179
|
-
];
|
|
180
|
-
const TEMPLATES = FRAMEWORKS.map((f) => f.variants.map((v) => `${v.name}`)).reduce((a, b) => a.concat(b), []);
|
|
181
|
-
/**
|
|
182
|
-
*
|
|
183
|
-
* @param customCommand
|
|
184
|
-
* @param pkgInfo { name: 'pnpm', version: '10.0.0' }
|
|
185
|
-
* @returns
|
|
186
|
-
*/
|
|
187
|
-
const getFullCustomCommand = (customCommand, pkgInfo) => {
|
|
188
|
-
console.log("💦customCommand", customCommand, pkgInfo);
|
|
189
|
-
const pkgManager = pkgInfo ? pkgInfo.name : "npm";
|
|
190
|
-
const isYarn1 = pkgManager === "yarn" && pkgInfo?.version.startsWith("1.");
|
|
191
|
-
return customCommand.replace(/^npm create (?:-- )?/, () => {
|
|
192
|
-
if (pkgManager === "bun") return "bun x create-";
|
|
193
|
-
if (pkgManager === "pnpm") return "pnpm create ";
|
|
194
|
-
/** 这里的 -- 时参数分隔符 前面的给 npm create 后面的 给 create-vite 这个脚手架 */
|
|
195
|
-
return customCommand.startsWith("npm create -- ") ? `${pkgManager} create -- ` : `${pkgManager} create `;
|
|
196
|
-
}).replace("@latest", () => isYarn1 ? "" : "@latest").replace(/^npm exec/, () => {
|
|
197
|
-
if (pkgManager === "pnpm") return "pnpm dlx";
|
|
198
|
-
if (pkgManager === "yarn" && !isYarn1) return "yarn dlx";
|
|
199
|
-
if (pkgManager === "bun") return "bun x";
|
|
200
|
-
return "npm exec";
|
|
201
|
-
});
|
|
202
|
-
};
|
|
203
|
-
/**
|
|
204
|
-
* 历史遗留问题 npmjs 会把.gitignore 忽略掉,于是vite官方把.g
|
|
205
|
-
*/
|
|
206
|
-
const renameFiles = { _gitignore: ".gitignore" };
|
|
207
|
-
const copyDir = (srcDir, destDir) => {
|
|
208
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
209
|
-
for (const file of fs.readdirSync(srcDir)) {
|
|
210
|
-
const srcFile = path.resolve(srcDir, file);
|
|
211
|
-
const destFile = path.resolve(destDir, file);
|
|
212
|
-
copy(srcFile, destFile);
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
const copy = (src, dest) => {
|
|
216
|
-
if (fs.statSync(src).isDirectory()) copyDir(src, dest);
|
|
217
|
-
else fs.copyFileSync(src, dest);
|
|
218
|
-
};
|
|
219
|
-
/**
|
|
220
|
-
* 修改文件内容
|
|
221
|
-
* @param file 文件路径
|
|
222
|
-
* @param callback 修改内容的回调 参数是当前内容
|
|
223
|
-
*/
|
|
224
|
-
const editFile = (file, callback) => {
|
|
225
|
-
const content = fs.readFileSync(file, "utf-8");
|
|
226
|
-
fs.writeFileSync(file, callback(content), "utf-8");
|
|
227
|
-
};
|
|
228
|
-
/**
|
|
229
|
-
* 对 react swc 的处理 就是将plugin-react 换成 plugin-react-swc
|
|
230
|
-
* @param src 源目录
|
|
231
|
-
* @param dest 目标目录
|
|
232
|
-
*/
|
|
233
|
-
const setupReactSwc = (root, isTs) => {
|
|
234
|
-
const reactSwcPluginVersion = "4.0.1";
|
|
235
|
-
editFile(path.resolve(root, "package.json"), (content) => {
|
|
236
|
-
return content.replace(/"@vitejs\/plugin-react": ".+?"/, `"@vitejs/plugin-react-swc": "^${reactSwcPluginVersion}"`);
|
|
237
|
-
});
|
|
238
|
-
editFile(path.resolve(root, `vite.config.${isTs ? "ts" : "js"}`), (content) => content.replace("@vitejs/plugin-react", "@vitejs/plugin-react-swc"));
|
|
239
|
-
};
|
|
240
|
-
const init = async () => {
|
|
241
|
-
console.log(argv);
|
|
242
|
-
/**
|
|
243
|
-
* 取的是那些没有 --key value的参数 这里代指模板
|
|
244
|
-
* bun index.js template-vue -t vue -s 12321 template-react
|
|
245
|
-
* {
|
|
246
|
-
* _: [ "template-vue", "template-react" ],
|
|
247
|
-
* t: "vue",
|
|
248
|
-
* s: 12321,
|
|
249
|
-
* template: "vue",
|
|
250
|
-
* }
|
|
251
|
-
*/
|
|
252
|
-
const argTargetDir = argv._[0] ? formatTargetDir(argv._[0]) : void 0;
|
|
253
|
-
const argTemplate = argv.template;
|
|
254
|
-
const argOverwrite = argv.overwrite;
|
|
255
|
-
const defaultTargetDir = "bubbles-project";
|
|
256
|
-
if (argv.help) {
|
|
257
|
-
console.log(helpMessage);
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* 获取用户执行命令的包管理工具的详细信息 包含 name 和 版本
|
|
262
|
-
*
|
|
263
|
-
* - bun x node packages/create-bubbles-tsdown/index.js
|
|
264
|
-
* - pnpm exec node packages/create-bubbles-tsdown/index.js
|
|
265
|
-
* - 或 npm exec node packages/create-bubbles-tsdown/index.js
|
|
266
|
-
*/
|
|
267
|
-
const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent);
|
|
268
|
-
const cancel = () => prompts.cancel("Operation cancelled");
|
|
269
|
-
/** 项目名 */
|
|
270
|
-
let targetDir = argTargetDir;
|
|
271
|
-
if (!targetDir) {
|
|
272
|
-
const projectName = await prompts.text({
|
|
273
|
-
message: "Project name",
|
|
274
|
-
defaultValue: defaultTargetDir,
|
|
275
|
-
placeholder: defaultTargetDir,
|
|
276
|
-
validate: (value) => {
|
|
277
|
-
return (value || []).length === 0 || formatTargetDir(value || "").length > 0 ? void 0 : "Invalid project name";
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
if (prompts.isCancel(projectName)) return cancel();
|
|
281
|
-
targetDir = formatTargetDir(projectName);
|
|
282
|
-
}
|
|
283
|
-
if (fs.existsSync(targetDir) && !isEmpty(targetDir)) {
|
|
284
|
-
const overwrite = argOverwrite ? "yes" : await prompts.select({
|
|
285
|
-
message: targetDir === "." ? "Current directory" : `Target directory "${targetDir}" is not empty. Please choose how to proceed:`,
|
|
286
|
-
options: [
|
|
287
|
-
{
|
|
288
|
-
label: `${colorMap.vue("Cancel operation")}`,
|
|
289
|
-
value: "no"
|
|
290
|
-
},
|
|
291
|
-
{
|
|
292
|
-
label: "Remove existing files and continue",
|
|
293
|
-
value: "yes"
|
|
294
|
-
},
|
|
295
|
-
{
|
|
296
|
-
label: "Ignore files and continue",
|
|
297
|
-
value: "ignore"
|
|
298
|
-
}
|
|
299
|
-
]
|
|
300
|
-
});
|
|
301
|
-
if (prompts.isCancel(overwrite)) return cancel();
|
|
302
|
-
switch (overwrite) {
|
|
303
|
-
case "yes":
|
|
304
|
-
emptyDir(targetDir);
|
|
305
|
-
break;
|
|
306
|
-
case "no":
|
|
307
|
-
cancel();
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
/** 提取绝对路径最后的path 与 targetDir 不同 因为 targetDir 可以输入 xxx/xxx */
|
|
312
|
-
let packageName = path.basename(path.resolve(targetDir));
|
|
313
|
-
if (!isValidPackageName(packageName)) {
|
|
314
|
-
const packageNameResult = await prompts.text({
|
|
315
|
-
message: "Package name is invalid. please input again:",
|
|
316
|
-
defaultValue: toValidPackageName(packageName),
|
|
317
|
-
placeholder: toValidPackageName(packageName),
|
|
318
|
-
validate(dir) {
|
|
319
|
-
if (!isValidPackageName(dir)) return "Invalid package.json name";
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
if (prompts.isCancel(packageNameResult)) return cancel();
|
|
323
|
-
packageName = packageNameResult;
|
|
324
|
-
}
|
|
325
|
-
let template = argTemplate;
|
|
326
|
-
let hasInvalidArgTemplate = false;
|
|
327
|
-
/** 输入的-t 模板不能存在 */
|
|
328
|
-
if (argTemplate && !TEMPLATES.includes(argTemplate)) {
|
|
329
|
-
template = void 0;
|
|
330
|
-
hasInvalidArgTemplate = true;
|
|
331
|
-
}
|
|
332
|
-
if (!template) {
|
|
333
|
-
const framework = await prompts.select({
|
|
334
|
-
message: hasInvalidArgTemplate ? `"${argTemplate}" isn't a valia template. please choose from below:` : "Select a framework",
|
|
335
|
-
options: FRAMEWORKS.map((framework) => {
|
|
336
|
-
const frameworkColor = framework.color;
|
|
337
|
-
return {
|
|
338
|
-
label: frameworkColor(framework.display),
|
|
339
|
-
value: framework
|
|
340
|
-
};
|
|
341
|
-
})
|
|
342
|
-
});
|
|
343
|
-
if (prompts.isCancel(framework)) return cancel();
|
|
344
|
-
console.log("💦pkgInfo", pkgInfo);
|
|
345
|
-
const variant = await prompts.select({
|
|
346
|
-
message: "Select a variant:",
|
|
347
|
-
options: framework.variants.map((variant) => {
|
|
348
|
-
const variantColor = variant.color;
|
|
349
|
-
const command = variant.customCommand ? getFullCustomCommand(variant.customCommand, pkgInfo).replace(/ TARGET_DIR$/, "") : void 0;
|
|
350
|
-
return {
|
|
351
|
-
label: variantColor(variant.display || variant.name),
|
|
352
|
-
value: variant.name,
|
|
353
|
-
hint: command
|
|
354
|
-
};
|
|
355
|
-
})
|
|
356
|
-
});
|
|
357
|
-
if (prompts.isCancel(variant)) return cancel();
|
|
358
|
-
template = variant;
|
|
359
|
-
}
|
|
360
|
-
/** 合起来就是 项目文件夹的 绝对路径 */
|
|
361
|
-
const root = path.join(cwd, targetDir);
|
|
362
|
-
fs.mkdirSync(root, { recursive: true });
|
|
363
|
-
let isReactSwc = false;
|
|
364
|
-
if (template.includes("-swc")) {
|
|
365
|
-
isReactSwc = true;
|
|
366
|
-
template = template.replace("-swc", "");
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* 用户输入命令的包管理工具
|
|
370
|
-
*/
|
|
371
|
-
const pkgManager = pkgInfo ? pkgInfo.name : "npm";
|
|
372
|
-
const { customCommand } = FRAMEWORKS.flatMap((f) => f.variants).find((v) => v.name === template) ?? {};
|
|
373
|
-
if (customCommand) {
|
|
374
|
-
const [command, ...args] = getFullCustomCommand(customCommand, pkgInfo).split(" ");
|
|
375
|
-
const replacedArgs = args.map((arg) => arg.replace("TARGET_DIR", () => targetDir));
|
|
376
|
-
console.log("💦replacedArgs", replacedArgs);
|
|
377
|
-
/** 这里的 inherit 表示继承父进程的输入输出 */
|
|
378
|
-
const { status } = spawn.sync(command, replacedArgs, { stdio: "inherit" });
|
|
379
|
-
process.exit(status ?? 0);
|
|
380
|
-
}
|
|
381
|
-
prompts.log.step(`scaffolding project in ${root}...`);
|
|
382
|
-
/** 选则的模板地址 */
|
|
383
|
-
const templateDir = path.resolve(fileURLToPath(import.meta.url), "../..", `template-${template}`);
|
|
384
|
-
const write = (file, content) => {
|
|
385
|
-
const targetPath = path.join(root, renameFiles[file] ?? file);
|
|
386
|
-
if (content) fs.writeFileSync(targetPath, content);
|
|
387
|
-
else copy(path.join(templateDir, file), targetPath);
|
|
388
|
-
};
|
|
389
|
-
const files = fs.readdirSync(templateDir);
|
|
390
|
-
for (const file of files) if (file !== "package.json") write(file);
|
|
391
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(templateDir, "package.json"), "utf-8"));
|
|
392
|
-
pkg.name = packageName;
|
|
393
|
-
/** null => replacer 主要是用于序列化的时候 做一些操作 数组 每一个属性都会执行该函数 如果是数组就是只有这些属性会留下
|
|
394
|
-
* 2 代表 缩进级别
|
|
395
|
-
*/
|
|
396
|
-
write("package.json", `${JSON.stringify(pkg, null, 2)}\n`);
|
|
397
|
-
if (isReactSwc) setupReactSwc(root, template.endsWith("-ts"));
|
|
398
|
-
let doneMessage = "";
|
|
399
|
-
/** 克隆文件夹之后 目录的 的相对路径 其实就是 root 和 cwd 的差值 */
|
|
400
|
-
const cdProjectName = path.relative(cwd, root);
|
|
401
|
-
console.log("💦cwd", cwd);
|
|
402
|
-
console.log("💦root", root);
|
|
403
|
-
console.log("💦cdProjectName", cdProjectName);
|
|
404
|
-
doneMessage += `Done. Now run:\n`;
|
|
405
|
-
if (root !== cwd) doneMessage += `\n cd ${cdProjectName.includes(" ") ? `"${cdProjectName}"` : cdProjectName} `;
|
|
406
|
-
switch (pkgManager) {
|
|
407
|
-
case "yarn":
|
|
408
|
-
doneMessage += `\n yarn`;
|
|
409
|
-
doneMessage += `\n yarn dev`;
|
|
410
|
-
break;
|
|
411
|
-
default:
|
|
412
|
-
doneMessage += gradient(["pink", "white"])(`\n ${pkgManager} install`);
|
|
413
|
-
doneMessage += gradient(["pink", "white"])(`\n ${pkgManager} run dev`);
|
|
414
|
-
break;
|
|
415
|
-
}
|
|
416
|
-
prompts.outro(doneMessage);
|
|
417
|
-
};
|
|
418
|
-
init().catch((e) => {
|
|
419
|
-
console.error("💦", e);
|
|
420
|
-
});
|
|
421
|
-
//#endregion
|
|
422
|
-
export {};
|