@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.
- package/assets/.github/workflows/ci.yml +4 -4
- package/assets/.github/workflows/release.yml +10 -7
- package/assets/package.json +2 -1
- package/assets/pnpm-workspace.yaml +2 -9
- package/assets/renovate.json +0 -1
- package/assets-data.mjs +0 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{prepare-CNCmvco-.mjs → prepare-D7eJqEqy.mjs} +21 -2
- package/package.json +5 -3
- package/templates/cli/package.json +1 -1
- package/templates/client/package.json +1 -1
- package/templates/server/package.json +1 -1
- package/templates/tsdown/package.json +1 -1
- package/templates/vitepress/.vitepress/config.ts +9 -119
- package/templates/vitepress/.vitepress/env.d.ts +1 -0
- package/templates/vitepress/.vitepress/home/content.ts +186 -0
- package/templates/vitepress/.vitepress/home/types.ts +65 -0
- package/templates/vitepress/.vitepress/navigation/routes.ts +138 -0
- package/templates/vitepress/.vitepress/seo.ts +40 -0
- package/templates/vitepress/.vitepress/theme/components/HomePage.vue +134 -0
- package/templates/vitepress/.vitepress/theme/home/foundation.css +180 -0
- package/templates/vitepress/.vitepress/theme/home/index.css +3 -0
- package/templates/vitepress/.vitepress/theme/home/responsive.css +49 -0
- package/templates/vitepress/.vitepress/theme/home/sections.css +153 -0
- package/templates/vitepress/.vitepress/theme/index.ts +9 -1
- package/templates/vitepress/ai/llms-txt.md +2 -2
- package/templates/vitepress/index.md +6 -60
- package/templates/vitepress/package.json +11 -4
- package/templates/vitepress/public/repoctl-doctor.png +0 -0
- package/templates/vitepress/repoctl/workflows.md +35 -0
- package/templates/vitepress/scripts/check-locales.ts +162 -0
- package/templates/vitepress/thinking.md +15 -0
- package/templates/vitepress/tools/changeset.md +17 -12
- package/templates/vitepress/tools/husky.md +41 -3
- package/templates/vitepress/tools/lint-staged.md +32 -5
- package/templates/vitepress/tools/pnpm.md +39 -7
- package/templates/vitepress/tools/renovate.md +27 -3
- package/templates/vitepress/tools/turborepo.md +33 -7
- package/templates/vitepress/tsconfig.json +7 -0
- package/templates/vitepress/why/examples/2.npm-basic-package/README.md +1 -1
- package/templates/vitepress/why/examples/3.npm-basic-package/README.md +1 -1
- package/templates/vitepress/why/how-to-reuse-js-code.md +36 -6
- package/templates/vitepress/why/js-cjs-mjs.md +31 -9
- package/templates/vitepress/why/js-keywords.md +33 -4
- package/templates/vitepress/why/modern/bundlers.md +37 -2
- package/templates/vitepress/why/modern/dts.md +44 -3
- package/templates/vitepress/why/modern/esm-vs-cjs.md +38 -3
- package/templates/vitepress/why/modern/index.md +33 -9
- package/templates/vitepress/why/modern/package-entry-points.md +33 -4
- package/templates/vitepress/why/modern/typescript.md +40 -4
- package/templates/vitepress/why/publish-basic-npm-package.md +37 -13
- package/templates/vitepress/why/what-is-npm-package.md +32 -3
- package/templates/vitepress/wrangler.jsonc +20 -3
- package/templates/vitepress/zh/ai/index.md +1 -1
- package/templates/vitepress/zh/ai/llms-txt.md +1 -1
- package/templates/vitepress/zh/index.md +6 -63
- package/templates/vitepress/zh/repoctl/workflows.md +35 -0
- package/templates/vitepress/zh/thinking.md +9 -6
- package/templates/vitepress/zh/why/examples/2.npm-basic-package/README.md +1 -1
- package/templates/vitepress/zh/why/examples/3.npm-basic-package/README.md +1 -1
- package/templates/vitepress/zh/why/index.md +38 -1
- package/templates/vue-lib/package.json +1 -1
- package/assets/.changeset/ledger.yaml +0 -51
- 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
|
|
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://
|
|
13
|
-
https://
|
|
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:
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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://
|
|
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
|
-
"
|
|
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
|
}
|
|
Binary file
|
|
@@ -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
|
-
|
|
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
|
-
##
|
|
5
|
+
## Record release intent
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
pnpm change
|
|
9
|
-
pnpm
|
|
9
|
+
pnpm version -r
|
|
10
|
+
pnpm publish -r
|
|
10
11
|
```
|
|
11
12
|
|
|
12
|
-
Choose
|
|
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
|
-
##
|
|
15
|
+
## Fixed packages
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
26
|
+
## Keep Reading
|
|
24
27
|
|
|
25
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
5
|
+
## Recommended responsibility
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
|
|
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)
|