@icebreakers/monorepo-templates 2.0.2 → 2.0.4
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/.agents/skills/repoctl/SKILL.md +9 -4
- package/assets/.agents/skills/repoctl/references/commands.md +2 -1
- package/assets/.github/workflows/ci.yml +4 -0
- package/assets/.github/workflows/release.yml +11 -0
- package/assets/AGENTS.md +16 -1
- package/assets/CLAUDE.md +8 -2
- package/assets/gitignore +6 -0
- package/assets/npmrc +20 -0
- package/assets/package.json +2 -2
- package/assets/pnpm-workspace.yaml +1 -0
- package/assets/turbo.json +3 -0
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +5 -1
- package/dist/index.mjs +73 -5
- package/dist/{prepare-zIcRa4-K.mjs → prepare-BapI8ioj.mjs} +38 -13
- package/package.json +1 -1
- package/templates/client/package.json +1 -1
- package/templates/server/package.json +1 -1
- package/templates/vitepress/ai/create.md +8 -4
- package/templates/vitepress/ai/index.md +1 -1
- package/templates/vitepress/tasks/release.md +15 -0
- package/templates/vitepress/zh/ai/create.md +7 -4
- package/templates/vitepress/zh/ai/index.md +1 -1
- package/templates/vitepress/zh/learn/monorepo/publish.md +1 -1
- package/templates/vitepress/zh/learn/tools/changeset.md +1 -1
- package/templates/vitepress/zh/tasks/release.md +15 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: repoctl
|
|
3
|
-
description: Create and maintain pnpm/Turborepo workspaces with repoctl. Use when the user says 使用 repoctl 创建项目, create a monorepo, pnpm create repoctl, scaffold Vue/Hono/tsdown/vitepress, or run repo init/doctor/new/check.
|
|
3
|
+
description: Create and maintain pnpm/Turborepo workspaces with repoctl. Use when the user says 使用 repoctl 创建项目, create a monorepo, pnpm create repoctl@latest, scaffold Vue/Hono/tsdown/vitepress, or run repo init/doctor/new/check.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# repoctl
|
|
@@ -9,17 +9,22 @@ The package name is `repoctl`. The command is `repo`.
|
|
|
9
9
|
|
|
10
10
|
## Empty directory
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Create into a new or empty directory outside the repoctl source workspace. Do
|
|
13
|
+
not add a business project under the source checkout, and do not point the
|
|
14
|
+
create command at a non-empty directory unless the user explicitly asks to
|
|
15
|
+
replace it with `--force`. Do not prompt. Map the request to template keys,
|
|
16
|
+
then:
|
|
13
17
|
|
|
14
18
|
```bash
|
|
15
|
-
pnpm create repoctl <dir> -- --yes --templates <keys>
|
|
19
|
+
pnpm create repoctl@latest <dir> -- --yes --templates <keys>
|
|
16
20
|
cd <dir>
|
|
21
|
+
corepack enable
|
|
17
22
|
pnpm install
|
|
18
23
|
pnpm exec repo init
|
|
19
24
|
pnpm exec repo doctor
|
|
20
25
|
```
|
|
21
26
|
|
|
22
|
-
`npm create` and `pnpm create` need the extra `--` before flags. `npx create-repoctl <dir> --yes --templates <keys>` does not.
|
|
27
|
+
`npm create` and `pnpm create` need the extra `--` before flags. `npx create-repoctl@latest <dir> --yes --templates <keys>` does not.
|
|
23
28
|
|
|
24
29
|
| User intent | `--templates` key |
|
|
25
30
|
| ------------------------ | ------------------ |
|
|
@@ -193,7 +193,8 @@ Usage:
|
|
|
193
193
|
|
|
194
194
|
## skills sync
|
|
195
195
|
|
|
196
|
-
Purpose: sync the `repoctl` skill into global agent skill directories.
|
|
196
|
+
Purpose: sync the `repoctl` skill into global agent skill directories. Use
|
|
197
|
+
`--all` when all supported agents should receive the same skill version.
|
|
197
198
|
Usage:
|
|
198
199
|
|
|
199
200
|
- pnpm exec repo skills sync
|
|
@@ -78,6 +78,10 @@ jobs:
|
|
|
78
78
|
- name: Test
|
|
79
79
|
run: pnpm test
|
|
80
80
|
|
|
81
|
+
- name: Check packaged monorepo creation
|
|
82
|
+
if: ${{ matrix.os == 'ubuntu-latest' && matrix.node-version == 22 }}
|
|
83
|
+
run: pnpm test:packaged-create
|
|
84
|
+
|
|
81
85
|
- name: Upload coverage reports to Codecov
|
|
82
86
|
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
|
|
83
87
|
env:
|
|
@@ -83,3 +83,14 @@ jobs:
|
|
|
83
83
|
REPO_RELEASE_VERSION: ${{ inputs.version }}
|
|
84
84
|
REPO_RELEASE_DRY_RUN: ${{ inputs.dry-run }}
|
|
85
85
|
run: pnpm exec repo release ci
|
|
86
|
+
|
|
87
|
+
- name: Preserve npm publish progress
|
|
88
|
+
if: ${{ always() }}
|
|
89
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
90
|
+
with:
|
|
91
|
+
name: npm-publish-progress-${{ github.run_id }}-${{ github.run_attempt }}
|
|
92
|
+
path: |
|
|
93
|
+
pnpm-publish-summary.json
|
|
94
|
+
repoctl-publish-progress.json
|
|
95
|
+
if-no-files-found: ignore
|
|
96
|
+
retention-days: 14
|
package/assets/AGENTS.md
CHANGED
|
@@ -22,6 +22,21 @@ pnpm exec repo new <name> --template <key>
|
|
|
22
22
|
|
|
23
23
|
Preview without writing files: `pnpm exec repo new <name> --template <key> --dry-run`.
|
|
24
24
|
|
|
25
|
+
## Create a new project
|
|
26
|
+
|
|
27
|
+
If the user asks you to create a new business project, do not add it inside an
|
|
28
|
+
existing unrelated repository. Use a new or empty directory outside the repoctl
|
|
29
|
+
source checkout. Start a new workspace with:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pnpm create repoctl@latest <dir> -- --yes --templates <keys>
|
|
33
|
+
cd <dir>
|
|
34
|
+
corepack enable
|
|
35
|
+
pnpm install
|
|
36
|
+
pnpm exec repo init
|
|
37
|
+
pnpm exec repo doctor
|
|
38
|
+
```
|
|
39
|
+
|
|
25
40
|
## Verify
|
|
26
41
|
|
|
27
42
|
```bash
|
|
@@ -38,5 +53,5 @@ pnpm exec repo check
|
|
|
38
53
|
## Requirements
|
|
39
54
|
|
|
40
55
|
- Node.js 22.13 or newer
|
|
41
|
-
- pnpm
|
|
56
|
+
- pnpm (run `corepack enable` so the declared workspace version is used)
|
|
42
57
|
- CLI output is English by default; `--lang zh-CN` or `REPOCTL_LANG=zh-CN` selects Simplified Chinese
|
package/assets/CLAUDE.md
CHANGED
|
@@ -7,8 +7,14 @@ Follow `AGENTS.md` in the workspace root. Create packages with
|
|
|
7
7
|
`pnpm exec repo doctor` and `pnpm exec repo check`.
|
|
8
8
|
|
|
9
9
|
If you were asked to create a new business project, do not add it inside an
|
|
10
|
-
existing unrelated repository. Use
|
|
10
|
+
existing unrelated repository. Use a new or empty directory outside the repoctl
|
|
11
|
+
source checkout:
|
|
11
12
|
|
|
12
13
|
```bash
|
|
13
|
-
pnpm create repoctl <dir> -- --yes --templates <keys>
|
|
14
|
+
pnpm create repoctl@latest <dir> -- --yes --templates <keys>
|
|
15
|
+
cd <dir>
|
|
16
|
+
corepack enable
|
|
17
|
+
pnpm install
|
|
18
|
+
pnpm exec repo init
|
|
19
|
+
pnpm exec repo doctor
|
|
14
20
|
```
|
package/assets/gitignore
CHANGED
|
@@ -40,3 +40,9 @@ config.ts.timestamp-*.mjs
|
|
|
40
40
|
|
|
41
41
|
# local AI task artifacts
|
|
42
42
|
/.qoder/
|
|
43
|
+
|
|
44
|
+
# release diagnostics (retained as CI artifacts)
|
|
45
|
+
/pnpm-publish-summary.json
|
|
46
|
+
/repoctl-publish-progress.json
|
|
47
|
+
/pnpm-publish-summary.json.*.tmp
|
|
48
|
+
/repoctl-publish-progress.json.*.tmp
|
package/assets/npmrc
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# 文档地址: https://pnpm.io/npmrc
|
|
2
|
+
## 默认地址
|
|
3
|
+
registry=https://registry.npmjs.org/
|
|
4
|
+
## 可使用国内镜像地址
|
|
5
|
+
# registry=http://registry.npmmirror.com/
|
|
6
|
+
|
|
7
|
+
## Git 配置
|
|
8
|
+
git-checks=false
|
|
9
|
+
|
|
10
|
+
## 包管理器配置
|
|
11
|
+
package-manager-strict=true
|
|
12
|
+
package-manager-strict-version=true
|
|
13
|
+
|
|
14
|
+
## 依赖处理
|
|
15
|
+
auto-install-peers=true
|
|
16
|
+
strict-peer-dependencies=false
|
|
17
|
+
resolution-mode=highest
|
|
18
|
+
|
|
19
|
+
## 部分场景可能需要开启此配置
|
|
20
|
+
# shamefully-hoist=true
|
package/assets/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"type": "module",
|
|
4
4
|
"version": "0.0.0",
|
|
5
5
|
"private": true,
|
|
6
|
-
"packageManager": "pnpm@12.
|
|
6
|
+
"packageManager": "pnpm@12.6.0",
|
|
7
7
|
"description": "Source workspace for repoctl and Icebreaker's shared development packages",
|
|
8
8
|
"author": "ice breaker <hi@sonofmagic.top>",
|
|
9
9
|
"license": "MIT",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "turbo run build",
|
|
30
30
|
"tooling:build": "turbo run build --filter=@icebreakers/monorepo --filter=repoctl",
|
|
31
|
-
"dev": "turbo run dev --
|
|
31
|
+
"dev": "turbo run dev --concurrency=20",
|
|
32
32
|
"test": "node -e \"require('node:fs').mkdirSync('coverage/.tmp', { recursive: true })\" && pnpm check:no-tracked-build-artifacts && vitest run && pnpm test:types",
|
|
33
33
|
"test:packaged-doctor": "node scripts/smoke-packaged-doctor.mjs",
|
|
34
34
|
"test:dev": "node -e \"require('node:fs').mkdirSync('coverage/.tmp', { recursive: true })\" && pnpm check:no-tracked-build-artifacts && vitest",
|
package/assets/turbo.json
CHANGED
package/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -23,13 +23,16 @@ interface SuggestTemplateKeyOptions extends GetTemplateChoicesOptions {
|
|
|
23
23
|
keys?: readonly string[];
|
|
24
24
|
}
|
|
25
25
|
//#endregion
|
|
26
|
+
//#region src/package-manager/index.d.ts
|
|
27
|
+
export declare function getWorkspacePackageManager(): Promise<string>;
|
|
28
|
+
//#endregion
|
|
26
29
|
//#region src/paths.d.ts
|
|
27
30
|
export declare const packageDir: string;
|
|
28
31
|
export declare const templatesDir: string;
|
|
29
32
|
export declare const assetsDir: string;
|
|
30
33
|
export declare const skeletonDir: string;
|
|
31
34
|
//#endregion
|
|
32
|
-
//#region src/prepare.d.ts
|
|
35
|
+
//#region src/prepare/index.d.ts
|
|
33
36
|
interface PrepareAssetsOptions {
|
|
34
37
|
overwriteExisting?: boolean;
|
|
35
38
|
silent?: boolean;
|
|
@@ -76,6 +79,7 @@ export declare function execaCommand(command: string, options?: RunCommandOption
|
|
|
76
79
|
//#region src/utils/gitignore.d.ts
|
|
77
80
|
export declare function toPublishGitignorePath(input: string): string;
|
|
78
81
|
export declare function toWorkspaceGitignorePath(input: string): string;
|
|
82
|
+
export declare function toWorkspaceAssetPath(input: string): string;
|
|
79
83
|
export declare function isGitignoreFile(name: string): boolean;
|
|
80
84
|
//#endregion
|
|
81
85
|
//#region src/utils/template-filter.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as isGitignoreFile, c as toWorkspaceGitignorePath, d as packageDir, f as skeletonDir, h as getAssetTargets$1, i as shouldSkipTemplatePath, l as templateChoices$1, m as assetTargets$1, n as sanitizePublishedWorkspaceContent, o as toPublishGitignorePath, p as templatesDir, r as createTemplateCopyFilter, s as toWorkspaceAssetPath, t as prepareAssets, u as assetsDir } from "./prepare-BapI8ioj.mjs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import fs, { access, open, rm } from "node:fs/promises";
|
|
3
|
+
import fs, { access, open, readFile, rm } from "node:fs/promises";
|
|
4
4
|
import exec from "nanoexec";
|
|
5
5
|
import checkbox from "@inquirer/checkbox";
|
|
6
6
|
import input from "@inquirer/input";
|
|
7
7
|
import select from "@inquirer/select";
|
|
8
8
|
import { Command, program } from "commander";
|
|
9
|
+
//#region src/package-manager/read.ts
|
|
10
|
+
const packageManagerPattern = /^pnpm@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u;
|
|
11
|
+
function validateWorkspacePackageManager(value, source) {
|
|
12
|
+
if (typeof value !== "string" || !packageManagerPattern.test(value)) throw new Error(`Invalid packageManager in ${source}; expected a pnpm version such as pnpm@12.5.1.`);
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
async function readPackageManagerFromManifest(manifestPath) {
|
|
16
|
+
const raw = await fs.readFile(manifestPath, "utf8");
|
|
17
|
+
return validateWorkspacePackageManager(JSON.parse(raw)?.packageManager, manifestPath);
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
9
20
|
//#region src/runtime-assets.ts
|
|
10
21
|
const lockFileName = ".prepare-assets.lock";
|
|
11
22
|
const lockPollIntervalMs = 200;
|
|
@@ -33,7 +44,55 @@ async function isPrepared() {
|
|
|
33
44
|
path.join(assetsDir, "LICENSE"),
|
|
34
45
|
path.join(templatesDir, "tsdown")
|
|
35
46
|
];
|
|
36
|
-
|
|
47
|
+
if (!(await Promise.all(checks.map(pathExists))).every(Boolean)) return false;
|
|
48
|
+
const sourceRoot = path.resolve(packageDir, "..", "..");
|
|
49
|
+
const sourceManifest = path.join(sourceRoot, "package.json");
|
|
50
|
+
let sourcePackage;
|
|
51
|
+
try {
|
|
52
|
+
sourcePackage = JSON.parse(await readFile(sourceManifest, "utf8"));
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error.code === "ENOENT") return true;
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
if (sourcePackage.name !== "repoctl-workspace") return true;
|
|
58
|
+
const [sourcePackageManager, sourceNpmrc, sourceWorkspace] = await Promise.all([
|
|
59
|
+
readPackageManagerFromManifest(sourceManifest),
|
|
60
|
+
readFile(path.join(sourceRoot, ".npmrc"), "utf8"),
|
|
61
|
+
readFile(path.join(sourceRoot, "pnpm-workspace.yaml"), "utf8")
|
|
62
|
+
]);
|
|
63
|
+
let preparedPackageManager;
|
|
64
|
+
let preparedNpmrc;
|
|
65
|
+
let preparedWorkspace;
|
|
66
|
+
let sourceAgentContent;
|
|
67
|
+
let preparedAgentContent;
|
|
68
|
+
let sourceSkillContent;
|
|
69
|
+
let preparedSkillContent;
|
|
70
|
+
try {
|
|
71
|
+
const sourceAgentFiles = ["AGENTS.md", "CLAUDE.md"];
|
|
72
|
+
const [prepared, sourceAgents, preparedAgents, sourceSkill, preparedSkill] = await Promise.all([
|
|
73
|
+
Promise.all([
|
|
74
|
+
readPackageManagerFromManifest(path.join(assetsDir, "package.json")),
|
|
75
|
+
readFile(path.join(assetsDir, "npmrc"), "utf8"),
|
|
76
|
+
readFile(path.join(assetsDir, "pnpm-workspace.yaml"), "utf8")
|
|
77
|
+
]),
|
|
78
|
+
Promise.all(sourceAgentFiles.map((filename) => readFile(path.join(sourceRoot, "packages/monorepo/assets", filename), "utf8"))),
|
|
79
|
+
Promise.all(sourceAgentFiles.map((filename) => readFile(path.join(assetsDir, filename), "utf8"))),
|
|
80
|
+
readFile(path.join(sourceRoot, "packages/monorepo/resources/skills/repoctl/SKILL.md"), "utf8"),
|
|
81
|
+
readFile(path.join(assetsDir, ".agents/skills/repoctl/SKILL.md"), "utf8")
|
|
82
|
+
]);
|
|
83
|
+
preparedPackageManager = prepared[0];
|
|
84
|
+
preparedNpmrc = prepared[1];
|
|
85
|
+
preparedWorkspace = prepared[2];
|
|
86
|
+
sourceAgentContent = sourceAgents;
|
|
87
|
+
preparedAgentContent = preparedAgents;
|
|
88
|
+
sourceSkillContent = sourceSkill;
|
|
89
|
+
preparedSkillContent = preparedSkill;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error.code === "ENOENT" || error instanceof SyntaxError) return false;
|
|
92
|
+
if (error instanceof Error && error.message.startsWith("Invalid packageManager in ")) return false;
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
return sourcePackageManager === preparedPackageManager && sourceNpmrc === preparedNpmrc && sanitizePublishedWorkspaceContent(sourceWorkspace) === preparedWorkspace && sourceAgentContent.every((content, index) => content === preparedAgentContent[index]) && sourceSkillContent === preparedSkillContent;
|
|
37
96
|
}
|
|
38
97
|
async function waitForPrepared() {
|
|
39
98
|
const deadline = Date.now() + lockTimeoutMs;
|
|
@@ -69,6 +128,12 @@ async function ensureTemplateAssetsPrepared() {
|
|
|
69
128
|
await ensurePromise;
|
|
70
129
|
}
|
|
71
130
|
//#endregion
|
|
131
|
+
//#region src/package-manager/index.ts
|
|
132
|
+
async function getWorkspacePackageManager() {
|
|
133
|
+
await ensureTemplateAssetsPrepared();
|
|
134
|
+
return readPackageManagerFromManifest(path.join(assetsDir, "package.json"));
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
72
137
|
//#region src/scaffold.ts
|
|
73
138
|
async function isEmptyDir(dir) {
|
|
74
139
|
try {
|
|
@@ -141,7 +206,10 @@ async function scaffoldWorkspace(options) {
|
|
|
141
206
|
if (includeAssets && path.resolve(assetsDir$1) === path.resolve(assetsDir) || templateKeys.length > 0 && path.resolve(templatesDir$1) === path.resolve(templatesDir)) await ensureTemplateAssetsPrepared();
|
|
142
207
|
if (targetMode === "prepare") await prepareTargetDir(targetDir, { force });
|
|
143
208
|
else if (targetMode === "ensure") await fs.mkdir(targetDir, { recursive: true });
|
|
144
|
-
if (includeAssets) await copyDirContents(assetsDir$1, targetDir, {
|
|
209
|
+
if (includeAssets) await copyDirContents(assetsDir$1, targetDir, {
|
|
210
|
+
rootDir: assetsDir$1,
|
|
211
|
+
renameEntry: toWorkspaceAssetPath
|
|
212
|
+
});
|
|
145
213
|
if (!templateKeys.length) return;
|
|
146
214
|
const selections = new Set(templateKeys);
|
|
147
215
|
for (const template of templateChoices$1) {
|
|
@@ -258,4 +326,4 @@ const templateSourceMap = Object.fromEntries(templateChoices.map((item) => [item
|
|
|
258
326
|
const templateTargetMap = Object.fromEntries(templateChoices.map((item) => [item.key, item.target]));
|
|
259
327
|
const templateMap = templateSourceMap;
|
|
260
328
|
//#endregion
|
|
261
|
-
export { Command, assetTargets, assetsDir, checkbox, createTemplateCopyFilter, ensureTemplateAssetsPrepared, execaCommand, getAssetTargets, getTemplateChoice, getTemplateChoices, getTemplateDefinition, getTemplateKeys, getTemplateSource, getTemplateTarget, input, isGitignoreFile, isTemplateCategory, isTemplateKey, packageDir, prepareAssets, program, runCommand, scaffoldTemplate, scaffoldWorkspace, select, shouldSkipTemplatePath, skeletonDir, suggestTemplateKey, templateCategories, templateChoices, templateMap, templateSourceMap, templateTargetMap, templatesDir, toPublishGitignorePath, toWorkspaceGitignorePath };
|
|
329
|
+
export { Command, assetTargets, assetsDir, checkbox, createTemplateCopyFilter, ensureTemplateAssetsPrepared, execaCommand, getAssetTargets, getTemplateChoice, getTemplateChoices, getTemplateDefinition, getTemplateKeys, getTemplateSource, getTemplateTarget, getWorkspacePackageManager, input, isGitignoreFile, isTemplateCategory, isTemplateKey, packageDir, prepareAssets, program, runCommand, scaffoldTemplate, scaffoldWorkspace, select, shouldSkipTemplatePath, skeletonDir, suggestTemplateKey, templateCategories, templateChoices, templateMap, templateSourceMap, templateTargetMap, templatesDir, toPublishGitignorePath, toWorkspaceAssetPath, toWorkspaceGitignorePath };
|
|
@@ -128,6 +128,9 @@ function toPublishGitignorePath(input) {
|
|
|
128
128
|
function toWorkspaceGitignorePath(input) {
|
|
129
129
|
return replaceBasename(input, publishBasename, workspaceBasename);
|
|
130
130
|
}
|
|
131
|
+
function toWorkspaceAssetPath(input) {
|
|
132
|
+
return replaceBasename(toWorkspaceGitignorePath(input), "npmrc", ".npmrc");
|
|
133
|
+
}
|
|
131
134
|
function isGitignoreFile(name) {
|
|
132
135
|
return toPublishGitignorePath(name) !== name || toWorkspaceGitignorePath(name) !== name;
|
|
133
136
|
}
|
|
@@ -165,8 +168,16 @@ function createTemplateCopyFilter(rootDir) {
|
|
|
165
168
|
return (src) => !shouldSkipTemplatePath(rootDir, src);
|
|
166
169
|
}
|
|
167
170
|
//#endregion
|
|
168
|
-
//#region src/prepare.ts
|
|
169
|
-
|
|
171
|
+
//#region src/prepare/published.ts
|
|
172
|
+
function sanitizePublishedManifestContent(content) {
|
|
173
|
+
const manifest = JSON.parse(content);
|
|
174
|
+
if (manifest.scripts) {
|
|
175
|
+
for (const name of Object.keys(manifest.scripts)) if (name.startsWith("dev:") || name === "test:dev-scenarios" || name === "test:worker-types" || name === "test:packaged-create") delete manifest.scripts[name];
|
|
176
|
+
manifest.scripts["dev"] = "turbo run dev --concurrency=20";
|
|
177
|
+
if (manifest.scripts["test"]) manifest.scripts["test"] = manifest.scripts["test"].replace(" && pnpm test:dev-scenarios", "");
|
|
178
|
+
}
|
|
179
|
+
return `${JSON.stringify(manifest, null, 2)}\n`;
|
|
180
|
+
}
|
|
170
181
|
const publishedToolingConfigs = {
|
|
171
182
|
"commitlint.config.ts": [
|
|
172
183
|
`import { defineCommitlintConfig } from 'repoctl/tooling'`,
|
|
@@ -224,6 +235,12 @@ function sanitizePublishedWorkspaceContent(content) {
|
|
|
224
235
|
}
|
|
225
236
|
return changed ? YAML.stringify(workspace) : content;
|
|
226
237
|
}
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/prepare/index.ts
|
|
240
|
+
const huskySkippedEntryPattern = /[\\/]_$/;
|
|
241
|
+
function toPublishedAssetPath(target) {
|
|
242
|
+
return target === ".npmrc" ? "npmrc" : toPublishGitignorePath(target);
|
|
243
|
+
}
|
|
227
244
|
async function pathExists(targetPath) {
|
|
228
245
|
try {
|
|
229
246
|
await fs.access(targetPath);
|
|
@@ -294,9 +311,17 @@ async function copyAssets(repoRoot, overwriteExisting) {
|
|
|
294
311
|
const repoAsset = path$1.join(repoRoot, target);
|
|
295
312
|
const from = publishedAgentSourceFiles.has(target) || !await pathExists(repoAsset) ? packageAsset : repoAsset;
|
|
296
313
|
if (!await pathExists(from)) continue;
|
|
297
|
-
const to = path$1.join(assetsDir,
|
|
314
|
+
const to = path$1.join(assetsDir, toPublishedAssetPath(target));
|
|
298
315
|
const stats = await fs.stat(from);
|
|
299
|
-
|
|
316
|
+
const filter = target === ".husky" ? (src) => !huskySkippedEntryPattern.test(src) : void 0;
|
|
317
|
+
const refreshManagedMetadata = [
|
|
318
|
+
"package.json",
|
|
319
|
+
".npmrc",
|
|
320
|
+
"pnpm-workspace.yaml",
|
|
321
|
+
"AGENTS.md",
|
|
322
|
+
"CLAUDE.md"
|
|
323
|
+
].includes(target);
|
|
324
|
+
await copyEntry(from, to, overwriteExisting || refreshManagedMetadata, filter);
|
|
300
325
|
if (target === "tsconfig.json") await fs.writeFile(to, `${JSON.stringify({
|
|
301
326
|
extends: "repoctl/tsconfig.json",
|
|
302
327
|
files: []
|
|
@@ -310,10 +335,10 @@ async function writePublishedToolingConfigs() {
|
|
|
310
335
|
if (await pathExists(targetPath)) await fs.writeFile(targetPath, content);
|
|
311
336
|
}));
|
|
312
337
|
}
|
|
313
|
-
async function writePublishedAgentSkill(repoRoot
|
|
338
|
+
async function writePublishedAgentSkill(repoRoot) {
|
|
314
339
|
const skillFrom = path$1.join(repoRoot, "packages/monorepo/resources/skills/repoctl");
|
|
315
340
|
if (!await pathExists(skillFrom)) return;
|
|
316
|
-
await copyEntry(skillFrom, path$1.join(assetsDir, ".agents", "skills", "repoctl"),
|
|
341
|
+
await copyEntry(skillFrom, path$1.join(assetsDir, ".agents", "skills", "repoctl"), true);
|
|
317
342
|
}
|
|
318
343
|
async function sanitizePublishedWorkspace() {
|
|
319
344
|
const workspacePath = path$1.join(assetsDir, "pnpm-workspace.yaml");
|
|
@@ -331,7 +356,7 @@ async function removeSourceRepoReleaseToolingBuildStep() {
|
|
|
331
356
|
const nextContent = removeSourceRepoReleaseToolingBuildStepContent(content);
|
|
332
357
|
if (nextContent !== content) await fs.writeFile(releaseWorkflowPath, nextContent, "utf8");
|
|
333
358
|
}
|
|
334
|
-
async function
|
|
359
|
+
async function removeSourceRepoChecks() {
|
|
335
360
|
const workflowPath = path$1.join(assetsDir, ".github/workflows/ci.yml");
|
|
336
361
|
if (await pathExists(workflowPath)) {
|
|
337
362
|
const workflow = await fs.readFile(workflowPath, "utf8");
|
|
@@ -339,9 +364,8 @@ async function removeSourceRepoWorkerTypeChecks() {
|
|
|
339
364
|
}
|
|
340
365
|
const manifestPath = path$1.join(assetsDir, "package.json");
|
|
341
366
|
if (await pathExists(manifestPath)) {
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
await fs.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
367
|
+
const content = await fs.readFile(manifestPath, "utf8");
|
|
368
|
+
await fs.writeFile(manifestPath, sanitizePublishedManifestContent(content));
|
|
345
369
|
}
|
|
346
370
|
}
|
|
347
371
|
async function copyTemplates(repoRoot, overwriteExisting) {
|
|
@@ -360,14 +384,15 @@ async function prepareAssets(options = {}) {
|
|
|
360
384
|
if (!await pathExists(path$1.join(repoRoot, "templates"))) return;
|
|
361
385
|
await resetDir(assetsDir, overwriteExisting);
|
|
362
386
|
await resetDir(templatesDir, overwriteExisting);
|
|
387
|
+
await fs.rm(path$1.join(assetsDir, ".npmrc"), { force: true });
|
|
363
388
|
await copyAssets(repoRoot, overwriteExisting);
|
|
364
389
|
await sanitizePublishedWorkspace();
|
|
365
390
|
await removePublishedReleaseState();
|
|
366
391
|
await writePublishedToolingConfigs();
|
|
367
|
-
await writePublishedAgentSkill(repoRoot
|
|
392
|
+
await writePublishedAgentSkill(repoRoot);
|
|
368
393
|
await removeSourceRepoReleaseToolingBuildStep();
|
|
369
|
-
await
|
|
394
|
+
await removeSourceRepoChecks();
|
|
370
395
|
await copyTemplates(repoRoot, overwriteExisting);
|
|
371
396
|
}
|
|
372
397
|
//#endregion
|
|
373
|
-
export {
|
|
398
|
+
export { isGitignoreFile as a, toWorkspaceGitignorePath as c, packageDir as d, skeletonDir as f, getAssetTargets as h, shouldSkipTemplatePath as i, templateChoices as l, assetTargets as m, sanitizePublishedWorkspaceContent as n, toPublishGitignorePath as o, templatesDir as p, createTemplateCopyFilter as r, toWorkspaceAssetPath as s, prepareAssets as t, assetsDir as u };
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Non-interactive recipe for AI agents to create a repoctl workspace with pnpm create repoctl --yes.
|
|
2
|
+
description: Non-interactive recipe for AI agents to create a repoctl workspace with pnpm create repoctl@latest --yes.
|
|
3
3
|
outline: deep
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -9,18 +9,22 @@ Use this page when an agent is asked to create a new project with repoctl.
|
|
|
9
9
|
|
|
10
10
|
## Empty directory
|
|
11
11
|
|
|
12
|
-
Do not
|
|
12
|
+
Use a new or empty directory outside the repoctl source checkout. Do not add a
|
|
13
|
+
business project inside the source workspace, and do not use a non-empty target
|
|
14
|
+
unless the user explicitly asks to replace it with `--force`. Do not prompt.
|
|
15
|
+
Run:
|
|
13
16
|
|
|
14
17
|
```bash
|
|
15
|
-
pnpm create repoctl <dir> -- --yes --templates <keys>
|
|
18
|
+
pnpm create repoctl@latest <dir> -- --yes --templates <keys>
|
|
16
19
|
cd <dir>
|
|
20
|
+
corepack enable
|
|
17
21
|
pnpm install
|
|
18
22
|
pnpm exec repo init
|
|
19
23
|
pnpm exec repo doctor
|
|
20
24
|
```
|
|
21
25
|
|
|
22
26
|
`npm create repoctl@latest <dir> -- --yes --templates <keys>` and
|
|
23
|
-
`npx create-repoctl <dir> --yes --templates <keys>` are equivalent.
|
|
27
|
+
`npx create-repoctl@latest <dir> --yes --templates <keys>` are equivalent.
|
|
24
28
|
`pnpm create` and `npm create` need the extra `--` before flags.
|
|
25
29
|
|
|
26
30
|
## Template keys
|
|
@@ -23,7 +23,7 @@ repoctl publishes AI-readable documentation alongside the human site.
|
|
|
23
23
|
- `/llms.txt`: compact English index and product summary.
|
|
24
24
|
- `/llms-full.txt`: combined page content when a larger context is appropriate.
|
|
25
25
|
- `/zh/`: Simplified Chinese documentation.
|
|
26
|
-
- [Create a project](./create.md): non-interactive `pnpm create repoctl --yes` recipe.
|
|
26
|
+
- [Create a project](./create.md): non-interactive `pnpm create repoctl@latest --yes` recipe.
|
|
27
27
|
- [llms.txt Guide](./llms-txt.md): usage and maintenance rules.
|
|
28
28
|
|
|
29
29
|
## Using It In AI Tools
|
|
@@ -26,6 +26,21 @@ The release report names the packages that will change, the version decisions, a
|
|
|
26
26
|
- Fixed group mismatch: inspect the package relationships before editing versions.
|
|
27
27
|
- Registry authentication failure: refresh the local token and rerun the publish step; do not commit credentials.
|
|
28
28
|
|
|
29
|
+
## Partial publication and registry visibility
|
|
30
|
+
|
|
31
|
+
The publisher attempts uploads at most three times, waiting 20 and 40 seconds before refreshing registry state and selecting retry packages. An exact version with explicit upload acceptance is never uploaded again during that invocation, even if `npm view` cannot find it yet. Ordinary permission errors and 404 responses without transient-failure evidence fail immediately. Conflicts require read-only registry confirmation.
|
|
32
|
+
|
|
33
|
+
Accepted uploads must become queryable before post-publish hooks, Git tags, GitHub Releases, or prerelease pushes proceed. Visibility checks query pending versions every 10 seconds for up to five minutes. A timeout exits unsuccessfully and lists pending versions.
|
|
34
|
+
|
|
35
|
+
Two files remain in the workspace root:
|
|
36
|
+
|
|
37
|
+
- `pnpm-publish-summary.json`: cumulative `publishedPackages`, including accepted uploads and registry-confirmed versions. On failure, this file alone does not prove that every listed version is available.
|
|
38
|
+
- `repoctl-publish-progress.json`: `schemaVersion: 1`, `candidates`, `acceptedPackages`, `confirmedPackages`, and `status` (`publishing`, `confirming`, `complete`, or `failed`). Accepted versions missing from `confirmedPackages` still need visibility confirmation.
|
|
39
|
+
|
|
40
|
+
The managed Release workflow uploads both files as `npm-publish-progress-<run_id>-<run_attempt>` on success or failure, retained for 14 days. Missing reports are ignored. After upgrading repoctl, run `repo upgrade` to refresh managed workflows; custom workflows can add the same upload step.
|
|
41
|
+
|
|
42
|
+
After a failure, download the artifact and reconcile exact versions with `npm view <name>@<version> version`. Continue read-only checks for accepted but unconfirmed versions instead of rerunning the entire release lifecycle. Use the workflow's `reconcile` mode to repair metadata for visible versions, and `publish-unpublished` only for versions verified as still requiring upload. Reports are diagnostic: a new invocation does not trust old reports or automatically resume across processes.
|
|
43
|
+
|
|
29
44
|
## Next
|
|
30
45
|
|
|
31
46
|
Read [publishing and changelogs](/learn/monorepo/publish) for repository policy and [reports and output](/tasks/reports) for CI artifacts.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: 给 AI 代理用的非交互配方:pnpm create repoctl --yes 创建 repoctl 工作区。
|
|
2
|
+
description: 给 AI 代理用的非交互配方:pnpm create repoctl@latest --yes 创建 repoctl 工作区。
|
|
3
3
|
outline: deep
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -9,18 +9,21 @@ outline: deep
|
|
|
9
9
|
|
|
10
10
|
## 空目录
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
目标目录应是 repoctl 源仓库之外的新目录或空目录。不要把业务项目放进
|
|
13
|
+
源仓库,也不要在非空目录上运行,除非用户明确要求使用 `--force` 覆盖。
|
|
14
|
+
不要提问,直接运行:
|
|
13
15
|
|
|
14
16
|
```bash
|
|
15
|
-
pnpm create repoctl <dir> -- --yes --templates <keys>
|
|
17
|
+
pnpm create repoctl@latest <dir> -- --yes --templates <keys>
|
|
16
18
|
cd <dir>
|
|
19
|
+
corepack enable
|
|
17
20
|
pnpm install
|
|
18
21
|
pnpm exec repo init
|
|
19
22
|
pnpm exec repo doctor
|
|
20
23
|
```
|
|
21
24
|
|
|
22
25
|
等价命令:`npm create repoctl@latest <dir> -- --yes --templates <keys>` 与
|
|
23
|
-
`npx create-repoctl <dir> --yes --templates <keys>`。
|
|
26
|
+
`npx create-repoctl@latest <dir> --yes --templates <keys>`。
|
|
24
27
|
`pnpm create` / `npm create` 的 flag 前必须加 `--`。
|
|
25
28
|
|
|
26
29
|
## 模板 key
|
|
@@ -12,7 +12,7 @@ AI 文档入口用于给 Cursor、Claude Code、Codex 和其他 AI 编程工具
|
|
|
12
12
|
| ------------------------------ | ------------------------------------------------ |
|
|
13
13
|
| `/llms.txt` | 轻量文档索引,适合先交给 AI 工具判断要读哪些页面 |
|
|
14
14
|
| `/llms-full.txt` | 合并后的完整文档,适合需要一次性提供上下文的场景 |
|
|
15
|
-
| [创建项目](./create.md) | 非交互 `pnpm create repoctl --yes` 配方
|
|
15
|
+
| [创建项目](./create.md) | 非交互 `pnpm create repoctl@latest --yes` 配方 |
|
|
16
16
|
| [llms.txt 说明](./llms-txt.md) | 维护约定、访问方式和页面 frontmatter 要求 |
|
|
17
17
|
|
|
18
18
|
## 2. 在 AI 工具里怎么用
|
|
@@ -33,7 +33,7 @@ CI 将版本号、各包 `CHANGELOG.md` 和 `.changeset/ledger.yaml` 提交到 R
|
|
|
33
33
|
pnpm publish -r --report-summary --provenance --no-git-checks
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
pnpm 根据 registry 中已有的版本自动跳过已发布包。CI 读取 `pnpm-publish-summary.json` 创建 `package@version` tag 和 GitHub Release
|
|
36
|
+
pnpm 根据 registry 中已有的版本自动跳过已发布包。CI 读取 `pnpm-publish-summary.json` 创建 `package@version` tag 和 GitHub Release,发布器在本次调用内保留已接收上传的版本,确认 registry 可见性后再执行后置流程;失败后的处理见[部分发布与 registry 延迟](/zh/tasks/release#部分发布与-registry-延迟)。
|
|
37
37
|
|
|
38
38
|
## Workspace versioning 配置
|
|
39
39
|
|
|
@@ -38,7 +38,7 @@ pnpm version -r
|
|
|
38
38
|
pnpm publish -r --report-summary --provenance --no-git-checks
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
pnpm 会跳过 registry 中已经存在的版本,并把新发布包写入 `pnpm-publish-summary.json`。CI 根据 summary 创建 `package@version` Git tag 和 GitHub Release
|
|
41
|
+
pnpm 会跳过 registry 中已经存在的版本,并把新发布包写入 `pnpm-publish-summary.json`。CI 根据 summary 创建 `package@version` Git tag 和 GitHub Release,发布器会保留本次调用的部分成功状态,等待 registry 确认可见后再创建元数据;不要在传播延迟期间直接重跑整个生命周期,详见[发布任务](/zh/tasks/release)。
|
|
42
42
|
|
|
43
43
|
本地推荐使用 repoctl 包装命令:
|
|
44
44
|
|
|
@@ -26,6 +26,21 @@ repo release --dry-run
|
|
|
26
26
|
- 固定版本组不一致:先检查包之间的关系,再修改版本。
|
|
27
27
|
- registry 认证失败:刷新本地 token 后重试,不要提交凭据。
|
|
28
28
|
|
|
29
|
+
## 部分发布与 registry 延迟
|
|
30
|
+
|
|
31
|
+
发布器最多尝试上传 3 次,重试前分别等待 20、40 秒并重新查询 registry。明确上传成功的精确版本不会再次上传,即使此时 `npm view` 暂不可查询。普通权限错误和没有瞬时故障依据的 404 会直接失败;冲突只能通过只读对账确认,不能当作成功。
|
|
32
|
+
|
|
33
|
+
已接收上传的版本需要确认可查询后,才会执行后置 hooks、Git tag 和 GitHub Release。确认期间每 10 秒查询待确认版本,最多等待 5 分钟;超时会非零退出并列出待确认版本。
|
|
34
|
+
|
|
35
|
+
工作区根目录保留两份文件:
|
|
36
|
+
|
|
37
|
+
- `pnpm-publish-summary.json`:跨尝试累计的 `publishedPackages`,包括已接收上传和 registry 已确认的版本;失败时不能仅凭此文件认定所有版本已可用。
|
|
38
|
+
- `repoctl-publish-progress.json`:`schemaVersion: 1`,记录 `candidates`、`acceptedPackages`、`confirmedPackages` 及 `status`(`publishing`、`confirming`、`complete`、`failed`)。已接收但未确认的版本是前两种成功状态之差。
|
|
39
|
+
|
|
40
|
+
受管 Release 工作流会在发布成功或失败后上传 `npm-publish-progress-<run_id>-<run_attempt>` artifact,保留 14 天;没有清单时跳过上传。现有项目升级 repoctl 后运行 `repo upgrade` 更新受管工作流;自定义工作流可自行添加相同上传步骤。
|
|
41
|
+
|
|
42
|
+
失败后先下载 artifact,并用 `npm view <name>@<version> version` 对账。对于已接收但未确认的版本,继续只读确认,不要直接重跑整个发布生命周期。确认已经可用的版本可通过工作流的 `reconcile` 模式补齐元数据;仅对核实仍未上传的版本使用 `publish-unpublished`。旧清单仅用于诊断,新调用不会自动信任旧清单或跨进程续传。
|
|
43
|
+
|
|
29
44
|
## 下一步
|
|
30
45
|
|
|
31
46
|
阅读[发包与变更日志](/zh/learn/monorepo/publish),再查看[报告与输出](/zh/tasks/reports)了解 CI 产物。
|