xi-create 1.0.0

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 ADDED
@@ -0,0 +1,120 @@
1
+ # xi-create
2
+
3
+ 一个简单易用的命令行工具,用于基于模板快速创建新项目,支持交互式与「项目名 + 模板」一行创建。
4
+
5
+ ---
6
+
7
+ ## 特性
8
+
9
+ - 支持多模板选择
10
+ - 支持交互式与一行命令创建
11
+ - 支持 `--template` / `-t` 指定模板
12
+ - 自动处理 package.json、.gitignore 等
13
+
14
+ ---
15
+
16
+ ## 安装
17
+
18
+ ### 全局安装
19
+
20
+ ```bash
21
+ npm install -g xi-create
22
+ # 或
23
+ pnpm add -g xi-create
24
+ ```
25
+
26
+ ### 临时使用(推荐)
27
+
28
+ ```bash
29
+ npx xi-create@latest
30
+ # 或
31
+ pnpm dlx xi-create
32
+ ```
33
+
34
+ ---
35
+
36
+ ## 使用方法
37
+
38
+ ### 1. 交互式创建
39
+
40
+ 不加参数时进入交互(选择模板、输入项目名):
41
+
42
+ ```bash
43
+ xi-create
44
+ # 或
45
+ npx xi-create@latest
46
+ ```
47
+
48
+ ### 2. 一行命令快速创建
49
+
50
+ 默认子命令为 `create`,可直接写项目名与模板(模板短名为 `template-` 后的部分,如 `react-ts`):
51
+
52
+ ```bash
53
+ xi-create my-app -t react-ts
54
+ # 或
55
+ xi-create my-app --template react-ts
56
+ ```
57
+
58
+ 显式写出子命令时等价:
59
+
60
+ ```bash
61
+ xi-create create my-app --template react-ts
62
+ ```
63
+
64
+ ### 3. 只指定部分参数
65
+
66
+ 只给项目名时会提示选择模板;只给模板时会提示输入项目名(通过 `-t` / `--template`):
67
+
68
+ ```bash
69
+ xi-create my-app
70
+ xi-create -t react-ts
71
+ ```
72
+
73
+ ### 4. 查看所有可用模板
74
+
75
+ ```bash
76
+ xi-create list
77
+ ```
78
+
79
+ ---
80
+
81
+ ## 关于 `npm create`
82
+
83
+ npm 的 `npm create <name>` 会执行 **`create-<name>`** 这个包(例如 `npm create vite` 对应 `create-vite`)。若要通过 `npm create xi-create` 使用本工具,需要单独发布 **`create-xi-create`** 包并在其中转发到 `xi-create`;当前仓库仅提供 **`xi-create`**,请优先使用 **`npx xi-create`** / **`pnpm dlx xi-create`** 或全局安装后的 **`xi-create`**。
84
+
85
+ 使用 `npx` / `pnpm dlx` 时,模板参数直接跟在项目名后面即可,**不要**在项目名与 `--template` 之间插入单独的 `--`。中间的 `--` 会原样传给本工具,Commander 会报 `too many arguments`(与本地用 `node` 执行时相同):
86
+
87
+ ```bash
88
+ npx xi-create@latest my-app --template react-ts
89
+ # 或
90
+ pnpm dlx xi-create my-app -t react-ts
91
+ ```
92
+
93
+ ```bash
94
+ node ./bin/xi-create.js my-app --template react-ts
95
+ ```
96
+
97
+ ---
98
+
99
+ ## 扩展模板
100
+
101
+ 1. 在 `templates` 目录下添加新的模板文件夹,命名格式为 `template-xxx`,如:
102
+ - `template-react-ts`
103
+ - `template-vue`
104
+ 2. 每个模板文件夹下建议包含自己的 `package.json`、`_gitignore` 等。
105
+ 3. 工具会自动识别并展示所有模板。
106
+
107
+ ---
108
+
109
+ ## 开发调试
110
+
111
+ ```bash
112
+ pnpm install
113
+ pnpm start
114
+ ```
115
+
116
+ ---
117
+
118
+ ## License
119
+
120
+ ISC
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander';
4
+ import chalk from 'chalk';
5
+ import inquirer from 'inquirer';
6
+ import fs from 'fs-extra';
7
+ import path from 'path';
8
+ import { readFileSync } from 'fs';
9
+ import { fileURLToPath } from 'url';
10
+ import ora from 'ora';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+
15
+ const pkgJsonPath = path.join(__dirname, '..', 'package.json');
16
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
17
+
18
+ function getTemplates() {
19
+ const templatesDir = path.join(__dirname, '..', 'templates');
20
+ if (!fs.existsSync(templatesDir)) {
21
+ return [];
22
+ }
23
+
24
+ const templates = fs
25
+ .readdirSync(templatesDir, { withFileTypes: true })
26
+ .filter((dirent) => dirent.isDirectory())
27
+ .map((dirent) => dirent.name)
28
+ .filter((name) => name.startsWith('template-'));
29
+
30
+ return templates.map((name) => ({
31
+ name: name.replace('template-', ''),
32
+ value: name,
33
+ path: path.join(templatesDir, name),
34
+ }));
35
+ }
36
+
37
+ async function copyTemplate(templatePath, targetPath, projectName) {
38
+ const spinner = ora('正在创建项目...').start();
39
+
40
+ try {
41
+ await fs.copy(templatePath, targetPath);
42
+
43
+ const packageJsonPath = path.join(targetPath, 'package.json');
44
+ if (await fs.pathExists(packageJsonPath)) {
45
+ const packageJson = await fs.readJson(packageJsonPath);
46
+ packageJson.name = projectName;
47
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
48
+ }
49
+
50
+ const gitignorePath = path.join(targetPath, '_gitignore');
51
+ if (await fs.pathExists(gitignorePath)) {
52
+ await fs.move(gitignorePath, path.join(targetPath, '.gitignore'));
53
+ }
54
+
55
+ const nodeModulesPath = path.join(targetPath, 'node_modules');
56
+ if (await fs.pathExists(nodeModulesPath)) {
57
+ await fs.remove(nodeModulesPath);
58
+ }
59
+
60
+ spinner.succeed('项目创建成功!');
61
+
62
+ console.log(chalk.green('\n🎉 项目已创建完成!'));
63
+ console.log(chalk.cyan(`\n📁 项目路径: ${targetPath}`));
64
+ console.log(chalk.yellow('\n📋 下一步操作:'));
65
+ console.log(chalk.white(` cd ${projectName}`));
66
+ console.log(chalk.white(' pnpm install'));
67
+ console.log(chalk.white(' pnpm dev'));
68
+ } catch (error) {
69
+ spinner.fail('项目创建失败!');
70
+ console.error(chalk.red('错误信息:'), error.message);
71
+ process.exit(1);
72
+ }
73
+ }
74
+
75
+ async function runCreate({ projectName, template } = {}) {
76
+ const templates = getTemplates();
77
+ if (templates.length === 0) {
78
+ console.log(chalk.red('❌ 没有找到可用的模板!'));
79
+ console.log(chalk.yellow('请确保 templates 目录下有模板文件夹。'));
80
+ process.exit(1);
81
+ }
82
+
83
+ const finalName = projectName?.trim() || undefined;
84
+ const finalTemplate = template?.trim() || undefined;
85
+
86
+ if (finalName && finalTemplate) {
87
+ const tpl = templates.find(
88
+ (t) => t.name === finalTemplate || t.value === finalTemplate
89
+ );
90
+ if (!tpl) {
91
+ console.log(chalk.red(`❌ 未找到模板: ${finalTemplate}`));
92
+ process.exit(1);
93
+ }
94
+ if (fs.existsSync(finalName)) {
95
+ console.log(chalk.red('❌ 项目目录已存在,请选择其他名称!'));
96
+ process.exit(1);
97
+ }
98
+ await copyTemplate(
99
+ tpl.path,
100
+ path.join(process.cwd(), finalName),
101
+ finalName
102
+ );
103
+ return;
104
+ }
105
+
106
+ const questions = [];
107
+ if (!finalTemplate) {
108
+ questions.push({
109
+ type: 'list',
110
+ name: 'template',
111
+ message: '请选择项目模板:',
112
+ choices: templates.map((t) => ({
113
+ name: `${t.name} (${t.value})`,
114
+ value: t.value,
115
+ })),
116
+ });
117
+ }
118
+ if (!finalName) {
119
+ questions.push({
120
+ type: 'input',
121
+ name: 'projectName',
122
+ message: '请输入项目名称:',
123
+ validate: (input) => {
124
+ if (!input.trim()) {
125
+ return '项目名称不能为空!';
126
+ }
127
+ if (fs.existsSync(input)) {
128
+ return '项目目录已存在,请选择其他名称!';
129
+ }
130
+ return true;
131
+ },
132
+ });
133
+ }
134
+
135
+ try {
136
+ const answers = questions.length > 0 ? await inquirer.prompt(questions) : {};
137
+ const resolvedName = finalName || answers.projectName;
138
+ const resolvedTemplate = finalTemplate || answers.template;
139
+ const tpl = templates.find(
140
+ (t) => t.name === resolvedTemplate || t.value === resolvedTemplate
141
+ );
142
+ if (!tpl) {
143
+ console.log(chalk.red(`❌ 未找到模板: ${resolvedTemplate}`));
144
+ process.exit(1);
145
+ }
146
+ await copyTemplate(
147
+ tpl.path,
148
+ path.join(process.cwd(), resolvedName),
149
+ resolvedName
150
+ );
151
+ } catch (error) {
152
+ console.error(chalk.red('创建项目时发生错误:'), error.message);
153
+ process.exit(1);
154
+ }
155
+ }
156
+
157
+ program.name('xi-create').description('一个用于创建项目的命令行工具').version(pkg.version);
158
+
159
+ program
160
+ .command('list')
161
+ .description('列出所有可用模板')
162
+ .action(() => {
163
+ const templates = getTemplates();
164
+ if (templates.length === 0) {
165
+ console.log(chalk.yellow('没有找到可用的模板。'));
166
+ } else {
167
+ console.log(chalk.green('可用的模板:'));
168
+ templates.forEach((t) => {
169
+ console.log(chalk.cyan(` - ${t.name}`));
170
+ });
171
+ }
172
+ });
173
+
174
+ program
175
+ .command('create [projectName]', { isDefault: true })
176
+ .description('创建新项目')
177
+ .option('-t, --template <name>', '模板短名(如 react-ts)或完整目录名(如 template-react-ts)')
178
+ .action(async (projectName, options) => {
179
+ await runCreate({ projectName, template: options.template });
180
+ });
181
+
182
+ if (process.argv.length === 2) {
183
+ runCreate({}).catch((err) => {
184
+ console.error(err);
185
+ process.exit(1);
186
+ });
187
+ } else {
188
+ program.parse();
189
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "xi-create",
3
+ "version": "1.0.0",
4
+ "description": "一个用于创建项目的命令行工具",
5
+ "type": "module",
6
+ "main": "./bin/xi-create.js",
7
+ "bin": {
8
+ "xi-create": "bin/xi-create.js"
9
+ },
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1",
12
+ "start": "node ./bin/xi-create.js"
13
+ },
14
+ "keywords": [
15
+ "cli",
16
+ "scaffold",
17
+ "template",
18
+ "create"
19
+ ],
20
+ "author": "",
21
+ "license": "ISC",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/The-End-Hero/xi-create.git"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public",
28
+ "registry": "https://registry.npmjs.org/"
29
+ },
30
+ "dependencies": {
31
+ "chalk": "^5.6.2",
32
+ "commander": "^14.0.3",
33
+ "fs-extra": "^11.3.4",
34
+ "inquirer": "^13.3.2",
35
+ "ora": "^9.3.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^24.12.0"
39
+ }
40
+ }
@@ -0,0 +1,50 @@
1
+ # React + TypeScript + Vite
2
+
3
+ This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+ Currently, two official plugins are available:
6
+
7
+ - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8
+ - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9
+
10
+ ## Expanding the ESLint configuration
11
+
12
+ If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
13
+
14
+ - Configure the top-level `parserOptions` property like this:
15
+
16
+ ```js
17
+ export default tseslint.config({
18
+ languageOptions: {
19
+ // other options...
20
+ parserOptions: {
21
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
22
+ tsconfigRootDir: import.meta.dirname,
23
+ },
24
+ },
25
+ })
26
+ ```
27
+
28
+ - Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
29
+ - Optionally add `...tseslint.configs.stylisticTypeChecked`
30
+ - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
31
+
32
+ ```js
33
+ // eslint.config.js
34
+ import react from 'eslint-plugin-react'
35
+
36
+ export default tseslint.config({
37
+ // Set the react version
38
+ settings: { react: { version: '18.3' } },
39
+ plugins: {
40
+ // Add the react plugin
41
+ react,
42
+ },
43
+ rules: {
44
+ // other rules...
45
+ // Enable its recommended rules
46
+ ...react.configs.recommended.rules,
47
+ ...react.configs['jsx-runtime'].rules,
48
+ },
49
+ })
50
+ ```
@@ -0,0 +1,41 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ dist-zip
14
+ *.local
15
+
16
+ # Editor directories and files
17
+ .vscode/*
18
+ !.vscode/extensions.json
19
+ .idea
20
+ .DS_Store
21
+ *.suo
22
+ *.ntvs*
23
+ *.njsproj
24
+ *.sln
25
+ *.sw?
26
+
27
+ .cursor
28
+ .claude
29
+ .gpt_engineer
30
+ .aider
31
+ .codeium
32
+ .tabnine
33
+ .continue
34
+ .v0
35
+ .bolt
36
+ .lovable
37
+ .grain
38
+ .coderabbit
39
+ .replit
40
+ .stackblitz
41
+ .qodeer
@@ -0,0 +1,28 @@
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
+
7
+ export default tseslint.config(
8
+ { ignores: ['dist'] },
9
+ {
10
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
11
+ files: ['**/*.{ts,tsx}'],
12
+ languageOptions: {
13
+ ecmaVersion: 2020,
14
+ globals: globals.browser,
15
+ },
16
+ plugins: {
17
+ 'react-hooks': reactHooks,
18
+ 'react-refresh': reactRefresh,
19
+ },
20
+ rules: {
21
+ ...reactHooks.configs.recommended.rules,
22
+ 'react-refresh/only-export-components': [
23
+ 'warn',
24
+ { allowConstantExport: true },
25
+ ],
26
+ },
27
+ },
28
+ )
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Vite + React + TS</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "vite-react-typescript-starter",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "lint": "eslint ",
10
+ "preview": "vite preview",
11
+ "preinstall": "npx only-allow pnpm"
12
+ },
13
+ "dependencies": {
14
+ "@tailwindcss/vite": "^4.2.2",
15
+ "@xiping/react-components": "^1.0.70",
16
+ "@zumer/snapdom": "^2.7.0",
17
+ "ahooks": "^3.9.7",
18
+ "axios": "^1.14.0",
19
+ "clsx": "^2.1.1",
20
+ "dayjs": "^1.11.20",
21
+ "disable-devtool": "^0.3.9",
22
+ "file-saver": "^2.0.5",
23
+ "lodash-es": "^4.18.1",
24
+ "lucide-react": "^1.7.0",
25
+ "motion": "^12.38.0",
26
+ "react": "^19.2.4",
27
+ "react-device-detect": "^2.2.3",
28
+ "react-dom": "^19.2.4",
29
+ "react-dropzone": "^15.0.0",
30
+ "react-hot-toast": "^2.6.0",
31
+ "react-router-dom": "^7.13.2",
32
+ "zustand": "^5.0.12"
33
+ },
34
+ "devDependencies": {
35
+ "@eslint/js": "^9.39.4",
36
+ "@types/file-saver": "^2.0.7",
37
+ "@types/lodash-es": "^4.17.12",
38
+ "@types/node": "^24.12.0",
39
+ "@types/react": "^19.2.14",
40
+ "@types/react-dom": "^19.2.3",
41
+ "@vitejs/plugin-react": "^6.0.1",
42
+ "@xiping/vite-version": "^1.0.71",
43
+ "autoprefixer": "^10.4.27",
44
+ "eslint": "^9.39.4",
45
+ "eslint-plugin-react-hooks": "^7.0.1",
46
+ "eslint-plugin-react-refresh": "^0.5.2",
47
+ "globals": "^17.4.0",
48
+ "less": "^4.6.4",
49
+ "only-allow": "^1.2.2",
50
+ "postcss": "^8.5.8",
51
+ "prettier": "^3.8.1",
52
+ "sharp": "^0.34.5",
53
+ "svgo": "^4.0.1",
54
+ "tailwind-scrollbar": "^4.0.2",
55
+ "tailwindcss": "^4.2.2",
56
+ "tailwindcss-safe-area": "^1.3.0",
57
+ "typescript": "~5.9.3",
58
+ "typescript-eslint": "^8.58.0",
59
+ "vite": "^8.0.3",
60
+ "vite-plugin-image-optimizer": "^2.0.3",
61
+ "vite-plugin-pwa": "^1.2.0",
62
+ "vite-plugin-zip-pack": "^1.2.4"
63
+ },
64
+ "engines": {
65
+ "node": ">=22"
66
+ }
67
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
File without changes
@@ -0,0 +1,29 @@
1
+ import { HashRouter, Route, Routes, useLocation } from "react-router-dom";
2
+ import Home from "@/pages/home/Home.tsx";
3
+ import "./App.css";
4
+ import Login from "@/pages/login/Login.tsx";
5
+ import { AnimatePresence } from "motion/react";
6
+
7
+ function App() {
8
+ return (
9
+ <HashRouter>
10
+ <AppContent />
11
+ </HashRouter>
12
+ );
13
+ }
14
+
15
+ // Separate component to use hooks inside HashRouter
16
+ function AppContent() {
17
+ const location = useLocation();
18
+
19
+ return (
20
+ <AnimatePresence mode="wait">
21
+ <Routes location={location} key={location.pathname}>
22
+ <Route path="/" element={<Home />} />
23
+ <Route path="/login" element={<Login />} />
24
+ </Routes>
25
+ </AnimatePresence>
26
+ );
27
+ }
28
+
29
+ export default App;
@@ -0,0 +1 @@
1
+ @import "tailwindcss";
@@ -0,0 +1,11 @@
1
+ // import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import "./index.css";
4
+ import App from "./App.tsx";
5
+
6
+ const root = createRoot(document.getElementById("root")!);
7
+ root.render(
8
+ // <StrictMode>
9
+ <App />,
10
+ // </StrictMode>,
11
+ );
@@ -0,0 +1,24 @@
1
+ import { Link } from "react-router-dom";
2
+ import { motion } from "motion/react";
3
+
4
+ const Home = () => {
5
+ return (
6
+ <motion.div
7
+ className="w-screen h-screen bg-blue-600 flex flex-col items-center justify-center text-white"
8
+ initial={{ opacity: 0, filter: "blur(8px)" }}
9
+ animate={{ opacity: 1, filter: "blur(0px)" }}
10
+ exit={{ opacity: 0, filter: "blur(8px)" }}
11
+ transition={{ duration: 0.3 }}
12
+ >
13
+ <h1 className="text-4xl font-bold mb-4">Home Page</h1>
14
+ <Link
15
+ to="/login"
16
+ className="px-4 py-2 bg-white text-blue-600 rounded-md hover:bg-gray-100 transition-colors"
17
+ >
18
+ Go to Login
19
+ </Link>
20
+ </motion.div>
21
+ );
22
+ };
23
+
24
+ export default Home;
@@ -0,0 +1,24 @@
1
+ import { Link } from "react-router-dom";
2
+ import { motion } from "motion/react";
3
+
4
+ const Login = () => {
5
+ return (
6
+ <motion.div
7
+ className="w-screen h-screen bg-amber-400 flex flex-col items-center justify-center text-white"
8
+ initial={{ opacity: 0, filter: "blur(8px)" }}
9
+ animate={{ opacity: 1, filter: "blur(0px)" }}
10
+ exit={{ opacity: 0, filter: "blur(8px)" }}
11
+ transition={{ duration: 0.3 }}
12
+ >
13
+ <h1 className="text-4xl font-bold mb-4">Login Page</h1>
14
+ <Link
15
+ to="/"
16
+ className="px-4 py-2 bg-white text-amber-700 rounded-md hover:bg-gray-100 transition-colors"
17
+ >
18
+ Back to Home
19
+ </Link>
20
+ </motion.div>
21
+ );
22
+ };
23
+
24
+ export default Login;
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,32 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2020",
5
+ "useDefineForClassFields": true,
6
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "Bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "isolatedModules": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+ "jsx": "react-jsx",
17
+
18
+ /* Linting */
19
+ "strict": true,
20
+ "noUnusedLocals": true,
21
+ "noUnusedParameters": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+ "noUncheckedSideEffectImports": true,
24
+
25
+ // alias
26
+ "baseUrl": "./",
27
+ "paths": {
28
+ "@/*": ["./src/*"]
29
+ }
30
+ },
31
+ "include": ["src"]
32
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ],
7
+
8
+ "compilerOptions": {
9
+ "baseUrl": "./",
10
+ "paths": {
11
+ "@/*": ["./src/*"]
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "ES2022",
5
+ "lib": [
6
+ "ES2023",
7
+ ],
8
+ "module": "ESNext",
9
+ "skipLibCheck": true,
10
+
11
+ /* Bundler mode */
12
+ "moduleResolution": "Bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "isolatedModules": true,
15
+ "moduleDetection": "force",
16
+ "noEmit": true,
17
+
18
+ /* Linting */
19
+ "strict": true,
20
+ "noUnusedLocals": true,
21
+ "noUnusedParameters": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+ "noUncheckedSideEffectImports": true
24
+ },
25
+ "include": [
26
+ "vite.config.ts"
27
+ ]
28
+ }
@@ -0,0 +1,99 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+ import zipPack from "vite-plugin-zip-pack";
4
+ import { ViteImageOptimizer } from "vite-plugin-image-optimizer";
5
+ import path from "path";
6
+ import { viteVersionPlugin } from "@xiping/vite-version";
7
+ import packageJSON from "./package.json";
8
+ import { VitePWA } from "vite-plugin-pwa";
9
+ import tailwindcss from "@tailwindcss/vite";
10
+
11
+ // https://vite.dev/config/
12
+ export default defineConfig(({ mode }) => {
13
+ console.log(mode);
14
+ return {
15
+ plugins: [
16
+ tailwindcss(),
17
+ react(),
18
+ VitePWA({
19
+ registerType: "autoUpdate",
20
+ devOptions: { enabled: false },
21
+ workbox: {
22
+ maximumFileSizeToCacheInBytes: 10 * 1024 * 1024, // 10MB
23
+ clientsClaim: true,
24
+ skipWaiting: true,
25
+ runtimeCaching: [
26
+ {
27
+ urlPattern: /^https:\/\/.*\/.*/i,
28
+ handler: "NetworkFirst",
29
+ options: {
30
+ cacheName: "api-cache",
31
+ networkTimeoutSeconds: 10,
32
+ expiration: {
33
+ maxEntries: 100,
34
+ maxAgeSeconds: 24 * 60 * 60, // 24 hours
35
+ },
36
+ cacheableResponse: {
37
+ statuses: [0, 200],
38
+ },
39
+ },
40
+ },
41
+ {
42
+ urlPattern: /\.(?:png|jpg|jpeg|svg|gif)$/,
43
+ handler: "CacheFirst",
44
+ options: {
45
+ cacheName: "images",
46
+ expiration: {
47
+ maxEntries: 60,
48
+ maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days
49
+ },
50
+ },
51
+ },
52
+ ],
53
+ },
54
+ manifest: {
55
+ name: packageJSON.name,
56
+ short_name: packageJSON.name,
57
+ theme_color: "#ffffff",
58
+ icons: [
59
+ {
60
+ src: "pwa-192x192.png",
61
+ sizes: "192x192",
62
+ type: "image/png",
63
+ },
64
+ {
65
+ src: "pwa-512x512.png",
66
+ sizes: "512x512",
67
+ type: "image/png",
68
+ },
69
+ ],
70
+ },
71
+ }),
72
+ ViteImageOptimizer({
73
+ /* pass your config */
74
+ png: {
75
+ quality: 80,
76
+ },
77
+ jpg: {
78
+ quality: 80,
79
+ },
80
+ jpeg: {
81
+ quality: 80,
82
+ },
83
+ webp: {
84
+ quality: 80,
85
+ },
86
+ }),
87
+ viteVersionPlugin({ filename: "version.txt" }),
88
+ zipPack({
89
+ outDir: "dist-zip",
90
+ outFileName: `${packageJSON.name}-${mode}.zip`,
91
+ }),
92
+ ],
93
+ resolve: {
94
+ alias: {
95
+ "@": path.resolve(__dirname, "src"),
96
+ },
97
+ },
98
+ };
99
+ });