@creativault/powerdata-cli 0.0.1

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 (69) hide show
  1. package/dist/index.js +240 -0
  2. package/dist/index.js.map +1 -0
  3. package/package.json +55 -0
  4. package/template/nextjs_template/.dockerignore +12 -0
  5. package/template/nextjs_template/.editorconfig +15 -0
  6. package/template/nextjs_template/.env.example +43 -0
  7. package/template/nextjs_template/Dockerfile +57 -0
  8. package/template/nextjs_template/README.md +83 -0
  9. package/template/nextjs_template/_gitignore +55 -0
  10. package/template/nextjs_template/biome.json +58 -0
  11. package/template/nextjs_template/components.json +21 -0
  12. package/template/nextjs_template/eslint.config.mjs +16 -0
  13. package/template/nextjs_template/next.config.ts +31 -0
  14. package/template/nextjs_template/package.json +57 -0
  15. package/template/nextjs_template/postcss.config.mjs +8 -0
  16. package/template/nextjs_template/public/.gitkeep +0 -0
  17. package/template/nextjs_template/src/app/demo/page.tsx +258 -0
  18. package/template/nextjs_template/src/app/layout.tsx +46 -0
  19. package/template/nextjs_template/src/app/page.tsx +101 -0
  20. package/template/nextjs_template/src/components/layout/footer.tsx +11 -0
  21. package/template/nextjs_template/src/components/layout/header.tsx +25 -0
  22. package/template/nextjs_template/src/components/theme-provider.tsx +17 -0
  23. package/template/nextjs_template/src/components/theme-toggle.tsx +21 -0
  24. package/template/nextjs_template/src/components/ui/badge.tsx +35 -0
  25. package/template/nextjs_template/src/components/ui/button.tsx +56 -0
  26. package/template/nextjs_template/src/components/ui/card.tsx +82 -0
  27. package/template/nextjs_template/src/config/index.ts +1 -0
  28. package/template/nextjs_template/src/config/website.ts +9 -0
  29. package/template/nextjs_template/src/env.js +22 -0
  30. package/template/nextjs_template/src/hooks/index.ts +1 -0
  31. package/template/nextjs_template/src/lib/constants.ts +15 -0
  32. package/template/nextjs_template/src/lib/urls.ts +22 -0
  33. package/template/nextjs_template/src/lib/utils.ts +6 -0
  34. package/template/nextjs_template/src/middleware.ts +53 -0
  35. package/template/nextjs_template/src/routes.ts +47 -0
  36. package/template/nextjs_template/src/styles/globals.css +157 -0
  37. package/template/nextjs_template/src/test/setup.ts +1 -0
  38. package/template/nextjs_template/src/types/index.ts +25 -0
  39. package/template/nextjs_template/tsconfig.json +34 -0
  40. package/template/nextjs_template/vitest.config.mts +34 -0
  41. package/template/nextjs_template_monorepo/.dockerignore +11 -0
  42. package/template/nextjs_template_monorepo/.env.example +43 -0
  43. package/template/nextjs_template_monorepo/Dockerfile +65 -0
  44. package/template/nextjs_template_monorepo/README.md +55 -0
  45. package/template/nextjs_template_monorepo/components.json +21 -0
  46. package/template/nextjs_template_monorepo/eslint.config.mjs +16 -0
  47. package/template/nextjs_template_monorepo/next.config.ts +38 -0
  48. package/template/nextjs_template_monorepo/package.json +44 -0
  49. package/template/nextjs_template_monorepo/postcss.config.mjs +8 -0
  50. package/template/nextjs_template_monorepo/public/.gitkeep +0 -0
  51. package/template/nextjs_template_monorepo/src/app/demo/page.tsx +255 -0
  52. package/template/nextjs_template_monorepo/src/app/layout.tsx +46 -0
  53. package/template/nextjs_template_monorepo/src/app/page.tsx +102 -0
  54. package/template/nextjs_template_monorepo/src/components/layout/footer.tsx +11 -0
  55. package/template/nextjs_template_monorepo/src/components/layout/header.tsx +25 -0
  56. package/template/nextjs_template_monorepo/src/components/theme-provider.tsx +17 -0
  57. package/template/nextjs_template_monorepo/src/components/theme-toggle.tsx +21 -0
  58. package/template/nextjs_template_monorepo/src/config/index.ts +1 -0
  59. package/template/nextjs_template_monorepo/src/config/website.ts +13 -0
  60. package/template/nextjs_template_monorepo/src/env.js +22 -0
  61. package/template/nextjs_template_monorepo/src/hooks/index.ts +6 -0
  62. package/template/nextjs_template_monorepo/src/lib/utils.ts +2 -0
  63. package/template/nextjs_template_monorepo/src/middleware.ts +53 -0
  64. package/template/nextjs_template_monorepo/src/routes.ts +47 -0
  65. package/template/nextjs_template_monorepo/src/styles/globals.css +157 -0
  66. package/template/nextjs_template_monorepo/src/test/setup.ts +1 -0
  67. package/template/nextjs_template_monorepo/src/types/index.ts +30 -0
  68. package/template/nextjs_template_monorepo/tsconfig.json +34 -0
  69. package/template/nextjs_template_monorepo/vitest.config.mts +34 -0
package/dist/index.js ADDED
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/create.ts
7
+ import path4 from "path";
8
+ import chalk2 from "chalk";
9
+ import prompts from "prompts";
10
+
11
+ // src/constants.ts
12
+ import { fileURLToPath } from "url";
13
+ import path from "path";
14
+ var __filename = fileURLToPath(import.meta.url);
15
+ var __dirname = path.dirname(__filename);
16
+ var TEMPLATE_DIR = path.resolve(__dirname, "..", "template");
17
+ var TEMPLATES = {
18
+ standalone: {
19
+ name: "standalone",
20
+ label: "Standalone \u72EC\u7ACB\u9879\u76EE",
21
+ description: "\u521B\u5EFA\u4E00\u4E2A\u5168\u65B0\u7684 Next.js \u72EC\u7ACB\u9879\u76EE",
22
+ dir: "nextjs_template"
23
+ },
24
+ "monorepo-app": {
25
+ name: "monorepo-app",
26
+ label: "Monorepo \u5B50\u5E94\u7528",
27
+ description: "\u5728\u5DF2\u6709 monorepo \u7684 apps/ \u4E0B\u6DFB\u52A0\u5B50\u5E94\u7528",
28
+ dir: "nextjs_template_monorepo"
29
+ }
30
+ };
31
+
32
+ // src/utils/logger.ts
33
+ import chalk from "chalk";
34
+ var prefix = chalk.cyan.bold("\u26A1 PowerData");
35
+ var logger = {
36
+ info: (msg) => console.log(`${prefix} ${msg}`),
37
+ success: (msg) => console.log(`${prefix} ${chalk.green("\u2714")} ${msg}`),
38
+ warn: (msg) => console.log(`${prefix} ${chalk.yellow("\u26A0")} ${msg}`),
39
+ error: (msg) => console.log(`${prefix} ${chalk.red("\u2716")} ${msg}`),
40
+ step: (step, total, msg) => console.log(`${prefix} ${chalk.dim(`[${step}/${total}]`)} ${msg}`),
41
+ blank: () => console.log()
42
+ };
43
+ function printBanner(version2) {
44
+ console.log();
45
+ console.log(chalk.cyan.bold(" \u26A1 PowerData CLI") + chalk.dim(` v${version2}`));
46
+ console.log(chalk.dim(" \u524D\u7AEF\u9879\u76EE\u811A\u624B\u67B6"));
47
+ console.log();
48
+ }
49
+
50
+ // src/utils/pkg.ts
51
+ import fs from "fs-extra";
52
+ import path2 from "path";
53
+ import { fileURLToPath as fileURLToPath2 } from "url";
54
+ var __filename3 = fileURLToPath2(import.meta.url);
55
+ var __dirname3 = path2.dirname(__filename3);
56
+ function getCliVersion() {
57
+ const pkgPath = path2.resolve(__dirname3, "..", "package.json");
58
+ try {
59
+ const pkg = fs.readJsonSync(pkgPath);
60
+ return pkg.version || "0.0.0";
61
+ } catch {
62
+ return "0.0.0";
63
+ }
64
+ }
65
+ async function updatePackageName(projectDir, name) {
66
+ const pkgPath = path2.join(projectDir, "package.json");
67
+ if (await fs.pathExists(pkgPath)) {
68
+ const pkg = await fs.readJson(pkgPath);
69
+ pkg.name = name;
70
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
71
+ }
72
+ }
73
+
74
+ // src/utils/template.ts
75
+ import fs2 from "fs-extra";
76
+ import path3 from "path";
77
+ var SKIP_PATTERNS = [
78
+ "node_modules",
79
+ ".next",
80
+ ".turbo",
81
+ "dist",
82
+ "pnpm-lock.yaml",
83
+ "tsconfig.tsbuildinfo",
84
+ "next-env.d.ts",
85
+ ".DS_Store"
86
+ ];
87
+ function shouldSkip(name) {
88
+ return SKIP_PATTERNS.includes(name);
89
+ }
90
+ async function copyTemplate(templateName, targetDir) {
91
+ const template = TEMPLATES[templateName];
92
+ const srcDir = path3.join(TEMPLATE_DIR, template.dir);
93
+ if (!await fs2.pathExists(srcDir)) {
94
+ throw new Error(`\u6A21\u677F\u76EE\u5F55\u4E0D\u5B58\u5728: ${srcDir}`);
95
+ }
96
+ await fs2.ensureDir(targetDir);
97
+ await fs2.copy(srcDir, targetDir, {
98
+ filter: (src) => {
99
+ const basename = path3.basename(src);
100
+ if (shouldSkip(basename)) {
101
+ return false;
102
+ }
103
+ return true;
104
+ }
105
+ });
106
+ const gitignoreSrc = path3.join(targetDir, "_gitignore");
107
+ const gitignoreDest = path3.join(targetDir, ".gitignore");
108
+ if (await fs2.pathExists(gitignoreSrc)) {
109
+ await fs2.rename(gitignoreSrc, gitignoreDest);
110
+ }
111
+ logger.success("\u6A21\u677F\u6587\u4EF6\u590D\u5236\u5B8C\u6210");
112
+ }
113
+ async function isDirectoryEmpty(dir) {
114
+ if (!await fs2.pathExists(dir)) {
115
+ return true;
116
+ }
117
+ const files = await fs2.readdir(dir);
118
+ return files.length === 0;
119
+ }
120
+
121
+ // src/commands/create.ts
122
+ async function create(projectName, options) {
123
+ logger.blank();
124
+ let templateName;
125
+ if (options.template && options.template in TEMPLATES) {
126
+ templateName = options.template;
127
+ } else {
128
+ const { template } = await prompts(
129
+ {
130
+ type: "select",
131
+ name: "template",
132
+ message: "\u9009\u62E9\u9879\u76EE\u6A21\u677F",
133
+ choices: Object.values(TEMPLATES).map((t) => ({
134
+ title: t.label,
135
+ description: t.description,
136
+ value: t.name
137
+ }))
138
+ },
139
+ { onCancel: () => process.exit(0) }
140
+ );
141
+ templateName = template;
142
+ }
143
+ const isMonorepoApp = templateName === "monorepo-app";
144
+ let name = projectName;
145
+ if (!name) {
146
+ const { inputName } = await prompts(
147
+ {
148
+ type: "text",
149
+ name: "inputName",
150
+ message: isMonorepoApp ? "\u5B50\u5E94\u7528\u540D\u79F0 (\u5C06\u521B\u5EFA\u5728 apps/ \u4E0B)" : "\u9879\u76EE\u540D\u79F0",
151
+ initial: isMonorepoApp ? "my-app" : "my-nextjs-app",
152
+ validate: (v) => v.trim() ? true : "\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A"
153
+ },
154
+ { onCancel: () => process.exit(0) }
155
+ );
156
+ name = inputName;
157
+ }
158
+ let targetDir;
159
+ if (isMonorepoApp) {
160
+ const appsDir = path4.resolve(process.cwd(), "apps");
161
+ const hasPnpmWorkspace = await import("fs-extra").then(
162
+ (fs3) => fs3.pathExists(path4.resolve(process.cwd(), "pnpm-workspace.yaml"))
163
+ );
164
+ if (!hasPnpmWorkspace) {
165
+ logger.warn("\u5F53\u524D\u76EE\u5F55\u672A\u68C0\u6D4B\u5230 pnpm-workspace.yaml");
166
+ const { proceed } = await prompts(
167
+ {
168
+ type: "confirm",
169
+ name: "proceed",
170
+ message: "\u662F\u5426\u7EE7\u7EED\u5728\u5F53\u524D\u76EE\u5F55\u7684 apps/ \u4E0B\u521B\u5EFA\uFF1F",
171
+ initial: false
172
+ },
173
+ { onCancel: () => process.exit(0) }
174
+ );
175
+ if (!proceed) {
176
+ logger.info("\u5DF2\u53D6\u6D88");
177
+ return;
178
+ }
179
+ }
180
+ targetDir = path4.join(appsDir, name);
181
+ } else {
182
+ targetDir = path4.resolve(process.cwd(), name);
183
+ }
184
+ const empty = await isDirectoryEmpty(targetDir);
185
+ if (!empty) {
186
+ const { overwrite } = await prompts(
187
+ {
188
+ type: "confirm",
189
+ name: "overwrite",
190
+ message: `\u76EE\u5F55 ${chalk2.yellow(targetDir)} \u4E0D\u4E3A\u7A7A\uFF0C\u662F\u5426\u8986\u76D6\uFF1F`,
191
+ initial: false
192
+ },
193
+ { onCancel: () => process.exit(0) }
194
+ );
195
+ if (!overwrite) {
196
+ logger.info("\u5DF2\u53D6\u6D88");
197
+ return;
198
+ }
199
+ }
200
+ const totalSteps = 3;
201
+ logger.step(1, totalSteps, "\u590D\u5236\u6A21\u677F\u6587\u4EF6...");
202
+ await copyTemplate(templateName, targetDir);
203
+ logger.step(2, totalSteps, "\u66F4\u65B0\u9879\u76EE\u914D\u7F6E...");
204
+ const pkgName = isMonorepoApp ? `@repo/${name}` : name;
205
+ await updatePackageName(targetDir, pkgName);
206
+ logger.step(3, totalSteps, "\u521D\u59CB\u5316\u5B8C\u6210");
207
+ logger.blank();
208
+ logger.success(`\u9879\u76EE\u5DF2\u521B\u5EFA: ${chalk2.cyan(targetDir)}`);
209
+ logger.blank();
210
+ if (isMonorepoApp) {
211
+ console.log(chalk2.dim(" \u540E\u7EED\u6B65\u9AA4:"));
212
+ console.log(` 1. \u5728 monorepo \u6839\u76EE\u5F55\u8FD0\u884C ${chalk2.cyan("pnpm install")}`);
213
+ console.log(` 2. \u8FD0\u884C ${chalk2.cyan(`pnpm --filter @repo/${name} dev`)}`);
214
+ console.log(
215
+ ` 3. \u6309\u9700\u5728 package.json \u4E2D\u6DFB\u52A0 workspace \u5305\u4F9D\u8D56 (\u5982 ${chalk2.dim('"@repo/auth": "workspace:*"')})`
216
+ );
217
+ } else {
218
+ const relativePath = path4.relative(process.cwd(), targetDir);
219
+ console.log(chalk2.dim(" \u540E\u7EED\u6B65\u9AA4:"));
220
+ console.log(` 1. ${chalk2.cyan(`cd ${relativePath}`)}`);
221
+ console.log(` 2. ${chalk2.cyan("pnpm install")}`);
222
+ console.log(` 3. ${chalk2.cyan("pnpm dev")}`);
223
+ }
224
+ logger.blank();
225
+ }
226
+
227
+ // src/index.ts
228
+ var version = getCliVersion();
229
+ var program = new Command();
230
+ program.name("powerdata").description("PowerData \u524D\u7AEF\u9879\u76EE\u811A\u624B\u67B6 CLI").version(version, "-v, --version");
231
+ program.command("create").description("\u521B\u5EFA\u65B0\u9879\u76EE\u6216\u5B50\u5E94\u7528").argument("[name]", "\u9879\u76EE\u540D\u79F0").option("-t, --template <template>", "\u6A21\u677F\u7C7B\u578B (standalone | monorepo-app)").action((name, options) => {
232
+ printBanner(version);
233
+ create(name, options);
234
+ });
235
+ if (process.argv.length <= 2) {
236
+ printBanner(version);
237
+ program.help();
238
+ }
239
+ program.parse();
240
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/create.ts","../src/constants.ts","../src/utils/logger.ts","../src/utils/pkg.ts","../src/utils/template.ts"],"sourcesContent":["import { Command } from 'commander';\r\nimport { create } from './commands/create.js';\r\nimport { printBanner } from './utils/logger.js';\r\nimport { getCliVersion } from './utils/pkg.js';\r\n\r\nconst version = getCliVersion();\r\nconst program = new Command();\r\n\r\nprogram\r\n .name('powerdata')\r\n .description('PowerData 前端项目脚手架 CLI')\r\n .version(version, '-v, --version');\r\n\r\nprogram\r\n .command('create')\r\n .description('创建新项目或子应用')\r\n .argument('[name]', '项目名称')\r\n .option('-t, --template <template>', '模板类型 (standalone | monorepo-app)')\r\n .action((name, options) => {\r\n printBanner(version);\r\n create(name, options);\r\n });\r\n\r\n// 无参数时显示 banner + help\r\nif (process.argv.length <= 2) {\r\n printBanner(version);\r\n program.help();\r\n}\r\n\r\nprogram.parse();\r\n","import path from 'node:path';\r\nimport chalk from 'chalk';\r\nimport prompts from 'prompts';\r\nimport { TEMPLATES, type TemplateName } from '../constants.js';\r\nimport { logger } from '../utils/logger.js';\r\nimport { updatePackageName } from '../utils/pkg.js';\r\nimport { copyTemplate, isDirectoryEmpty } from '../utils/template.js';\r\n\r\ninterface CreateOptions {\r\n template?: string;\r\n name?: string;\r\n}\r\n\r\nexport async function create(projectName: string | undefined, options: CreateOptions) {\r\n logger.blank();\r\n\r\n // 1. 选择模板类型\r\n let templateName: TemplateName;\r\n if (options.template && options.template in TEMPLATES) {\r\n templateName = options.template as TemplateName;\r\n } else {\r\n const { template } = await prompts(\r\n {\r\n type: 'select',\r\n name: 'template',\r\n message: '选择项目模板',\r\n choices: Object.values(TEMPLATES).map((t) => ({\r\n title: t.label,\r\n description: t.description,\r\n value: t.name,\r\n })),\r\n },\r\n { onCancel: () => process.exit(0) }\r\n );\r\n templateName = template;\r\n }\r\n\r\n const isMonorepoApp = templateName === 'monorepo-app';\r\n\r\n // 2. 输入项目名称\r\n let name = projectName;\r\n if (!name) {\r\n const { inputName } = await prompts(\r\n {\r\n type: 'text',\r\n name: 'inputName',\r\n message: isMonorepoApp ? '子应用名称 (将创建在 apps/ 下)' : '项目名称',\r\n initial: isMonorepoApp ? 'my-app' : 'my-nextjs-app',\r\n validate: (v: string) => (v.trim() ? true : '名称不能为空'),\r\n },\r\n { onCancel: () => process.exit(0) }\r\n );\r\n name = inputName;\r\n }\r\n\r\n // 3. 确定目标目录\r\n let targetDir: string;\r\n if (isMonorepoApp) {\r\n // monorepo 子应用:检查是否在 monorepo 根目录\r\n const appsDir = path.resolve(process.cwd(), 'apps');\r\n const hasPnpmWorkspace = await import('fs-extra').then((fs) =>\r\n fs.pathExists(path.resolve(process.cwd(), 'pnpm-workspace.yaml'))\r\n );\r\n\r\n if (!hasPnpmWorkspace) {\r\n logger.warn('当前目录未检测到 pnpm-workspace.yaml');\r\n const { proceed } = await prompts(\r\n {\r\n type: 'confirm',\r\n name: 'proceed',\r\n message: '是否继续在当前目录的 apps/ 下创建?',\r\n initial: false,\r\n },\r\n { onCancel: () => process.exit(0) }\r\n );\r\n if (!proceed) {\r\n logger.info('已取消');\r\n return;\r\n }\r\n }\r\n\r\n targetDir = path.join(appsDir, name!);\r\n } else {\r\n targetDir = path.resolve(process.cwd(), name!);\r\n }\r\n\r\n // 4. 检查目标目录\r\n const empty = await isDirectoryEmpty(targetDir);\r\n if (!empty) {\r\n const { overwrite } = await prompts(\r\n {\r\n type: 'confirm',\r\n name: 'overwrite',\r\n message: `目录 ${chalk.yellow(targetDir)} 不为空,是否覆盖?`,\r\n initial: false,\r\n },\r\n { onCancel: () => process.exit(0) }\r\n );\r\n if (!overwrite) {\r\n logger.info('已取消');\r\n return;\r\n }\r\n }\r\n\r\n // 5. 复制模板\r\n const totalSteps = 3;\r\n logger.step(1, totalSteps, '复制模板文件...');\r\n await copyTemplate(templateName, targetDir);\r\n\r\n // 6. 更新 package.json name\r\n logger.step(2, totalSteps, '更新项目配置...');\r\n const pkgName = isMonorepoApp ? `@repo/${name}` : name!;\r\n await updatePackageName(targetDir, pkgName);\r\n\r\n // 7. 完成提示\r\n logger.step(3, totalSteps, '初始化完成');\r\n logger.blank();\r\n logger.success(`项目已创建: ${chalk.cyan(targetDir)}`);\r\n logger.blank();\r\n\r\n if (isMonorepoApp) {\r\n console.log(chalk.dim(' 后续步骤:'));\r\n console.log(` 1. 在 monorepo 根目录运行 ${chalk.cyan('pnpm install')}`);\r\n console.log(` 2. 运行 ${chalk.cyan(`pnpm --filter @repo/${name} dev`)}`);\r\n console.log(\r\n ` 3. 按需在 package.json 中添加 workspace 包依赖 (如 ${chalk.dim('\"@repo/auth\": \"workspace:*\"')})`\r\n );\r\n } else {\r\n const relativePath = path.relative(process.cwd(), targetDir);\r\n console.log(chalk.dim(' 后续步骤:'));\r\n console.log(` 1. ${chalk.cyan(`cd ${relativePath}`)}`);\r\n console.log(` 2. ${chalk.cyan('pnpm install')}`);\r\n console.log(` 3. ${chalk.cyan('pnpm dev')}`);\r\n }\r\n\r\n logger.blank();\r\n}\r\n","import { fileURLToPath } from 'node:url';\r\nimport path from 'node:path';\r\n\r\nconst __filename = fileURLToPath(import.meta.url);\r\nconst __dirname = path.dirname(__filename);\r\n\r\n/** CLI 名称 */\r\nexport const CLI_NAME = 'powerdata';\r\n\r\n/** 模板根目录 */\r\nexport const TEMPLATE_DIR = path.resolve(__dirname, '..', 'template');\r\n\r\n/** 模板类型 */\r\nexport const TEMPLATES = {\r\n standalone: {\r\n name: 'standalone',\r\n label: 'Standalone 独立项目',\r\n description: '创建一个全新的 Next.js 独立项目',\r\n dir: 'nextjs_template',\r\n },\r\n 'monorepo-app': {\r\n name: 'monorepo-app',\r\n label: 'Monorepo 子应用',\r\n description: '在已有 monorepo 的 apps/ 下添加子应用',\r\n dir: 'nextjs_template_monorepo',\r\n },\r\n} as const;\r\n\r\nexport type TemplateName = keyof typeof TEMPLATES;\r\n","import chalk from 'chalk';\r\n\r\nconst prefix = chalk.cyan.bold('⚡ PowerData');\r\n\r\nexport const logger = {\r\n info: (msg: string) => console.log(`${prefix} ${msg}`),\r\n success: (msg: string) => console.log(`${prefix} ${chalk.green('✔')} ${msg}`),\r\n warn: (msg: string) => console.log(`${prefix} ${chalk.yellow('⚠')} ${msg}`),\r\n error: (msg: string) => console.log(`${prefix} ${chalk.red('✖')} ${msg}`),\r\n step: (step: number, total: number, msg: string) =>\r\n console.log(`${prefix} ${chalk.dim(`[${step}/${total}]`)} ${msg}`),\r\n blank: () => console.log(),\r\n};\r\n\r\n/** 打印 CLI banner */\r\nexport function printBanner(version: string) {\r\n console.log();\r\n console.log(chalk.cyan.bold(' ⚡ PowerData CLI') + chalk.dim(` v${version}`));\r\n console.log(chalk.dim(' 前端项目脚手架'));\r\n console.log();\r\n}\r\n","import fs from 'fs-extra';\r\nimport path from 'node:path';\r\nimport { fileURLToPath } from 'node:url';\r\n\r\nconst __filename = fileURLToPath(import.meta.url);\r\nconst __dirname = path.dirname(__filename);\r\n\r\n/** 读取 CLI 自身的 package.json */\r\nexport function getCliVersion(): string {\r\n const pkgPath = path.resolve(__dirname, '..', 'package.json');\r\n try {\r\n const pkg = fs.readJsonSync(pkgPath);\r\n return pkg.version || '0.0.0';\r\n } catch {\r\n return '0.0.0';\r\n }\r\n}\r\n\r\n/** 更新目标项目的 package.json 中的 name 字段 */\r\nexport async function updatePackageName(projectDir: string, name: string) {\r\n const pkgPath = path.join(projectDir, 'package.json');\r\n if (await fs.pathExists(pkgPath)) {\r\n const pkg = await fs.readJson(pkgPath);\r\n pkg.name = name;\r\n await fs.writeJson(pkgPath, pkg, { spaces: 2 });\r\n }\r\n}\r\n","import fs from 'fs-extra';\r\nimport path from 'node:path';\r\nimport { TEMPLATE_DIR, TEMPLATES, type TemplateName } from '../constants.js';\r\nimport { logger } from './logger.js';\r\n\r\n/** 需要跳过的文件/目录 */\r\nconst SKIP_PATTERNS = [\r\n 'node_modules', '.next', '.turbo', 'dist',\r\n 'pnpm-lock.yaml', 'tsconfig.tsbuildinfo', 'next-env.d.ts', '.DS_Store',\r\n];\r\n\r\nfunction shouldSkip(name: string): boolean {\r\n return SKIP_PATTERNS.includes(name);\r\n}\r\n\r\n/** 复制模板到目标目录 */\r\nexport async function copyTemplate(templateName: TemplateName, targetDir: string) {\r\n const template = TEMPLATES[templateName];\r\n const srcDir = path.join(TEMPLATE_DIR, template.dir);\r\n\r\n if (!(await fs.pathExists(srcDir))) {\r\n throw new Error(`模板目录不存在: ${srcDir}`);\r\n }\r\n\r\n await fs.ensureDir(targetDir);\r\n\r\n // 递归复制,跳过不需要的文件\r\n await fs.copy(srcDir, targetDir, {\r\n filter: (src) => {\r\n const basename = path.basename(src);\r\n if (shouldSkip(basename)) {\r\n return false;\r\n }\r\n return true;\r\n },\r\n });\r\n\r\n // _gitignore → .gitignore (npm publish 不会打包 .gitignore,所以模板里用 _gitignore)\r\n const gitignoreSrc = path.join(targetDir, '_gitignore');\r\n const gitignoreDest = path.join(targetDir, '.gitignore');\r\n if (await fs.pathExists(gitignoreSrc)) {\r\n await fs.rename(gitignoreSrc, gitignoreDest);\r\n }\r\n\r\n logger.success('模板文件复制完成');\r\n}\r\n\r\n/** 检查目标目录是否为空或不存在 */\r\nexport async function isDirectoryEmpty(dir: string): Promise<boolean> {\r\n if (!(await fs.pathExists(dir))) {\r\n return true;\r\n }\r\n const files = await fs.readdir(dir);\r\n return files.length === 0;\r\n}\r\n"],"mappings":";;;AAAA,SAAS,eAAe;;;ACAxB,OAAOA,WAAU;AACjB,OAAOC,YAAW;AAClB,OAAO,aAAa;;;ACFpB,SAAS,qBAAqB;AAC9B,OAAO,UAAU;AAEjB,IAAM,aAAa,cAAc,YAAY,GAAG;AAChD,IAAM,YAAY,KAAK,QAAQ,UAAU;AAMlC,IAAM,eAAe,KAAK,QAAQ,WAAW,MAAM,UAAU;AAG7D,IAAM,YAAY;AAAA,EACvB,YAAY;AAAA,IACV,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,IACb,KAAK;AAAA,EACP;AAAA,EACA,gBAAgB;AAAA,IACd,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,IACb,KAAK;AAAA,EACP;AACF;;;AC1BA,OAAO,WAAW;AAElB,IAAM,SAAS,MAAM,KAAK,KAAK,kBAAa;AAErC,IAAM,SAAS;AAAA,EACpB,MAAM,CAAC,QAAgB,QAAQ,IAAI,GAAG,MAAM,IAAI,GAAG,EAAE;AAAA,EACrD,SAAS,CAAC,QAAgB,QAAQ,IAAI,GAAG,MAAM,IAAI,MAAM,MAAM,QAAG,CAAC,IAAI,GAAG,EAAE;AAAA,EAC5E,MAAM,CAAC,QAAgB,QAAQ,IAAI,GAAG,MAAM,IAAI,MAAM,OAAO,QAAG,CAAC,IAAI,GAAG,EAAE;AAAA,EAC1E,OAAO,CAAC,QAAgB,QAAQ,IAAI,GAAG,MAAM,IAAI,MAAM,IAAI,QAAG,CAAC,IAAI,GAAG,EAAE;AAAA,EACxE,MAAM,CAAC,MAAc,OAAe,QAClC,QAAQ,IAAI,GAAG,MAAM,IAAI,MAAM,IAAI,IAAI,IAAI,IAAI,KAAK,GAAG,CAAC,IAAI,GAAG,EAAE;AAAA,EACnE,OAAO,MAAM,QAAQ,IAAI;AAC3B;AAGO,SAAS,YAAYC,UAAiB;AAC3C,UAAQ,IAAI;AACZ,UAAQ,IAAI,MAAM,KAAK,KAAK,wBAAmB,IAAI,MAAM,IAAI,KAAKA,QAAO,EAAE,CAAC;AAC5E,UAAQ,IAAI,MAAM,IAAI,8CAAW,CAAC;AAClC,UAAQ,IAAI;AACd;;;ACpBA,OAAO,QAAQ;AACf,OAAOC,WAAU;AACjB,SAAS,iBAAAC,sBAAqB;AAE9B,IAAMC,cAAaD,eAAc,YAAY,GAAG;AAChD,IAAME,aAAYH,MAAK,QAAQE,WAAU;AAGlC,SAAS,gBAAwB;AACtC,QAAM,UAAUF,MAAK,QAAQG,YAAW,MAAM,cAAc;AAC5D,MAAI;AACF,UAAM,MAAM,GAAG,aAAa,OAAO;AACnC,WAAO,IAAI,WAAW;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAsB,kBAAkB,YAAoB,MAAc;AACxE,QAAM,UAAUH,MAAK,KAAK,YAAY,cAAc;AACpD,MAAI,MAAM,GAAG,WAAW,OAAO,GAAG;AAChC,UAAM,MAAM,MAAM,GAAG,SAAS,OAAO;AACrC,QAAI,OAAO;AACX,UAAM,GAAG,UAAU,SAAS,KAAK,EAAE,QAAQ,EAAE,CAAC;AAAA,EAChD;AACF;;;AC1BA,OAAOI,SAAQ;AACf,OAAOC,WAAU;AAKjB,IAAM,gBAAgB;AAAA,EACpB;AAAA,EAAgB;AAAA,EAAS;AAAA,EAAU;AAAA,EACnC;AAAA,EAAkB;AAAA,EAAwB;AAAA,EAAiB;AAC7D;AAEA,SAAS,WAAW,MAAuB;AACzC,SAAO,cAAc,SAAS,IAAI;AACpC;AAGA,eAAsB,aAAa,cAA4B,WAAmB;AAChF,QAAM,WAAW,UAAU,YAAY;AACvC,QAAM,SAASC,MAAK,KAAK,cAAc,SAAS,GAAG;AAEnD,MAAI,CAAE,MAAMC,IAAG,WAAW,MAAM,GAAI;AAClC,UAAM,IAAI,MAAM,+CAAY,MAAM,EAAE;AAAA,EACtC;AAEA,QAAMA,IAAG,UAAU,SAAS;AAG5B,QAAMA,IAAG,KAAK,QAAQ,WAAW;AAAA,IAC/B,QAAQ,CAAC,QAAQ;AACf,YAAM,WAAWD,MAAK,SAAS,GAAG;AAClC,UAAI,WAAW,QAAQ,GAAG;AACxB,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AAGD,QAAM,eAAeA,MAAK,KAAK,WAAW,YAAY;AACtD,QAAM,gBAAgBA,MAAK,KAAK,WAAW,YAAY;AACvD,MAAI,MAAMC,IAAG,WAAW,YAAY,GAAG;AACrC,UAAMA,IAAG,OAAO,cAAc,aAAa;AAAA,EAC7C;AAEA,SAAO,QAAQ,kDAAU;AAC3B;AAGA,eAAsB,iBAAiB,KAA+B;AACpE,MAAI,CAAE,MAAMA,IAAG,WAAW,GAAG,GAAI;AAC/B,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,MAAMA,IAAG,QAAQ,GAAG;AAClC,SAAO,MAAM,WAAW;AAC1B;;;AJzCA,eAAsB,OAAO,aAAiC,SAAwB;AACpF,SAAO,MAAM;AAGb,MAAI;AACJ,MAAI,QAAQ,YAAY,QAAQ,YAAY,WAAW;AACrD,mBAAe,QAAQ;AAAA,EACzB,OAAO;AACL,UAAM,EAAE,SAAS,IAAI,MAAM;AAAA,MACzB;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT,SAAS,OAAO,OAAO,SAAS,EAAE,IAAI,CAAC,OAAO;AAAA,UAC5C,OAAO,EAAE;AAAA,UACT,aAAa,EAAE;AAAA,UACf,OAAO,EAAE;AAAA,QACX,EAAE;AAAA,MACJ;AAAA,MACA,EAAE,UAAU,MAAM,QAAQ,KAAK,CAAC,EAAE;AAAA,IACpC;AACA,mBAAe;AAAA,EACjB;AAEA,QAAM,gBAAgB,iBAAiB;AAGvC,MAAI,OAAO;AACX,MAAI,CAAC,MAAM;AACT,UAAM,EAAE,UAAU,IAAI,MAAM;AAAA,MAC1B;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS,gBAAgB,2EAAyB;AAAA,QAClD,SAAS,gBAAgB,WAAW;AAAA,QACpC,UAAU,CAAC,MAAe,EAAE,KAAK,IAAI,OAAO;AAAA,MAC9C;AAAA,MACA,EAAE,UAAU,MAAM,QAAQ,KAAK,CAAC,EAAE;AAAA,IACpC;AACA,WAAO;AAAA,EACT;AAGA,MAAI;AACJ,MAAI,eAAe;AAEjB,UAAM,UAAUC,MAAK,QAAQ,QAAQ,IAAI,GAAG,MAAM;AAClD,UAAM,mBAAmB,MAAM,OAAO,UAAU,EAAE;AAAA,MAAK,CAACC,QACtDA,IAAG,WAAWD,MAAK,QAAQ,QAAQ,IAAI,GAAG,qBAAqB,CAAC;AAAA,IAClE;AAEA,QAAI,CAAC,kBAAkB;AACrB,aAAO,KAAK,sEAA8B;AAC1C,YAAM,EAAE,QAAQ,IAAI,MAAM;AAAA,QACxB;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,UACT,SAAS;AAAA,QACX;AAAA,QACA,EAAE,UAAU,MAAM,QAAQ,KAAK,CAAC,EAAE;AAAA,MACpC;AACA,UAAI,CAAC,SAAS;AACZ,eAAO,KAAK,oBAAK;AACjB;AAAA,MACF;AAAA,IACF;AAEA,gBAAYA,MAAK,KAAK,SAAS,IAAK;AAAA,EACtC,OAAO;AACL,gBAAYA,MAAK,QAAQ,QAAQ,IAAI,GAAG,IAAK;AAAA,EAC/C;AAGA,QAAM,QAAQ,MAAM,iBAAiB,SAAS;AAC9C,MAAI,CAAC,OAAO;AACV,UAAM,EAAE,UAAU,IAAI,MAAM;AAAA,MAC1B;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS,gBAAME,OAAM,OAAO,SAAS,CAAC;AAAA,QACtC,SAAS;AAAA,MACX;AAAA,MACA,EAAE,UAAU,MAAM,QAAQ,KAAK,CAAC,EAAE;AAAA,IACpC;AACA,QAAI,CAAC,WAAW;AACd,aAAO,KAAK,oBAAK;AACjB;AAAA,IACF;AAAA,EACF;AAGA,QAAM,aAAa;AACnB,SAAO,KAAK,GAAG,YAAY,yCAAW;AACtC,QAAM,aAAa,cAAc,SAAS;AAG1C,SAAO,KAAK,GAAG,YAAY,yCAAW;AACtC,QAAM,UAAU,gBAAgB,SAAS,IAAI,KAAK;AAClD,QAAM,kBAAkB,WAAW,OAAO;AAG1C,SAAO,KAAK,GAAG,YAAY,gCAAO;AAClC,SAAO,MAAM;AACb,SAAO,QAAQ,mCAAUA,OAAM,KAAK,SAAS,CAAC,EAAE;AAChD,SAAO,MAAM;AAEb,MAAI,eAAe;AACjB,YAAQ,IAAIA,OAAM,IAAI,6BAAS,CAAC;AAChC,YAAQ,IAAI,uDAAyBA,OAAM,KAAK,cAAc,CAAC,EAAE;AACjE,YAAQ,IAAI,qBAAWA,OAAM,KAAK,uBAAuB,IAAI,MAAM,CAAC,EAAE;AACtE,YAAQ;AAAA,MACN,gGAA8CA,OAAM,IAAI,6BAA6B,CAAC;AAAA,IACxF;AAAA,EACF,OAAO;AACL,UAAM,eAAeF,MAAK,SAAS,QAAQ,IAAI,GAAG,SAAS;AAC3D,YAAQ,IAAIE,OAAM,IAAI,6BAAS,CAAC;AAChC,YAAQ,IAAI,QAAQA,OAAM,KAAK,MAAM,YAAY,EAAE,CAAC,EAAE;AACtD,YAAQ,IAAI,QAAQA,OAAM,KAAK,cAAc,CAAC,EAAE;AAChD,YAAQ,IAAI,QAAQA,OAAM,KAAK,UAAU,CAAC,EAAE;AAAA,EAC9C;AAEA,SAAO,MAAM;AACf;;;ADnIA,IAAM,UAAU,cAAc;AAC9B,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,WAAW,EAChB,YAAY,0DAAuB,EACnC,QAAQ,SAAS,eAAe;AAEnC,QACG,QAAQ,QAAQ,EAChB,YAAY,wDAAW,EACvB,SAAS,UAAU,0BAAM,EACzB,OAAO,6BAA6B,sDAAkC,EACtE,OAAO,CAAC,MAAM,YAAY;AACzB,cAAY,OAAO;AACnB,SAAO,MAAM,OAAO;AACtB,CAAC;AAGH,IAAI,QAAQ,KAAK,UAAU,GAAG;AAC5B,cAAY,OAAO;AACnB,UAAQ,KAAK;AACf;AAEA,QAAQ,MAAM;","names":["path","chalk","version","path","fileURLToPath","__filename","__dirname","fs","path","path","fs","path","fs","chalk"]}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@creativault/powerdata-cli",
3
+ "version": "0.0.1",
4
+ "description": "PowerData 前端项目脚手架 CLI",
5
+ "license": "MIT",
6
+ "author": "",
7
+ "type": "module",
8
+ "bin": {
9
+ "powerdata": "./dist/index.js",
10
+ "pd": "./dist/index.js"
11
+ },
12
+ "main": "./dist/index.js",
13
+ "files": [
14
+ "dist",
15
+ "template"
16
+ ],
17
+ "scripts": {
18
+ "dev": "tsup --watch",
19
+ "build": "tsup",
20
+ "start": "node dist/index.js",
21
+ "typecheck": "tsc --noEmit",
22
+ "lint": "biome check .",
23
+ "lint:fix": "biome check --fix --unsafe .",
24
+ "prepublishOnly": "pnpm build"
25
+ },
26
+ "dependencies": {
27
+ "chalk": "^5.4.1",
28
+ "commander": "^13.1.0",
29
+ "fs-extra": "^11.3.0",
30
+ "prompts": "^2.4.2"
31
+ },
32
+ "devDependencies": {
33
+ "@biomejs/biome": "^1.9.4",
34
+ "@types/fs-extra": "^11.0.4",
35
+ "@types/node": "^22",
36
+ "@types/prompts": "^2.4.9",
37
+ "tsup": "^8.5.0",
38
+ "typescript": "^5.8.3"
39
+ },
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ },
43
+ "keywords": [
44
+ "cli",
45
+ "scaffold",
46
+ "nextjs",
47
+ "monorepo",
48
+ "template",
49
+ "creativault",
50
+ "powerdata"
51
+ ],
52
+ "publishConfig": {
53
+ "access": "public"
54
+ }
55
+ }
@@ -0,0 +1,12 @@
1
+ node_modules
2
+ .next
3
+ .git
4
+ .gitignore
5
+ *.md
6
+ .env
7
+ .env.*
8
+ !.env.example
9
+ coverage
10
+ .vscode
11
+ .cursor
12
+ .idea
@@ -0,0 +1,15 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = space
5
+ indent_size = 2
6
+ end_of_line = lf
7
+ charset = utf-8
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
10
+
11
+ [*.md]
12
+ trim_trailing_whitespace = false
13
+
14
+ [*.{js,ts,tsx}]
15
+ quote_type = single
@@ -0,0 +1,43 @@
1
+ # =============================================================================
2
+ # 环境变量配置
3
+ # =============================================================================
4
+
5
+ NODE_ENV=development
6
+
7
+ # -----------------------------------------------------------------------------
8
+ # 应用配置
9
+ # -----------------------------------------------------------------------------
10
+ NEXT_PUBLIC_APP_URL="http://localhost:3000"
11
+ PORT=3000
12
+
13
+ # 跳过环境变量验证 (Docker 构建时使用)
14
+ # SKIP_ENV_VALIDATION=true
15
+
16
+ # -----------------------------------------------------------------------------
17
+ # 数据库配置
18
+ # -----------------------------------------------------------------------------
19
+ # DATABASE_URL="postgresql://user:password@localhost:5432/myapp"
20
+
21
+ # -----------------------------------------------------------------------------
22
+ # 后端 API 配置
23
+ # -----------------------------------------------------------------------------
24
+ # API_URL="http://localhost:8080"
25
+
26
+ # -----------------------------------------------------------------------------
27
+ # 认证配置 (better-auth)
28
+ # -----------------------------------------------------------------------------
29
+ # BETTER_AUTH_SECRET="your-secret-key"
30
+ # BETTER_AUTH_URL="http://localhost:3000"
31
+
32
+ # -----------------------------------------------------------------------------
33
+ # Sentry 错误监控
34
+ # -----------------------------------------------------------------------------
35
+ # SENTRY_DSN=""
36
+ # SENTRY_AUTH_TOKEN=""
37
+
38
+ # -----------------------------------------------------------------------------
39
+ # Stripe 支付
40
+ # -----------------------------------------------------------------------------
41
+ # STRIPE_SECRET_KEY=""
42
+ # STRIPE_WEBHOOK_SECRET=""
43
+ # NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=""
@@ -0,0 +1,57 @@
1
+ # ============================================
2
+ # Stage 1: Dependencies
3
+ # ============================================
4
+ FROM node:20-alpine AS deps
5
+ RUN apk add --no-cache libc6-compat
6
+ WORKDIR /app
7
+
8
+ RUN corepack enable && corepack prepare pnpm@9.0.0 --activate
9
+
10
+ COPY package.json pnpm-lock.yaml ./
11
+ RUN pnpm install --frozen-lockfile
12
+
13
+ # ============================================
14
+ # Stage 2: Builder
15
+ # ============================================
16
+ FROM node:20-alpine AS builder
17
+ RUN apk add --no-cache libc6-compat
18
+ WORKDIR /app
19
+
20
+ RUN corepack enable && corepack prepare pnpm@9.0.0 --activate
21
+
22
+ COPY --from=deps /app/node_modules ./node_modules
23
+ COPY . .
24
+
25
+ ARG NEXT_PUBLIC_APP_URL
26
+ ARG NODE_ENV=production
27
+
28
+ ENV NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL
29
+ ENV NODE_ENV=$NODE_ENV
30
+ ENV NEXT_TELEMETRY_DISABLED=1
31
+
32
+ RUN pnpm build
33
+
34
+ # ============================================
35
+ # Stage 3: Runner
36
+ # ============================================
37
+ FROM node:20-alpine AS runner
38
+ WORKDIR /app
39
+
40
+ ENV NODE_ENV=production
41
+ ENV NEXT_TELEMETRY_DISABLED=1
42
+
43
+ RUN addgroup --system --gid 1001 nodejs
44
+ RUN adduser --system --uid 1001 nextjs
45
+
46
+ COPY --from=builder /app/.next/standalone ./
47
+ COPY --from=builder /app/.next/static ./.next/static
48
+ COPY --from=builder /app/public ./public
49
+
50
+ USER nextjs
51
+
52
+ EXPOSE 3000
53
+
54
+ ENV PORT=3000
55
+ ENV HOSTNAME="0.0.0.0"
56
+
57
+ CMD ["node", "server.js"]
@@ -0,0 +1,83 @@
1
+ # Next.js Template
2
+
3
+ 基于 Next.js 16 + Tailwind CSS 4 + shadcn/ui 的现代化项目模板。
4
+
5
+ ## 技术栈
6
+
7
+ - **框架**: Next.js 16 (App Router)
8
+ - **样式**: Tailwind CSS 4 + shadcn/ui
9
+ - **语言**: TypeScript (Strict Mode)
10
+ - **代码规范**: Biome (Lint + Format) + ESLint (next/core-web-vitals)
11
+ - **测试**: Vitest + Testing Library
12
+ - **环境变量**: @t3-oss/env-nextjs (类型安全验证)
13
+ - **主题**: next-themes (Light/Dark Mode)
14
+ - **部署**: Docker (Multi-stage Build)
15
+
16
+ ## 项目结构
17
+
18
+ ```
19
+ src/
20
+ ├── app/ # Next.js App Router 页面
21
+ │ ├── layout.tsx # 根布局
22
+ │ ├── page.tsx # 首页
23
+ │ └── demo/ # Demo 页面
24
+ ├── components/
25
+ │ ├── layout/ # 布局组件 (Header, Footer)
26
+ │ ├── ui/ # shadcn/ui 组件
27
+ │ ├── theme-provider.tsx
28
+ │ └── theme-toggle.tsx
29
+ ├── config/ # 应用配置
30
+ ├── hooks/ # 自定义 Hooks
31
+ ├── lib/ # 工具函数
32
+ ├── styles/ # 全局样式
33
+ ├── test/ # 测试配置
34
+ ├── env.js # 环境变量验证
35
+ ├── middleware.ts # Next.js 中间件
36
+ └── routes.ts # 路由配置
37
+ ```
38
+
39
+ ## 快速开始
40
+
41
+ ```bash
42
+ # 安装依赖
43
+ pnpm install
44
+
45
+ # 启动开发服务器
46
+ pnpm dev
47
+
48
+ # 构建
49
+ pnpm build
50
+
51
+ # 代码检查
52
+ pnpm lint
53
+
54
+ # 类型检查
55
+ pnpm typecheck
56
+
57
+ # 运行测试
58
+ pnpm test
59
+ ```
60
+
61
+ ## 环境变量
62
+
63
+ 复制 `.env.example` 为 `.env.local` 并填写配置:
64
+
65
+ ```bash
66
+ cp .env.example .env.local
67
+ ```
68
+
69
+ ## Docker 部署
70
+
71
+ ```bash
72
+ # 构建镜像
73
+ docker build -t myapp .
74
+
75
+ # 运行容器
76
+ docker run -p 3000:3000 myapp
77
+ ```
78
+
79
+ ## 添加 shadcn/ui 组件
80
+
81
+ ```bash
82
+ npx shadcn@latest add [component-name]
83
+ ```
@@ -0,0 +1,55 @@
1
+ # dependencies
2
+ node_modules
3
+ /.pnp
4
+ .pnp.*
5
+ .yarn/*
6
+ !.yarn/patches
7
+ !.yarn/plugins
8
+ !.yarn/releases
9
+ !.yarn/versions
10
+
11
+ # testing
12
+ /coverage
13
+
14
+ # next.js
15
+ .next
16
+ /out/
17
+
18
+ # production
19
+ /build
20
+ /dist
21
+
22
+ # misc
23
+ .DS_Store
24
+ *.pem
25
+
26
+ # debug
27
+ npm-debug.log*
28
+ yarn-debug.log*
29
+ yarn-error.log*
30
+ .pnpm-debug.log*
31
+
32
+ # env files
33
+ .env
34
+ .env.local
35
+ .env.*.local
36
+
37
+ # vercel
38
+ .vercel
39
+
40
+ # typescript
41
+ *.tsbuildinfo
42
+ next-env.d.ts
43
+
44
+ # turbo
45
+ .turbo
46
+
47
+ # IDE
48
+ /.idea
49
+ /.vscode
50
+ /.cursor
51
+ /.trae
52
+
53
+ # Sentry
54
+ .sentryclirc
55
+ .env.sentry-build-plugin
@@ -0,0 +1,58 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3
+ "vcs": {
4
+ "enabled": false,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": false
7
+ },
8
+ "files": {
9
+ "ignoreUnknown": true,
10
+ "ignore": [
11
+ ".next/**",
12
+ "node_modules/**",
13
+ "dist/**",
14
+ "build/**",
15
+ "src/components/ui/*.tsx"
16
+ ]
17
+ },
18
+ "formatter": {
19
+ "enabled": true,
20
+ "indentStyle": "space",
21
+ "indentWidth": 2,
22
+ "lineWidth": 80,
23
+ "formatWithErrors": true
24
+ },
25
+ "organizeImports": {
26
+ "enabled": true
27
+ },
28
+ "linter": {
29
+ "enabled": true,
30
+ "rules": {
31
+ "recommended": true,
32
+ "suspicious": {
33
+ "noExplicitAny": "off",
34
+ "noArrayIndexKey": "off"
35
+ },
36
+ "complexity": {
37
+ "noForEach": "off"
38
+ },
39
+ "correctness": {
40
+ "useExhaustiveDependencies": "off"
41
+ },
42
+ "style": {
43
+ "noNonNullAssertion": "off",
44
+ "useNodejsImportProtocol": "off"
45
+ },
46
+ "a11y": {
47
+ "useKeyWithClickEvents": "off"
48
+ }
49
+ }
50
+ },
51
+ "javascript": {
52
+ "formatter": {
53
+ "quoteStyle": "single",
54
+ "trailingCommas": "es5",
55
+ "semicolons": "always"
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/styles/globals.css",
9
+ "baseColor": "zinc",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "aliases": {
15
+ "components": "@/components",
16
+ "utils": "@/lib/utils",
17
+ "ui": "@/components/ui",
18
+ "lib": "@/lib",
19
+ "hooks": "@/hooks"
20
+ }
21
+ }