@icebreakers/monorepo-templates 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/.github/workflows/release.yml +42 -2
- package/assets/commitlint.config.ts +1 -3
- package/assets/eslint.config.js +2 -14
- package/assets/lint-staged.config.js +1 -3
- package/assets/package.json +8 -6
- package/assets/stylelint.config.js +1 -3
- package/assets/tsconfig.json +2 -1
- package/assets/vitest.config.ts +1 -3
- package/assets-data.mjs +0 -1
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +12 -0
- package/dist/index.mjs +1 -260
- package/dist/prepare-CtCifHGT.mjs +306 -0
- package/package.json +2 -2
- package/templates/client/package.json +1 -1
- package/templates/vitepress/.vitepress/config.ts +142 -69
- package/templates/vitepress/en/index.md +57 -0
- package/templates/vitepress/en/knowledge/monorepo.md +37 -0
- package/templates/vitepress/en/repoctl/adopt-existing.md +131 -0
- package/templates/vitepress/en/repoctl/aliases.md +71 -0
- package/templates/vitepress/en/repoctl/commands.md +120 -0
- package/templates/vitepress/en/repoctl/config.md +69 -0
- package/templates/vitepress/en/repoctl/getting-started.md +116 -0
- package/templates/vitepress/en/repoctl/index.md +74 -0
- package/templates/vitepress/en/repoctl/scenarios.md +117 -0
- package/templates/vitepress/en/repoctl/templates.md +128 -0
- package/templates/vitepress/en/repoctl/troubleshooting.md +96 -0
- package/templates/vitepress/en/repoctl/workflows.md +89 -0
- package/templates/vitepress/getting-started.md +21 -224
- package/templates/vitepress/index.md +41 -176
- package/templates/vitepress/monorepo/commands.md +13 -283
- package/templates/vitepress/monorepo/manage.md +16 -3
- package/templates/vitepress/monorepo/publish.md +52 -6
- package/templates/vitepress/monorepo/templates.md +14 -60
- package/templates/vitepress/monorepo/troubleshooting.md +14 -220
- package/templates/vitepress/repoctl/adopt-existing.md +135 -0
- package/templates/vitepress/repoctl/aliases.md +71 -0
- package/templates/vitepress/repoctl/commands.md +154 -0
- package/templates/vitepress/repoctl/config.md +81 -0
- package/templates/vitepress/repoctl/getting-started.md +122 -0
- package/templates/vitepress/repoctl/index.md +74 -0
- package/templates/vitepress/repoctl/scenarios.md +119 -0
- package/templates/vitepress/repoctl/templates.md +156 -0
- package/templates/vitepress/repoctl/troubleshooting.md +98 -0
- package/templates/vitepress/repoctl/workflows.md +105 -0
- package/templates/vitepress/tools/changeset.md +11 -0
- package/templates/vitepress/tools/husky.md +17 -0
- package/templates/vitepress/tools/lint-staged.md +10 -0
- package/templates/vitepress/tools/pnpm.md +26 -0
- package/templates/vitepress/tools/turborepo.md +17 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import * as path$1 from "node:path";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
//#region assets-data.mjs
|
|
6
|
+
const coreAssetTargets = [
|
|
7
|
+
".changeset",
|
|
8
|
+
".husky",
|
|
9
|
+
".vscode",
|
|
10
|
+
".editorconfig",
|
|
11
|
+
".gitattributes",
|
|
12
|
+
".gitignore",
|
|
13
|
+
".npmrc",
|
|
14
|
+
"commitlint.config.ts",
|
|
15
|
+
"eslint.config.js",
|
|
16
|
+
"lint-staged.config.js",
|
|
17
|
+
"stylelint.config.js",
|
|
18
|
+
"repoctl.config.ts",
|
|
19
|
+
"package.json",
|
|
20
|
+
"AGENTS.md",
|
|
21
|
+
"pnpm-workspace.yaml",
|
|
22
|
+
"tsconfig.json",
|
|
23
|
+
"turbo.json",
|
|
24
|
+
"vitest.config.ts",
|
|
25
|
+
"Dockerfile",
|
|
26
|
+
".dockerignore"
|
|
27
|
+
];
|
|
28
|
+
const extraAssetTargets = [
|
|
29
|
+
".github",
|
|
30
|
+
"LICENSE",
|
|
31
|
+
"renovate.json",
|
|
32
|
+
"SECURITY.md",
|
|
33
|
+
"CODE_OF_CONDUCT.md",
|
|
34
|
+
"CONTRIBUTING.md",
|
|
35
|
+
"netlify.toml"
|
|
36
|
+
];
|
|
37
|
+
const assetTargets = [...coreAssetTargets, ...extraAssetTargets];
|
|
38
|
+
function getAssetTargets(core = false) {
|
|
39
|
+
return core ? [...coreAssetTargets] : [...assetTargets];
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/paths.ts
|
|
43
|
+
const packageDir = path$1.resolve(fileURLToPath(new URL("../", import.meta.url)));
|
|
44
|
+
const templatesDir = path$1.join(packageDir, "templates");
|
|
45
|
+
const assetsDir = path$1.join(packageDir, "assets");
|
|
46
|
+
const skeletonDir = assetsDir;
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region template-data.mjs
|
|
49
|
+
const templateChoices = [
|
|
50
|
+
{
|
|
51
|
+
key: "tsdown",
|
|
52
|
+
label: "tsdown 打包",
|
|
53
|
+
source: "tsdown",
|
|
54
|
+
target: "packages/tsdown",
|
|
55
|
+
category: "library",
|
|
56
|
+
description: "使用 tsdown 构建的 TypeScript 库包"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
key: "vue-lib",
|
|
60
|
+
label: "vue 组件",
|
|
61
|
+
source: "vue-lib",
|
|
62
|
+
target: "packages/vue-lib",
|
|
63
|
+
category: "library",
|
|
64
|
+
description: "Vue 3 组件库模板,适合沉淀可复用 UI"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
key: "vue-hono",
|
|
68
|
+
label: "vue hono 全栈",
|
|
69
|
+
source: "client",
|
|
70
|
+
target: "apps/client",
|
|
71
|
+
category: "app",
|
|
72
|
+
description: "Vue 3 + Hono 的前后端一体应用模板"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
key: "hono-server",
|
|
76
|
+
label: "hono 模板",
|
|
77
|
+
source: "server",
|
|
78
|
+
target: "apps/server",
|
|
79
|
+
category: "service",
|
|
80
|
+
description: "Hono API 服务模板"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
key: "vitepress",
|
|
84
|
+
label: "vitepress 文档",
|
|
85
|
+
source: "vitepress",
|
|
86
|
+
target: "apps/website",
|
|
87
|
+
category: "docs",
|
|
88
|
+
description: "VitePress 文档站模板"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
key: "cli",
|
|
92
|
+
label: "cli 模板",
|
|
93
|
+
source: "cli",
|
|
94
|
+
target: "apps/cli",
|
|
95
|
+
category: "tool",
|
|
96
|
+
description: "TypeScript 命令行工具模板"
|
|
97
|
+
}
|
|
98
|
+
];
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/utils/gitignore.ts
|
|
101
|
+
const publishBasename = "gitignore";
|
|
102
|
+
const workspaceBasename = ".gitignore";
|
|
103
|
+
const normalizedPathPattern = /[\\/]/;
|
|
104
|
+
function detectSeparator(input) {
|
|
105
|
+
if (input.includes("\\") && !input.includes("/")) return "\\";
|
|
106
|
+
return "/";
|
|
107
|
+
}
|
|
108
|
+
function replaceBasename(input, from, to) {
|
|
109
|
+
if (!input) return input;
|
|
110
|
+
const separator = detectSeparator(input);
|
|
111
|
+
const normalized = input.replace(normalizedPathPattern, separator);
|
|
112
|
+
const hasTrailingSeparator = normalized.endsWith(separator);
|
|
113
|
+
const segments = normalized.split(separator);
|
|
114
|
+
if (hasTrailingSeparator && segments.at(-1) === "") segments.pop();
|
|
115
|
+
const lastIndex = segments.length - 1;
|
|
116
|
+
if (lastIndex >= 0 && segments[lastIndex] === from) {
|
|
117
|
+
segments[lastIndex] = to;
|
|
118
|
+
const rebuilt = segments.join(separator);
|
|
119
|
+
return hasTrailingSeparator ? `${rebuilt}${separator}` : rebuilt;
|
|
120
|
+
}
|
|
121
|
+
return input;
|
|
122
|
+
}
|
|
123
|
+
function toPublishGitignorePath(input) {
|
|
124
|
+
return replaceBasename(input, workspaceBasename, publishBasename);
|
|
125
|
+
}
|
|
126
|
+
function toWorkspaceGitignorePath(input) {
|
|
127
|
+
return replaceBasename(input, publishBasename, workspaceBasename);
|
|
128
|
+
}
|
|
129
|
+
function isGitignoreFile(name) {
|
|
130
|
+
return toPublishGitignorePath(name) !== name || toWorkspaceGitignorePath(name) !== name;
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
//#region src/utils/template-filter.ts
|
|
134
|
+
const skipDirs = new Set([
|
|
135
|
+
"node_modules",
|
|
136
|
+
"dist",
|
|
137
|
+
".turbo",
|
|
138
|
+
".cache",
|
|
139
|
+
".vite",
|
|
140
|
+
".tmp",
|
|
141
|
+
".vue-global-types",
|
|
142
|
+
".wrangler"
|
|
143
|
+
]);
|
|
144
|
+
const skipFiles = new Set([
|
|
145
|
+
".DS_Store",
|
|
146
|
+
"CHANGELOG.md",
|
|
147
|
+
"route-map.d.ts"
|
|
148
|
+
]);
|
|
149
|
+
function shouldSkipTemplatePath(rootDir, targetPath) {
|
|
150
|
+
const basename = path.basename(targetPath);
|
|
151
|
+
if (skipFiles.has(basename)) return true;
|
|
152
|
+
if (basename.endsWith(".tsbuildinfo")) return true;
|
|
153
|
+
if (/\.timestamp-\d+-[a-z0-9]+\.mjs$/i.test(basename)) return true;
|
|
154
|
+
const relative = path.relative(rootDir, targetPath);
|
|
155
|
+
if (!relative || relative.startsWith("..")) return false;
|
|
156
|
+
const segments = relative.split(path.sep);
|
|
157
|
+
if (segments.some((segment) => skipDirs.has(segment))) return true;
|
|
158
|
+
for (let i = 0; i < segments.length - 1; i += 1) if (segments[i] === ".vitepress" && segments[i + 1] === "cache") return true;
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
function createTemplateCopyFilter(rootDir) {
|
|
162
|
+
return (src) => !shouldSkipTemplatePath(rootDir, src);
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region src/prepare.ts
|
|
166
|
+
const huskySkippedEntryPattern = /[\\/]_$/;
|
|
167
|
+
const publishedToolingConfigs = {
|
|
168
|
+
"commitlint.config.ts": [
|
|
169
|
+
`import { defineCommitlintConfig } from 'repoctl/tooling'`,
|
|
170
|
+
"",
|
|
171
|
+
"export default await defineCommitlintConfig()",
|
|
172
|
+
""
|
|
173
|
+
].join("\n"),
|
|
174
|
+
"eslint.config.js": [
|
|
175
|
+
`import { defineEslintConfig } from 'repoctl/tooling'`,
|
|
176
|
+
"",
|
|
177
|
+
"export default await defineEslintConfig()",
|
|
178
|
+
""
|
|
179
|
+
].join("\n"),
|
|
180
|
+
"lint-staged.config.js": [
|
|
181
|
+
`import { defineLintStagedConfig } from 'repoctl/tooling'`,
|
|
182
|
+
"",
|
|
183
|
+
"export default await defineLintStagedConfig()",
|
|
184
|
+
""
|
|
185
|
+
].join("\n"),
|
|
186
|
+
"stylelint.config.js": [
|
|
187
|
+
`import { defineStylelintConfig } from 'repoctl/tooling'`,
|
|
188
|
+
"",
|
|
189
|
+
"export default await defineStylelintConfig()",
|
|
190
|
+
""
|
|
191
|
+
].join("\n"),
|
|
192
|
+
"vitest.config.ts": [
|
|
193
|
+
`import { defineConfig } from 'vitest/config'`,
|
|
194
|
+
`import { defineVitestConfig } from 'repoctl/tooling'`,
|
|
195
|
+
"",
|
|
196
|
+
"export default defineConfig(async () => await defineVitestConfig())",
|
|
197
|
+
""
|
|
198
|
+
].join("\n")
|
|
199
|
+
};
|
|
200
|
+
async function pathExists(targetPath) {
|
|
201
|
+
try {
|
|
202
|
+
await fs.access(targetPath);
|
|
203
|
+
return true;
|
|
204
|
+
} catch {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async function renameGitignoreFiles(targetDir) {
|
|
209
|
+
let entries;
|
|
210
|
+
try {
|
|
211
|
+
entries = await fs.readdir(targetDir, { withFileTypes: true });
|
|
212
|
+
} catch (error) {
|
|
213
|
+
if (error.code === "ENOENT") return;
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
await Promise.all(entries.map(async (entry) => {
|
|
217
|
+
const current = path$1.join(targetDir, entry.name);
|
|
218
|
+
if (entry.isDirectory()) {
|
|
219
|
+
await renameGitignoreFiles(current);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const renamed = toPublishGitignorePath(entry.name);
|
|
223
|
+
if (renamed !== entry.name) await fs.rename(current, path$1.join(targetDir, renamed));
|
|
224
|
+
}));
|
|
225
|
+
}
|
|
226
|
+
async function resetDir(targetDir, overwriteExisting) {
|
|
227
|
+
if (!overwriteExisting && await pathExists(targetDir)) return;
|
|
228
|
+
try {
|
|
229
|
+
await fs.rm(targetDir, {
|
|
230
|
+
recursive: true,
|
|
231
|
+
force: true
|
|
232
|
+
});
|
|
233
|
+
} catch (error) {
|
|
234
|
+
const err = error;
|
|
235
|
+
if (err?.code !== "ENOTEMPTY" && err?.code !== "EBUSY" && err?.code !== "EPERM") throw error;
|
|
236
|
+
}
|
|
237
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
238
|
+
}
|
|
239
|
+
async function copyEntry(from, to, overwriteExisting, filter) {
|
|
240
|
+
if (!overwriteExisting && await pathExists(to)) {
|
|
241
|
+
const [fromStats, toStats] = await Promise.all([fs.stat(from), fs.stat(to)]);
|
|
242
|
+
if (fromStats.isDirectory() && toStats.isDirectory()) await fs.cp(from, to, {
|
|
243
|
+
recursive: true,
|
|
244
|
+
force: false,
|
|
245
|
+
errorOnExist: false,
|
|
246
|
+
filter
|
|
247
|
+
});
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
await fs.cp(from, to, {
|
|
252
|
+
recursive: true,
|
|
253
|
+
force: overwriteExisting,
|
|
254
|
+
filter
|
|
255
|
+
});
|
|
256
|
+
} catch (error) {
|
|
257
|
+
const err = error;
|
|
258
|
+
if (!overwriteExisting && (err?.code === "EEXIST" || err?.code === "ENOTEMPTY")) return;
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async function copyAssets(repoRoot, overwriteExisting) {
|
|
263
|
+
const monorepoAssetRoot = path$1.resolve(repoRoot, "packages/monorepo/assets");
|
|
264
|
+
for (const target of assetTargets) {
|
|
265
|
+
const packageAsset = path$1.join(monorepoAssetRoot, toPublishGitignorePath(target));
|
|
266
|
+
const repoAsset = path$1.join(repoRoot, target);
|
|
267
|
+
const from = await pathExists(repoAsset) ? repoAsset : packageAsset;
|
|
268
|
+
if (!await pathExists(from)) continue;
|
|
269
|
+
const to = path$1.join(assetsDir, toPublishGitignorePath(target));
|
|
270
|
+
const stats = await fs.stat(from);
|
|
271
|
+
await copyEntry(from, to, overwriteExisting, target === ".husky" ? (src) => !huskySkippedEntryPattern.test(src) : void 0);
|
|
272
|
+
if (target === "tsconfig.json") await fs.writeFile(to, `${JSON.stringify({
|
|
273
|
+
extends: "repoctl/tsconfig.json",
|
|
274
|
+
files: []
|
|
275
|
+
}, null, 2)}\n`);
|
|
276
|
+
if (stats.isDirectory()) await renameGitignoreFiles(to);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function writePublishedToolingConfigs() {
|
|
280
|
+
await Promise.all(Object.entries(publishedToolingConfigs).map(async ([filename, content]) => {
|
|
281
|
+
const targetPath = path$1.join(assetsDir, filename);
|
|
282
|
+
if (await pathExists(targetPath)) await fs.writeFile(targetPath, content);
|
|
283
|
+
}));
|
|
284
|
+
}
|
|
285
|
+
async function copyTemplates(repoRoot, overwriteExisting) {
|
|
286
|
+
for (const template of templateChoices) {
|
|
287
|
+
const from = path$1.join(repoRoot, "templates", template.source);
|
|
288
|
+
if (!await pathExists(from)) continue;
|
|
289
|
+
const to = path$1.join(templatesDir, template.source);
|
|
290
|
+
const filter = (src) => !shouldSkipTemplatePath(from, src);
|
|
291
|
+
await copyEntry(from, to, overwriteExisting, filter);
|
|
292
|
+
await renameGitignoreFiles(to);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
async function prepareAssets(options = {}) {
|
|
296
|
+
const overwriteExisting = options.overwriteExisting ?? true;
|
|
297
|
+
const repoRoot = path$1.resolve(packageDir, "..", "..");
|
|
298
|
+
if (!await pathExists(path$1.join(repoRoot, "templates"))) return;
|
|
299
|
+
await resetDir(assetsDir, overwriteExisting);
|
|
300
|
+
await resetDir(templatesDir, overwriteExisting);
|
|
301
|
+
await copyAssets(repoRoot, overwriteExisting);
|
|
302
|
+
await writePublishedToolingConfigs();
|
|
303
|
+
await copyTemplates(repoRoot, overwriteExisting);
|
|
304
|
+
}
|
|
305
|
+
//#endregion
|
|
306
|
+
export { toPublishGitignorePath as a, assetsDir as c, templatesDir as d, assetTargets as f, isGitignoreFile as i, packageDir as l, createTemplateCopyFilter as n, toWorkspaceGitignorePath as o, getAssetTargets as p, shouldSkipTemplatePath as r, templateChoices as s, prepareAssets as t, skeletonDir as u };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@icebreakers/monorepo-templates",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.3",
|
|
5
5
|
"description": "Template assets for icebreaker monorepo scaffolding",
|
|
6
6
|
"author": "ice breaker <hi@sonofmagic.top>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"build": "tsdown",
|
|
58
58
|
"typecheck": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json",
|
|
59
59
|
"tsd": "pnpm build && tsd --files test-d/**/*.test-d.ts",
|
|
60
|
-
"sync:assets": "pnpm run build && node
|
|
60
|
+
"sync:assets": "pnpm run build && node dist/cli.mjs",
|
|
61
61
|
"lint": "eslint ."
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -1,76 +1,157 @@
|
|
|
1
1
|
import Tailwindcss from '@tailwindcss/vite'
|
|
2
2
|
import { defineConfig } from 'vitepress'
|
|
3
3
|
import { withMermaid } from 'vitepress-plugin-mermaid'
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
const repoctlSidebar = [
|
|
6
|
+
{
|
|
7
|
+
text: 'repoctl',
|
|
8
|
+
items: [
|
|
9
|
+
{ text: '概览', link: '/repoctl/' },
|
|
10
|
+
{ text: '快速开始', link: '/repoctl/getting-started' },
|
|
11
|
+
{ text: '接入已有仓库', link: '/repoctl/adopt-existing' },
|
|
12
|
+
{ text: '按场景选命令', link: '/repoctl/scenarios' },
|
|
13
|
+
{ text: '命令速查', link: '/repoctl/commands' },
|
|
14
|
+
{ text: '配置文件', link: '/repoctl/config' },
|
|
15
|
+
{ text: '模板与创建', link: '/repoctl/templates' },
|
|
16
|
+
{ text: '工作流与 CI', link: '/repoctl/workflows' },
|
|
17
|
+
{ text: '排障与报告', link: '/repoctl/troubleshooting' },
|
|
18
|
+
{ text: '命令别名', link: '/repoctl/aliases' },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
text: '知识库',
|
|
23
|
+
items: [
|
|
24
|
+
{ text: '为什么往 monorepo 方向演进', link: '/monorepo/' },
|
|
25
|
+
{ text: '如何管理 monorepo', link: '/monorepo/manage' },
|
|
26
|
+
{ text: '发包与变更日志', link: '/monorepo/publish' },
|
|
27
|
+
{ text: '如何复用 js 代码', link: '/why/how-to-reuse-js-code' },
|
|
28
|
+
{ text: 'JS 文件的各种后缀', link: '/why/js-cjs-mjs' },
|
|
29
|
+
{ text: 'CJS 和 ESM 关键字/全局变量对比', link: '/why/js-keywords' },
|
|
30
|
+
{ text: '什么是 npm 包', link: '/why/what-is-npm-package' },
|
|
31
|
+
{ text: '如何发布 npm 包', link: '/why/publish-basic-npm-package' },
|
|
32
|
+
{ text: '改进并发布现代 npm 包', link: '/why/index' },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
text: '工具专题',
|
|
37
|
+
items: [
|
|
38
|
+
{ text: 'pnpm', link: '/tools/pnpm' },
|
|
39
|
+
{ text: 'turborepo', link: '/tools/turborepo' },
|
|
40
|
+
{ text: 'changeset', link: '/tools/changeset' },
|
|
41
|
+
{ text: 'husky', link: '/tools/husky' },
|
|
42
|
+
{ text: 'lint-staged', link: '/tools/lint-staged' },
|
|
43
|
+
{ text: 'renovate', link: '/tools/renovate' },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
const enSidebar = [
|
|
49
|
+
{
|
|
50
|
+
text: 'repoctl',
|
|
51
|
+
items: [
|
|
52
|
+
{ text: 'Overview', link: '/en/repoctl/' },
|
|
53
|
+
{ text: 'Getting Started', link: '/en/repoctl/getting-started' },
|
|
54
|
+
{ text: 'Adopt Existing Repos', link: '/en/repoctl/adopt-existing' },
|
|
55
|
+
{ text: 'Choose By Scenario', link: '/en/repoctl/scenarios' },
|
|
56
|
+
{ text: 'Command Reference', link: '/en/repoctl/commands' },
|
|
57
|
+
{ text: 'Configuration', link: '/en/repoctl/config' },
|
|
58
|
+
{ text: 'Templates', link: '/en/repoctl/templates' },
|
|
59
|
+
{ text: 'Workflows and CI', link: '/en/repoctl/workflows' },
|
|
60
|
+
{ text: 'Troubleshooting', link: '/en/repoctl/troubleshooting' },
|
|
61
|
+
{ text: 'Command Aliases', link: '/en/repoctl/aliases' },
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
text: 'Knowledge Base',
|
|
66
|
+
items: [
|
|
67
|
+
{ text: 'Why Monorepo', link: '/en/knowledge/monorepo' },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
]
|
|
71
|
+
|
|
5
72
|
export default withMermaid(defineConfig({
|
|
6
73
|
outDir: '.vitepress/dist',
|
|
7
|
-
title: '
|
|
8
|
-
description: '
|
|
74
|
+
title: 'repoctl',
|
|
75
|
+
description: 'repoctl documentation for practical pnpm and Turborepo monorepos.',
|
|
76
|
+
lastUpdated: true,
|
|
77
|
+
cleanUrls: true,
|
|
78
|
+
head: [
|
|
79
|
+
['link', { rel: 'icon', href: '/logo.jpg' }],
|
|
80
|
+
],
|
|
9
81
|
themeConfig: {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
{ text: 'Monorepo', link: '/monorepo/' },
|
|
15
|
-
{ text: '工具专题', link: '/tools/turborepo' },
|
|
16
|
-
{ text: '一些思考', link: '/thinking' },
|
|
17
|
-
],
|
|
82
|
+
logo: '/logo.jpg',
|
|
83
|
+
search: {
|
|
84
|
+
provider: 'local',
|
|
85
|
+
},
|
|
18
86
|
socialLinks: [
|
|
19
87
|
{ icon: 'github', link: 'https://github.com/sonofmagic/monorepo-template' },
|
|
20
88
|
],
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
'
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
89
|
+
},
|
|
90
|
+
locales: {
|
|
91
|
+
root: {
|
|
92
|
+
label: '简体中文',
|
|
93
|
+
lang: 'zh-CN',
|
|
94
|
+
title: 'repoctl',
|
|
95
|
+
description: '围绕 repoctl 的 pnpm + Turborepo monorepo 使用文档。',
|
|
96
|
+
themeConfig: {
|
|
97
|
+
nav: [
|
|
98
|
+
{ text: 'repoctl', link: '/repoctl/' },
|
|
99
|
+
{ text: '快速开始', link: '/repoctl/getting-started' },
|
|
100
|
+
{ text: '命令', link: '/repoctl/commands' },
|
|
101
|
+
{ text: '知识库', link: '/monorepo/' },
|
|
102
|
+
{ text: '工具专题', link: '/tools/turborepo' },
|
|
103
|
+
],
|
|
104
|
+
outline: {
|
|
105
|
+
label: '目录',
|
|
106
|
+
level: [2, 3],
|
|
107
|
+
},
|
|
108
|
+
sidebar: {
|
|
109
|
+
'/': repoctlSidebar,
|
|
35
110
|
},
|
|
36
|
-
{
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
{ text: '为什么往 monorepo 方向演进', link: '/monorepo/' },
|
|
40
|
-
{ text: '如何管理 monorepo', link: '/monorepo/manage' },
|
|
41
|
-
{ text: '模板速查', link: '/monorepo/templates' },
|
|
42
|
-
{ text: '命令速查', link: '/monorepo/commands' },
|
|
43
|
-
{ text: '常见问题排障', link: '/monorepo/troubleshooting' },
|
|
44
|
-
{ text: '发包与变更日志', link: '/monorepo/publish' },
|
|
45
|
-
],
|
|
111
|
+
editLink: {
|
|
112
|
+
pattern: 'https://github.com/sonofmagic/monorepo-template/edit/main/templates/vitepress/:path',
|
|
113
|
+
text: '为此页提供修改建议',
|
|
46
114
|
},
|
|
47
|
-
{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
{ text: '如何复用 js 代码', link: '/why/how-to-reuse-js-code' },
|
|
51
|
-
{ text: 'JS 文件的各种后缀', link: '/why/js-cjs-mjs' },
|
|
52
|
-
{ text: 'CJS 和 ESM 关键字/全局变量对比', link: '/why/js-keywords' },
|
|
53
|
-
{ text: '什么是 npm 包', link: '/why/what-is-npm-package' },
|
|
54
|
-
{ text: '如何发布 npm 包', link: '/why/publish-basic-npm-package' },
|
|
55
|
-
{ text: '改进并发布现代 npm 包', link: '/why/index' },
|
|
56
|
-
],
|
|
115
|
+
docFooter: {
|
|
116
|
+
prev: '上一页',
|
|
117
|
+
next: '下一页',
|
|
57
118
|
},
|
|
58
|
-
{
|
|
59
|
-
text: '
|
|
60
|
-
items: [
|
|
61
|
-
{ text: 'pnpm', link: '/tools/pnpm' },
|
|
62
|
-
{ text: 'turborepo', link: '/tools/turborepo' },
|
|
63
|
-
{ text: 'changeset', link: '/tools/changeset' },
|
|
64
|
-
{ text: 'husky', link: '/tools/husky' },
|
|
65
|
-
{ text: 'lint-staged', link: '/tools/lint-staged' },
|
|
66
|
-
{ text: 'renovate', link: '/tools/renovate' },
|
|
67
|
-
],
|
|
119
|
+
lastUpdated: {
|
|
120
|
+
text: '最后更新',
|
|
68
121
|
},
|
|
69
|
-
|
|
122
|
+
},
|
|
70
123
|
},
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
124
|
+
en: {
|
|
125
|
+
label: 'English',
|
|
126
|
+
lang: 'en-US',
|
|
127
|
+
title: 'repoctl',
|
|
128
|
+
description: 'Documentation for using repoctl in practical pnpm and Turborepo monorepos.',
|
|
129
|
+
themeConfig: {
|
|
130
|
+
nav: [
|
|
131
|
+
{ text: 'repoctl', link: '/en/repoctl/' },
|
|
132
|
+
{ text: 'Getting Started', link: '/en/repoctl/getting-started' },
|
|
133
|
+
{ text: 'Commands', link: '/en/repoctl/commands' },
|
|
134
|
+
{ text: 'Knowledge Base', link: '/en/knowledge/monorepo' },
|
|
135
|
+
],
|
|
136
|
+
outline: {
|
|
137
|
+
label: 'On This Page',
|
|
138
|
+
level: [2, 3],
|
|
139
|
+
},
|
|
140
|
+
sidebar: {
|
|
141
|
+
'/en/': enSidebar,
|
|
142
|
+
},
|
|
143
|
+
editLink: {
|
|
144
|
+
pattern: 'https://github.com/sonofmagic/monorepo-template/edit/main/templates/vitepress/:path',
|
|
145
|
+
text: 'Edit this page',
|
|
146
|
+
},
|
|
147
|
+
docFooter: {
|
|
148
|
+
prev: 'Previous page',
|
|
149
|
+
next: 'Next page',
|
|
150
|
+
},
|
|
151
|
+
lastUpdated: {
|
|
152
|
+
text: 'Last updated',
|
|
153
|
+
},
|
|
154
|
+
},
|
|
74
155
|
},
|
|
75
156
|
},
|
|
76
157
|
vite: {
|
|
@@ -78,14 +159,6 @@ export default withMermaid(defineConfig({
|
|
|
78
159
|
// @ts-ignore
|
|
79
160
|
Tailwindcss(),
|
|
80
161
|
],
|
|
81
|
-
// server: {
|
|
82
|
-
// hmr: {
|
|
83
|
-
// overlay: false,
|
|
84
|
-
// },
|
|
85
|
-
// },
|
|
86
|
-
},
|
|
87
|
-
mermaid: {
|
|
88
|
-
|
|
89
162
|
},
|
|
90
|
-
}
|
|
91
|
-
)
|
|
163
|
+
mermaid: {},
|
|
164
|
+
}))
|
|
@@ -0,0 +1,57 @@
|
|
|
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: /en/repoctl/getting-started
|
|
15
|
+
- theme: alt
|
|
16
|
+
text: Commands
|
|
17
|
+
link: /en/repoctl/commands
|
|
18
|
+
|
|
19
|
+
features:
|
|
20
|
+
- title: Adopt existing workspaces
|
|
21
|
+
details: Run setup and doctor to align workspace patterns, root scripts, config files, and commit hooks.
|
|
22
|
+
- title: Create packages and apps
|
|
23
|
+
details: Scaffold libraries, apps, services, docs, and CLIs with interactive prompts or explicit template keys.
|
|
24
|
+
- title: Make checks repeatable
|
|
25
|
+
details: Preview and run local verification plans that match your lint, typecheck, build, test, and tsd workflow.
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Fast Path
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm add -D repoctl
|
|
32
|
+
pnpm exec repo setup
|
|
33
|
+
pnpm exec repo doctor
|
|
34
|
+
pnpm exec repo new
|
|
35
|
+
pnpm exec repo check
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Generated repositories expose the same workflow as shorter root scripts:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pnpm setup
|
|
42
|
+
pnpm doctor
|
|
43
|
+
pnpm new
|
|
44
|
+
pnpm check
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Keep Reading
|
|
48
|
+
|
|
49
|
+
- [repoctl Overview](./repoctl/index.md)
|
|
50
|
+
- [Getting Started](./repoctl/getting-started.md)
|
|
51
|
+
- [Adopt Existing Repositories](./repoctl/adopt-existing.md)
|
|
52
|
+
- [Choose By Scenario](./repoctl/scenarios.md)
|
|
53
|
+
- [Command Reference](./repoctl/commands.md)
|
|
54
|
+
- [Configuration](./repoctl/config.md)
|
|
55
|
+
- [Templates](./repoctl/templates.md)
|
|
56
|
+
- [Workflows and CI](./repoctl/workflows.md)
|
|
57
|
+
- [Knowledge Base: Why Monorepo](./knowledge/monorepo.md)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Why Monorepo
|
|
2
|
+
|
|
3
|
+
This project uses monorepo practices because multiple packages, applications, CLIs, and docs sites usually share the same engineering rules.
|
|
4
|
+
|
|
5
|
+
The goal is not to make the repository look more complex. The goal is to reduce repeated setup work.
|
|
6
|
+
|
|
7
|
+
## Problems A Monorepo Solves
|
|
8
|
+
|
|
9
|
+
### Shared Tooling
|
|
10
|
+
|
|
11
|
+
In separate repositories, each package tends to maintain its own lint, typecheck, test, release, and commit setup. That makes upgrades slow and inconsistent.
|
|
12
|
+
|
|
13
|
+
In a pnpm workspace, those rules can be centralized at the root and reused by every package.
|
|
14
|
+
|
|
15
|
+
### Atomic Cross-Package Changes
|
|
16
|
+
|
|
17
|
+
When one type change affects a library, an app, and a CLI, a monorepo lets you update, verify, review, and revert that change together.
|
|
18
|
+
|
|
19
|
+
### Incremental Builds
|
|
20
|
+
|
|
21
|
+
pnpm handles workspace dependencies. Turborepo coordinates tasks and caching. Together they make it practical to run build, lint, test, and typecheck flows across many packages.
|
|
22
|
+
|
|
23
|
+
## Where repoctl Fits
|
|
24
|
+
|
|
25
|
+
repoctl adds the missing workflow layer:
|
|
26
|
+
|
|
27
|
+
- `repo setup` aligns root scripts and standard files.
|
|
28
|
+
- `repo doctor` checks whether the workspace is ready.
|
|
29
|
+
- `repo new` creates packages and apps from known templates.
|
|
30
|
+
- `repo check` previews and runs local verification.
|
|
31
|
+
- `repo upgrade` syncs standard assets as the template evolves.
|
|
32
|
+
|
|
33
|
+
## Keep Reading
|
|
34
|
+
|
|
35
|
+
- [repoctl Overview](../repoctl/index.md)
|
|
36
|
+
- [Command Reference](../repoctl/commands.md)
|
|
37
|
+
- [Templates](../repoctl/templates.md)
|