create-next-imagicma 0.0.1 → 0.0.3

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 CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  ```bash
8
8
  npm install -g create-next-imagicma
9
- create-next-imagicma <project-dir> [--port <1-65535>]
9
+ create-next-imagicma <project-dir> [--port <1-65535>] [--theme <name>]
10
10
  ```
11
11
 
12
12
  本地(未发布)使用示例:
@@ -19,6 +19,7 @@ node ./create-next-imagicma/bin/create-next-imagicma.mjs demo-5001 --port 5001
19
19
  ## 参数
20
20
 
21
21
  - `--port <number>`:设置新项目 `dev/start` 的默认端口(写入 `next ... -p <port>`)。
22
+ - `--theme <name>`:设置新项目默认主题(可选:`quadratic`、`nomad`、`honey`、`zen-garden`、`highlighter`)。
22
23
  - 运行时仍可覆盖,例如:`pnpm dev -- -p 6001` 或 `PORT=6001 pnpm start`
23
24
 
24
25
  ## 依赖安装策略
@@ -8,12 +8,20 @@ import { fileURLToPath } from "node:url";
8
8
  const __filename = fileURLToPath(import.meta.url);
9
9
  const __dirname = path.dirname(__filename);
10
10
  const TEMPLATE_DIR = path.resolve(__dirname, "..", "template");
11
+ const THEME_STYLES = [
12
+ "quadratic",
13
+ "nomad",
14
+ "honey",
15
+ "zen-garden",
16
+ "highlighter",
17
+ ];
11
18
 
12
19
  function printHelp() {
13
- console.log(`create-next-imagicma <project-dir> [--port <1-65535>]
20
+ console.log(`create-next-imagicma <project-dir> [--port <1-65535>] [--theme <name>]
14
21
 
15
22
  参数:
16
23
  --port <number> 设置新项目 dev/start 的默认端口(next ... -p)
24
+ --theme <name> 设置默认主题(可选:${THEME_STYLES.join(", ")})
17
25
  -h, --help 显示帮助
18
26
  `);
19
27
  }
@@ -28,6 +36,25 @@ function parsePort(raw) {
28
36
  return port;
29
37
  }
30
38
 
39
+ function parseTheme(raw) {
40
+ const normalized = String(raw ?? "")
41
+ .trim()
42
+ .toLowerCase()
43
+ .replace(/[_\s]+/gu, "-");
44
+
45
+ if (!normalized) {
46
+ throw new Error("--theme 缺少值:请使用 --theme <name>");
47
+ }
48
+
49
+ if (!THEME_STYLES.includes(normalized)) {
50
+ throw new Error(
51
+ `--theme 参数不合法:${JSON.stringify(raw)}(可选:${THEME_STYLES.join(", ")})`,
52
+ );
53
+ }
54
+
55
+ return normalized;
56
+ }
57
+
31
58
  function sanitizePackageName(raw) {
32
59
  const normalized = raw
33
60
  .toLowerCase()
@@ -39,6 +66,7 @@ function sanitizePackageName(raw) {
39
66
  function parseArgs(argv) {
40
67
  let projectDir;
41
68
  let port;
69
+ let theme;
42
70
 
43
71
  for (let i = 0; i < argv.length; i += 1) {
44
72
  const arg = argv[i];
@@ -62,6 +90,21 @@ function parseArgs(argv) {
62
90
  continue;
63
91
  }
64
92
 
93
+ if (arg === "--theme") {
94
+ const value = argv[i + 1];
95
+ if (!value) {
96
+ throw new Error("--theme 缺少值:请使用 --theme <name>");
97
+ }
98
+ theme = parseTheme(value);
99
+ i += 1;
100
+ continue;
101
+ }
102
+
103
+ if (arg.startsWith("--theme=")) {
104
+ theme = parseTheme(arg.slice("--theme=".length));
105
+ continue;
106
+ }
107
+
65
108
  if (arg.startsWith("-")) {
66
109
  throw new Error(`未知参数:${arg}`);
67
110
  }
@@ -78,21 +121,18 @@ function parseArgs(argv) {
78
121
  throw new Error("缺少 <project-dir>:请指定要创建的项目目录名/路径");
79
122
  }
80
123
 
81
- return { projectDir, port, help: false };
124
+ return { projectDir, port, theme, help: false };
82
125
  }
83
126
 
84
- async function ensureTargetDirEmpty(targetDir) {
127
+ async function ensureTargetDirReady(targetDir) {
85
128
  try {
86
129
  const stat = await fs.stat(targetDir);
87
130
  if (!stat.isDirectory()) {
88
131
  throw new Error(`目标路径已存在且不是目录:${targetDir}`);
89
132
  }
90
- const entries = await fs.readdir(targetDir);
91
- if (entries.length > 0) {
92
- throw new Error(`目标目录已存在且非空:${targetDir}`);
93
- }
94
133
  } catch (error) {
95
134
  if (error && typeof error === "object" && error.code === "ENOENT") {
135
+ await fs.mkdir(targetDir, { recursive: true });
96
136
  return;
97
137
  }
98
138
  throw error;
@@ -116,6 +156,45 @@ async function updatePackageName(targetDir, port) {
116
156
  await fs.writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
117
157
  }
118
158
 
159
+ async function updateProcessComposePort(targetDir, port) {
160
+ if (port === undefined) return;
161
+
162
+ const filePath = path.join(targetDir, "process-compose.yaml");
163
+
164
+ let raw;
165
+ try {
166
+ raw = await fs.readFile(filePath, "utf8");
167
+ } catch (error) {
168
+ if (error && typeof error === "object" && error.code === "ENOENT") {
169
+ return;
170
+ }
171
+ throw error;
172
+ }
173
+
174
+ const next = raw.replace(
175
+ /^(\s*-\s*["']?PORT=)\d+(["']?\s*)$/m,
176
+ `$1${port}$2`,
177
+ );
178
+ await fs.writeFile(filePath, next);
179
+ }
180
+
181
+ async function updateDefaultTheme(targetDir, theme) {
182
+ if (theme === undefined) return;
183
+
184
+ const filePath = path.join(targetDir, "lib", "theme", "default-theme.ts");
185
+ const raw = await fs.readFile(filePath, "utf8");
186
+ const next = raw.replace(
187
+ /(export const DEFAULT_THEME_STYLE:\s*ThemeStyle\s*=\s*")[^"]+(";\s*)/u,
188
+ `$1${theme}$2`,
189
+ );
190
+
191
+ if (next === raw) {
192
+ throw new Error(`未能更新默认主题,请检查文件:${filePath}`);
193
+ }
194
+
195
+ await fs.writeFile(filePath, next);
196
+ }
197
+
119
198
  function runCommand(command, args, options) {
120
199
  return new Promise((resolve, reject) => {
121
200
  const child = spawn(command, args, { stdio: "inherit", ...options });
@@ -133,6 +212,36 @@ function isCommandMissingError(error) {
133
212
  );
134
213
  }
135
214
 
215
+ const INITIAL_COMMIT_MESSAGE = "Initial commit";
216
+
217
+ async function initGit(targetDir) {
218
+ try {
219
+ const initCode = await runCommand("git", ["init"], { cwd: targetDir });
220
+ if (initCode !== 0) return false;
221
+
222
+ const addCode = await runCommand("git", ["add", "."], { cwd: targetDir });
223
+ if (addCode !== 0) {
224
+ console.log("git add 未成功,跳过 git commit\n");
225
+ return true;
226
+ }
227
+
228
+ const commitCode = await runCommand(
229
+ "git",
230
+ ["commit", "-m", INITIAL_COMMIT_MESSAGE],
231
+ { cwd: targetDir },
232
+ );
233
+ if (commitCode === 0) {
234
+ console.log("已执行 git init、git add、git commit\n");
235
+ } else {
236
+ console.log("git commit 未成功(如未配置 user.name/user.email 可稍后手动提交)\n");
237
+ }
238
+ return true;
239
+ } catch (_error) {
240
+ // 不存在 git 或其它异常时静默跳过,不报错
241
+ return false;
242
+ }
243
+ }
244
+
136
245
  async function installDependencies(targetDir) {
137
246
  console.log("\n正在安装依赖(优先 pnpm)...\n");
138
247
 
@@ -178,14 +287,21 @@ async function main() {
178
287
  return;
179
288
  }
180
289
 
181
- const { projectDir, port } = parsed;
290
+ const { projectDir, port, theme } = parsed;
182
291
  const targetDir = path.resolve(process.cwd(), projectDir);
183
292
 
184
- await ensureTargetDirEmpty(targetDir);
185
- await fs.mkdir(targetDir, { recursive: true });
293
+ await ensureTargetDirReady(targetDir);
186
294
 
187
- await fs.cp(TEMPLATE_DIR, targetDir, { recursive: true });
295
+ await fs.cp(TEMPLATE_DIR, targetDir, {
296
+ recursive: true,
297
+ force: true,
298
+ errorOnExist: false,
299
+ });
188
300
  await updatePackageName(targetDir, port);
301
+ await updateProcessComposePort(targetDir, port);
302
+ await updateDefaultTheme(targetDir, theme);
303
+
304
+ await initGit(targetDir);
189
305
 
190
306
  const installResult = await installDependencies(targetDir);
191
307
  if (!installResult.ok) {
@@ -204,6 +320,9 @@ async function main() {
204
320
  if (port !== undefined) {
205
321
  console.log(`已设置默认端口:${port}(写入 dev/start 启动命令)`);
206
322
  }
323
+ if (theme !== undefined) {
324
+ console.log(`已设置默认主题:${theme}`);
325
+ }
207
326
  console.log("\n下一步:");
208
327
  console.log(` cd ${projectDir}`);
209
328
  if (installResult.installer === "npm") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-next-imagicma",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "create-next-imagicma": "./bin/create-next-imagicma.mjs"
@@ -41,7 +41,7 @@
41
41
 
42
42
  ## 3) 常用命令(pnpm)
43
43
 
44
- 本项目**仅允许使用 pnpm** 安装依赖(见 `package.json` 的 `packageManager` / `engines` / `preinstall`)。
44
+ 本项目默认**优先使用 pnpm** 安装依赖;若环境受限可使用 npm 作为回退方案(见 `package.json` 的 `packageManager` / `engines`)。
45
45
 
46
46
  以仓库根目录为工作目录:
47
47
 
@@ -144,3 +144,82 @@
144
144
  - 确认 `tailwind.config.mjs` 的 `content` 覆盖到了 `components/ui` 等目录
145
145
  - 构建报 Turbopack/受限环境问题
146
146
  - 当前 `pnpm build` 已固定使用 `next build --webpack`(见 `package.json`)
147
+
148
+ ## 13) Docs-First 工作流(强约束,模板默认)
149
+
150
+ 本模板默认与 `ibuild + idesigner + igen-*` agents 配合。以下规则是**必须执行**,不是建议。
151
+
152
+ - 状态真相源只有一个:`docs/project_state.json`。
153
+ - `docs/index.md` 仅作导航与摘要,允许滞后,不能作为阶段判定依据。
154
+ - 所有实现必须可回溯到 docs;禁止“先写代码、后补文档”的反向流程。
155
+
156
+ ### 13.1 文档缺失处理(必须)
157
+
158
+ - 若用户请求“生成项目/落地开发”,但 `docs/` 不完整:
159
+ 1. 先补齐 docs,再进入代码实现。
160
+ 2. 补齐顺序固定:`igen-prd -> igen-persona -> igen-feature-modules -> igen-style-guide -> igen-db-schema`。
161
+ 3. 完成后更新 `docs/project_state.json`(`docs_ready=true`)再进入实现。
162
+
163
+ ### 13.2 实现前必读清单(按范围)
164
+
165
+ - 前端改动(页面/交互/样式)前,必须读取:
166
+ - `docs/prd/prd.md`
167
+ - `docs/feature_plan/feature_modules.md` + 对应 `feature_module_*.md`
168
+ - `docs/style_guide/*.style-guide.md`
169
+ - `docs/implementation/frontend_mock_scope.md`
170
+ - `docs/codegen_contract.md`
171
+ - 后端改动(API/DB/数据流)前,必须读取:
172
+ - `docs/prd/prd.md`
173
+ - `docs/feature_plan/feature_modules.md` + 对应 `feature_module_*.md`
174
+ - `docs/db/db_schema.md` + `docs/db/db_schema_core.md`
175
+ - `docs/db/mock_data_mapping.md`
176
+ - `docs/implementation/backend_core_scope.md`
177
+ - `docs/codegen_contract.md`
178
+
179
+ ### 13.3 冲突处理规则(docs vs 当前代码/需求)
180
+
181
+ - 默认优先级:`docs/project_state.json` + docs 契约 > 现有实现细节 > 记忆/猜测。
182
+ - 若用户明确要求偏离 docs:先更新 docs,再改代码;禁止只改代码不更新 docs。
183
+ - 若 docs 内部自相矛盾:先修 docs 再实现;不能在矛盾状态下继续大规模开发。
184
+
185
+ ### 13.4 阶段推进规则(禁止“建议即停止”)
186
+
187
+ - `docs_ready=true` 后,默认连续推进 Stage B -> Stage C。
188
+ - 仅在真实阻塞(密钥/权限/外部依赖缺失)时停止并向用户提问。
189
+ - 禁止以“立即启动开发/推荐阅读顺序/命令清单”替代实际执行。
190
+ - 若应用未运行,必须主动启动(优先 `restart_workflow(name="web")`),而不是让用户手动执行 `pnpm dev`。
191
+
192
+ ## 14) 文档一致性门禁(完成前必须全部通过)
193
+
194
+ ### 14.1 路由与模块一致性
195
+
196
+ - 页面路由需与 `docs/feature_plan/feature_module_*.md` 的页面清单一致。
197
+ - API 路径需与 docs 规划和 `shared/routes.ts` 契约一致。
198
+ - 禁止新增 docs 未定义的核心页面/API(除非先更新 docs)。
199
+
200
+ ### 14.2 数据契约一致性
201
+
202
+ - Stage B Mock 字段必须与 `docs/db/mock_data_mapping.md` 对齐。
203
+ - Stage C API/DB 字段必须与 `docs/db/*.md` 对齐。
204
+ - 返回数据需按 `shared/routes.ts` 对应 zod schema 解析/校验。
205
+
206
+ ### 14.3 运行与重定向安全
207
+
208
+ - 禁止自重定向或重定向环(例如 `/` -> `/`)。
209
+ - 验证关键路由时,重定向链长度必须有界(建议 <= 3)。
210
+ - `/`, `/spots`, `/catches`, `/community`, `/profile`, `/hello` 不得出现 500 或循环跳转。
211
+
212
+ ### 14.4 质量门禁
213
+
214
+ - `pnpm check` 通过。
215
+ - `app/api` 与 `server` 不允许残留阻塞性占位实现(如 `TODO: Implement`)。
216
+ - 未通过门禁前,`docs/project_state.json` 不能标记对应阶段为 `completed`。
217
+
218
+ ## 15) 交付与同步要求(模板仓库)
219
+
220
+ - 每次阶段状态变更后,同步更新:
221
+ - `docs/project_state.json`(真相源)
222
+ - `docs/index.md`(派生状态展示)
223
+ - 输出总结时必须给出“文档溯源”:
224
+ - 本次实现对应了哪些 docs 文件(至少列出路径)。
225
+ - 若有偏差,说明已更新的 docs 路径。
@@ -14,8 +14,6 @@ pnpm dev
14
14
  bun dev
15
15
  ```
16
16
 
17
- Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
-
19
17
  You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20
18
 
21
19
  This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
@@ -309,7 +309,7 @@ export function DevPreviewShield({ children }: { children: React.ReactNode }) {
309
309
  }, [clearTimer]);
310
310
 
311
311
  const handleBuildOk = useCallback(() => {
312
- const hadPendingShow = showTimerRef.current != null;
312
+ const hadVisibleOverlay = overlayRef.current !== "none";
313
313
 
314
314
  clearTimer(showTimerRef);
315
315
  clearTimer(hideTimerRef);
@@ -317,15 +317,15 @@ export function DevPreviewShield({ children }: { children: React.ReactNode }) {
317
317
  setDebugMode(false);
318
318
  setResetKey((k) => k + 1);
319
319
 
320
- if (hadPendingShow && !debugModeRef.current) {
321
- showToast();
322
- }
323
-
324
- if (overlayRef.current === "none") {
320
+ if (!hadVisibleOverlay) {
325
321
  setErrorInfo(null);
326
322
  return;
327
323
  }
328
324
 
325
+ if (!debugModeRef.current) {
326
+ showToast();
327
+ }
328
+
329
329
  hideOverlayWithMinVisible();
330
330
  }, [clearTimer, hideOverlayWithMinVisible, showToast]);
331
331
 
@@ -113,6 +113,297 @@
113
113
  --badge-outline: hsl(var(--border));
114
114
  }
115
115
 
116
+ /* Theme style presets (used by html[data-theme-style="<preset>"]) */
117
+ :root[data-theme-style="quadratic"] {
118
+ --background: 240 8.3% 95.3%;
119
+ --foreground: 225 14.3% 5.5%;
120
+
121
+ --card: 0 0% 100%;
122
+ --card-foreground: 225 14.3% 5.5%;
123
+ --popover: 0 0% 100%;
124
+ --popover-foreground: 225 14.3% 5.5%;
125
+
126
+ --primary: 240 8.6% 93.1%;
127
+ --primary-foreground: 225 14.3% 5.5%;
128
+ --secondary: 240 7% 86.1%;
129
+ --secondary-foreground: 225 14.3% 5.5%;
130
+ --muted: 240 8.6% 93.1%;
131
+ --muted-foreground: 225 7% 35%;
132
+ --accent: 240 7% 86.1%;
133
+ --accent-foreground: 225 14.3% 5.5%;
134
+
135
+ --border: 240 7% 82%;
136
+ --input: 240 7% 82%;
137
+ --ring: 225 14.3% 5.5%;
138
+
139
+ --chart-1: 225 14.3% 35%;
140
+ --chart-2: 240 8% 55%;
141
+ --chart-3: 220 40% 55%;
142
+ --chart-4: 270 20% 62%;
143
+ --chart-5: 190 30% 52%;
144
+ }
145
+
146
+ .dark[data-theme-style="quadratic"] {
147
+ --background: 225 14.3% 5.5%;
148
+ --foreground: 240 8.3% 95.3%;
149
+
150
+ --card: 225 12% 9%;
151
+ --card-foreground: 240 8.3% 95.3%;
152
+ --popover: 225 12% 9%;
153
+ --popover-foreground: 240 8.3% 95.3%;
154
+
155
+ --primary: 240 8.6% 93.1%;
156
+ --primary-foreground: 225 14.3% 5.5%;
157
+ --secondary: 225 8% 15%;
158
+ --secondary-foreground: 240 8.3% 95.3%;
159
+ --muted: 225 8% 12%;
160
+ --muted-foreground: 240 7% 70%;
161
+ --accent: 225 8% 12%;
162
+ --accent-foreground: 240 8.3% 95.3%;
163
+
164
+ --border: 225 8% 22%;
165
+ --input: 225 8% 22%;
166
+ --ring: 240 8.6% 93.1%;
167
+
168
+ --chart-1: 240 8.6% 70%;
169
+ --chart-2: 220 30% 65%;
170
+ --chart-3: 190 30% 60%;
171
+ --chart-4: 270 20% 65%;
172
+ --chart-5: 45 20% 62%;
173
+ }
174
+
175
+ :root[data-theme-style="nomad"] {
176
+ --background: 240 9.1% 95.7%;
177
+ --foreground: 220 12.5% 9.4%;
178
+
179
+ --card: 240 12% 98%;
180
+ --card-foreground: 220 12.5% 9.4%;
181
+ --popover: 240 12% 98%;
182
+ --popover-foreground: 220 12.5% 9.4%;
183
+
184
+ --primary: 339 89.8% 53.9%;
185
+ --primary-foreground: 220 12.5% 9.4%;
186
+ --secondary: 231 10.4% 86.9%;
187
+ --secondary-foreground: 220 12.5% 9.4%;
188
+ --muted: 240 9% 92%;
189
+ --muted-foreground: 220 8% 35%;
190
+ --accent: 231 10.4% 86.9%;
191
+ --accent-foreground: 220 12.5% 9.4%;
192
+
193
+ --border: 231 10% 82%;
194
+ --input: 231 10% 82%;
195
+ --ring: 339 89.8% 53.9%;
196
+
197
+ --chart-1: 339 89.8% 53.9%;
198
+ --chart-2: 220 28% 40%;
199
+ --chart-3: 40 72% 60%;
200
+ --chart-4: 173 25% 45%;
201
+ --chart-5: 280 45% 60%;
202
+ }
203
+
204
+ .dark[data-theme-style="nomad"] {
205
+ --background: 220 12.5% 9.4%;
206
+ --foreground: 240 9.1% 95.7%;
207
+
208
+ --card: 220 12% 12%;
209
+ --card-foreground: 240 9.1% 95.7%;
210
+ --popover: 220 12% 12%;
211
+ --popover-foreground: 240 9.1% 95.7%;
212
+
213
+ --primary: 339 90% 62%;
214
+ --primary-foreground: 220 12.5% 9.4%;
215
+ --secondary: 220 10% 17%;
216
+ --secondary-foreground: 240 9.1% 95.7%;
217
+ --muted: 220 9% 14%;
218
+ --muted-foreground: 240 7% 70%;
219
+ --accent: 220 9% 14%;
220
+ --accent-foreground: 240 9.1% 95.7%;
221
+
222
+ --border: 220 8% 22%;
223
+ --input: 220 8% 22%;
224
+ --ring: 339 90% 62%;
225
+
226
+ --chart-1: 339 90% 66%;
227
+ --chart-2: 40 72% 65%;
228
+ --chart-3: 173 30% 58%;
229
+ --chart-4: 280 50% 72%;
230
+ --chart-5: 220 18% 72%;
231
+ }
232
+
233
+ :root[data-theme-style="honey"] {
234
+ --background: 49 38.1% 91.8%;
235
+ --foreground: 40 52.9% 27.5%;
236
+
237
+ --card: 49 42% 95%;
238
+ --card-foreground: 40 52.9% 27.5%;
239
+ --popover: 49 42% 95%;
240
+ --popover-foreground: 40 52.9% 27.5%;
241
+
242
+ --primary: 40 72.8% 68.2%;
243
+ --primary-foreground: 40 52.9% 18%;
244
+ --secondary: 63 20.8% 79.2%;
245
+ --secondary-foreground: 40 52.9% 27.5%;
246
+ --muted: 49 30% 88%;
247
+ --muted-foreground: 40 24% 38%;
248
+ --accent: 63 20.8% 79.2%;
249
+ --accent-foreground: 40 52.9% 27.5%;
250
+
251
+ --border: 52 22% 74%;
252
+ --input: 52 22% 74%;
253
+ --ring: 40 72.8% 68.2%;
254
+
255
+ --chart-1: 40 72.8% 55%;
256
+ --chart-2: 63 30% 45%;
257
+ --chart-3: 173 25% 42%;
258
+ --chart-4: 18 56% 50%;
259
+ --chart-5: 240 16% 52%;
260
+ }
261
+
262
+ .dark[data-theme-style="honey"] {
263
+ --background: 36 38% 10%;
264
+ --foreground: 49 30% 88%;
265
+
266
+ --card: 34 30% 14%;
267
+ --card-foreground: 49 30% 88%;
268
+ --popover: 34 30% 14%;
269
+ --popover-foreground: 49 30% 88%;
270
+
271
+ --primary: 40 72.8% 68.2%;
272
+ --primary-foreground: 36 38% 10%;
273
+ --secondary: 38 20% 20%;
274
+ --secondary-foreground: 49 30% 88%;
275
+ --muted: 38 18% 18%;
276
+ --muted-foreground: 49 18% 70%;
277
+ --accent: 38 18% 18%;
278
+ --accent-foreground: 49 30% 88%;
279
+
280
+ --border: 38 18% 26%;
281
+ --input: 38 18% 26%;
282
+ --ring: 40 72.8% 68.2%;
283
+
284
+ --chart-1: 40 72.8% 68.2%;
285
+ --chart-2: 63 35% 58%;
286
+ --chart-3: 173 30% 56%;
287
+ --chart-4: 20 56% 64%;
288
+ --chart-5: 240 22% 70%;
289
+ }
290
+
291
+ :root[data-theme-style="zen-garden"] {
292
+ --background: 84 9.8% 90%;
293
+ --foreground: 225 18.2% 8.6%;
294
+
295
+ --card: 84 12% 94%;
296
+ --card-foreground: 225 18.2% 8.6%;
297
+ --popover: 84 12% 94%;
298
+ --popover-foreground: 225 18.2% 8.6%;
299
+
300
+ --primary: 173 24.7% 62%;
301
+ --primary-foreground: 225 18.2% 8.6%;
302
+ --secondary: 289 32.9% 72%;
303
+ --secondary-foreground: 225 18.2% 8.6%;
304
+ --muted: 84 10% 86%;
305
+ --muted-foreground: 225 10% 36%;
306
+ --accent: 289 32.9% 72%;
307
+ --accent-foreground: 225 18.2% 8.6%;
308
+
309
+ --border: 84 8% 78%;
310
+ --input: 84 8% 78%;
311
+ --ring: 173 24.7% 62%;
312
+
313
+ --chart-1: 173 24.7% 48%;
314
+ --chart-2: 289 32.9% 58%;
315
+ --chart-3: 40 68% 56%;
316
+ --chart-4: 220 26% 50%;
317
+ --chart-5: 0 55% 58%;
318
+ }
319
+
320
+ .dark[data-theme-style="zen-garden"] {
321
+ --background: 225 18.2% 8.6%;
322
+ --foreground: 84 9.8% 90%;
323
+
324
+ --card: 225 16% 12%;
325
+ --card-foreground: 84 9.8% 90%;
326
+ --popover: 225 16% 12%;
327
+ --popover-foreground: 84 9.8% 90%;
328
+
329
+ --primary: 173 30% 66%;
330
+ --primary-foreground: 225 18.2% 8.6%;
331
+ --secondary: 225 12% 18%;
332
+ --secondary-foreground: 84 9.8% 90%;
333
+ --muted: 225 10% 15%;
334
+ --muted-foreground: 84 8% 72%;
335
+ --accent: 225 10% 15%;
336
+ --accent-foreground: 84 9.8% 90%;
337
+
338
+ --border: 225 10% 24%;
339
+ --input: 225 10% 24%;
340
+ --ring: 173 30% 66%;
341
+
342
+ --chart-1: 173 30% 66%;
343
+ --chart-2: 289 36% 70%;
344
+ --chart-3: 40 68% 64%;
345
+ --chart-4: 220 30% 70%;
346
+ --chart-5: 355 65% 68%;
347
+ }
348
+
349
+ :root[data-theme-style="highlighter"] {
350
+ --background: 228 23.8% 95.9%;
351
+ --foreground: 223 18.9% 7.3%;
352
+
353
+ --card: 228 24% 98%;
354
+ --card-foreground: 223 18.9% 7.3%;
355
+ --popover: 228 24% 98%;
356
+ --popover-foreground: 223 18.9% 7.3%;
357
+
358
+ --primary: 97 70.5% 64.1%;
359
+ --primary-foreground: 223 18.9% 7.3%;
360
+ --secondary: 249 35.6% 88.4%;
361
+ --secondary-foreground: 223 18.9% 7.3%;
362
+ --muted: 228 20% 92%;
363
+ --muted-foreground: 223 10% 35%;
364
+ --accent: 249 35.6% 88.4%;
365
+ --accent-foreground: 223 18.9% 7.3%;
366
+
367
+ --border: 238 20% 84%;
368
+ --input: 238 20% 84%;
369
+ --ring: 97 70.5% 64.1%;
370
+
371
+ --chart-1: 97 70.5% 48%;
372
+ --chart-2: 249 45% 56%;
373
+ --chart-3: 202 72% 54%;
374
+ --chart-4: 339 78% 58%;
375
+ --chart-5: 40 78% 58%;
376
+ }
377
+
378
+ .dark[data-theme-style="highlighter"] {
379
+ --background: 223 18.9% 7.3%;
380
+ --foreground: 228 23.8% 95.9%;
381
+
382
+ --card: 223 16% 11%;
383
+ --card-foreground: 228 23.8% 95.9%;
384
+ --popover: 223 16% 11%;
385
+ --popover-foreground: 228 23.8% 95.9%;
386
+
387
+ --primary: 97 74% 66%;
388
+ --primary-foreground: 223 18.9% 7.3%;
389
+ --secondary: 223 12% 18%;
390
+ --secondary-foreground: 228 23.8% 95.9%;
391
+ --muted: 223 10% 15%;
392
+ --muted-foreground: 228 14% 72%;
393
+ --accent: 223 10% 15%;
394
+ --accent-foreground: 228 23.8% 95.9%;
395
+
396
+ --border: 223 10% 24%;
397
+ --input: 223 10% 24%;
398
+ --ring: 97 74% 66%;
399
+
400
+ --chart-1: 97 74% 66%;
401
+ --chart-2: 249 50% 72%;
402
+ --chart-3: 202 78% 70%;
403
+ --chart-4: 339 80% 70%;
404
+ --chart-5: 40 78% 68%;
405
+ }
406
+
116
407
  @layer base {
117
408
  * {
118
409
  border-color: hsl(var(--border));
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
2
2
  import "./globals.css";
3
3
  import { DevPreviewShield } from "./_components/DevPreviewShield";
4
4
  import { Providers } from "./providers";
5
+ import { DEFAULT_THEME_STYLE } from "@/lib/theme/default-theme";
5
6
 
6
7
  export const metadata: Metadata = {
7
8
  title: "Create Next App",
@@ -14,7 +15,11 @@ export default function RootLayout({
14
15
  children: React.ReactNode;
15
16
  }>) {
16
17
  return (
17
- <html lang="zh-CN" suppressHydrationWarning>
18
+ <html
19
+ lang="zh-CN"
20
+ suppressHydrationWarning
21
+ data-theme-style={DEFAULT_THEME_STYLE}
22
+ >
18
23
  <body className="antialiased">
19
24
  <Providers>
20
25
  {process.env.NODE_ENV === "development" ? (
@@ -0,0 +1,11 @@
1
+ export const THEME_STYLES = [
2
+ "quadratic",
3
+ "nomad",
4
+ "honey",
5
+ "zen-garden",
6
+ "highlighter",
7
+ ] as const;
8
+
9
+ export type ThemeStyle = (typeof THEME_STYLES)[number];
10
+
11
+ export const DEFAULT_THEME_STYLE: ThemeStyle = "quadratic";
@@ -2,14 +2,9 @@
2
2
  "name": "nextjs-app",
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
- "packageManager": "pnpm@9",
6
- "engines": {
7
- "packageManager": "pnpm@>=9"
8
- },
9
5
  "scripts": {
10
- "preinstall": "npx only-allow pnpm",
11
6
  "dev": "next dev",
12
- "build": "next build --webpack",
7
+ "build": "next build",
13
8
  "start": "next start",
14
9
  "check": "tsc -p tsconfig.json --noEmit",
15
10
  "db:push": "drizzle-kit push",
@@ -0,0 +1,13 @@
1
+ version: "0.5"
2
+ log_location: ".opencode/logs/process-compose.log"
3
+
4
+ processes:
5
+ web:
6
+ command: "pnpm dev"
7
+ working_dir: "."
8
+ environment:
9
+ - "PORT=5000"
10
+ availability:
11
+ restart: "always"
12
+ backoff_seconds: 5
13
+ max_restarts: 20