@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.
@@ -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
- Do not prompt. Map the request to template keys, then:
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
@@ -3,7 +3,7 @@
3
3
  "type": "module",
4
4
  "version": "0.0.0",
5
5
  "private": true,
6
- "packageManager": "pnpm@12.5.1",
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 --parallel",
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",
@@ -4,6 +4,7 @@ packages:
4
4
  - templates/*
5
5
  - "!**/test/**"
6
6
  minimumReleaseAge: 0
7
+ pmOnFail: error
7
8
  versioning:
8
9
  changelog:
9
10
  storage: repository
package/assets/turbo.json CHANGED
@@ -40,6 +40,9 @@
40
40
  "outputs": []
41
41
  },
42
42
  "dev": {
43
+ "dependsOn": [
44
+ "^build"
45
+ ],
43
46
  "cache": false,
44
47
  "persistent": true
45
48
  },
package/dist/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as prepareAssets } from "./prepare-zIcRa4-K.mjs";
1
+ import { t as prepareAssets } from "./prepare-BapI8ioj.mjs";
2
2
  import process from "node:process";
3
3
  //#region src/cli.ts
4
4
  async function main() {
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 toPublishGitignorePath, c as assetsDir, d as templatesDir, f as assetTargets$1, i as isGitignoreFile, l as packageDir, n as createTemplateCopyFilter, o as toWorkspaceGitignorePath, p as getAssetTargets$1, r as shouldSkipTemplatePath, s as templateChoices$1, t as prepareAssets, u as skeletonDir } from "./prepare-zIcRa4-K.mjs";
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
- return (await Promise.all(checks.map(pathExists))).every(Boolean);
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, { rootDir: assetsDir$1 });
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
- const huskySkippedEntryPattern = /[\\/]_$/;
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, toPublishGitignorePath(target));
314
+ const to = path$1.join(assetsDir, toPublishedAssetPath(target));
298
315
  const stats = await fs.stat(from);
299
- await copyEntry(from, to, overwriteExisting, target === ".husky" ? (src) => !huskySkippedEntryPattern.test(src) : void 0);
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, overwriteExisting) {
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"), overwriteExisting);
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 removeSourceRepoWorkerTypeChecks() {
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 manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
343
- delete manifest.scripts?.["test:worker-types"];
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, overwriteExisting);
392
+ await writePublishedAgentSkill(repoRoot);
368
393
  await removeSourceRepoReleaseToolingBuildStep();
369
- await removeSourceRepoWorkerTypeChecks();
394
+ await removeSourceRepoChecks();
370
395
  await copyTemplates(repoRoot, overwriteExisting);
371
396
  }
372
397
  //#endregion
373
- 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 };
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,7 +1,7 @@
1
1
  {
2
2
  "name": "@icebreakers/monorepo-templates",
3
3
  "type": "module",
4
- "version": "2.0.2",
4
+ "version": "2.0.4",
5
5
  "description": "Built-in project templates and managed workspace assets for repoctl",
6
6
  "author": "ice breaker <hi@sonofmagic.top>",
7
7
  "license": "MIT",
@@ -60,7 +60,7 @@
60
60
  "@vitejs/plugin-vue-jsx": "^5.1.6",
61
61
  "@vue/tsconfig": "^0.9.1",
62
62
  "consola": "^3.4.2",
63
- "hono": "^4.13.8",
63
+ "hono": "^4.13.9",
64
64
  "tailwindcss": "^4.3.3",
65
65
  "typescript": "~6.0.3",
66
66
  "vite": "^8.3.0",
@@ -66,7 +66,7 @@
66
66
  "devDependencies": {
67
67
  "@hono/node-server": "^2.1.1",
68
68
  "consola": "^3.4.2",
69
- "hono": "^4.13.8",
69
+ "hono": "^4.13.9",
70
70
  "tsdown": "0.23.0",
71
71
  "wrangler": "^4.135.0",
72
72
  "zod": "^4.6.5"
@@ -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 prompt. Run:
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 产物。