create-repoctl 0.0.13 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,13 +1,29 @@
1
1
  # create-repoctl
2
2
 
3
- `create-repoctl` 是 `create-icebreaker` 的 repoctl 品牌入口,方便用户用不同包管理器的 create 语法快速创建项目。
3
+ English | [简体中文](README.zh-CN.md)
4
4
 
5
- ## 使用方式
5
+ The recommended create command for starting a repoctl-managed pnpm and Turborepo workspace.
6
6
 
7
- ```sh
7
+ ```bash
8
8
  npm create repoctl@latest
9
9
  pnpm create repoctl
10
10
  yarn create repoctl
11
11
  ```
12
12
 
13
- 这些命令会进入和 `create-icebreaker` 相同的脚手架流程:选择目标目录、选择保留模板,并在完成后提示 `pnpm setup` / `pnpm new` / `pnpm check` 等 repoctl onboarding 步骤。
13
+ The interactive flow selects a target directory and any built-in templates to include. After creation:
14
+
15
+ ```bash
16
+ cd <project>
17
+ pnpm install
18
+ pnpm exec repo init
19
+ pnpm exec repo doctor
20
+ pnpm exec repo check
21
+ ```
22
+
23
+ Output is English by default. Pass `--lang zh-CN` or set `REPOCTL_LANG=zh-CN` for Simplified Chinese.
24
+
25
+ ```bash
26
+ pnpm create repoctl -- --lang zh-CN
27
+ ```
28
+
29
+ Documentation: https://repo.icebreaker.top
@@ -0,0 +1,25 @@
1
+ # create-repoctl
2
+
3
+ [English](README.md) | 简体中文
4
+
5
+ 用于创建 repoctl 管理的 pnpm 与 Turborepo 工作区的推荐 create 命令。
6
+
7
+ ```bash
8
+ npm create repoctl@latest
9
+ pnpm create repoctl
10
+ yarn create repoctl
11
+ ```
12
+
13
+ 交互流程会选择目标目录和需要包含的内置模板。创建完成后运行:
14
+
15
+ ```bash
16
+ cd <project>
17
+ pnpm install
18
+ pnpm exec repo init
19
+ pnpm exec repo doctor
20
+ pnpm exec repo check
21
+ ```
22
+
23
+ 默认输出英文。传入 `--lang zh-CN` 或设置 `REPOCTL_LANG=zh-CN` 可切换为简体中文。
24
+
25
+ 文档:https://repo.icebreaker.top
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import '../dist/index.mjs'
2
+ import '../dist/cli.mjs'
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export {}
package/dist/cli.mjs ADDED
@@ -0,0 +1,9 @@
1
+ import { t as createCreateRepoctlProgram } from "./src-B2gA_DOK.mjs";
2
+ import process from "node:process";
3
+ //#region src/cli.ts
4
+ createCreateRepoctlProgram().parseAsync(process.argv).catch((error) => {
5
+ process.stderr.write(`[create-repoctl] ${error instanceof Error ? error.message : error}\n`);
6
+ process.exitCode = 1;
7
+ });
8
+ //#endregion
9
+ export {};
package/dist/index.d.mts CHANGED
@@ -1 +1,16 @@
1
- import "create-icebreaker";
1
+ import { Command } from "@icebreakers/monorepo-templates";
2
+ //#region src/i18n/index.d.ts
3
+ type CreateRepoctlLocale = 'en' | 'zh-CN';
4
+ declare function resolveCreateRepoctlLocale(argv?: readonly string[], env?: Record<string, string | undefined>): CreateRepoctlLocale;
5
+ //#endregion
6
+ //#region src/index.d.ts
7
+ interface CreateRepoctlProgramOptions {
8
+ commandName?: string;
9
+ defaultTarget?: string;
10
+ cwd?: string;
11
+ argv?: readonly string[];
12
+ env?: Record<string, string | undefined>;
13
+ }
14
+ declare function createCreateRepoctlProgram(options?: CreateRepoctlProgramOptions): Command;
15
+ //#endregion
16
+ export { type CreateRepoctlLocale, CreateRepoctlProgramOptions, createCreateRepoctlProgram, resolveCreateRepoctlLocale };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import "create-icebreaker";
2
- export {};
1
+ import { n as resolveCreateRepoctlLocale, t as createCreateRepoctlProgram } from "./src-B2gA_DOK.mjs";
2
+ export { createCreateRepoctlProgram, resolveCreateRepoctlLocale };
@@ -0,0 +1,318 @@
1
+ import path from "node:path";
2
+ import process from "node:process";
3
+ import { Command, checkbox, input, scaffoldWorkspace, templateChoices } from "@icebreakers/monorepo-templates";
4
+ import fs from "node:fs/promises";
5
+ //#region src/i18n/locales/en/error.ts
6
+ const error$1 = {
7
+ commanderAllowedChoices: "Allowed choices are",
8
+ commanderArgumentMissing: "argument missing",
9
+ commanderDidYouMean: "Did you mean",
10
+ commanderDidYouMeanOneOf: "Did you mean one of",
11
+ commanderErrorPrefix: "error:",
12
+ commanderMissingRequiredArgument: "missing required argument",
13
+ commanderNotSpecified: "not specified",
14
+ commanderRequiredOption: "required option",
15
+ commanderTooManyArguments: "too many arguments",
16
+ commanderUnknownCommand: "unknown command",
17
+ commanderUnknownOption: "unknown option",
18
+ invalidLocale: "Unsupported locale: {locale}. Use en or zh-CN."
19
+ };
20
+ //#endregion
21
+ //#region src/i18n/locales/en/help.ts
22
+ const help$1 = {
23
+ argumentsTitle: "Arguments:",
24
+ choicesLabel: "choices:",
25
+ commandsTitle: "Commands:",
26
+ defaultLabel: "default:",
27
+ description: "Create a repoctl-managed pnpm and Turborepo workspace",
28
+ envLabel: "env:",
29
+ targetArgument: "Target directory",
30
+ templatesOption: "Comma-separated template keys or indexes to include",
31
+ forceOption: "Remove an existing target directory before creating the workspace",
32
+ globalOptionsTitle: "Global Options:",
33
+ helpOption: "Display help for command",
34
+ languageOption: "Output language: en or zh-CN",
35
+ optionsTitle: "Options:",
36
+ presetLabel: "preset:",
37
+ usageTitle: "Usage:"
38
+ };
39
+ //#endregion
40
+ //#region src/i18n/locales/en/prompt.ts
41
+ const prompt$1 = {
42
+ targetPrompt: "Project directory (default: {defaultDir})",
43
+ templatesPrompt: "Select templates to include (none by default)"
44
+ };
45
+ //#endregion
46
+ //#region src/i18n/locales/en/report.ts
47
+ const report$1 = {
48
+ unknownTemplates: "Ignoring unknown templates: {templates}",
49
+ complete: "Workspace created. Next steps:"
50
+ };
51
+ //#endregion
52
+ //#region src/i18n/locales/en/index.ts
53
+ const en = {
54
+ ...help$1,
55
+ ...prompt$1,
56
+ ...report$1,
57
+ ...error$1
58
+ };
59
+ //#endregion
60
+ //#region src/i18n/locales/zh-CN/error.ts
61
+ const error = {
62
+ commanderAllowedChoices: "可选值为",
63
+ commanderArgumentMissing: "缺少参数",
64
+ commanderDidYouMean: "你是否想输入",
65
+ commanderDidYouMeanOneOf: "你是否想输入以下之一",
66
+ commanderErrorPrefix: "错误:",
67
+ commanderMissingRequiredArgument: "缺少必填参数",
68
+ commanderNotSpecified: "未指定",
69
+ commanderRequiredOption: "必填选项",
70
+ commanderTooManyArguments: "参数过多",
71
+ commanderUnknownCommand: "未知命令",
72
+ commanderUnknownOption: "未知选项",
73
+ invalidLocale: "不支持的语言:{locale}。请使用 en 或 zh-CN。"
74
+ };
75
+ //#endregion
76
+ //#region src/i18n/locales/zh-CN/help.ts
77
+ const help = {
78
+ argumentsTitle: "参数:",
79
+ choicesLabel: "可选值:",
80
+ commandsTitle: "命令:",
81
+ defaultLabel: "默认值:",
82
+ description: "创建由 repoctl 管理的 pnpm 与 Turborepo 工作区",
83
+ envLabel: "环境变量:",
84
+ targetArgument: "目标目录",
85
+ templatesOption: "要包含的模板 key 或序号,以逗号分隔",
86
+ forceOption: "创建前删除已存在的目标目录",
87
+ globalOptionsTitle: "全局选项:",
88
+ helpOption: "显示命令帮助",
89
+ languageOption: "输出语言:en 或 zh-CN",
90
+ optionsTitle: "选项:",
91
+ presetLabel: "预设值:",
92
+ usageTitle: "用法:"
93
+ };
94
+ //#endregion
95
+ //#region src/i18n/locales/zh-CN/prompt.ts
96
+ const prompt = {
97
+ targetPrompt: "项目目录(默认:{defaultDir})",
98
+ templatesPrompt: "选择要包含的模板(默认不选择)"
99
+ };
100
+ //#endregion
101
+ //#region src/i18n/locales/zh-CN/report.ts
102
+ const report = {
103
+ unknownTemplates: "忽略未知模板:{templates}",
104
+ complete: "工作区创建完成。后续步骤:"
105
+ };
106
+ //#endregion
107
+ //#region src/i18n/locales/zh-CN/index.ts
108
+ const zhCN = {
109
+ ...help,
110
+ ...prompt,
111
+ ...report,
112
+ ...error
113
+ };
114
+ //#endregion
115
+ //#region src/i18n/index.ts
116
+ const supportedLocales = ["en", "zh-CN"];
117
+ function isCreateRepoctlLocale(value) {
118
+ return supportedLocales.includes(value);
119
+ }
120
+ function readArgvLocale(argv) {
121
+ const equalsArg = argv.find((arg) => arg.startsWith("--lang="));
122
+ if (equalsArg) return equalsArg.slice(7);
123
+ const index = argv.indexOf("--lang");
124
+ return index >= 0 ? argv[index + 1] : void 0;
125
+ }
126
+ function resolveCreateRepoctlLocale(argv = process.argv, env = process.env) {
127
+ const argvLocale = readArgvLocale(argv);
128
+ if (isCreateRepoctlLocale(argvLocale)) return argvLocale;
129
+ const envLocale = env["REPOCTL_LANG"];
130
+ return isCreateRepoctlLocale(envLocale) ? envLocale : "en";
131
+ }
132
+ function createTranslator(locale) {
133
+ const catalog = locale === "zh-CN" ? zhCN : en;
134
+ return (key, values = {}) => {
135
+ return Object.entries(values).reduce((result, [name, value]) => result.replaceAll(`{${name}}`, value), catalog[key]);
136
+ };
137
+ }
138
+ //#endregion
139
+ //#region src/next-steps.ts
140
+ function formatNextSteps(targetDir, cwd = process.cwd(), heading = "Workspace created. Next steps:") {
141
+ return [
142
+ "",
143
+ heading,
144
+ ` cd ${path.relative(cwd, targetDir) || "."}`,
145
+ " pnpm install",
146
+ " pnpm exec repo init",
147
+ " pnpm exec repo doctor",
148
+ " pnpm exec repo check",
149
+ ""
150
+ ].join("\n");
151
+ }
152
+ //#endregion
153
+ //#region src/package-json.ts
154
+ const repoctlPackageName = "repoctl";
155
+ const legacyToolPackageName = "@icebreakers/monorepo";
156
+ const publishedRepoctlVersion = "latest";
157
+ async function updateRootPackageJson(targetDir, projectName) {
158
+ const pkgPath = path.join(targetDir, "package.json");
159
+ const raw = await fs.readFile(pkgPath, "utf8");
160
+ const pkg = JSON.parse(raw);
161
+ pkg.name = projectName;
162
+ const devDependencies = pkg.devDependencies;
163
+ if (devDependencies && devDependencies[legacyToolPackageName]) delete devDependencies[legacyToolPackageName];
164
+ if (devDependencies && devDependencies[repoctlPackageName]?.startsWith("workspace:")) devDependencies[repoctlPackageName] = publishedRepoctlVersion;
165
+ if (devDependencies && Object.keys(devDependencies).length === 0) delete pkg.devDependencies;
166
+ const scripts = pkg.scripts;
167
+ if (scripts) {
168
+ const nextScripts = {};
169
+ for (const [key, value] of Object.entries(scripts)) {
170
+ if (typeof value === "string" && value.includes("monorepo")) continue;
171
+ nextScripts[key] = value;
172
+ }
173
+ if (Object.keys(nextScripts).length) pkg.scripts = nextScripts;
174
+ else delete pkg.scripts;
175
+ }
176
+ await fs.writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf8");
177
+ }
178
+ //#endregion
179
+ //#region src/templates.ts
180
+ const templateTokenSplitPattern = /[,\s]+/;
181
+ const numericTemplatePattern = /^\d+$/;
182
+ function parseTemplateInput(input) {
183
+ const selections = /* @__PURE__ */ new Set();
184
+ const unknown = [];
185
+ const tokens = input.split(templateTokenSplitPattern).map((token) => token.trim()).filter(Boolean);
186
+ for (const token of tokens) {
187
+ const normalized = token.toLowerCase();
188
+ if (numericTemplatePattern.test(normalized)) {
189
+ const choice = templateChoices[Number(normalized) - 1];
190
+ if (choice) selections.add(choice.key);
191
+ else unknown.push(token);
192
+ continue;
193
+ }
194
+ const match = templateChoices.find((item) => item.key === normalized);
195
+ if (match) selections.add(match.key);
196
+ else unknown.push(token);
197
+ }
198
+ return {
199
+ selections: [...selections],
200
+ unknown
201
+ };
202
+ }
203
+ function resolveTemplateSelections(input) {
204
+ if (!input) return {
205
+ selections: [],
206
+ unknown: []
207
+ };
208
+ return parseTemplateInput(input);
209
+ }
210
+ //#endregion
211
+ //#region src/prompts.ts
212
+ async function promptTargetDir(defaultDir, message) {
213
+ return ((await input({
214
+ message,
215
+ default: defaultDir
216
+ }))?.trim?.() ?? defaultDir) || defaultDir;
217
+ }
218
+ async function promptTemplates(message) {
219
+ return await checkbox({
220
+ message,
221
+ choices: templateChoices.map((item) => ({
222
+ name: `${item.key} - ${item.label}`,
223
+ value: item.key
224
+ }))
225
+ });
226
+ }
227
+ //#endregion
228
+ //#region src/source-npm.ts
229
+ async function scaffoldFromNpm(targetDir, selectedTemplates, force = false) {
230
+ await scaffoldWorkspace({
231
+ targetDir,
232
+ templateKeys: selectedTemplates,
233
+ targetMode: "prepare",
234
+ force
235
+ });
236
+ }
237
+ //#endregion
238
+ //#region src/tsconfig.ts
239
+ const workspaceRoots = [
240
+ "apps",
241
+ "packages",
242
+ "templates"
243
+ ];
244
+ async function pathExists(targetPath) {
245
+ try {
246
+ await fs.access(targetPath);
247
+ return true;
248
+ } catch {
249
+ return false;
250
+ }
251
+ }
252
+ async function readWorkspaceReferences(targetDir, workspaceRoot) {
253
+ const rootDir = path.join(targetDir, workspaceRoot);
254
+ if (!await pathExists(rootDir)) return [];
255
+ const entries = await fs.readdir(rootDir, { withFileTypes: true });
256
+ const references = [];
257
+ for (const entry of entries) {
258
+ if (!entry.isDirectory()) continue;
259
+ const workspaceDir = path.join(rootDir, entry.name);
260
+ const [hasPackageJson, hasTsconfig] = await Promise.all([pathExists(path.join(workspaceDir, "package.json")), pathExists(path.join(workspaceDir, "tsconfig.json"))]);
261
+ if (hasPackageJson && hasTsconfig) references.push({ path: `./${workspaceRoot}/${entry.name}` });
262
+ }
263
+ return references;
264
+ }
265
+ async function updateRootTsconfigReferences(targetDir) {
266
+ const tsconfigPath = path.join(targetDir, "tsconfig.json");
267
+ const raw = await fs.readFile(tsconfigPath, "utf8");
268
+ const tsconfig = JSON.parse(raw);
269
+ const references = (await Promise.all(workspaceRoots.map((workspaceRoot) => readWorkspaceReferences(targetDir, workspaceRoot)))).flat();
270
+ tsconfig.extends = tsconfig.extends ?? "repoctl/tsconfig.json";
271
+ tsconfig.files = [];
272
+ tsconfig.references = references;
273
+ await fs.writeFile(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}\n`, "utf8");
274
+ }
275
+ //#endregion
276
+ //#region src/index.ts
277
+ function createCreateRepoctlProgram(options = {}) {
278
+ const commandName = options.commandName ?? "create-repoctl";
279
+ const defaultTarget = options.defaultTarget ?? "repoctl-workspace";
280
+ const cwd = options.cwd ?? process.cwd();
281
+ const t = createTranslator(resolveCreateRepoctlLocale(options.argv ?? process.argv, options.env ?? process.env));
282
+ const program = new Command();
283
+ const defaultHelp = program.createHelp();
284
+ const titles = {
285
+ "Arguments:": t("argumentsTitle"),
286
+ "Commands:": t("commandsTitle"),
287
+ "Global Options:": t("globalOptionsTitle"),
288
+ "Options:": t("optionsTitle"),
289
+ "Usage:": t("usageTitle")
290
+ };
291
+ const localizeHelpMetadata = (value) => value.replaceAll("choices:", t("choicesLabel")).replaceAll("default:", t("defaultLabel")).replaceAll("preset:", t("presetLabel")).replaceAll("env:", t("envLabel"));
292
+ const localizeCommanderError = (value) => value.replaceAll("Did you mean one of", t("commanderDidYouMeanOneOf")).replaceAll("Did you mean", t("commanderDidYouMean")).replaceAll("Allowed choices are", t("commanderAllowedChoices")).replaceAll("missing required argument", t("commanderMissingRequiredArgument")).replaceAll("required option", t("commanderRequiredOption")).replaceAll("argument missing", t("commanderArgumentMissing")).replaceAll("not specified", t("commanderNotSpecified")).replaceAll("too many arguments", t("commanderTooManyArguments")).replaceAll("unknown command", t("commanderUnknownCommand")).replaceAll("unknown option", t("commanderUnknownOption")).replace(/^error:/, t("commanderErrorPrefix"));
293
+ program.name(commandName).description(t("description")).argument("[dir]", t("targetArgument"), defaultTarget).option("-t, --templates <list>", t("templatesOption")).option("-f, --force", t("forceOption"), false).option("--lang <locale>", t("languageOption")).helpOption("-h, --help", t("helpOption")).configureOutput({ outputError: (value, write) => write(localizeCommanderError(value)) }).configureHelp({
294
+ argumentDescription: (argument) => localizeHelpMetadata(defaultHelp.argumentDescription(argument)),
295
+ optionDescription: (option) => localizeHelpMetadata(defaultHelp.optionDescription(option)),
296
+ styleTitle: (title) => titles[title] ?? title
297
+ }).action(async (targetDirInput, createOptions) => {
298
+ if (createOptions.lang && !isCreateRepoctlLocale(createOptions.lang)) throw new Error(t("invalidLocale", { locale: createOptions.lang }));
299
+ const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
300
+ let targetInput = targetDirInput || defaultTarget;
301
+ if (isInteractive) targetInput = await promptTargetDir(targetInput, t("targetPrompt", { defaultDir: targetInput }));
302
+ let selectedTemplates = [];
303
+ if (createOptions.templates) {
304
+ const { selections, unknown } = resolveTemplateSelections(createOptions.templates);
305
+ if (unknown.length) process.stderr.write(`${t("unknownTemplates", { templates: unknown.join(", ") })}\n`);
306
+ selectedTemplates = selections;
307
+ } else if (isInteractive) selectedTemplates = await promptTemplates(t("templatesPrompt"));
308
+ const targetDir = path.resolve(cwd, targetInput);
309
+ const projectName = path.basename(targetDir) || targetInput;
310
+ await scaffoldFromNpm(targetDir, selectedTemplates, Boolean(createOptions.force));
311
+ await updateRootPackageJson(targetDir, projectName);
312
+ await updateRootTsconfigReferences(targetDir);
313
+ process.stdout.write(formatNextSteps(targetDir, cwd, t("complete")));
314
+ });
315
+ return program;
316
+ }
317
+ //#endregion
318
+ export { resolveCreateRepoctlLocale as n, createCreateRepoctlProgram as t };
package/package.json CHANGED
@@ -1,17 +1,18 @@
1
1
  {
2
2
  "name": "create-repoctl",
3
3
  "type": "module",
4
- "version": "0.0.13",
5
- "description": "Bootstrap repoctl monorepo projects via npm create, yarn create, or pnpm create",
4
+ "version": "0.1.0",
5
+ "description": "Create a repoctl-managed pnpm and Turborepo workspace",
6
6
  "author": "ice breaker <hi@sonofmagic.top>",
7
7
  "license": "MIT",
8
+ "homepage": "https://repo.icebreaker.top",
8
9
  "repository": {
9
10
  "type": "git",
10
- "url": "git+https://github.com/sonofmagic/monorepo-template.git",
11
+ "url": "git+https://github.com/sonofmagic/repoctl.git",
11
12
  "directory": "packages/create-repoctl"
12
13
  },
13
14
  "bugs": {
14
- "url": "https://github.com/sonofmagic/monorepo-template/issues"
15
+ "url": "https://github.com/sonofmagic/repoctl/issues"
15
16
  },
16
17
  "keywords": [
17
18
  "repoctl",
@@ -21,6 +22,16 @@
21
22
  "scaffold"
22
23
  ],
23
24
  "sideEffects": false,
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.mts",
28
+ "default": "./dist/index.mjs"
29
+ },
30
+ "./package.json": "./package.json"
31
+ },
32
+ "main": "./dist/index.mjs",
33
+ "module": "./dist/index.mjs",
34
+ "types": "./dist/index.d.mts",
24
35
  "bin": {
25
36
  "create-repoctl": "bin/create-repoctl.js"
26
37
  },
@@ -32,7 +43,10 @@
32
43
  "node": ">=18.0.0"
33
44
  },
34
45
  "dependencies": {
35
- "create-icebreaker": "1.0.15"
46
+ "@icebreakers/monorepo-templates": "1.2.0"
47
+ },
48
+ "devDependencies": {
49
+ "tsd": "^0.33.0"
36
50
  },
37
51
  "publishConfig": {
38
52
  "access": "public"
@@ -40,7 +54,8 @@
40
54
  "scripts": {
41
55
  "dev": "tsdown --watch --sourcemap",
42
56
  "build": "tsdown",
43
- "typecheck": "pnpm --filter create-icebreaker build && node ../../node_modules/typescript/bin/tsc -p tsconfig.json",
57
+ "typecheck": "pnpm --filter @icebreakers/monorepo-templates build && node ../../node_modules/typescript/bin/tsc -p tsconfig.json",
58
+ "tsd": "pnpm build && tsd --files test-d/**/*.test-d.ts",
44
59
  "lint": "eslint .",
45
60
  "test": "vitest run --passWithNoTests"
46
61
  }