@farmzone/fz-template-react 1.0.6 → 1.0.8
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/README.md +102 -102
- package/bin/create.js +108 -108
- package/package.json +24 -24
- package/template/.env.example +5 -5
- package/template/.prettierrc +9 -9
- package/template/eslint.config.js +26 -26
- package/template/index.css +32 -32
- package/template/index.html +19 -19
- package/template/package.json +54 -54
- package/template/pnpm-lock.yaml +4214 -4214
- package/template/public/mockServiceWorker.js +349 -349
- package/template/src/app/App.tsx +26 -26
- package/template/src/app/api/api.ts +178 -178
- package/template/src/app/api/queries.ts +335 -335
- package/template/src/app/api/queryKey.ts +7 -7
- package/template/src/app/api/token.ts +8 -7
- package/template/src/app/layout/Layout.tsx +33 -33
- package/template/src/app/layout/ListContents.tsx +9 -9
- package/template/src/app/layout/ListHeader.tsx +41 -41
- package/template/src/app/layout/MultiTabNav.tsx +106 -101
- package/template/src/app/layout/Sidebar.tsx +33 -33
- package/template/src/app/layout/UserInfo.tsx +95 -94
- package/template/src/app/layout/menu.ts +79 -55
- package/template/src/app/layout/tabSwitchStore.ts +11 -11
- package/template/src/app/router/Router.tsx +56 -56
- package/template/src/app/store/index.ts +26 -26
- package/template/src/index.tsx +21 -21
- package/template/src/mocks/browser.ts +17 -17
- package/template/src/mocks/handlers.ts +43 -43
- package/template/src/mocks/scenarios.ts +57 -57
- package/template/src/pages/dashboard/index.tsx +541 -541
- package/template/src/pages/error/Error.tsx +29 -29
- package/template/src/pages/error/NotFound.tsx +27 -27
- package/template/src/pages/login/index.tsx +317 -317
- package/template/src/pages/post/PostFormModal.tsx +128 -128
- package/template/src/pages/post/detail/index.tsx +545 -545
- package/template/src/pages/post/index.tsx +266 -266
- package/template/src/pages/sample/SampleFormModal.tsx +188 -188
- package/template/src/pages/sample/detail/index.tsx +551 -517
- package/template/src/pages/sample/index.tsx +298 -298
- package/template/src/pages/sample/modal/index.tsx +308 -308
- package/template/src/pages/system/log/index.tsx +173 -173
- package/template/src/pages/user/config/columns.tsx +102 -102
- package/template/src/pages/user/config/schema.ts +54 -54
- package/template/src/pages/user/index.tsx +704 -650
- package/template/src/shared/components/CommentInput.tsx +243 -243
- package/template/src/shared/components/FilePreviewCard.tsx +71 -71
- package/template/src/shared/config/text.ts +27 -27
- package/template/src/shared/config/type.ts +40 -40
- package/template/src/shared/utils/format.ts +11 -11
- package/template/src/types/auth.ts +10 -10
- package/template/src/types/comment.ts +33 -33
- package/template/src/types/common.ts +19 -19
- package/template/src/types/dashboard.ts +53 -53
- package/template/src/types/index.ts +16 -16
- package/template/src/types/log.ts +21 -21
- package/template/src/types/post.ts +32 -32
- package/template/src/types/sample.ts +33 -33
- package/template/src/types/user.ts +51 -51
- package/template/src/vite-env.d.ts +10 -10
- package/template/tsconfig.app.json +32 -32
- package/template/tsconfig.json +7 -7
- package/template/tsconfig.node.json +26 -26
- package/template/vite.config.ts +13 -13
package/README.md
CHANGED
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
# @farmzone/fz-template-react
|
|
2
|
-
|
|
3
|
-
Farmzone React 프로젝트 보일러플레이트 생성 CLI.
|
|
4
|
-
|
|
5
|
-
## 사용법
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npx @farmzone/fz-template-react <프로젝트명>
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
예시:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npx @farmzone/fz-template-react my-app
|
|
15
|
-
cd my-app
|
|
16
|
-
pnpm install
|
|
17
|
-
pnpm dev
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
프로젝트명을 생략하면 대화형으로 입력을 받습니다.
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
npx @farmzone/fz-template-react
|
|
24
|
-
# 프로젝트 이름을 입력하세요: my-app
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## 포함된 기술 스택
|
|
28
|
-
|
|
29
|
-
| 항목 | 버전 |
|
|
30
|
-
| --------------------- | ------- |
|
|
31
|
-
| React | ^19 |
|
|
32
|
-
| Vite | ^7 |
|
|
33
|
-
| TypeScript | ~5.9 |
|
|
34
|
-
| Tailwind CSS | ^4 |
|
|
35
|
-
| React Router | ^7 |
|
|
36
|
-
| TanStack Query | ^5 |
|
|
37
|
-
| @farmzone/fz-react-ui | ^0.0.6 |
|
|
38
|
-
| react-hook-form | ^7 |
|
|
39
|
-
| zod | ^3 |
|
|
40
|
-
| Zustand | ^5 |
|
|
41
|
-
| axios | ^1 |
|
|
42
|
-
| dayjs | ^1 |
|
|
43
|
-
| MSW | ^2 |
|
|
44
|
-
| lucide-react | ^1 |
|
|
45
|
-
|
|
46
|
-
## 메뉴 추가
|
|
47
|
-
|
|
48
|
-
`src/app/layout/menu.ts`의 `MENU_SECTIONS` 배열에 추가:
|
|
49
|
-
|
|
50
|
-
```ts
|
|
51
|
-
import type { MenuSection } from "@farmzone/fz-react-ui";
|
|
52
|
-
import { LayoutDashboard, Users, FileText, ScrollText } from "lucide-react";
|
|
53
|
-
|
|
54
|
-
export const MENU_SECTIONS: Array<MenuSection> = [
|
|
55
|
-
{
|
|
56
|
-
items: [
|
|
57
|
-
{ icon: LayoutDashboard, label: "대시보드", path: "/" },
|
|
58
|
-
{
|
|
59
|
-
icon: FileText,
|
|
60
|
-
label: "게시글 관리",
|
|
61
|
-
children: [{ label: "게시글 관리", path: "/post" }],
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
icon: Users,
|
|
65
|
-
label: "사용자 관리",
|
|
66
|
-
children: [{ label: "사용자 관리", path: "/user" }],
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
title: "시스템",
|
|
72
|
-
items: [{ icon: ScrollText, label: "로그 관리", path: "/system/log" }],
|
|
73
|
-
},
|
|
74
|
-
];
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
`children` 배열을 사용하면 아코디언 서브메뉴로 렌더링됩니다.
|
|
78
|
-
|
|
79
|
-
## 라우트 추가
|
|
80
|
-
|
|
81
|
-
`src/app/router/Router.tsx`의 `children` 배열에 추가:
|
|
82
|
-
|
|
83
|
-
```tsx
|
|
84
|
-
children: [
|
|
85
|
-
{ index: true, element: <DashboardPage /> },
|
|
86
|
-
{ path: "sample", element: <SamplePage /> },
|
|
87
|
-
{ path: "sample/modal", element: <SampleModalPage /> },
|
|
88
|
-
{ path: "sample/:id", element: <SampleDetailPage /> },
|
|
89
|
-
{ path: "post", element: <PostPage /> },
|
|
90
|
-
{ path: "post/:id", element: <PostDetailPage /> },
|
|
91
|
-
{ path: "user", element: <UserPage /> },
|
|
92
|
-
{ path: "system/log", element: <LogPage /> },
|
|
93
|
-
],
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
`/login` 경로와 `authLoader`(토큰 미존재 시 `/login` 리다이렉트)는 Router.tsx에 이미 포함되어 있습니다.
|
|
97
|
-
|
|
98
|
-
## npm 배포
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
npm publish --access public
|
|
102
|
-
```
|
|
1
|
+
# @farmzone/fz-template-react
|
|
2
|
+
|
|
3
|
+
Farmzone React 프로젝트 보일러플레이트 생성 CLI.
|
|
4
|
+
|
|
5
|
+
## 사용법
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @farmzone/fz-template-react <프로젝트명>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
예시:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @farmzone/fz-template-react my-app
|
|
15
|
+
cd my-app
|
|
16
|
+
pnpm install
|
|
17
|
+
pnpm dev
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
프로젝트명을 생략하면 대화형으로 입력을 받습니다.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @farmzone/fz-template-react
|
|
24
|
+
# 프로젝트 이름을 입력하세요: my-app
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 포함된 기술 스택
|
|
28
|
+
|
|
29
|
+
| 항목 | 버전 |
|
|
30
|
+
| --------------------- | ------- |
|
|
31
|
+
| React | ^19 |
|
|
32
|
+
| Vite | ^7 |
|
|
33
|
+
| TypeScript | ~5.9 |
|
|
34
|
+
| Tailwind CSS | ^4 |
|
|
35
|
+
| React Router | ^7 |
|
|
36
|
+
| TanStack Query | ^5 |
|
|
37
|
+
| @farmzone/fz-react-ui | ^0.0.6 |
|
|
38
|
+
| react-hook-form | ^7 |
|
|
39
|
+
| zod | ^3 |
|
|
40
|
+
| Zustand | ^5 |
|
|
41
|
+
| axios | ^1 |
|
|
42
|
+
| dayjs | ^1 |
|
|
43
|
+
| MSW | ^2 |
|
|
44
|
+
| lucide-react | ^1 |
|
|
45
|
+
|
|
46
|
+
## 메뉴 추가
|
|
47
|
+
|
|
48
|
+
`src/app/layout/menu.ts`의 `MENU_SECTIONS` 배열에 추가:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import type { MenuSection } from "@farmzone/fz-react-ui";
|
|
52
|
+
import { LayoutDashboard, Users, FileText, ScrollText } from "lucide-react";
|
|
53
|
+
|
|
54
|
+
export const MENU_SECTIONS: Array<MenuSection> = [
|
|
55
|
+
{
|
|
56
|
+
items: [
|
|
57
|
+
{ icon: LayoutDashboard, label: "대시보드", path: "/" },
|
|
58
|
+
{
|
|
59
|
+
icon: FileText,
|
|
60
|
+
label: "게시글 관리",
|
|
61
|
+
children: [{ label: "게시글 관리", path: "/post" }],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
icon: Users,
|
|
65
|
+
label: "사용자 관리",
|
|
66
|
+
children: [{ label: "사용자 관리", path: "/user" }],
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
title: "시스템",
|
|
72
|
+
items: [{ icon: ScrollText, label: "로그 관리", path: "/system/log" }],
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`children` 배열을 사용하면 아코디언 서브메뉴로 렌더링됩니다.
|
|
78
|
+
|
|
79
|
+
## 라우트 추가
|
|
80
|
+
|
|
81
|
+
`src/app/router/Router.tsx`의 `children` 배열에 추가:
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
children: [
|
|
85
|
+
{ index: true, element: <DashboardPage /> },
|
|
86
|
+
{ path: "sample", element: <SamplePage /> },
|
|
87
|
+
{ path: "sample/modal", element: <SampleModalPage /> },
|
|
88
|
+
{ path: "sample/:id", element: <SampleDetailPage /> },
|
|
89
|
+
{ path: "post", element: <PostPage /> },
|
|
90
|
+
{ path: "post/:id", element: <PostDetailPage /> },
|
|
91
|
+
{ path: "user", element: <UserPage /> },
|
|
92
|
+
{ path: "system/log", element: <LogPage /> },
|
|
93
|
+
],
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`/login` 경로와 `authLoader`(토큰 미존재 시 `/login` 리다이렉트)는 Router.tsx에 이미 포함되어 있습니다.
|
|
97
|
+
|
|
98
|
+
## npm 배포
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npm publish --access public
|
|
102
|
+
```
|
package/bin/create.js
CHANGED
|
@@ -1,108 +1,108 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { createInterface } from "readline";
|
|
4
|
-
import {
|
|
5
|
-
existsSync,
|
|
6
|
-
mkdirSync,
|
|
7
|
-
readdirSync,
|
|
8
|
-
statSync,
|
|
9
|
-
copyFileSync,
|
|
10
|
-
readFileSync,
|
|
11
|
-
writeFileSync,
|
|
12
|
-
renameSync,
|
|
13
|
-
} from "fs";
|
|
14
|
-
import { join, dirname } from "path";
|
|
15
|
-
import { fileURLToPath } from "url";
|
|
16
|
-
|
|
17
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
-
|
|
19
|
-
function prompt(question) {
|
|
20
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
21
|
-
return new Promise((resolve) => {
|
|
22
|
-
rl.question(question, (answer) => {
|
|
23
|
-
rl.close();
|
|
24
|
-
resolve(answer.trim());
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function copyDir(src, dest) {
|
|
30
|
-
mkdirSync(dest, { recursive: true });
|
|
31
|
-
for (const entry of readdirSync(src)) {
|
|
32
|
-
const srcPath = join(src, entry);
|
|
33
|
-
const destPath = join(dest, entry);
|
|
34
|
-
if (statSync(srcPath).isDirectory()) {
|
|
35
|
-
copyDir(srcPath, destPath);
|
|
36
|
-
} else {
|
|
37
|
-
copyFileSync(srcPath, destPath);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function replaceInFile(filePath, search, replacement) {
|
|
43
|
-
if (!existsSync(filePath)) return;
|
|
44
|
-
const content = readFileSync(filePath, "utf-8");
|
|
45
|
-
writeFileSync(filePath, content.replaceAll(search, replacement));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function main() {
|
|
49
|
-
console.log("\n🌱 Farmzone React Template\n");
|
|
50
|
-
|
|
51
|
-
let projectName = process.argv[2];
|
|
52
|
-
|
|
53
|
-
if (!projectName) {
|
|
54
|
-
projectName = await prompt("프로젝트 이름을 입력하세요: ");
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (!projectName) {
|
|
58
|
-
console.error("❌ 프로젝트 이름을 입력해야 합니다.");
|
|
59
|
-
process.exit(1);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(projectName)) {
|
|
63
|
-
console.error(
|
|
64
|
-
"❌ 프로젝트 이름은 소문자, 숫자, 하이픈(-)만 사용 가능하며 하이픈으로 시작하거나 끝날 수 없습니다.",
|
|
65
|
-
);
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const targetDir = join(process.cwd(), projectName);
|
|
70
|
-
|
|
71
|
-
if (existsSync(targetDir)) {
|
|
72
|
-
console.error(`❌ '${projectName}' 디렉터리가 이미 존재합니다.`);
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const templateDir = join(__dirname, "..", "template");
|
|
77
|
-
|
|
78
|
-
console.log(`\n🚀 '${projectName}' 프로젝트를 생성합니다...\n`);
|
|
79
|
-
|
|
80
|
-
// 템플릿 복사
|
|
81
|
-
copyDir(templateDir, targetDir);
|
|
82
|
-
|
|
83
|
-
// gitignore -> .gitignore 이름 변경 (npm publish 시 .gitignore는 자동으로 제외됨)
|
|
84
|
-
const gitignoreSrc = join(targetDir, "gitignore");
|
|
85
|
-
const gitignoreDest = join(targetDir, ".gitignore");
|
|
86
|
-
if (existsSync(gitignoreSrc)) renameSync(gitignoreSrc, gitignoreDest);
|
|
87
|
-
|
|
88
|
-
// __PROJECT_NAME__ 치환
|
|
89
|
-
replaceInFile(
|
|
90
|
-
join(targetDir, "package.json"),
|
|
91
|
-
"__PROJECT_NAME__",
|
|
92
|
-
projectName,
|
|
93
|
-
);
|
|
94
|
-
replaceInFile(join(targetDir, "index.html"), "__PROJECT_NAME__", projectName);
|
|
95
|
-
|
|
96
|
-
console.log("✅ 프로젝트 생성 완료!\n");
|
|
97
|
-
console.log("──────────────────────────────");
|
|
98
|
-
console.log("다음 명령어로 시작하세요:\n");
|
|
99
|
-
console.log(` cd ${projectName}`);
|
|
100
|
-
console.log(` pnpm install`);
|
|
101
|
-
console.log(` pnpm run dev`);
|
|
102
|
-
console.log("──────────────────────────────\n");
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
main().catch((err) => {
|
|
106
|
-
console.error(err);
|
|
107
|
-
process.exit(1);
|
|
108
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createInterface } from "readline";
|
|
4
|
+
import {
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
statSync,
|
|
9
|
+
copyFileSync,
|
|
10
|
+
readFileSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
renameSync,
|
|
13
|
+
} from "fs";
|
|
14
|
+
import { join, dirname } from "path";
|
|
15
|
+
import { fileURLToPath } from "url";
|
|
16
|
+
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
|
|
19
|
+
function prompt(question) {
|
|
20
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
rl.question(question, (answer) => {
|
|
23
|
+
rl.close();
|
|
24
|
+
resolve(answer.trim());
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function copyDir(src, dest) {
|
|
30
|
+
mkdirSync(dest, { recursive: true });
|
|
31
|
+
for (const entry of readdirSync(src)) {
|
|
32
|
+
const srcPath = join(src, entry);
|
|
33
|
+
const destPath = join(dest, entry);
|
|
34
|
+
if (statSync(srcPath).isDirectory()) {
|
|
35
|
+
copyDir(srcPath, destPath);
|
|
36
|
+
} else {
|
|
37
|
+
copyFileSync(srcPath, destPath);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function replaceInFile(filePath, search, replacement) {
|
|
43
|
+
if (!existsSync(filePath)) return;
|
|
44
|
+
const content = readFileSync(filePath, "utf-8");
|
|
45
|
+
writeFileSync(filePath, content.replaceAll(search, replacement));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function main() {
|
|
49
|
+
console.log("\n🌱 Farmzone React Template\n");
|
|
50
|
+
|
|
51
|
+
let projectName = process.argv[2];
|
|
52
|
+
|
|
53
|
+
if (!projectName) {
|
|
54
|
+
projectName = await prompt("프로젝트 이름을 입력하세요: ");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!projectName) {
|
|
58
|
+
console.error("❌ 프로젝트 이름을 입력해야 합니다.");
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(projectName)) {
|
|
63
|
+
console.error(
|
|
64
|
+
"❌ 프로젝트 이름은 소문자, 숫자, 하이픈(-)만 사용 가능하며 하이픈으로 시작하거나 끝날 수 없습니다.",
|
|
65
|
+
);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const targetDir = join(process.cwd(), projectName);
|
|
70
|
+
|
|
71
|
+
if (existsSync(targetDir)) {
|
|
72
|
+
console.error(`❌ '${projectName}' 디렉터리가 이미 존재합니다.`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const templateDir = join(__dirname, "..", "template");
|
|
77
|
+
|
|
78
|
+
console.log(`\n🚀 '${projectName}' 프로젝트를 생성합니다...\n`);
|
|
79
|
+
|
|
80
|
+
// 템플릿 복사
|
|
81
|
+
copyDir(templateDir, targetDir);
|
|
82
|
+
|
|
83
|
+
// gitignore -> .gitignore 이름 변경 (npm publish 시 .gitignore는 자동으로 제외됨)
|
|
84
|
+
const gitignoreSrc = join(targetDir, "gitignore");
|
|
85
|
+
const gitignoreDest = join(targetDir, ".gitignore");
|
|
86
|
+
if (existsSync(gitignoreSrc)) renameSync(gitignoreSrc, gitignoreDest);
|
|
87
|
+
|
|
88
|
+
// __PROJECT_NAME__ 치환
|
|
89
|
+
replaceInFile(
|
|
90
|
+
join(targetDir, "package.json"),
|
|
91
|
+
"__PROJECT_NAME__",
|
|
92
|
+
projectName,
|
|
93
|
+
);
|
|
94
|
+
replaceInFile(join(targetDir, "index.html"), "__PROJECT_NAME__", projectName);
|
|
95
|
+
|
|
96
|
+
console.log("✅ 프로젝트 생성 완료!\n");
|
|
97
|
+
console.log("──────────────────────────────");
|
|
98
|
+
console.log("다음 명령어로 시작하세요:\n");
|
|
99
|
+
console.log(` cd ${projectName}`);
|
|
100
|
+
console.log(` pnpm install`);
|
|
101
|
+
console.log(` pnpm run dev`);
|
|
102
|
+
console.log("──────────────────────────────\n");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
main().catch((err) => {
|
|
106
|
+
console.error(err);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
});
|
package/package.json
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@farmzone/fz-template-react",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Farmzone React 프로젝트 보일러플레이트 생성 CLI",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"fz-template-react": "./bin/create.js"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"bin",
|
|
11
|
-
"template"
|
|
12
|
-
],
|
|
13
|
-
"keywords": [
|
|
14
|
-
"farmzone",
|
|
15
|
-
"react",
|
|
16
|
-
"template",
|
|
17
|
-
"boilerplate",
|
|
18
|
-
"vite"
|
|
19
|
-
],
|
|
20
|
-
"license": "UNLICENSED",
|
|
21
|
-
"engines": {
|
|
22
|
-
"node": ">=18"
|
|
23
|
-
}
|
|
24
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@farmzone/fz-template-react",
|
|
3
|
+
"version": "1.0.8",
|
|
4
|
+
"description": "Farmzone React 프로젝트 보일러플레이트 생성 CLI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"fz-template-react": "./bin/create.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"template"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"farmzone",
|
|
15
|
+
"react",
|
|
16
|
+
"template",
|
|
17
|
+
"boilerplate",
|
|
18
|
+
"vite"
|
|
19
|
+
],
|
|
20
|
+
"license": "UNLICENSED",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/template/.env.example
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# API 서버 호스트 (예: http://localhost:8080)
|
|
2
|
-
VITE_APP_API_HOST=http://localhost:8080
|
|
3
|
-
|
|
4
|
-
# API 버전 (예: v1)
|
|
5
|
-
VITE_APP_API_VERSION=v1
|
|
1
|
+
# API 서버 호스트 (예: http://localhost:8080)
|
|
2
|
+
VITE_APP_API_HOST=http://localhost:8080
|
|
3
|
+
|
|
4
|
+
# API 버전 (예: v1)
|
|
5
|
+
VITE_APP_API_VERSION=v1
|
package/template/.prettierrc
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
{
|
|
2
|
-
"printWidth": 110,
|
|
3
|
-
"tabWidth": 2,
|
|
4
|
-
"semi": true,
|
|
5
|
-
"bracketSpacing": true,
|
|
6
|
-
"singleQuote": false,
|
|
7
|
-
"jsxSingleQuote": false,
|
|
8
|
-
"trailingComma": "all"
|
|
9
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"printWidth": 110,
|
|
3
|
+
"tabWidth": 2,
|
|
4
|
+
"semi": true,
|
|
5
|
+
"bracketSpacing": true,
|
|
6
|
+
"singleQuote": false,
|
|
7
|
+
"jsxSingleQuote": false,
|
|
8
|
+
"trailingComma": "all"
|
|
9
|
+
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import js from "@eslint/js";
|
|
2
|
-
import globals from "globals";
|
|
3
|
-
import reactHooks from "eslint-plugin-react-hooks";
|
|
4
|
-
import reactRefresh from "eslint-plugin-react-refresh";
|
|
5
|
-
import tseslint from "typescript-eslint";
|
|
6
|
-
import { defineConfig, globalIgnores } from "eslint/config";
|
|
7
|
-
|
|
8
|
-
export default defineConfig([
|
|
9
|
-
globalIgnores(["dist"]),
|
|
10
|
-
{
|
|
11
|
-
files: ["**/*.{ts,tsx}"],
|
|
12
|
-
extends: [
|
|
13
|
-
js.configs.recommended,
|
|
14
|
-
tseslint.configs.recommended,
|
|
15
|
-
reactHooks.configs["recommended-latest"],
|
|
16
|
-
reactRefresh.configs.vite,
|
|
17
|
-
],
|
|
18
|
-
languageOptions: {
|
|
19
|
-
ecmaVersion: 2020,
|
|
20
|
-
globals: globals.browser,
|
|
21
|
-
},
|
|
22
|
-
rules: {
|
|
23
|
-
"@typescript-eslint/array-type": ["error", { default: "generic" }],
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
]);
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import reactHooks from "eslint-plugin-react-hooks";
|
|
4
|
+
import reactRefresh from "eslint-plugin-react-refresh";
|
|
5
|
+
import tseslint from "typescript-eslint";
|
|
6
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
7
|
+
|
|
8
|
+
export default defineConfig([
|
|
9
|
+
globalIgnores(["dist"]),
|
|
10
|
+
{
|
|
11
|
+
files: ["**/*.{ts,tsx}"],
|
|
12
|
+
extends: [
|
|
13
|
+
js.configs.recommended,
|
|
14
|
+
tseslint.configs.recommended,
|
|
15
|
+
reactHooks.configs["recommended-latest"],
|
|
16
|
+
reactRefresh.configs.vite,
|
|
17
|
+
],
|
|
18
|
+
languageOptions: {
|
|
19
|
+
ecmaVersion: 2020,
|
|
20
|
+
globals: globals.browser,
|
|
21
|
+
},
|
|
22
|
+
rules: {
|
|
23
|
+
"@typescript-eslint/array-type": ["error", { default: "generic" }],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
]);
|
package/template/index.css
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
@import "tailwindcss";
|
|
2
|
-
@import "tw-animate-css";
|
|
3
|
-
@import "@farmzone/fz-react-ui/tw.css";
|
|
4
|
-
|
|
5
|
-
@custom-variant dark (&:is(.dark *));
|
|
6
|
-
|
|
7
|
-
@tailwind utilities;
|
|
8
|
-
|
|
9
|
-
@theme inline {
|
|
10
|
-
--color-main: #3b82f6;
|
|
11
|
-
--color-sub-lightgray: #f3f4f6;
|
|
12
|
-
--color-sub-darkgray: #6b7280;
|
|
13
|
-
--color-sub-darkgray-2: #374151;
|
|
14
|
-
--color-basic-gray: #9ca3af;
|
|
15
|
-
--color-th: rgb(250 250 250);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
@layer utilities {
|
|
19
|
-
.bg-th {
|
|
20
|
-
background-color: var(--color-th);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
body {
|
|
25
|
-
font-family:
|
|
26
|
-
"Pretendard Variable",
|
|
27
|
-
Pretendard,
|
|
28
|
-
-apple-system,
|
|
29
|
-
BlinkMacSystemFont,
|
|
30
|
-
system-ui,
|
|
31
|
-
sans-serif;
|
|
32
|
-
}
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
@import "@farmzone/fz-react-ui/tw.css";
|
|
4
|
+
|
|
5
|
+
@custom-variant dark (&:is(.dark *));
|
|
6
|
+
|
|
7
|
+
@tailwind utilities;
|
|
8
|
+
|
|
9
|
+
@theme inline {
|
|
10
|
+
--color-main: #3b82f6;
|
|
11
|
+
--color-sub-lightgray: #f3f4f6;
|
|
12
|
+
--color-sub-darkgray: #6b7280;
|
|
13
|
+
--color-sub-darkgray-2: #374151;
|
|
14
|
+
--color-basic-gray: #9ca3af;
|
|
15
|
+
--color-th: rgb(250 250 250);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@layer utilities {
|
|
19
|
+
.bg-th {
|
|
20
|
+
background-color: var(--color-th);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
body {
|
|
25
|
+
font-family:
|
|
26
|
+
"Pretendard Variable",
|
|
27
|
+
Pretendard,
|
|
28
|
+
-apple-system,
|
|
29
|
+
BlinkMacSystemFont,
|
|
30
|
+
system-ui,
|
|
31
|
+
sans-serif;
|
|
32
|
+
}
|
package/template/index.html
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="ko">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
|
6
|
-
<link
|
|
7
|
-
rel="stylesheet"
|
|
8
|
-
as="style"
|
|
9
|
-
crossorigin
|
|
10
|
-
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard-dynamic-subset.min.css"
|
|
11
|
-
/>
|
|
12
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
13
|
-
<title>fz-template-react</title>
|
|
14
|
-
</head>
|
|
15
|
-
<body>
|
|
16
|
-
<div id="root"></div>
|
|
17
|
-
<script type="module" src="/src/index.tsx"></script>
|
|
18
|
-
</body>
|
|
19
|
-
</html>
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="ko">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
|
6
|
+
<link
|
|
7
|
+
rel="stylesheet"
|
|
8
|
+
as="style"
|
|
9
|
+
crossorigin
|
|
10
|
+
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard-dynamic-subset.min.css"
|
|
11
|
+
/>
|
|
12
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
13
|
+
<title>fz-template-react</title>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<div id="root"></div>
|
|
17
|
+
<script type="module" src="/src/index.tsx"></script>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|