@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.
- package/dist/index.js +240 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
- package/template/nextjs_template/.dockerignore +12 -0
- package/template/nextjs_template/.editorconfig +15 -0
- package/template/nextjs_template/.env.example +43 -0
- package/template/nextjs_template/Dockerfile +57 -0
- package/template/nextjs_template/README.md +83 -0
- package/template/nextjs_template/_gitignore +55 -0
- package/template/nextjs_template/biome.json +58 -0
- package/template/nextjs_template/components.json +21 -0
- package/template/nextjs_template/eslint.config.mjs +16 -0
- package/template/nextjs_template/next.config.ts +31 -0
- package/template/nextjs_template/package.json +57 -0
- package/template/nextjs_template/postcss.config.mjs +8 -0
- package/template/nextjs_template/public/.gitkeep +0 -0
- package/template/nextjs_template/src/app/demo/page.tsx +258 -0
- package/template/nextjs_template/src/app/layout.tsx +46 -0
- package/template/nextjs_template/src/app/page.tsx +101 -0
- package/template/nextjs_template/src/components/layout/footer.tsx +11 -0
- package/template/nextjs_template/src/components/layout/header.tsx +25 -0
- package/template/nextjs_template/src/components/theme-provider.tsx +17 -0
- package/template/nextjs_template/src/components/theme-toggle.tsx +21 -0
- package/template/nextjs_template/src/components/ui/badge.tsx +35 -0
- package/template/nextjs_template/src/components/ui/button.tsx +56 -0
- package/template/nextjs_template/src/components/ui/card.tsx +82 -0
- package/template/nextjs_template/src/config/index.ts +1 -0
- package/template/nextjs_template/src/config/website.ts +9 -0
- package/template/nextjs_template/src/env.js +22 -0
- package/template/nextjs_template/src/hooks/index.ts +1 -0
- package/template/nextjs_template/src/lib/constants.ts +15 -0
- package/template/nextjs_template/src/lib/urls.ts +22 -0
- package/template/nextjs_template/src/lib/utils.ts +6 -0
- package/template/nextjs_template/src/middleware.ts +53 -0
- package/template/nextjs_template/src/routes.ts +47 -0
- package/template/nextjs_template/src/styles/globals.css +157 -0
- package/template/nextjs_template/src/test/setup.ts +1 -0
- package/template/nextjs_template/src/types/index.ts +25 -0
- package/template/nextjs_template/tsconfig.json +34 -0
- package/template/nextjs_template/vitest.config.mts +34 -0
- package/template/nextjs_template_monorepo/.dockerignore +11 -0
- package/template/nextjs_template_monorepo/.env.example +43 -0
- package/template/nextjs_template_monorepo/Dockerfile +65 -0
- package/template/nextjs_template_monorepo/README.md +55 -0
- package/template/nextjs_template_monorepo/components.json +21 -0
- package/template/nextjs_template_monorepo/eslint.config.mjs +16 -0
- package/template/nextjs_template_monorepo/next.config.ts +38 -0
- package/template/nextjs_template_monorepo/package.json +44 -0
- package/template/nextjs_template_monorepo/postcss.config.mjs +8 -0
- package/template/nextjs_template_monorepo/public/.gitkeep +0 -0
- package/template/nextjs_template_monorepo/src/app/demo/page.tsx +255 -0
- package/template/nextjs_template_monorepo/src/app/layout.tsx +46 -0
- package/template/nextjs_template_monorepo/src/app/page.tsx +102 -0
- package/template/nextjs_template_monorepo/src/components/layout/footer.tsx +11 -0
- package/template/nextjs_template_monorepo/src/components/layout/header.tsx +25 -0
- package/template/nextjs_template_monorepo/src/components/theme-provider.tsx +17 -0
- package/template/nextjs_template_monorepo/src/components/theme-toggle.tsx +21 -0
- package/template/nextjs_template_monorepo/src/config/index.ts +1 -0
- package/template/nextjs_template_monorepo/src/config/website.ts +13 -0
- package/template/nextjs_template_monorepo/src/env.js +22 -0
- package/template/nextjs_template_monorepo/src/hooks/index.ts +6 -0
- package/template/nextjs_template_monorepo/src/lib/utils.ts +2 -0
- package/template/nextjs_template_monorepo/src/middleware.ts +53 -0
- package/template/nextjs_template_monorepo/src/routes.ts +47 -0
- package/template/nextjs_template_monorepo/src/styles/globals.css +157 -0
- package/template/nextjs_template_monorepo/src/test/setup.ts +1 -0
- package/template/nextjs_template_monorepo/src/types/index.ts +30 -0
- package/template/nextjs_template_monorepo/tsconfig.json +34 -0
- 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":"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","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,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
|
+
}
|