@icebreakers/monorepo-templates 2.0.7 → 2.1.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 (81) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/assets/.agents/skills/repoctl/SKILL.md +2 -2
  4. package/assets/.agents/skills/repoctl/references/templates.md +3 -2
  5. package/assets/.github/workflows/ci.yml +0 -4
  6. package/assets/package.json +3 -7
  7. package/assets/pnpm-workspace.yaml +2 -0
  8. package/assets/turbo.json +11 -4
  9. package/dist/cli.mjs +1 -1
  10. package/dist/index.mjs +2 -2
  11. package/dist/{prepare-BapI8ioj.mjs → prepare-2VvFwYDz.mjs} +23 -3
  12. package/package.json +1 -1
  13. package/template-data.mjs +8 -0
  14. package/templates/client/package.json +1 -1
  15. package/templates/nimbus/AGENTS.md +12 -0
  16. package/templates/nimbus/LICENSE +21 -0
  17. package/templates/nimbus/README.md +17 -0
  18. package/templates/nimbus/UPSTREAM.md +16 -0
  19. package/templates/nimbus/astro.config.ts +28 -0
  20. package/templates/nimbus/eslint.config.js +25 -0
  21. package/templates/nimbus/gitignore +5 -0
  22. package/templates/nimbus/nimbus.json +4 -0
  23. package/templates/nimbus/package.json +34 -0
  24. package/templates/nimbus/public/favicon.svg +1 -0
  25. package/templates/nimbus/scripts/check-locales.mjs +11 -0
  26. package/templates/nimbus/src/components/aside.astro +9 -0
  27. package/templates/nimbus/src/components/head.astro +43 -0
  28. package/templates/nimbus/src/components/search/client.ts +62 -0
  29. package/templates/nimbus/src/components/search/index.astro +26 -0
  30. package/templates/nimbus/src/components/sidebar.astro +26 -0
  31. package/templates/nimbus/src/components.ts +3 -0
  32. package/templates/nimbus/src/content/docs/get-started.mdx +36 -0
  33. package/templates/nimbus/src/content/docs/index.mdx +16 -0
  34. package/templates/nimbus/src/content/docs/writing.mdx +35 -0
  35. package/templates/nimbus/src/content/docs/zh/get-started.mdx +36 -0
  36. package/templates/nimbus/src/content/docs/zh/index.mdx +16 -0
  37. package/templates/nimbus/src/content/docs/zh/writing.mdx +35 -0
  38. package/templates/nimbus/src/content.config.ts +6 -0
  39. package/templates/nimbus/src/env.d.ts +6 -0
  40. package/templates/nimbus/src/layouts/docs.astro +70 -0
  41. package/templates/nimbus/src/lib/locale.ts +50 -0
  42. package/templates/nimbus/src/pages/[...slug]/index.md.ts +4 -0
  43. package/templates/nimbus/src/pages/[...slug]/index.mdx.ts +4 -0
  44. package/templates/nimbus/src/pages/[...slug].astro +36 -0
  45. package/templates/nimbus/src/pages/[section]/llms.txt.ts +4 -0
  46. package/templates/nimbus/src/pages/llms-full.txt.ts +4 -0
  47. package/templates/nimbus/src/pages/llms.txt.ts +4 -0
  48. package/templates/nimbus/src/pages/robots.txt.ts +18 -0
  49. package/templates/nimbus/src/styles/base.css +121 -0
  50. package/templates/nimbus/src/styles/site.css +186 -0
  51. package/templates/nimbus/stylelint.config.js +3 -0
  52. package/templates/nimbus/tsconfig.json +10 -0
  53. package/templates/server/package.json +1 -1
  54. package/templates/vitepress/.vitepress/config.ts +4 -5
  55. package/templates/vitepress/.vitepress/home/content.ts +2 -2
  56. package/templates/vitepress/.vitepress/home/faq.ts +16 -0
  57. package/templates/vitepress/.vitepress/mermaid/markdown.ts +18 -0
  58. package/templates/vitepress/.vitepress/mermaid/mermaid.vue +66 -0
  59. package/templates/vitepress/.vitepress/mermaid/render.ts +37 -0
  60. package/templates/vitepress/.vitepress/seo.ts +12 -1
  61. package/templates/vitepress/.vitepress/theme/components/HomePage.vue +2 -16
  62. package/templates/vitepress/.vitepress/theme/index.ts +3 -1
  63. package/templates/vitepress/ai/create.md +1 -1
  64. package/templates/vitepress/package.json +4 -8
  65. package/templates/vitepress/reference/commands.md +3 -3
  66. package/templates/vitepress/reference/template-assets.md +1 -1
  67. package/templates/vitepress/reference/templates.md +13 -9
  68. package/templates/vitepress/tasks/index.md +1 -1
  69. package/templates/vitepress/test/mermaid.test.ts +39 -0
  70. package/templates/vitepress/test/render.test.ts +48 -0
  71. package/templates/vitepress/test/seo.test.ts +19 -0
  72. package/templates/vitepress/tsconfig.json +8 -8
  73. package/templates/vitepress/vitest.config.ts +8 -0
  74. package/templates/vitepress/zh/ai/create.md +1 -1
  75. package/templates/vitepress/zh/learn/monorepo/manage.md +1 -1
  76. package/templates/vitepress/zh/reference/commands.md +3 -3
  77. package/templates/vitepress/zh/reference/execution-model.md +1 -1
  78. package/templates/vitepress/zh/reference/template-assets.md +3 -2
  79. package/templates/vitepress/zh/reference/templates.md +13 -9
  80. package/templates/vitepress/zh/tasks/index.md +1 -1
  81. package/templates/vitepress/zh/tasks/reports.md +1 -1
@@ -0,0 +1,186 @@
1
+ @import './base.css';
2
+
3
+ .site-header {
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: space-between;
7
+ min-height: 4.5rem;
8
+ padding: 0.8rem max(1.5rem, calc((100vw - 90rem) / 2));
9
+ border-bottom: 1px solid var(--line);
10
+ }
11
+
12
+ .brand {
13
+ font-weight: 650;
14
+ color: var(--ink);
15
+ text-decoration: none;
16
+ }
17
+
18
+ .language-link {
19
+ font-size: 0.875rem;
20
+ }
21
+
22
+ .docs-grid {
23
+ display: grid;
24
+ grid-template-columns: 15rem minmax(0, 1fr) 12rem;
25
+ gap: 3rem;
26
+ max-width: 90rem;
27
+ padding: 3rem 1.5rem;
28
+ margin: auto;
29
+ }
30
+
31
+ .site-rail, .page-rail {
32
+ font-size: 0.875rem;
33
+ }
34
+
35
+ .rail-title {
36
+ font-size: 0.75rem;
37
+ font-weight: 650;
38
+ color: var(--muted);
39
+ letter-spacing: 0.04em;
40
+ }
41
+
42
+ .navigation-list {
43
+ padding: 0;
44
+ list-style: none;
45
+ }
46
+
47
+ .navigation-list ul {
48
+ padding-left: 1rem;
49
+ }
50
+
51
+ .navigation-list a {
52
+ display: block;
53
+ padding: 0.3rem 0;
54
+ color: var(--muted);
55
+ text-decoration: none;
56
+ }
57
+
58
+ .navigation-list a[aria-current="page"] {
59
+ font-weight: 650;
60
+ color: var(--accent);
61
+ }
62
+
63
+ .navigation-list a:hover {
64
+ color: var(--accent);
65
+ }
66
+
67
+ .index-link {
68
+ display: inline-block;
69
+ margin-top: 2rem;
70
+ font-family: ui-monospace, monospace;
71
+ }
72
+
73
+ main {
74
+ min-width: 0;
75
+ max-width: 48rem;
76
+ }
77
+
78
+ .description {
79
+ margin-bottom: 2.5rem;
80
+ font-size: 1.15rem;
81
+ color: var(--muted);
82
+ }
83
+
84
+ .callout {
85
+ padding: 1rem 1.25rem;
86
+ margin: 1.5rem 0;
87
+ background: var(--soft);
88
+ border-left: 3px solid var(--accent);
89
+ }
90
+
91
+ .callout p:last-child {
92
+ margin-bottom: 0;
93
+ }
94
+
95
+ footer {
96
+ padding-top: 1rem;
97
+ margin-top: 3rem;
98
+ font-size: 0.875rem;
99
+ border-top: 1px solid var(--line);
100
+ }
101
+
102
+ .search-panel {
103
+ margin-bottom: 2rem;
104
+ }
105
+
106
+ .search-panel form {
107
+ margin-top: 1rem;
108
+ }
109
+
110
+ .search-controls {
111
+ display: flex;
112
+ gap: 0.3rem;
113
+ margin-top: 0.5rem;
114
+ }
115
+
116
+ .search-results {
117
+ padding-left: 1.2rem;
118
+ }
119
+
120
+ .search-results li {
121
+ margin-bottom: 0.6rem;
122
+ }
123
+
124
+ .skip-link {
125
+ position: absolute;
126
+ top: -5rem;
127
+ left: 1rem;
128
+ padding: 0.5rem;
129
+ background: var(--surface);
130
+ }
131
+
132
+ .skip-link:focus {
133
+ top: 1rem;
134
+ }
135
+
136
+ @media (max-width: 72rem) {
137
+ .docs-grid {
138
+ grid-template-columns: 13rem minmax(0, 1fr);
139
+ gap: 2rem;
140
+ }
141
+
142
+ .page-rail {
143
+ display: none;
144
+ }
145
+ }
146
+
147
+ @media (max-width: 48rem) {
148
+ .docs-grid {
149
+ grid-template-columns: minmax(0, 1fr);
150
+ padding-top: 1.5rem;
151
+ }
152
+
153
+ .site-rail {
154
+ padding-bottom: 1rem;
155
+ border-bottom: 1px solid var(--line);
156
+ }
157
+
158
+ .site-rail .navigation-list {
159
+ display: flex;
160
+ flex-wrap: wrap;
161
+ gap: 0.5rem 1rem;
162
+ }
163
+
164
+ .site-rail .navigation-list ul {
165
+ padding-left: 0;
166
+ }
167
+
168
+ .index-link {
169
+ margin-top: 0.5rem;
170
+ }
171
+
172
+ .search-panel {
173
+ margin-bottom: 1rem;
174
+ }
175
+ }
176
+
177
+ @media (prefers-color-scheme: dark) {
178
+ :root {
179
+ --surface: #141c2b;
180
+ --ink: #e3eaf5;
181
+ --muted: #a8b6cc;
182
+ --line: #33415a;
183
+ --accent: #97baff;
184
+ --soft: #222f46;
185
+ }
186
+ }
@@ -0,0 +1,3 @@
1
+ import { defineStylelintConfig } from 'repoctl/tooling'
2
+
3
+ export default await defineStylelintConfig()
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "astro/tsconfigs/strict",
3
+ "compilerOptions": {
4
+ "paths": {
5
+ "@/*": ["./src/*"]
6
+ }
7
+ },
8
+ "include": [".astro/types.d.ts", "**/*"],
9
+ "exclude": ["dist"]
10
+ }
@@ -66,7 +66,7 @@
66
66
  "devDependencies": {
67
67
  "@hono/node-server": "^2.1.3",
68
68
  "consola": "^3.4.2",
69
- "hono": "^4.13.11",
69
+ "hono": "^4.13.12",
70
70
  "tsdown": "0.23.0",
71
71
  "wrangler": "^4.135.0",
72
72
  "zod": "^4.6.5"
@@ -1,11 +1,11 @@
1
1
  import Tailwindcss from '@tailwindcss/vite'
2
2
  import { defineConfig } from 'vitepress'
3
3
  import llmstxt from 'vitepress-plugin-llms'
4
- import { withMermaid } from 'vitepress-plugin-mermaid'
4
+ import { mermaidMarkdown } from './mermaid/markdown'
5
5
  import { createNav, createSidebars } from './navigation/routes'
6
6
  import { createPageHead, siteOrigin } from './seo'
7
7
 
8
- export default withMermaid(defineConfig({
8
+ export default defineConfig({
9
9
  outDir: '.vitepress/dist',
10
10
  srcExclude: ['CHANGELOG.md', 'learn/packages/examples/**', 'zh/learn/packages/examples/**'],
11
11
  title: 'repoctl',
@@ -69,7 +69,6 @@ export default withMermaid(defineConfig({
69
69
  },
70
70
  vite: {
71
71
  plugins: [
72
- // @ts-ignore vitepress-plugin-llms currently exposes Vite 6 plugin types.
73
72
  Tailwindcss(),
74
73
  llmstxt({
75
74
  title: 'repoctl',
@@ -89,5 +88,5 @@ export default withMermaid(defineConfig({
89
88
  }),
90
89
  ],
91
90
  },
92
- mermaid: {},
93
- }))
91
+ markdown: { config: mermaidMarkdown },
92
+ })
@@ -32,7 +32,7 @@ const english = {
32
32
  evidence: {
33
33
  title: 'See the plan before the repository changes',
34
34
  description: 'Preview work, redact local paths, and keep the exact command sequence with the report.',
35
- code: 'pnpm exec repo new docs --template vitepress --dry-run\npnpm exec repo check --dry-run\npnpm exec repo doctor --markdown --redact',
35
+ code: 'pnpm exec repo new docs --template nimbus --dry-run\npnpm exec repo check --dry-run\npnpm exec repo doctor --markdown --redact',
36
36
  link: { label: 'See how diagnosis works', href: '/start/diagnose' },
37
37
  },
38
38
  layers: {
@@ -77,7 +77,7 @@ const chinese = {
77
77
  evidence: {
78
78
  title: '仓库变化前,先看清执行计划',
79
79
  description: '预览操作、脱敏本地路径,并让报告保留准确的命令顺序。',
80
- code: 'pnpm exec repo new docs --template vitepress --dry-run\npnpm exec repo check --dry-run\npnpm exec repo doctor --markdown --redact',
80
+ code: 'pnpm exec repo new docs --template nimbus --dry-run\npnpm exec repo check --dry-run\npnpm exec repo doctor --markdown --redact',
81
81
  link: { label: '了解诊断流程', href: '/zh/start/diagnose' },
82
82
  },
83
83
  layers: {
@@ -0,0 +1,16 @@
1
+ import type { DocsLocale } from '../navigation/routes'
2
+
3
+ export const faqContent = {
4
+ zh: [
5
+ ['repoctl 是什么?', 'repoctl 是面向 pnpm 与 Turborepo monorepo 的任务型 CLI,用于初始化、诊断、创建、校验和发布工作区。'],
6
+ ['如何安装 repoctl?', '在项目中运行 pnpm add -D repoctl,然后使用 pnpm exec repo init 和 pnpm exec repo doctor。'],
7
+ ['repoctl 与 pnpm、Turborepo 是什么关系?', 'pnpm 和 Turborepo 负责包管理与任务编排,repoctl 负责把团队约定、检查和日常操作组织成可复用任务。'],
8
+ ['repo doctor 与 repo check 有什么区别?', 'repo doctor 诊断仓库健康状况,repo check 规划并执行 lint、类型、构建和测试校验。'],
9
+ ],
10
+ en: [
11
+ ['What is repoctl?', 'repoctl is a task-first CLI for initializing, diagnosing, creating, checking, and releasing pnpm and Turborepo monorepos.'],
12
+ ['How do I install repoctl?', 'Run pnpm add -D repoctl, then use pnpm exec repo init and pnpm exec repo doctor.'],
13
+ ['How does repoctl relate to pnpm and Turborepo?', 'pnpm and Turborepo provide package management and task orchestration; repoctl turns repository conventions and checks into reusable tasks.'],
14
+ ['What is the difference between repo doctor and repo check?', 'repo doctor diagnoses repository health, while repo check plans and runs lint, type, build, and test checks.'],
15
+ ],
16
+ } satisfies Record<DocsLocale, [string, string][]>
@@ -0,0 +1,18 @@
1
+ import type { MarkdownRenderer } from 'vitepress'
2
+
3
+ export function mermaidMarkdown(md: MarkdownRenderer) {
4
+ const fence = md.renderer.rules.fence!
5
+ md.renderer.rules.fence = (tokens, index, options, env, renderer) => {
6
+ const token = tokens[index]!
7
+ const language = token.info.trim()
8
+ if (language === 'mermaid') {
9
+ const graph = md.utils.escapeHtml(encodeURIComponent(token.content))
10
+ return `<Mermaid graph="${graph}" />\n`
11
+ }
12
+ // Keep the old plugin's escape hatch for displaying Mermaid source code.
13
+ if (language === 'mmd') {
14
+ token.info = 'mermaid'
15
+ }
16
+ return fence(tokens, index, options, env, renderer)
17
+ }
18
+ }
@@ -0,0 +1,66 @@
1
+ <script setup lang="ts">
2
+ import { useData } from 'vitepress'
3
+ import { computed, onMounted, ref, watch } from 'vue'
4
+ import { renderMermaid, resolveMermaidTheme } from './render'
5
+
6
+ const props = defineProps<{ graph: string }>()
7
+ const { frontmatter, isDark, lang } = useData()
8
+ const mounted = ref(false)
9
+ const diagram = ref<HTMLElement>()
10
+ const error = ref('')
11
+ const loading = ref(true)
12
+ const theme = computed(() => resolveMermaidTheme(isDark.value, frontmatter.value.mermaidTheme))
13
+
14
+ onMounted(() => {
15
+ mounted.value = true
16
+ })
17
+
18
+ watch([mounted, () => props.graph, theme], async ([ready, graph, selectedTheme], _, onCleanup) => {
19
+ if (!ready || !diagram.value) {
20
+ return
21
+ }
22
+ let active = true
23
+ onCleanup(() => {
24
+ active = false
25
+ })
26
+ error.value = ''
27
+ loading.value = true
28
+ diagram.value.replaceChildren()
29
+ try {
30
+ const result = await renderMermaid(decodeURIComponent(graph), selectedTheme)
31
+ if (active && diagram.value) {
32
+ // Mermaid sanitizes SVG using its strict security mode.
33
+ diagram.value.innerHTML = result.svg
34
+ result.bindFunctions?.(diagram.value)
35
+ }
36
+ }
37
+ catch (cause) {
38
+ if (active) {
39
+ error.value = cause instanceof Error ? cause.message : String(cause)
40
+ }
41
+ }
42
+ finally {
43
+ if (active) {
44
+ loading.value = false
45
+ }
46
+ }
47
+ }, { flush: 'post' })
48
+ </script>
49
+
50
+ <template>
51
+ <div class="mermaid" :aria-busy="loading">
52
+ <p v-if="loading" role="status">
53
+ {{ lang === 'zh-CN' ? '正在绘制图表…' : 'Rendering diagram…' }}
54
+ </p>
55
+ <p v-if="error" role="alert">
56
+ {{ lang === 'zh-CN' ? '图表绘制失败:' : 'Unable to render diagram: ' }}{{ error }}
57
+ </p>
58
+ <div ref="diagram" class="mermaid__diagram" />
59
+ </div>
60
+ </template>
61
+
62
+ <style scoped>
63
+ .mermaid__diagram {
64
+ overflow-x: auto;
65
+ }
66
+ </style>
@@ -0,0 +1,37 @@
1
+ import type { MermaidConfig, RenderResult } from 'mermaid'
2
+
3
+ let pending: Promise<unknown> = Promise.resolve()
4
+ let sequence = 0
5
+
6
+ export type MermaidTheme = NonNullable<MermaidConfig['theme']>
7
+ const themes: readonly MermaidTheme[] = ['base', 'dark', 'default', 'forest', 'neutral', 'neo', 'neo-dark', 'redux', 'redux-dark', 'redux-color', 'redux-dark-color', 'null']
8
+
9
+ export function resolveMermaidTheme(dark: boolean, theme: unknown): MermaidTheme {
10
+ if (dark) {
11
+ return 'dark'
12
+ }
13
+ return typeof theme === 'string' && themes.includes(theme as MermaidTheme)
14
+ ? theme as MermaidTheme
15
+ : 'default'
16
+ }
17
+
18
+ export function renderMermaid(source: string, theme: MermaidTheme): Promise<RenderResult> {
19
+ // initialize() changes global state, so it must share the render queue.
20
+ const result = pending.then(async () => {
21
+ const { default: mermaid } = await import('mermaid')
22
+ const container = document.createElement('div')
23
+ container.setAttribute('aria-hidden', 'true')
24
+ container.style.cssText = 'position:absolute;left:-10000px;top:0;'
25
+ document.body.append(container)
26
+ try {
27
+ mermaid.initialize({ startOnLoad: false, securityLevel: 'strict', suppressErrorRendering: true, theme })
28
+ return await mermaid.render(`repoctl-mermaid-${++sequence}`, source, container)
29
+ }
30
+ finally {
31
+ container.remove()
32
+ }
33
+ })
34
+ // A malformed diagram must not block later diagrams.
35
+ pending = result.catch(() => {})
36
+ return result
37
+ }
@@ -1,4 +1,5 @@
1
1
  import type { HeadConfig, TransformContext } from 'vitepress'
2
+ import { faqContent } from './home/faq'
2
3
 
3
4
  export const siteOrigin = 'https://repoctl.icebreaker.top'
4
5
 
@@ -35,6 +36,16 @@ export function createPageHead({ page, title, description }: TransformContext):
35
36
  { '@type': 'Organization', 'name': 'repoctl', 'url': siteOrigin, 'logo': `${siteOrigin}/brand/repoctl-mark.svg`, 'sameAs': ['https://github.com/icelib/repoctl', 'https://www.npmjs.com/package/repoctl'] },
36
37
  { '@type': 'SoftwareApplication', 'name': 'repoctl', 'applicationCategory': 'DeveloperApplication', 'operatingSystem': 'macOS, Linux, Windows', 'url': siteOrigin, description, 'sameAs': ['https://github.com/icelib/repoctl', 'https://www.npmjs.com/package/repoctl'], 'offers': { '@type': 'Offer', 'price': '0', 'priceCurrency': 'USD' } },
37
38
  { '@type': 'WebSite', 'name': 'repoctl', 'url': siteOrigin, 'potentialAction': { '@type': 'SearchAction', 'target': `${siteOrigin}/reference/commands?q={search_term_string}`, 'query-input': 'required name=search_term_string' } },
39
+ ...(page === 'index.md' || page === 'zh/index.md'
40
+ ? [{
41
+ '@type': 'FAQPage',
42
+ 'mainEntity': faqContent[isChinese ? 'zh' : 'en'].map(([name, text]) => ({
43
+ '@type': 'Question',
44
+ name,
45
+ 'acceptedAnswer': { '@type': 'Answer', text },
46
+ })),
47
+ }]
48
+ : []),
38
49
  ],
39
50
  }
40
51
 
@@ -61,6 +72,6 @@ export function createPageHead({ page, title, description }: TransformContext):
61
72
  ['meta', { name: 'twitter:description', content: description }],
62
73
  ['meta', { name: 'twitter:image', content: socialImage }],
63
74
  ['meta', { name: 'twitter:image:alt', content: socialImageAlt }],
64
- ['script', { type: 'application/ld+json' }, JSON.stringify(jsonLd)],
75
+ ['script', { type: 'application/ld+json' }, JSON.stringify(jsonLd).replace(/</g, '\\u003c')],
65
76
  ]
66
77
  }
@@ -2,22 +2,11 @@
2
2
  import type { DocsLocale } from '../../navigation/routes'
3
3
  import { computed } from 'vue'
4
4
  import { homeContent } from '../../home/content'
5
+ import { faqContent } from '../../home/faq'
5
6
 
6
7
  const props = defineProps<{ locale: DocsLocale }>()
7
8
  const content = computed(() => homeContent[props.locale])
8
- const faq = computed(() => props.locale === 'zh'
9
- ? [
10
- ['repoctl 是什么?', 'repoctl 是面向 pnpm 与 Turborepo monorepo 的任务型 CLI,用于初始化、诊断、创建、校验和发布工作区。'],
11
- ['如何安装 repoctl?', '在项目中运行 pnpm add -D repoctl,然后使用 pnpm exec repo init 和 pnpm exec repo doctor。'],
12
- ['repoctl 与 pnpm、Turborepo 是什么关系?', 'pnpm 和 Turborepo 负责包管理与任务编排,repoctl 负责把团队约定、检查和日常操作组织成可复用任务。'],
13
- ['repo doctor 与 repo check 有什么区别?', 'repo doctor 诊断仓库健康状况,repo check 规划并执行 lint、类型、构建和测试校验。'],
14
- ]
15
- : [
16
- ['What is repoctl?', 'repoctl is a task-first CLI for initializing, diagnosing, creating, checking, and releasing pnpm and Turborepo monorepos.'],
17
- ['How do I install repoctl?', 'Run pnpm add -D repoctl, then use pnpm exec repo init and pnpm exec repo doctor.'],
18
- ['How does repoctl relate to pnpm and Turborepo?', 'pnpm and Turborepo provide package management and task orchestration; repoctl turns repository conventions and checks into reusable tasks.'],
19
- ['What is the difference between repo doctor and repo check?', 'repo doctor diagnoses repository health, while repo check plans and runs lint, type, build, and test checks.'],
20
- ])
9
+ const faq = computed(() => faqContent[props.locale])
21
10
  </script>
22
11
 
23
12
  <template>
@@ -129,9 +118,6 @@ const faq = computed(() => props.locale === 'zh'
129
118
  <summary>{{ item[0] }}</summary>
130
119
  <p>{{ item[1] }}</p>
131
120
  </details>
132
- <script type="application/ld+json">
133
- {{ JSON.stringify({ '@context': 'https://schema.org', '@type': 'FAQPage', "mainEntity": faq.map(item => ({ '@type': 'Question', "name": item[0], "acceptedAnswer": { '@type': 'Answer', "text": item[1] } })) }) }}
134
- </script>
135
121
  </section>
136
122
  </main>
137
123
  </template>
@@ -1,14 +1,16 @@
1
1
  import type { EnhanceAppContext, Theme } from 'vitepress'
2
2
  import DefaultTheme from 'vitepress/theme'
3
+ import Mermaid from '../mermaid/mermaid.vue'
3
4
  import HomePage from './components/HomePage.vue'
4
5
  import Layout from './Layout.vue'
5
6
  import './tailwind.css'
6
7
  import './home/index.css'
7
8
 
8
9
  export default {
9
- ...DefaultTheme,
10
+ extends: DefaultTheme,
10
11
  Layout,
11
12
  enhanceApp({ app }: EnhanceAppContext) {
12
13
  app.component('HomePage', HomePage)
14
+ app.component('Mermaid', Mermaid)
13
15
  },
14
16
  } satisfies Theme
@@ -35,7 +35,7 @@ pnpm exec repo doctor
35
35
  | API / Hono service | `hono-server` |
36
36
  | TypeScript library / SDK | `tsdown` |
37
37
  | Vue component library | `vue-lib` |
38
- | Documentation site | `vitepress` |
38
+ | Documentation site | `nimbus` |
39
39
  | CLI | `cli` |
40
40
 
41
41
  Omit `--templates` when the intent is unclear. After init, create a package with:
@@ -34,23 +34,19 @@
34
34
  "deploy:dry-run": "wrangler deploy --dry-run",
35
35
  "lint": "eslint .",
36
36
  "lint:fix": "eslint . --fix",
37
- "typecheck": "vue-tsc -p tsconfig.json --noEmit"
37
+ "typecheck": "vue-tsc -p tsconfig.json --noEmit",
38
+ "test": "vitest run"
38
39
  },
39
40
  "dependencies": {
40
41
  "vue": "^3.5.43"
41
42
  },
42
43
  "devDependencies": {
43
- "@braintree/sanitize-url": "^7.1.2",
44
44
  "@tailwindcss/vite": "^4.3.3",
45
- "cytoscape": "^3.34.3",
46
- "cytoscape-cose-bilkent": "^4.1.0",
47
- "dayjs": "^1.11.23",
48
- "debug": "^4.4.3",
49
45
  "mermaid": "^12.0.0",
50
46
  "tailwindcss": "^4.3.3",
51
- "vitepress": "^1.6.4",
47
+ "vitepress": "2.0.0-alpha.20",
52
48
  "vitepress-plugin-llms": "^1.14.0",
53
- "vitepress-plugin-mermaid": "^2.0.17",
49
+ "vitest": "~5.0.3",
54
50
  "vue-tsc": "^3.3.11",
55
51
  "wrangler": "^4.135.0"
56
52
  }
@@ -64,9 +64,9 @@ Use it to discover built-in template keys, inspect metadata, verify template hea
64
64
  ```bash
65
65
  repo new
66
66
  repo new sdk --template tsdown
67
- repo new docs --template vitepress
68
- repo new docs --template vitepress --dry-run
69
- repo new docs --template vitepress --json --out plans/docs.json
67
+ repo new docs --template nimbus
68
+ repo new docs --template nimbus --dry-run
69
+ repo new docs --template nimbus --json --out plans/docs.json
70
70
  ```
71
71
 
72
72
  Use it to create packages and apps. Explicit template keys are validated first; invalid keys fail with suggestions instead of silently falling back.
@@ -20,7 +20,7 @@ repo templates --category library
20
20
 
21
21
  ```bash
22
22
  repo new sdk --template tsdown --dry-run
23
- repo new docs --template vitepress --json --out plans/docs.json
23
+ repo new docs --template nimbus --json --out plans/docs.json
24
24
  ```
25
25
 
26
26
  A plan records the selected template, source, destination, package name, workspace pattern, and whether fallback behavior was used. Unknown explicit keys fail with a suggestion.
@@ -4,14 +4,17 @@ repoctl templates are maintained by `@icebreakers/monorepo-templates`. The CLI,
4
4
 
5
5
  ## Built-In Templates
6
6
 
7
- | Key | Category | Default target | Use case |
8
- | ------------- | -------- | ------------------ | ----------------------- |
9
- | `tsdown` | library | `packages/tsdown` | TypeScript library |
10
- | `vue-lib` | library | `packages/vue-lib` | Vue 3 component library |
11
- | `vue-hono` | app | `apps/client` | Vue 3 + Hono app |
12
- | `hono-server` | service | `apps/server` | Hono API service |
13
- | `vitepress` | docs | `apps/website` | VitePress docs site |
14
- | `cli` | tool | `apps/cli` | TypeScript CLI |
7
+ | Key | Category | Default target | Use case |
8
+ | ------------- | -------- | ------------------ | -------------------------------------- |
9
+ | `tsdown` | library | `packages/tsdown` | TypeScript library |
10
+ | `vue-lib` | library | `packages/vue-lib` | Vue 3 component library |
11
+ | `vue-hono` | app | `apps/client` | Vue 3 + Hono app |
12
+ | `hono-server` | service | `apps/server` | Hono API service |
13
+ | `vitepress` | docs | `apps/website` | VitePress docs site |
14
+ | `nimbus` | docs | `apps/docs` | Nimbus + Astro, default bilingual docs |
15
+ | `cli` | tool | `apps/cli` | TypeScript CLI |
16
+
17
+ Nimbus is the default for the interactive **Docs Site** goal. It includes English at `/`, Chinese at `/zh/`, search and AI documentation endpoints. VitePress remains an explicit alternative. Their default targets are `apps/docs` and `apps/website`, so both can coexist. General creation still defaults to `tsdown`; naming a project `docs` does not infer its template. In noninteractive workflows, pass `--template nimbus` explicitly.
15
18
 
16
19
  ## Discover Templates
17
20
 
@@ -29,6 +32,7 @@ repo templates --markdown --out docs/templates.md
29
32
  repo new sdk --template tsdown
30
33
  repo new ui --template vue-lib
31
34
  repo new api --template hono-server
35
+ repo new docs --template nimbus
32
36
  repo new website --template vitepress
33
37
  repo new toolbox --template cli
34
38
  ```
@@ -85,7 +89,7 @@ For existing projects, update these scripts and the Turbo inputs/outputs, then r
85
89
  ### Create a docs site
86
90
 
87
91
  ```bash
88
- repo new docs --template vitepress
92
+ repo new docs --template nimbus
89
93
  ```
90
94
 
91
95
  Check first:
@@ -36,7 +36,7 @@ Continue with [Adopt an existing workspace](/tasks/adopt-existing).
36
36
  ```bash
37
37
  pnpm exec repo templates
38
38
  pnpm exec repo new sdk --template tsdown
39
- pnpm exec repo new docs --template vitepress --dry-run
39
+ pnpm exec repo new docs --template nimbus --dry-run
40
40
  ```
41
41
 
42
42
  For daily development, root scripts also work:
@@ -0,0 +1,39 @@
1
+ import { createMarkdownRenderer } from 'vitepress'
2
+ import { describe, expect, it } from 'vitest'
3
+ import { mermaidMarkdown } from '../.vitepress/mermaid/markdown'
4
+ import { resolveMermaidTheme } from '../.vitepress/mermaid/render'
5
+
6
+ describe('Mermaid Markdown integration', () => {
7
+ it('round-trips diagram text without injecting attributes or Vue expressions', async () => {
8
+ const md = await createMarkdownRenderer(process.cwd(), { config: mermaidMarkdown })
9
+ const source = 'flowchart LR\n A["引号 & <tag> {{ value }}"] --> B["it\'s fine"]\n'
10
+ const html = await md.renderAsync(`\`\`\`mermaid\n${source}\`\`\``)
11
+ const graph = html.match(/graph="([^"]+)"/)![1]!
12
+ expect(decodeURIComponent(graph)).toBe(source)
13
+ expect(html).not.toContain('<tag>')
14
+ expect(html).not.toContain('{{ value }}')
15
+ expect(html).toContain('<Mermaid ')
16
+ })
17
+
18
+ it('keeps normal fences and the mmd source-code escape hatch highlighted', async () => {
19
+ const md = await createMarkdownRenderer(process.cwd(), { config: mermaidMarkdown })
20
+ const html = await md.renderAsync('```ts\nconst answer = 42\n```\n\n```mmd\nflowchart LR\n A --> B\n```')
21
+ expect(html).toContain('language-ts')
22
+ expect(html).toContain('language-mermaid')
23
+ expect(html).not.toContain('<Mermaid ')
24
+ expect(html).toContain('shiki')
25
+ })
26
+
27
+ it('renders multiple independent component instances', async () => {
28
+ const md = await createMarkdownRenderer(process.cwd(), { config: mermaidMarkdown })
29
+ const html = await md.renderAsync('```mermaid\nflowchart LR\n A --> B\n```\n\n```mermaid\nsequenceDiagram\n A->>B: Hello\n```')
30
+ expect(html.match(/<Mermaid /g)).toHaveLength(2)
31
+ })
32
+
33
+ it('validates page themes and lets dark mode take precedence', () => {
34
+ expect(resolveMermaidTheme(false, 'forest')).toBe('forest')
35
+ expect(resolveMermaidTheme(true, 'forest')).toBe('dark')
36
+ expect(resolveMermaidTheme(false, { theme: 'forest' })).toBe('default')
37
+ expect(resolveMermaidTheme(false, undefined)).toBe('default')
38
+ })
39
+ })