create-bubbles 0.1.18 → 0.1.19

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