@icebreakers/monorepo-templates 2.0.7 → 2.2.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.
Files changed (167) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/assets/.agents/skills/repoctl/SKILL.md +4 -2
  4. package/assets/.agents/skills/repoctl/references/commands.md +224 -13
  5. package/assets/.agents/skills/repoctl/references/config.md +70 -6
  6. package/assets/.agents/skills/repoctl/references/dependencies.md +37 -0
  7. package/assets/.agents/skills/repoctl/references/knip.md +34 -0
  8. package/assets/.agents/skills/repoctl/references/templates.md +26 -2
  9. package/assets/.github/workflows/ci.yml +10 -14
  10. package/assets/package.json +8 -16
  11. package/assets/pnpm-workspace.yaml +4 -1
  12. package/assets/repoctl.config.ts +1 -1
  13. package/assets/turbo.json +12 -4
  14. package/dist/cli.mjs +1 -1
  15. package/dist/index.d.mts +281 -1
  16. package/dist/index.mjs +1138 -75
  17. package/dist/{prepare-BapI8ioj.mjs → prepare-BYNSxvoB.mjs} +96 -4
  18. package/package.json +2 -1
  19. package/template-data.mjs +32 -0
  20. package/templates/client/package.json +2 -2
  21. package/templates/next/README.md +31 -0
  22. package/templates/next/eslint.config.js +5 -0
  23. package/templates/next/gitignore +7 -0
  24. package/templates/next/next.config.ts +5 -0
  25. package/templates/next/package.json +36 -0
  26. package/templates/next/src/app/api/health/route.ts +5 -0
  27. package/templates/next/src/app/counter.tsx +19 -0
  28. package/templates/next/src/app/favicon.ico +0 -0
  29. package/templates/next/src/app/globals.css +109 -0
  30. package/templates/next/src/app/layout.tsx +17 -0
  31. package/templates/next/src/app/page.tsx +16 -0
  32. package/templates/next/stylelint.config.js +3 -0
  33. package/templates/next/test/counter.test.tsx +22 -0
  34. package/templates/next/tsconfig.json +40 -0
  35. package/templates/next/turbo.json +24 -0
  36. package/templates/next/vitest.config.js +9 -0
  37. package/templates/nimbus/AGENTS.md +12 -0
  38. package/templates/nimbus/LICENSE +21 -0
  39. package/templates/nimbus/README.md +17 -0
  40. package/templates/nimbus/UPSTREAM.md +16 -0
  41. package/templates/nimbus/astro.config.ts +28 -0
  42. package/templates/nimbus/eslint.config.js +25 -0
  43. package/templates/nimbus/gitignore +5 -0
  44. package/templates/nimbus/nimbus.json +4 -0
  45. package/templates/nimbus/package.json +34 -0
  46. package/templates/nimbus/public/favicon.svg +1 -0
  47. package/templates/nimbus/scripts/check-locales.mjs +11 -0
  48. package/templates/nimbus/src/components/aside.astro +9 -0
  49. package/templates/nimbus/src/components/head.astro +43 -0
  50. package/templates/nimbus/src/components/search/client.ts +62 -0
  51. package/templates/nimbus/src/components/search/index.astro +26 -0
  52. package/templates/nimbus/src/components/sidebar.astro +26 -0
  53. package/templates/nimbus/src/components.ts +3 -0
  54. package/templates/nimbus/src/content/docs/get-started.mdx +36 -0
  55. package/templates/nimbus/src/content/docs/index.mdx +16 -0
  56. package/templates/nimbus/src/content/docs/writing.mdx +35 -0
  57. package/templates/nimbus/src/content/docs/zh/get-started.mdx +36 -0
  58. package/templates/nimbus/src/content/docs/zh/index.mdx +16 -0
  59. package/templates/nimbus/src/content/docs/zh/writing.mdx +35 -0
  60. package/templates/nimbus/src/content.config.ts +6 -0
  61. package/templates/nimbus/src/env.d.ts +6 -0
  62. package/templates/nimbus/src/layouts/docs.astro +70 -0
  63. package/templates/nimbus/src/lib/locale.ts +50 -0
  64. package/templates/nimbus/src/pages/[...slug]/index.md.ts +4 -0
  65. package/templates/nimbus/src/pages/[...slug]/index.mdx.ts +4 -0
  66. package/templates/nimbus/src/pages/[...slug].astro +36 -0
  67. package/templates/nimbus/src/pages/[section]/llms.txt.ts +4 -0
  68. package/templates/nimbus/src/pages/llms-full.txt.ts +4 -0
  69. package/templates/nimbus/src/pages/llms.txt.ts +4 -0
  70. package/templates/nimbus/src/pages/robots.txt.ts +18 -0
  71. package/templates/nimbus/src/styles/base.css +121 -0
  72. package/templates/nimbus/src/styles/site.css +186 -0
  73. package/templates/nimbus/stylelint.config.js +3 -0
  74. package/templates/nimbus/tsconfig.json +10 -0
  75. package/templates/react-lib/LICENSE +21 -0
  76. package/templates/react-lib/README.md +52 -0
  77. package/templates/react-lib/eslint.config.js +5 -0
  78. package/templates/react-lib/gitignore +4 -0
  79. package/templates/react-lib/package.json +48 -0
  80. package/templates/react-lib/src/counter.tsx +28 -0
  81. package/templates/react-lib/src/index.ts +2 -0
  82. package/templates/react-lib/src/style.css +36 -0
  83. package/templates/react-lib/stylelint.config.js +3 -0
  84. package/templates/react-lib/test/counter.test.tsx +28 -0
  85. package/templates/react-lib/test-d/index.test-d.ts +11 -0
  86. package/templates/react-lib/tsconfig.app.json +15 -0
  87. package/templates/react-lib/tsconfig.build.json +12 -0
  88. package/templates/react-lib/tsconfig.json +4 -0
  89. package/templates/react-lib/tsconfig.node.json +11 -0
  90. package/templates/react-lib/vite.config.ts +22 -0
  91. package/templates/react-lib/vitest.config.ts +5 -0
  92. package/templates/react-vite/README.md +27 -0
  93. package/templates/react-vite/eslint.config.js +5 -0
  94. package/templates/react-vite/gitignore +6 -0
  95. package/templates/react-vite/index.html +13 -0
  96. package/templates/react-vite/package.json +36 -0
  97. package/templates/react-vite/src/app.tsx +22 -0
  98. package/templates/react-vite/src/main.tsx +15 -0
  99. package/templates/react-vite/src/style.css +95 -0
  100. package/templates/react-vite/stylelint.config.js +3 -0
  101. package/templates/react-vite/test/app.test.tsx +22 -0
  102. package/templates/react-vite/tsconfig.app.json +26 -0
  103. package/templates/react-vite/tsconfig.json +11 -0
  104. package/templates/react-vite/tsconfig.node.json +18 -0
  105. package/templates/react-vite/vite.config.ts +6 -0
  106. package/templates/react-vite/vitest.config.ts +9 -0
  107. package/templates/server/package.json +1 -1
  108. package/templates/vitepress/.vitepress/config.ts +4 -5
  109. package/templates/vitepress/.vitepress/home/content.ts +2 -2
  110. package/templates/vitepress/.vitepress/home/faq.ts +16 -0
  111. package/templates/vitepress/.vitepress/mermaid/markdown.ts +18 -0
  112. package/templates/vitepress/.vitepress/mermaid/mermaid.vue +66 -0
  113. package/templates/vitepress/.vitepress/mermaid/render.ts +37 -0
  114. package/templates/vitepress/.vitepress/navigation/routes.ts +7 -0
  115. package/templates/vitepress/.vitepress/seo.ts +12 -1
  116. package/templates/vitepress/.vitepress/theme/components/HomePage.vue +2 -16
  117. package/templates/vitepress/.vitepress/theme/index.ts +3 -1
  118. package/templates/vitepress/ai/create.md +1 -1
  119. package/templates/vitepress/package.json +4 -8
  120. package/templates/vitepress/reference/artifacts.md +51 -0
  121. package/templates/vitepress/reference/commands.md +152 -3
  122. package/templates/vitepress/reference/config.md +101 -7
  123. package/templates/vitepress/reference/dependencies.md +116 -0
  124. package/templates/vitepress/reference/dependency-admission.md +67 -0
  125. package/templates/vitepress/reference/devcontainer.md +25 -0
  126. package/templates/vitepress/reference/index.md +1 -0
  127. package/templates/vitepress/reference/install-security.md +51 -0
  128. package/templates/vitepress/reference/knip.md +55 -0
  129. package/templates/vitepress/reference/move.md +39 -0
  130. package/templates/vitepress/reference/organization-presets.md +89 -0
  131. package/templates/vitepress/reference/removal.md +42 -0
  132. package/templates/vitepress/reference/template-assets.md +40 -1
  133. package/templates/vitepress/reference/templates.md +376 -9
  134. package/templates/vitepress/start/diagnose.md +19 -2
  135. package/templates/vitepress/tasks/checks.md +149 -1
  136. package/templates/vitepress/tasks/ci.md +93 -1
  137. package/templates/vitepress/tasks/index.md +1 -1
  138. package/templates/vitepress/tasks/release.md +72 -6
  139. package/templates/vitepress/test/mermaid.test.ts +39 -0
  140. package/templates/vitepress/test/render.test.ts +48 -0
  141. package/templates/vitepress/test/seo.test.ts +19 -0
  142. package/templates/vitepress/tsconfig.json +8 -8
  143. package/templates/vitepress/vitest.config.ts +8 -0
  144. package/templates/vitepress/zh/ai/create.md +1 -1
  145. package/templates/vitepress/zh/learn/monorepo/manage.md +1 -1
  146. package/templates/vitepress/zh/reference/artifacts.md +51 -0
  147. package/templates/vitepress/zh/reference/commands.md +151 -3
  148. package/templates/vitepress/zh/reference/config.md +94 -2
  149. package/templates/vitepress/zh/reference/dependencies.md +116 -0
  150. package/templates/vitepress/zh/reference/dependency-admission.md +67 -0
  151. package/templates/vitepress/zh/reference/devcontainer.md +25 -0
  152. package/templates/vitepress/zh/reference/execution-model.md +1 -1
  153. package/templates/vitepress/zh/reference/index.md +1 -0
  154. package/templates/vitepress/zh/reference/install-security.md +51 -0
  155. package/templates/vitepress/zh/reference/knip.md +55 -0
  156. package/templates/vitepress/zh/reference/move.md +39 -0
  157. package/templates/vitepress/zh/reference/organization-presets.md +89 -0
  158. package/templates/vitepress/zh/reference/removal.md +42 -0
  159. package/templates/vitepress/zh/reference/template-assets.md +22 -6
  160. package/templates/vitepress/zh/reference/templates.md +356 -9
  161. package/templates/vitepress/zh/start/diagnose.md +24 -13
  162. package/templates/vitepress/zh/tasks/checks.md +150 -0
  163. package/templates/vitepress/zh/tasks/ci.md +93 -1
  164. package/templates/vitepress/zh/tasks/index.md +1 -1
  165. package/templates/vitepress/zh/tasks/release.md +72 -6
  166. package/templates/vitepress/zh/tasks/reports.md +1 -1
  167. package/templates/vue-lib/package.json +1 -1
@@ -73,6 +73,14 @@ const templateChoices = [
73
73
  category: "app",
74
74
  description: "Vue 3 + Hono 的前后端一体应用模板"
75
75
  },
76
+ {
77
+ key: "react-vite",
78
+ label: "React Vite 应用",
79
+ source: "react-vite",
80
+ target: "apps/react-vite",
81
+ category: "app",
82
+ description: "React + Vite + TypeScript 单页应用,包含交互测试与共享工程规范"
83
+ },
76
84
  {
77
85
  key: "hono-server",
78
86
  label: "hono 模板",
@@ -96,6 +104,30 @@ const templateChoices = [
96
104
  target: "apps/cli",
97
105
  category: "tool",
98
106
  description: "TypeScript 命令行工具模板"
107
+ },
108
+ {
109
+ key: "nimbus",
110
+ label: "Nimbus 文档(默认)",
111
+ source: "nimbus",
112
+ target: "apps/docs",
113
+ category: "docs",
114
+ description: "默认文档模板:Nimbus + Astro,中英双语与 AI 文档入口"
115
+ },
116
+ {
117
+ key: "react-lib",
118
+ label: "React 组件库",
119
+ source: "react-lib",
120
+ target: "packages/react-lib",
121
+ category: "library",
122
+ description: "带类型声明、显式 CSS 出口和发布消费验证的 React 组件库"
123
+ },
124
+ {
125
+ key: "next",
126
+ label: "Next.js App Router 应用",
127
+ source: "next",
128
+ target: "apps/next",
129
+ category: "app",
130
+ description: "Next.js + TypeScript SSR 应用,包含客户端交互、健康接口与工作区库集成"
99
131
  }
100
132
  ];
101
133
  //#endregion
@@ -144,13 +176,17 @@ const skipDirs = /* @__PURE__ */ new Set([
144
176
  ".vite",
145
177
  ".tmp",
146
178
  ".vue-global-types",
147
- ".wrangler"
179
+ ".wrangler",
180
+ ".astro",
181
+ ".nimbus",
182
+ ".next"
148
183
  ]);
149
184
  const skipFiles = /* @__PURE__ */ new Set([
150
185
  ".DS_Store",
151
186
  "CHANGELOG.md",
152
187
  "route-map.d.ts",
153
- "worker-configuration.d.ts"
188
+ "worker-configuration.d.ts",
189
+ "next-env.d.ts"
154
190
  ]);
155
191
  function shouldSkipTemplatePath(rootDir, targetPath) {
156
192
  const basename = path.basename(targetPath);
@@ -169,12 +205,33 @@ function createTemplateCopyFilter(rootDir) {
169
205
  }
170
206
  //#endregion
171
207
  //#region src/prepare/published.ts
208
+ const sourceScriptNames = /* @__PURE__ */ new Set([
209
+ "tooling:build",
210
+ "check:no-tracked-build-artifacts",
211
+ "check:workflows",
212
+ "test:dev-scenarios",
213
+ "test:worker-types"
214
+ ]);
215
+ const isSourceOnlyScript = (name) => name.startsWith("dev:") || sourceScriptNames.has(name) || name.startsWith("test:packaged-");
172
216
  function sanitizePublishedManifestContent(content) {
173
217
  const manifest = JSON.parse(content);
218
+ if (manifest.devDependencies) {
219
+ for (const name of [
220
+ "@icebreakers/commitlint-config",
221
+ "@icebreakers/eslint-config",
222
+ "@icebreakers/monorepo",
223
+ "@icebreakers/stylelint-config",
224
+ "make-fetch-happen",
225
+ "playwright"
226
+ ]) delete manifest.devDependencies[name];
227
+ if (manifest.devDependencies["repoctl"]?.startsWith("workspace:")) manifest.devDependencies["repoctl"] = "latest";
228
+ }
174
229
  if (manifest.scripts) {
175
- for (const name of Object.keys(manifest.scripts)) if (name.startsWith("dev:") || name === "test:dev-scenarios" || name === "test:worker-types" || name === "test:packaged-create") delete manifest.scripts[name];
230
+ for (const name of Object.keys(manifest.scripts)) if (isSourceOnlyScript(name)) delete manifest.scripts[name];
176
231
  manifest.scripts["dev"] = "turbo run dev --concurrency=20";
232
+ if (manifest.scripts["lint"]) manifest.scripts["lint"] = manifest.scripts["lint"].replace(/^pnpm run tooling:build && /u, "");
177
233
  if (manifest.scripts["test"]) manifest.scripts["test"] = manifest.scripts["test"].replace(" && pnpm test:dev-scenarios", "");
234
+ for (const name of ["test", "test:dev"]) if (manifest.scripts[name]) manifest.scripts[name] = manifest.scripts[name].replace("pnpm check:no-tracked-build-artifacts && ", "");
178
235
  }
179
236
  return `${JSON.stringify(manifest, null, 2)}\n`;
180
237
  }
@@ -236,6 +293,40 @@ function sanitizePublishedWorkspaceContent(content) {
236
293
  return changed ? YAML.stringify(workspace) : content;
237
294
  }
238
295
  //#endregion
296
+ //#region src/prepare/workflows.ts
297
+ function sourceCommand(command) {
298
+ if (/[;&|`$<>]/u.test(command)) return false;
299
+ const script = /^pnpm\s+(?:--[\w-]+(?:=[\w./:-]+)?\s+)*(?:run\s+(?:--[\w-]+(?:=[\w./:-]+)?\s+)*)?([\w:-]+)(?:\s|$)/u.exec(command)?.[1];
300
+ return script !== void 0 && isSourceOnlyScript(script) || /^pnpm\s+exec\s+playwright\s+install(?:\s|$)/u.test(command);
301
+ }
302
+ function mentionsSourceCommand(command) {
303
+ return command.split(/[^\w:-]+/u).some(isSourceOnlyScript) || /\bplaywright\s+install\b/u.test(command);
304
+ }
305
+ /** Filter YAML steps, so neighboring steps and additional fields cannot escape publication rules. */
306
+ function sanitizePublishedCiWorkflowContent(content) {
307
+ const document = YAML.parseDocument(content);
308
+ if (document.errors.length) throw new Error("Cannot publish an invalid CI workflow.");
309
+ const jobs = document.get("jobs", true);
310
+ if (!YAML.isMap(jobs)) return content;
311
+ let changed = false;
312
+ for (const { value: job } of jobs.items) {
313
+ if (!YAML.isMap(job)) continue;
314
+ const steps = job.get("steps", true);
315
+ if (!YAML.isSeq(steps)) continue;
316
+ for (let index = steps.items.length - 1; index >= 0; index--) {
317
+ const step = steps.items[index];
318
+ const run = YAML.isMap(step) ? step.get("run") : void 0;
319
+ if (typeof run !== "string") continue;
320
+ const commands = run.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
321
+ if (!commands.some(mentionsSourceCommand)) continue;
322
+ if (!commands.every(sourceCommand)) throw new Error("Move source-only commands into separate CI steps before publishing templates.");
323
+ steps.delete(index);
324
+ changed = true;
325
+ }
326
+ }
327
+ return changed ? document.toString({ lineWidth: 0 }) : content;
328
+ }
329
+ //#endregion
239
330
  //#region src/prepare/index.ts
240
331
  const huskySkippedEntryPattern = /[\\/]_$/;
241
332
  function toPublishedAssetPath(target) {
@@ -357,10 +448,11 @@ async function removeSourceRepoReleaseToolingBuildStep() {
357
448
  if (nextContent !== content) await fs.writeFile(releaseWorkflowPath, nextContent, "utf8");
358
449
  }
359
450
  async function removeSourceRepoChecks() {
451
+ await fs.rm(path$1.join(assetsDir, ".github/workflows/devcontainer.yml"), { force: true });
360
452
  const workflowPath = path$1.join(assetsDir, ".github/workflows/ci.yml");
361
453
  if (await pathExists(workflowPath)) {
362
454
  const workflow = await fs.readFile(workflowPath, "utf8");
363
- await fs.writeFile(workflowPath, workflow.replace(/\r?\n\s+- name: Check Worker type generation from packaged templates\r?\n\s+run: pnpm test:worker-types\r?\n/g, "\n"));
455
+ await fs.writeFile(workflowPath, sanitizePublishedCiWorkflowContent(workflow));
364
456
  }
365
457
  const manifestPath = path$1.join(assetsDir, "package.json");
366
458
  if (await pathExists(manifestPath)) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@icebreakers/monorepo-templates",
3
3
  "type": "module",
4
- "version": "2.0.7",
4
+ "version": "2.2.0",
5
5
  "description": "Built-in project templates and managed workspace assets for repoctl",
6
6
  "author": "ice breaker <hi@sonofmagic.top>",
7
7
  "license": "MIT",
@@ -48,6 +48,7 @@
48
48
  "dependencies": {
49
49
  "@inquirer/checkbox": "^5.2.5",
50
50
  "@inquirer/input": "^5.1.6",
51
+ "@inquirer/password": "^5.2.2",
51
52
  "@inquirer/select": "^5.2.5",
52
53
  "commander": "^15.0.0",
53
54
  "nanoexec": "^1.1.0",
package/template-data.mjs CHANGED
@@ -23,6 +23,14 @@ export const templateChoices = [
23
23
  category: 'app',
24
24
  description: 'Vue 3 + Hono 的前后端一体应用模板',
25
25
  },
26
+ {
27
+ key: 'react-vite',
28
+ label: 'React Vite 应用',
29
+ source: 'react-vite',
30
+ target: 'apps/react-vite',
31
+ category: 'app',
32
+ description: 'React + Vite + TypeScript 单页应用,包含交互测试与共享工程规范',
33
+ },
26
34
  {
27
35
  key: 'hono-server',
28
36
  label: 'hono 模板',
@@ -47,4 +55,28 @@ export const templateChoices = [
47
55
  category: 'tool',
48
56
  description: 'TypeScript 命令行工具模板',
49
57
  },
58
+ {
59
+ key: 'nimbus',
60
+ label: 'Nimbus 文档(默认)',
61
+ source: 'nimbus',
62
+ target: 'apps/docs',
63
+ category: 'docs',
64
+ description: '默认文档模板:Nimbus + Astro,中英双语与 AI 文档入口',
65
+ },
66
+ {
67
+ key: 'react-lib',
68
+ label: 'React 组件库',
69
+ source: 'react-lib',
70
+ target: 'packages/react-lib',
71
+ category: 'library',
72
+ description: '带类型声明、显式 CSS 出口和发布消费验证的 React 组件库',
73
+ },
74
+ {
75
+ key: 'next',
76
+ label: 'Next.js App Router 应用',
77
+ source: 'next',
78
+ target: 'apps/next',
79
+ category: 'app',
80
+ description: 'Next.js + TypeScript SSR 应用,包含客户端交互、健康接口与工作区库集成',
81
+ },
50
82
  ]
@@ -60,10 +60,10 @@
60
60
  "@vitejs/plugin-vue-jsx": "^5.1.6",
61
61
  "@vue/tsconfig": "^0.9.1",
62
62
  "consola": "^3.4.2",
63
- "hono": "^4.13.11",
63
+ "hono": "^4.13.12",
64
64
  "tailwindcss": "^4.3.3",
65
65
  "typescript": "~6.0.3",
66
- "vite": "^8.3.1",
66
+ "vite": "^8.3.2",
67
67
  "vite-plugin-vue-devtools": "^8.2.1",
68
68
  "vite-tsconfig-paths": "^6.1.1",
69
69
  "vue-tsc": "3.3.11",
@@ -0,0 +1,31 @@
1
+ # Next.js App Router workspace
2
+
3
+ This TypeScript template follows `create-next-app@16.3.8`'s App Router layout and uses the shared repoctl tooling. It includes a server page, a client counter and `GET /api/health`. It requires Node 22.13 or newer and shares the workspace's pnpm version and lockfile.
4
+
5
+ Run these from the generated workspace root:
6
+
7
+ ```sh
8
+ corepack enable
9
+ pnpm install
10
+ pnpm build
11
+ pnpm lint
12
+ pnpm typecheck
13
+ pnpm test
14
+ pnpm --dir apps/next start
15
+ ```
16
+
17
+ For development, run `pnpm --dir apps/next dev`. When created with `repo new portal --template next`, use `apps/portal` instead. Lint runs ESLint and Stylelint independently of Next build. `typecheck` runs `next typegen` before `tsc --noEmit`, so route types exist even before the first production build. Unit tests cover client state; the repoctl source repository also tests the built server and browser using actual published tarballs.
18
+
19
+ ## Internal packages
20
+
21
+ For a compiled workspace library, add `"@acme/shared": "workspace:*"` to dependencies and import its public package name. The library should expose its built JavaScript and declaration files through `exports`. Turbo's inherited `^build` builds the library first; do not reach into another package's source via relative imports.
22
+
23
+ To consume an intentionally source-exporting TypeScript package, add its package name to `transpilePackages` in `next.config.ts`. Its exports and server/client compatibility must support the target environment. `transpilePackages` does not generate that library's standalone declaration files, and server-only code must not enter a client component. The packaged acceptance test covers both compiled and source-exporting internal packages.
24
+
25
+ ## Cache and runtime boundaries
26
+
27
+ The local `turbo.json` includes all authored app inputs and caches `.next/**` except `.next/cache/**`. Development and production server tasks are persistent and uncached; type generation is uncached to keep Next's generated route declarations available. `.next`, `next-env.d.ts`, and TypeScript build info are generated locally and never shipped as template inputs.
28
+
29
+ No external font download is needed to build this starter. Configure environment values and production hosting for your application separately; no authentication, database or deployment platform is included.
30
+
31
+ References: [create-next-app](https://nextjs.org/docs/app/api-reference/cli/create-next-app), [Next CLI and typegen](https://nextjs.org/docs/app/api-reference/cli/next), [transpilePackages](https://nextjs.org/docs/app/api-reference/config/next-config-js/transpilePackages).
@@ -0,0 +1,5 @@
1
+ import { defineEslintConfig } from 'repoctl/tooling'
2
+
3
+ export default await defineEslintConfig({
4
+ options: { react: true, typescript: true, ignores: ['.next/**', 'next-env.d.ts'] },
5
+ })
@@ -0,0 +1,7 @@
1
+ .next/
2
+ node_modules/
3
+ next-env.d.ts
4
+ *.tsbuildinfo
5
+ coverage/
6
+ .env*
7
+ !.env.example
@@ -0,0 +1,5 @@
1
+ import type { NextConfig } from 'next'
2
+
3
+ const nextConfig: NextConfig = {}
4
+
5
+ export default nextConfig
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@icebreakers/next-template",
3
+ "type": "module",
4
+ "version": "0.0.0",
5
+ "private": true,
6
+ "description": "Internal Next.js App Router and TypeScript application template shipped with repoctl",
7
+ "license": "MIT",
8
+ "engines": {
9
+ "node": ">=22.13.0"
10
+ },
11
+ "scripts": {
12
+ "dev": "next dev",
13
+ "build": "next build",
14
+ "start": "next start",
15
+ "lint": "eslint . && stylelint \"src/**/*.css\"",
16
+ "lint:fix": "eslint . --fix && stylelint \"src/**/*.css\" --fix",
17
+ "typecheck": "next typegen && tsc --noEmit",
18
+ "test": "vitest run",
19
+ "test:dev": "vitest"
20
+ },
21
+ "dependencies": {
22
+ "next": "16.3.8",
23
+ "react": "^19.3.0",
24
+ "react-dom": "^19.3.0"
25
+ },
26
+ "devDependencies": {
27
+ "@testing-library/dom": "^10.4.2",
28
+ "@testing-library/react": "^16.3.3",
29
+ "@types/react": "^19.3.0",
30
+ "@types/react-dom": "^19.3.0",
31
+ "@vitejs/plugin-react": "^6.1.1",
32
+ "jsdom": "^30.1.1",
33
+ "typescript": "~6.0.3",
34
+ "vitest": "~5.0.3"
35
+ }
36
+ }
@@ -0,0 +1,5 @@
1
+ export const dynamic = 'force-dynamic'
2
+
3
+ export function GET() {
4
+ return Response.json({ status: 'ok' }, { headers: { 'Cache-Control': 'no-store' } })
5
+ }
@@ -0,0 +1,19 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+
5
+ export default function Counter() {
6
+ const [count, setCount] = useState(0)
7
+ return (
8
+ <section className="counter" aria-label="Client interaction">
9
+ <div>
10
+ <p className="eyebrow">Client state</p>
11
+ <output aria-label="Count" aria-live="polite">{count}</output>
12
+ </div>
13
+ <div className="actions">
14
+ <button type="button" onClick={() => setCount(value => value + 1)}>Add one</button>
15
+ <button type="button" className="secondary" disabled={count === 0} onClick={() => setCount(0)}>Reset</button>
16
+ </div>
17
+ </section>
18
+ )
19
+ }
@@ -0,0 +1,109 @@
1
+ @charset "UTF-8";
2
+
3
+ :root {
4
+ font-family: Arial, Helvetica, sans-serif;
5
+ font-synthesis: none;
6
+ color: #1b263b;
7
+ background: #f5f3ee;
8
+ text-rendering: optimizelegibility;
9
+ }
10
+
11
+ * {
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ body {
16
+ margin: 0;
17
+ }
18
+
19
+ main {
20
+ width: min(100% - 3rem, 48rem);
21
+ margin: 12vh auto;
22
+ }
23
+
24
+ .eyebrow {
25
+ margin: 0 0 1.5rem;
26
+ font-size: 0.75rem;
27
+ font-weight: 700;
28
+ text-transform: uppercase;
29
+ letter-spacing: 0.12em;
30
+ }
31
+
32
+ h1 {
33
+ margin: 0 0 1.5rem;
34
+ font-size: clamp(2.5rem, 7vw, 4.5rem);
35
+ line-height: 1.05;
36
+ letter-spacing: -0.05em;
37
+ }
38
+
39
+ .intro {
40
+ max-width: 35rem;
41
+ font-size: 1.125rem;
42
+ line-height: 1.7;
43
+ color: #536073;
44
+ }
45
+
46
+ .counter {
47
+ display: flex;
48
+ flex-wrap: wrap;
49
+ gap: 2rem;
50
+ align-items: center;
51
+ justify-content: space-between;
52
+ padding: 2rem 0;
53
+ margin: 3rem 0;
54
+ border-block: 1px solid #c7cbd1;
55
+ }
56
+
57
+ .counter .eyebrow {
58
+ margin-bottom: 0.75rem;
59
+ }
60
+
61
+ output {
62
+ font-size: 3rem;
63
+ font-variant-numeric: tabular-nums;
64
+ }
65
+
66
+ .actions {
67
+ display: flex;
68
+ flex-wrap: wrap;
69
+ gap: 0.75rem;
70
+ }
71
+
72
+ button {
73
+ min-height: 2.75rem;
74
+ padding: 0.75rem 1.125rem;
75
+ font: inherit;
76
+ color: #fff;
77
+ cursor: pointer;
78
+ background: #1b263b;
79
+ border: 1px solid #1b263b;
80
+ border-radius: 0.25rem;
81
+ }
82
+
83
+ .secondary {
84
+ color: #1b263b;
85
+ background: transparent;
86
+ }
87
+
88
+ button:disabled {
89
+ cursor: default;
90
+ opacity: 0.45;
91
+ }
92
+
93
+ button:focus-visible,
94
+ a:focus-visible {
95
+ outline: 3px solid #b95732;
96
+ outline-offset: 4px;
97
+ }
98
+
99
+ footer {
100
+ display: flex;
101
+ flex-wrap: wrap;
102
+ gap: 1rem 2rem;
103
+ font-size: 0.875rem;
104
+ }
105
+
106
+ a {
107
+ color: inherit;
108
+ text-underline-offset: 0.25em;
109
+ }
@@ -0,0 +1,17 @@
1
+ /* eslint react-refresh/only-export-components: ["error", { "allowExportNames": ["metadata"] }] -- Next.js reads metadata from route layouts. */
2
+ import type { Metadata } from 'next'
3
+ import type { ReactNode } from 'react'
4
+ import './globals.css'
5
+
6
+ export const metadata: Metadata = {
7
+ title: 'Next.js workspace',
8
+ description: 'A Next.js App Router application in your pnpm workspace.',
9
+ }
10
+
11
+ export default function RootLayout({ children }: { children: ReactNode }) {
12
+ return (
13
+ <html lang="en">
14
+ <body>{children}</body>
15
+ </html>
16
+ )
17
+ }
@@ -0,0 +1,16 @@
1
+ import Counter from './counter'
2
+
3
+ export default function HomePage() {
4
+ return (
5
+ <main>
6
+ <p className="eyebrow">repoctl / Next.js</p>
7
+ <h1>Next.js workspace</h1>
8
+ <p className="intro">A server-rendered page with a small client component. Ready for your next feature.</p>
9
+ <Counter />
10
+ <footer>
11
+ <a href="/api/health">Health endpoint</a>
12
+ <a href="https://nextjs.org/docs/app">App Router documentation</a>
13
+ </footer>
14
+ </main>
15
+ )
16
+ }
@@ -0,0 +1,3 @@
1
+ import { defineStylelintConfig } from 'repoctl/tooling'
2
+
3
+ export default await defineStylelintConfig()
@@ -0,0 +1,22 @@
1
+ import { cleanup, fireEvent, render, screen } from '@testing-library/react'
2
+ import { afterEach, expect, it } from 'vitest'
3
+ import Counter from '../src/app/counter'
4
+
5
+ afterEach(cleanup)
6
+
7
+ it('increments the count and resets it to zero', () => {
8
+ render(<Counter />)
9
+ const count = screen.getByLabelText('Count')
10
+ const reset = screen.getByRole('button', { name: 'Reset' })
11
+ expect(count.textContent).toBe('0')
12
+ expect(reset.hasAttribute('disabled')).toBe(true)
13
+
14
+ fireEvent.click(screen.getByRole('button', { name: 'Add one' }))
15
+ fireEvent.click(screen.getByRole('button', { name: 'Add one' }))
16
+ expect(count.textContent).toBe('2')
17
+ expect(reset.hasAttribute('disabled')).toBe(false)
18
+
19
+ fireEvent.click(reset)
20
+ expect(count.textContent).toBe('0')
21
+ expect(reset.hasAttribute('disabled')).toBe(true)
22
+ })
@@ -0,0 +1,40 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "incremental": true,
5
+ "tsBuildInfoFile": "./node_modules/.tmp/next.tsbuildinfo",
6
+ "target": "ES2022",
7
+ "jsx": "react-jsx",
8
+ "lib": [
9
+ "DOM",
10
+ "DOM.Iterable",
11
+ "ESNext"
12
+ ],
13
+ "paths": {
14
+ "@/*": [
15
+ "./src/*"
16
+ ]
17
+ },
18
+ "types": [
19
+ "node"
20
+ ],
21
+ "isolatedModules": true,
22
+ "plugins": [
23
+ {
24
+ "name": "next"
25
+ }
26
+ ]
27
+ },
28
+ "include": [
29
+ "next-env.d.ts",
30
+ "src/**/*.ts",
31
+ "src/**/*.tsx",
32
+ "test/**/*.tsx",
33
+ "next.config.ts",
34
+ ".next/types/**/*.ts",
35
+ ".next/dev/types/**/*.ts"
36
+ ],
37
+ "exclude": [
38
+ "node_modules"
39
+ ]
40
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "extends": [
3
+ "//"
4
+ ],
5
+ "tasks": {
6
+ "build": {
7
+ "inputs": [
8
+ "$TURBO_DEFAULT$",
9
+ ".env*"
10
+ ],
11
+ "outputs": [
12
+ ".next/**",
13
+ "!.next/cache/**"
14
+ ]
15
+ },
16
+ "typecheck": {
17
+ "cache": false
18
+ },
19
+ "start": {
20
+ "cache": false,
21
+ "persistent": true
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,9 @@
1
+ import react from '@vitejs/plugin-react'
2
+ import { defineVitestProjectConfig } from 'repoctl/tooling'
3
+ import { mergeConfig } from 'vitest/config'
4
+
5
+ export default mergeConfig(await defineVitestProjectConfig({
6
+ options: { environment: 'jsdom' },
7
+ }), {
8
+ plugins: [react()],
9
+ })
@@ -0,0 +1,12 @@
1
+ # Documentation workspace
2
+
3
+ This is a static Nimbus + Astro documentation site. Run commands in this workspace; install dependencies from the monorepo root after `corepack enable`.
4
+
5
+ - Content lives in `src/content/docs/`; English uses the root and Chinese uses `zh/` with matching relative filenames. Keep both translations and links in sync. The directory tree supplies routes and navigation.
6
+ - Layouts and components are project-owned. Keep modules small. Put styles in `src/styles/` so Stylelint checks every authored style.
7
+ - Register MDX components in `src/components.ts`. Add a text conversion in `astro.config.ts` when a custom component conveys information that would otherwise disappear from Markdown.
8
+ - Set the canonical `site` URL and project title in `astro.config.ts` before deployment. Static output is `dist/` and can be hosted anywhere.
9
+ - Run `pnpm build`, `pnpm lint`, and `pnpm typecheck`. `typecheck` combines Astro diagnostics with TypeScript checks. Search uses the production Pagefind index; verify it with `pnpm preview` after building.
10
+ - Verify `/llms.txt`, `/zh/llms.txt`, `/llms-full.txt`, and each page's `/index.md` and `/index.mdx`. Keep private content outside public content collections; `noindex` is not access control.
11
+ - `draft: true` excludes unfinished pages from production. When adding a page, create its translation before publishing so the language switch remains valid.
12
+ - Keep `nimbus.json` and `UPSTREAM.md` when upgrading. Review Nimbus migrations and project-owned changes before advancing the reviewed version.
@@ -0,0 +1,21 @@
1
+ MIT License Copyright (c) 2025 Cloudflare, Inc.
2
+
3
+ Permission is hereby granted, free of
4
+ charge, to any person obtaining a copy of this software and associated
5
+ documentation files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use, copy, modify, merge,
7
+ publish, distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice
12
+ (including the next paragraph) shall be included in all copies or substantial
13
+ portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16
+ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
18
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
19
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,17 @@
1
+ # Project docs / 项目文档
2
+
3
+ A small bilingual Nimbus + Astro documentation site. 英文位于 `/`,中文位于 `/zh/`。
4
+
5
+ From the monorepo root, run `corepack enable` and `pnpm install`. Then run these commands in this directory:
6
+
7
+ ```sh
8
+ pnpm dev
9
+ pnpm build
10
+ pnpm preview
11
+ pnpm lint
12
+ pnpm typecheck
13
+ ```
14
+
15
+ Edit `src/content/docs/` and its `zh/` translations. Set your project title and canonical site URL in `astro.config.ts` before publishing `dist/` to a static host. Search is available after a production build.
16
+
17
+ 机器可读入口包括 `/llms.txt`、`/zh/llms.txt`、`/llms-full.txt`,以及页面的 `/index.md` 和 `/index.mdx`。详见 `AGENTS.md` 和 `UPSTREAM.md`。