@icebreakers/monorepo-templates 1.2.0 → 1.3.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 (65) hide show
  1. package/assets/.github/workflows/ci.yml +4 -4
  2. package/assets/.github/workflows/release.yml +10 -7
  3. package/assets/package.json +2 -1
  4. package/assets/pnpm-workspace.yaml +2 -9
  5. package/assets/renovate.json +0 -1
  6. package/assets-data.mjs +0 -1
  7. package/dist/cli.mjs +1 -1
  8. package/dist/index.mjs +1 -1
  9. package/dist/{prepare-CNCmvco-.mjs → prepare-D7eJqEqy.mjs} +21 -2
  10. package/package.json +5 -3
  11. package/templates/cli/package.json +1 -1
  12. package/templates/client/package.json +1 -1
  13. package/templates/server/package.json +1 -1
  14. package/templates/tsdown/package.json +1 -1
  15. package/templates/vitepress/.vitepress/config.ts +9 -119
  16. package/templates/vitepress/.vitepress/env.d.ts +1 -0
  17. package/templates/vitepress/.vitepress/home/content.ts +186 -0
  18. package/templates/vitepress/.vitepress/home/types.ts +65 -0
  19. package/templates/vitepress/.vitepress/navigation/routes.ts +138 -0
  20. package/templates/vitepress/.vitepress/seo.ts +40 -0
  21. package/templates/vitepress/.vitepress/theme/components/HomePage.vue +134 -0
  22. package/templates/vitepress/.vitepress/theme/home/foundation.css +180 -0
  23. package/templates/vitepress/.vitepress/theme/home/index.css +3 -0
  24. package/templates/vitepress/.vitepress/theme/home/responsive.css +49 -0
  25. package/templates/vitepress/.vitepress/theme/home/sections.css +153 -0
  26. package/templates/vitepress/.vitepress/theme/index.ts +9 -1
  27. package/templates/vitepress/ai/llms-txt.md +2 -2
  28. package/templates/vitepress/index.md +6 -60
  29. package/templates/vitepress/package.json +11 -4
  30. package/templates/vitepress/public/repoctl-doctor.png +0 -0
  31. package/templates/vitepress/repoctl/workflows.md +35 -0
  32. package/templates/vitepress/scripts/check-locales.ts +162 -0
  33. package/templates/vitepress/thinking.md +15 -0
  34. package/templates/vitepress/tools/changeset.md +17 -12
  35. package/templates/vitepress/tools/husky.md +41 -3
  36. package/templates/vitepress/tools/lint-staged.md +32 -5
  37. package/templates/vitepress/tools/pnpm.md +39 -7
  38. package/templates/vitepress/tools/renovate.md +27 -3
  39. package/templates/vitepress/tools/turborepo.md +33 -7
  40. package/templates/vitepress/tsconfig.json +7 -0
  41. package/templates/vitepress/why/examples/2.npm-basic-package/README.md +1 -1
  42. package/templates/vitepress/why/examples/3.npm-basic-package/README.md +1 -1
  43. package/templates/vitepress/why/how-to-reuse-js-code.md +36 -6
  44. package/templates/vitepress/why/js-cjs-mjs.md +31 -9
  45. package/templates/vitepress/why/js-keywords.md +33 -4
  46. package/templates/vitepress/why/modern/bundlers.md +37 -2
  47. package/templates/vitepress/why/modern/dts.md +44 -3
  48. package/templates/vitepress/why/modern/esm-vs-cjs.md +38 -3
  49. package/templates/vitepress/why/modern/index.md +33 -9
  50. package/templates/vitepress/why/modern/package-entry-points.md +33 -4
  51. package/templates/vitepress/why/modern/typescript.md +40 -4
  52. package/templates/vitepress/why/publish-basic-npm-package.md +37 -13
  53. package/templates/vitepress/why/what-is-npm-package.md +32 -3
  54. package/templates/vitepress/wrangler.jsonc +20 -3
  55. package/templates/vitepress/zh/ai/index.md +1 -1
  56. package/templates/vitepress/zh/ai/llms-txt.md +1 -1
  57. package/templates/vitepress/zh/index.md +6 -63
  58. package/templates/vitepress/zh/repoctl/workflows.md +35 -0
  59. package/templates/vitepress/zh/thinking.md +9 -6
  60. package/templates/vitepress/zh/why/examples/2.npm-basic-package/README.md +1 -1
  61. package/templates/vitepress/zh/why/examples/3.npm-basic-package/README.md +1 -1
  62. package/templates/vitepress/zh/why/index.md +38 -1
  63. package/templates/vue-lib/package.json +1 -1
  64. package/assets/.changeset/ledger.yaml +0 -51
  65. package/assets/netlify.toml +0 -14
@@ -0,0 +1,153 @@
1
+ .repoctl-home__path-grid,
2
+ .repoctl-home__automation {
3
+ display: grid;
4
+ grid-template-columns: repeat(2, minmax(0, 1fr));
5
+ gap: 20px;
6
+ }
7
+
8
+ .repoctl-home__path-grid article {
9
+ padding: 28px;
10
+ background: color-mix(in srgb, var(--vp-c-bg-soft) 72%, transparent);
11
+ border-left: 3px solid var(--home-accent);
12
+ }
13
+
14
+ .repoctl-home__path-grid h3 {
15
+ font-size: 22px;
16
+ }
17
+
18
+ .repoctl-home__path-grid p,
19
+ .repoctl-home__capability-grid p {
20
+ margin-top: 10px !important;
21
+ }
22
+
23
+ .repoctl-home__path-grid pre,
24
+ .repoctl-home__automation pre,
25
+ .repoctl-home__quickstart pre {
26
+ padding: 16px;
27
+ margin-top: 20px !important;
28
+ overflow-x: auto;
29
+ background: var(--vp-code-block-bg);
30
+ border: 1px solid var(--home-border);
31
+ border-radius: 6px;
32
+ }
33
+
34
+ .repoctl-home__path-grid a {
35
+ display: inline-block;
36
+ margin-top: 18px;
37
+ font-weight: 650;
38
+ }
39
+
40
+ .repoctl-home__capability-grid {
41
+ display: grid;
42
+ grid-template-columns: 1.3fr 0.7fr;
43
+ gap: 1px;
44
+ background: var(--home-border);
45
+ border: 1px solid var(--home-border);
46
+ }
47
+
48
+ .repoctl-home__capability-grid article {
49
+ min-height: 152px;
50
+ padding: 24px;
51
+ background: var(--vp-c-bg);
52
+ }
53
+
54
+ .repoctl-home__capability-grid article:nth-child(2),
55
+ .repoctl-home__capability-grid article:nth-child(3) {
56
+ background: var(--vp-c-bg-soft);
57
+ }
58
+
59
+ .repoctl-home__commands dl {
60
+ display: grid;
61
+ grid-template-columns: repeat(2, minmax(0, 1fr));
62
+ gap: 0 32px;
63
+ margin: 0;
64
+ }
65
+
66
+ .repoctl-home__commands dl > div {
67
+ display: flex;
68
+ gap: 16px;
69
+ justify-content: space-between;
70
+ padding: 16px 0;
71
+ border-bottom: 1px solid var(--home-border);
72
+ }
73
+
74
+ .repoctl-home__commands dd {
75
+ margin: 0;
76
+ color: var(--home-muted);
77
+ text-align: right;
78
+ }
79
+
80
+ .repoctl-home__automation {
81
+ align-items: center;
82
+ padding: 38px;
83
+ background: color-mix(in srgb, var(--vp-c-bg-soft) 80%, transparent);
84
+ border: 1px solid var(--home-border);
85
+ border-radius: 6px;
86
+ }
87
+
88
+ .repoctl-home__automation ul {
89
+ display: flex;
90
+ flex-wrap: wrap;
91
+ gap: 8px 18px;
92
+ padding: 0;
93
+ color: var(--home-muted);
94
+ list-style: none;
95
+ }
96
+
97
+ .repoctl-home__automation li::before {
98
+ display: inline-block;
99
+ width: 6px;
100
+ height: 6px;
101
+ margin-right: 7px;
102
+ content: ' ';
103
+ background: var(--home-accent);
104
+ border-radius: 50%;
105
+ }
106
+
107
+ .repoctl-home__automation pre,
108
+ .repoctl-home__quickstart pre {
109
+ margin: 0 !important;
110
+ }
111
+
112
+ .repoctl-home__quickstart {
113
+ display: grid;
114
+ grid-template-columns: 0.85fr 1.15fr;
115
+ gap: 24px;
116
+ align-items: end;
117
+ }
118
+
119
+ .repoctl-home__quickstart .repoctl-home__button {
120
+ grid-column: 2;
121
+ justify-self: start;
122
+ }
123
+
124
+ .repoctl-home__docs > div {
125
+ display: grid;
126
+ grid-template-columns: repeat(3, minmax(0, 1fr));
127
+ gap: 1px;
128
+ background: var(--home-border);
129
+ border: 1px solid var(--home-border);
130
+ }
131
+
132
+ .repoctl-home__docs a {
133
+ min-height: 144px;
134
+ padding: 20px;
135
+ text-decoration: none !important;
136
+ background: var(--vp-c-bg);
137
+ }
138
+
139
+ .repoctl-home__docs a:hover {
140
+ background: var(--vp-c-bg-soft);
141
+ }
142
+
143
+ .repoctl-home__docs strong,
144
+ .repoctl-home__docs span {
145
+ display: block;
146
+ }
147
+
148
+ .repoctl-home__docs span {
149
+ margin-top: 8px;
150
+ font-size: 14px;
151
+ line-height: 1.55;
152
+ color: var(--home-muted);
153
+ }
@@ -1,4 +1,12 @@
1
+ import type { EnhanceAppContext, Theme } from 'vitepress'
1
2
  import DefaultTheme from 'vitepress/theme'
3
+ import HomePage from './components/HomePage.vue'
2
4
  import './tailwind.css'
5
+ import './home/index.css'
3
6
 
4
- export default DefaultTheme
7
+ export default {
8
+ ...DefaultTheme,
9
+ enhanceApp({ app }: EnhanceAppContext) {
10
+ app.component('HomePage', HomePage)
11
+ },
12
+ } satisfies Theme
@@ -9,8 +9,8 @@ The documentation build generates `/llms.txt` and `/llms-full.txt`. English is t
9
9
  ## Usage
10
10
 
11
11
  ```text
12
- https://repo.icebreaker.top/llms.txt
13
- https://repo.icebreaker.top/llms-full.txt
12
+ https://repoctl.icebreaker.top/llms.txt
13
+ https://repoctl.icebreaker.top/llms-full.txt
14
14
  ```
15
15
 
16
16
  Use the compact index for discovery and the full file only when the consumer has enough context budget.
@@ -1,63 +1,9 @@
1
1
  ---
2
- layout: home
3
-
4
- hero:
5
- name: repoctl
6
- text: Short, stable workflows for pnpm monorepos
7
- tagline: Initialize, diagnose, scaffold, verify, and upgrade practical Turborepo workspaces from one CLI.
8
- image:
9
- src: /logo.jpg
10
- alt: repoctl logo
11
- actions:
12
- - theme: brand
13
- text: Get Started
14
- link: /repoctl/getting-started
15
- - theme: alt
16
- text: Commands
17
- link: /repoctl/commands
18
- - theme: alt
19
- text: AI Docs
20
- link: /ai/
21
-
22
- features:
23
- - title: Adopt existing workspaces
24
- details: Run setup and doctor to align workspace patterns, root scripts, config files, and commit hooks.
25
- - title: Create packages and apps
26
- details: Scaffold libraries, apps, services, docs, and CLIs with interactive prompts or explicit template keys.
27
- - title: Make checks repeatable
28
- details: Preview and run local verification plans that match your lint, typecheck, build, test, and tsd workflow.
29
- - title: AI-readable documentation
30
- details: Generate llms.txt, llms-full.txt, and Markdown page output so AI coding tools can read the repoctl docs directly.
2
+ layout: page
3
+ aside: false
4
+ pageClass: repoctl-home-page
5
+ title: repoctl
6
+ description: Task-first CLI for initializing, maintaining, validating, and releasing pnpm and Turborepo monorepos.
31
7
  ---
32
8
 
33
- ## Fast Path
34
-
35
- ```bash
36
- pnpm add -D repoctl
37
- pnpm exec repo init
38
- pnpm exec repo doctor
39
- pnpm exec repo new
40
- pnpm exec repo check
41
- ```
42
-
43
- Generated repositories expose the same workflow as shorter root scripts:
44
-
45
- ```bash
46
- pnpm run repo:init
47
- pnpm run repo:doctor
48
- pnpm run repo:new
49
- pnpm run repo:check
50
- ```
51
-
52
- ## Keep Reading
53
-
54
- - [repoctl Overview](./repoctl/index.md)
55
- - [Getting Started](./repoctl/getting-started.md)
56
- - [Adopt Existing Repositories](./repoctl/adopt-existing.md)
57
- - [Choose By Scenario](./repoctl/scenarios.md)
58
- - [Command Reference](./repoctl/commands.md)
59
- - [Configuration](./repoctl/config.md)
60
- - [Templates](./repoctl/templates.md)
61
- - [Workflows and CI](./repoctl/workflows.md)
62
- - [AI Docs: llms.txt](./ai/llms-txt.md)
63
- - [Knowledge Base](./knowledge/index.md)
9
+ <HomePage locale="en" />
@@ -5,7 +5,7 @@
5
5
  "private": true,
6
6
  "description": "Documentation website for repoctl",
7
7
  "author": "ice breaker <hi@sonofmagic.top>",
8
- "homepage": "https://repo.icebreaker.top",
8
+ "homepage": "https://repoctl.icebreaker.top",
9
9
  "repository": {
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/sonofmagic/repoctl.git",
@@ -22,11 +22,15 @@
22
22
  ],
23
23
  "scripts": {
24
24
  "dev": "vitepress dev",
25
- "build": "vitepress build",
25
+ "check:locales": "tsx scripts/check-locales.ts",
26
+ "build": "pnpm run check:locales && vitepress build",
26
27
  "preview": "vitepress preview",
28
+ "deploy": "wrangler deploy",
29
+ "deploy:preview": "wrangler versions upload",
30
+ "deploy:dry-run": "wrangler deploy --dry-run",
27
31
  "lint": "eslint .",
28
32
  "lint:fix": "eslint . --fix",
29
- "typecheck": "tsc -p tsconfig.json"
33
+ "typecheck": "vue-tsc -p tsconfig.json --noEmit"
30
34
  },
31
35
  "dependencies": {
32
36
  "vue": "^3.5.41"
@@ -40,8 +44,11 @@
40
44
  "debug": "^4.4.3",
41
45
  "mermaid": "^11.16.1",
42
46
  "tailwindcss": "^4.3.3",
47
+ "tsx": "^4.23.12",
43
48
  "vitepress": "^1.6.4",
44
49
  "vitepress-plugin-llms": "^1.13.5",
45
- "vitepress-plugin-mermaid": "^2.0.17"
50
+ "vitepress-plugin-mermaid": "^2.0.17",
51
+ "vue-tsc": "^3.3.10",
52
+ "wrangler": "^4.123.0"
46
53
  }
47
54
  }
@@ -82,6 +82,41 @@ repo verify pre-push
82
82
  | Output for scripts | `--json --out <file>` |
83
83
  | Share a redacted report | `--markdown --redact --out <file>` |
84
84
 
85
+ ## Documentation Worker
86
+
87
+ The repoctl documentation is deployed as the `repoctl-docs` Cloudflare Worker. VitePress produces static assets, and Workers Static Assets serves them without an application handler or an `ASSETS` binding.
88
+
89
+ ### Workers Builds settings
90
+
91
+ | Setting | Value |
92
+ | --------------------- | ------------------------------------------------------- |
93
+ | Repository root | Repository root |
94
+ | Production branch | `main` |
95
+ | Build command | `pnpm --filter @icebreakers/website build` |
96
+ | Production deploy | `pnpm --filter @icebreakers/website run deploy` |
97
+ | Non-production deploy | `pnpm --filter @icebreakers/website run deploy:preview` |
98
+
99
+ The build command checks locale parity before VitePress generates `.vitepress/dist`. The Worker config serves the generated `404.html` for missing routes and keeps `public/_redirects` active for legacy `/en/*` links.
100
+
101
+ ### Preview, release, and rollback
102
+
103
+ Run a local validation before changing production:
104
+
105
+ ```bash
106
+ pnpm --filter @icebreakers/website build
107
+ pnpm --filter @icebreakers/website run deploy:dry-run
108
+ pnpm --filter @icebreakers/website exec wrangler dev
109
+ ```
110
+
111
+ Non-production Workers Builds upload a preview version. Promote a validated build through the production deploy command. To roll back, inspect the version history and select the last known-good version:
112
+
113
+ ```bash
114
+ pnpm --filter @icebreakers/website exec wrangler versions list
115
+ pnpm --filter @icebreakers/website exec wrangler rollback <VERSION_ID>
116
+ ```
117
+
118
+ `repoctl.icebreaker.top` is the canonical custom domain. Cloudflare Redirect Rules send `repo.icebreaker.top/*` and `monorepo.icebreaker.top/*` to the canonical host with a permanent redirect while preserving the path and query string.
119
+
85
120
  ## Keep Reading
86
121
 
87
122
  - [Command Reference](./commands.md)
@@ -0,0 +1,162 @@
1
+ import { readdir, readFile, stat } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+ import { homeContent } from '../.vitepress/home/content'
5
+
6
+ const root = process.cwd()
7
+ const ignoredDirectories = new Set(['.vitepress', 'node_modules'])
8
+ const ignoredFiles = new Set(['CHANGELOG.md'])
9
+
10
+ function toPosixPath(value: string) {
11
+ return value.replaceAll('\\', '/')
12
+ }
13
+
14
+ function relativePath(file: string) {
15
+ return toPosixPath(path.relative(root, file))
16
+ }
17
+
18
+ async function markdownFiles(directory: string): Promise<string[]> {
19
+ const entries = await readdir(directory)
20
+ const files = await Promise.all(entries.map(async (entry) => {
21
+ if (ignoredDirectories.has(entry)) {
22
+ return []
23
+ }
24
+ const absolute = path.join(directory, entry)
25
+ const metadata = await stat(absolute)
26
+ if (metadata.isDirectory()) {
27
+ return markdownFiles(absolute)
28
+ }
29
+ return entry.endsWith('.md') && !ignoredFiles.has(entry) ? [absolute] : []
30
+ }))
31
+ return files.flat()
32
+ }
33
+
34
+ function normalizeRoute(route: string) {
35
+ const withoutHash = toPosixPath(route).split('#', 1)[0]!.split('?', 1)[0]!
36
+ const withoutLocale = withoutHash.replace(/^\/zh(?=\/|$)/, '')
37
+ return withoutLocale.replace(/\/index(?:\.md)?$/, '/').replace(/\.md$/, '').replace(/\/$/, '') || '/'
38
+ }
39
+
40
+ function localLinks(markdown: string) {
41
+ const links = [...markdown.matchAll(/\[[^\]]+\]\(([^)\s]+)(?:\s[^)]*)?\)/g)]
42
+ .map(match => match[1]!)
43
+ .filter(link => !/^(?:https?:|mailto:|#)/.test(link))
44
+ return links
45
+ }
46
+
47
+ function headingLevels(markdown: string) {
48
+ const withoutCode = markdown.replace(/```[\s\S]*?```/g, '')
49
+ return [...withoutCode.matchAll(/^(#{1,6})\s+(?:\S.*|[\t\v\f \xA0\u1680\u2000-\u200A\u202F\u205F\u3000\uFEFF])$/gm)].map(match => match[1]!.length)
50
+ }
51
+
52
+ function fencedLanguages(markdown: string) {
53
+ return [...markdown.matchAll(/^```([^\s`]*)/gm)]
54
+ .map(match => match[1]!)
55
+ .filter((_, index) => index % 2 === 0)
56
+ }
57
+
58
+ function contentKeys(value: unknown, prefix = ''): string[] {
59
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
60
+ return [prefix]
61
+ }
62
+ return Object.entries(value).flatMap(([key, nested]) => contentKeys(nested, prefix ? `${prefix}.${key}` : key))
63
+ }
64
+
65
+ async function existsAtLocale(sourceFile: string, href: string) {
66
+ const route = href.split('#', 1)[0]!.split('?', 1)[0]!
67
+ const resolved = route.startsWith('/')
68
+ ? path.join(root, route.replace(/^\//, ''))
69
+ : path.resolve(path.dirname(sourceFile), route)
70
+ const candidates = route.endsWith('/')
71
+ ? [path.join(resolved, 'index.md')]
72
+ : [resolved, `${resolved}.md`, path.join(resolved, 'index.md')]
73
+ return (await Promise.all(candidates.map(async (candidate) => {
74
+ try {
75
+ return (await stat(candidate)).isFile()
76
+ }
77
+ catch {
78
+ return false
79
+ }
80
+ }))).some(Boolean)
81
+ }
82
+
83
+ function validateDocument(relativePath: string, markdown: string) {
84
+ const errors: string[] = []
85
+ const isHome = relativePath === 'index.md' || relativePath === 'zh/index.md'
86
+ const isExampleReadme = /(?:^|\/)why\/examples\/.*\/README\.md$/.test(relativePath)
87
+ if (isHome || isExampleReadme) {
88
+ return errors
89
+ }
90
+ const headings = headingLevels(markdown)
91
+ if (headings.filter(level => level === 1).length !== 1) {
92
+ errors.push(`${relativePath}: expected one H1`)
93
+ }
94
+ for (let index = 1; index < headings.length; index += 1) {
95
+ if (headings[index]! > headings[index - 1]! + 1) {
96
+ errors.push(`${relativePath}: heading level jumps from H${headings[index - 1]} to H${headings[index]}`)
97
+ }
98
+ }
99
+ const fenceLines = [...markdown.matchAll(/^```([^\s`]*)/gm)]
100
+ if (fenceLines.length % 2 !== 0) {
101
+ errors.push(`${relativePath}: unbalanced code fence`)
102
+ }
103
+ for (const language of fencedLanguages(markdown)) {
104
+ if (!language) {
105
+ errors.push(`${relativePath}: code fences require a language marker`)
106
+ }
107
+ }
108
+ return errors
109
+ }
110
+
111
+ const files = await markdownFiles(root)
112
+ const englishFiles = files.filter(file => !relativePath(file).startsWith('zh/'))
113
+ const chineseFiles = files.filter(file => relativePath(file).startsWith('zh/'))
114
+ const englishPaths = new Set(englishFiles.map(relativePath))
115
+ const chinesePaths = new Set(chineseFiles.map(file => relativePath(file).replace(/^zh\//, '')))
116
+ const errors: string[] = []
117
+
118
+ for (const relativePath of englishPaths) {
119
+ if (!chinesePaths.has(relativePath)) {
120
+ errors.push(`missing Chinese counterpart: zh/${relativePath}`)
121
+ }
122
+ }
123
+ for (const relativePath of chinesePaths) {
124
+ if (!englishPaths.has(relativePath)) {
125
+ errors.push(`missing English counterpart: ${relativePath}`)
126
+ }
127
+ }
128
+
129
+ for (const file of files) {
130
+ const relative = relativePath(file)
131
+ const markdown = await readFile(file, 'utf8')
132
+ errors.push(...validateDocument(relative, markdown))
133
+ const links = localLinks(markdown)
134
+ for (const href of links) {
135
+ if (!await existsAtLocale(file, href)) {
136
+ errors.push(`${relative}: unresolved internal link ${href}`)
137
+ }
138
+ }
139
+
140
+ const counterpart = relative.startsWith('zh/')
141
+ ? path.join(root, relative.slice(3))
142
+ : path.join(root, 'zh', relative)
143
+ const counterpartRoute = `/${normalizeRoute(relativePath(counterpart))}`
144
+ if (await existsAtLocale(file, counterpartRoute === '/' ? '/' : counterpartRoute)) {
145
+ continue
146
+ }
147
+ errors.push(`${relative}: counterpart route cannot be resolved`)
148
+ }
149
+
150
+ const englishKeys = contentKeys(homeContent.en).sort()
151
+ const chineseKeys = contentKeys(homeContent.zh).sort()
152
+ if (englishKeys.join('\n') !== chineseKeys.join('\n')) {
153
+ errors.push('homepage locale content keys differ')
154
+ }
155
+
156
+ if (errors.length > 0) {
157
+ console.error(`Locale parity failed with ${errors.length} issue(s):\n${errors.map(error => `- ${error}`).join('\n')}`)
158
+ process.exitCode = 1
159
+ }
160
+ else {
161
+ console.log(`Locale parity passed for ${englishPaths.size} English and ${chinesePaths.size} Chinese Markdown pages.`)
162
+ }
@@ -5,3 +5,18 @@ This repository began as a reusable monorepo template. It has since evolved into
5
5
  Templates remain an important capability, but they are now private source workspaces packaged through `@icebreakers/monorepo-templates`, not the product identity or independent release units.
6
6
 
7
7
  See the [repoctl overview](./repoctl/index.md) for the current architecture and public entrypoints.
8
+
9
+ ## Documentation Deployment
10
+
11
+ The documentation is a VitePress static site deployed as a Cloudflare Worker named `repoctl-docs`. Workers Static Assets serves the generated `.vitepress/dist` directory directly, including the custom 404 page and `_redirects` compatibility rules. There is no application Worker handler because the site has no dynamic request path.
12
+
13
+ Cloudflare Workers Builds uses the repository root and runs these commands:
14
+
15
+ ```bash
16
+ pnpm --filter @icebreakers/website build
17
+ pnpm --filter @icebreakers/website run deploy
18
+ ```
19
+
20
+ Non-production branches upload preview versions with `pnpm --filter @icebreakers/website run deploy:preview`. Before a production change, `pnpm --filter @icebreakers/website run deploy:dry-run` validates the asset manifest and Wrangler configuration. A production rollback uses `wrangler rollback <VERSION_ID>` after reviewing `wrangler versions list`.
21
+
22
+ The canonical host is `https://repoctl.icebreaker.top`. The legacy `repo.icebreaker.top` and `monorepo.icebreaker.top` hosts are redirect-only entrypoints and preserve the original path and query string.
@@ -1,25 +1,30 @@
1
1
  # pnpm Versioning
2
2
 
3
- This repository uses pnpm change intents and workspace versioning for releases.
3
+ repoctl uses pnpm's native versioning workflow. A change intent records why a publishable package needs a release, and the repository ledger keeps that intent with the source change.
4
4
 
5
- ## Daily Development
5
+ ## Record release intent
6
6
 
7
7
  ```bash
8
8
  pnpm change
9
- pnpm change status
9
+ pnpm version -r
10
+ pnpm publish -r
10
11
  ```
11
12
 
12
- Choose every publishable package whose public behavior changed and record the correct patch, minor, or major level. Private template workspaces are not release units.
13
+ Choose the smallest version bump that matches the public contract. Removing a managed asset from a published template package is a minor change because consumers no longer receive that asset. Metadata-only URL changes are patch changes.
13
14
 
14
- ## Release
15
+ ## Fixed packages
15
16
 
16
- ```bash
17
- repo release stable prepare
18
- repo release stable publish
19
- ```
17
+ Some packages version together because they share a public contract. The versioning configuration defines those groups; do not manually align versions in package manifests.
18
+
19
+ ## Before publishing
20
20
 
21
- repoctl coordinates the release workflow, validates configuration, and publishes metadata. pnpm owns the versioning state and repository changelog storage.
21
+ 1. Build the affected packages.
22
+ 2. Run lint and typecheck.
23
+ 3. Run unit, integration, and end-to-end tests where available.
24
+ 4. Confirm the generated changelog and package contents match the intent.
22
25
 
23
- ## Prerelease Lanes
26
+ ## Keep Reading
24
27
 
25
- Use `repo release pre enter <tag>`, `repo release pre publish`, and `repo release pre exit` for alpha, beta, rc, or next lanes.
28
+ - [Publishing and Changelogs](../monorepo/publish.md)
29
+ - [Workflows and CI](../repoctl/workflows.md)
30
+ - [Template Assets](../repoctl/template-assets.md)
@@ -1,12 +1,50 @@
1
1
  # Husky
2
2
 
3
- Husky connects repository scripts to Git hooks. repoctl-managed workspaces use it as a thin entrypoint, keeping verification logic in versioned commands.
3
+ Husky connects Git hooks to versioned repository scripts. In a repoctl workspace it is only a trigger: the actual verification lives in `repo verify` so people, CI, and hooks use the same contract.
4
+
5
+ ## Recommended hooks
4
6
 
5
7
  ```sh
6
8
  # .husky/pre-commit
7
9
  pnpm exec repo verify pre-commit
10
+
11
+ # .husky/commit-msg
12
+ pnpm exec repo verify commit-msg "$1"
13
+
14
+ # .husky/pre-push
15
+ pnpm exec repo verify pre-push
8
16
  ```
9
17
 
10
- Use `commit-msg` for commitlint and `pre-push` for broader build and test verification. Hooks should remain fast enough for local work and should not contain duplicated task logic.
18
+ Keep hooks small. A hook should select a repository task, not duplicate lint, typecheck, or build commands that will drift from CI.
19
+
20
+ ## What each hook protects
21
+
22
+ | Hook | Purpose | Usual scope |
23
+ | ------------ | ------------------------------------ | --------------------------------------------------- |
24
+ | `pre-commit` | Fast feedback before a commit exists | Staged formatting, lint, and typecheck |
25
+ | `commit-msg` | Enforce the commit message contract | The pending commit message file |
26
+ | `pre-push` | Catch broader integration failures | Root checks and affected build, test, and tsd tasks |
27
+
28
+ `repo doctor` reports incomplete Husky and lint-staged configuration. Use `repo upgrade --no-overwrite` to preview managed hook changes before accepting them.
29
+
30
+ ## Install and initialize
31
+
32
+ ```bash
33
+ pnpm add -D husky
34
+ pnpm exec husky init
35
+ ```
36
+
37
+ The initializer creates `.husky/pre-commit`. Replace its generated command with the repository task instead of adding a second tool-specific pipeline.
38
+
39
+ ## Troubleshooting
40
+
41
+ - Confirm the hook file is executable after a fresh clone.
42
+ - Run the command from the hook directly before debugging Git.
43
+ - Do not use interactive prompts in a hook.
44
+ - Keep network calls and release work out of `pre-commit`.
45
+
46
+ ## Keep Reading
11
47
 
12
- `repo doctor` reports when Husky and lint-staged are only partially configured. `repo upgrade` can synchronize the managed defaults.
48
+ - [lint-staged](./lint-staged.md)
49
+ - [Workflows and CI](../repoctl/workflows.md)
50
+ - [Verification](../repoctl/checks.md)
@@ -1,12 +1,39 @@
1
1
  # lint-staged
2
2
 
3
- lint-staged runs checks only for files included in the next commit.
3
+ lint-staged runs focused checks against files staged for a commit. It keeps the pre-commit hook quick while preserving the same ESLint, Stylelint, and typecheck rules used elsewhere in the workspace.
4
4
 
5
- The repository policy routes JavaScript, TypeScript, and Vue files through ESLint, style files through Stylelint, and TypeScript or Vue files to the owning workspace's typecheck script.
5
+ ## Recommended responsibility
6
6
 
7
- ```bash
7
+ Use lint-staged for checks that can safely operate on selected files. Keep full builds, integration tests, and release checks in `repo check --full` or CI.
8
+
9
+ ```js
10
+ // lint-staged.config.js
11
+ export default {
12
+ '*.{js,mjs,cjs,ts,tsx,vue}': ['eslint --fix'],
13
+ '*.{css,scss,less,vue}': ['stylelint --fix'],
14
+ }
15
+ ```
16
+
17
+ repoctl-managed workspaces also route staged TypeScript and Vue files into the appropriate workspace typecheck command. This matters because Vue packages need `vue-tsc`, while TypeScript-only packages use `tsc`.
18
+
19
+ ## Pair it with Husky
20
+
21
+ ```sh
22
+ # .husky/pre-commit
8
23
  pnpm exec repo verify pre-commit
9
- pnpm exec repo verify staged-typecheck <files...>
10
24
  ```
11
25
 
12
- Vue workspaces should use `vue-tsc`; plain TypeScript workspaces should use `tsc`. Keep the routing in `lint-staged.config.js` so local hooks and CI can share the same contract.
26
+ The hook selects repoctl's pre-commit workflow. That workflow can call lint-staged without embedding a second copy of the configuration in the hook file.
27
+
28
+ ## Safe fixes
29
+
30
+ - Use `--fix` only for deterministic formatting and lint repairs.
31
+ - Do not stage unrelated generated output from a task.
32
+ - Run `pnpm lint` before opening a pull request because it covers more than staged files.
33
+ - Test hook behavior from a clean index, not only with already-staged fixes.
34
+
35
+ ## Keep Reading
36
+
37
+ - [Husky](./husky.md)
38
+ - [Verification](../repoctl/checks.md)
39
+ - [Workflows and CI](../repoctl/workflows.md)