create-icebreaker 1.1.0 → 1.1.2
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 +9 -41
- package/README.zh-CN.md +18 -0
- package/dist/index.mjs +5 -173
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -1,50 +1,18 @@
|
|
|
1
1
|
# create-icebreaker
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
English | [简体中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Compatibility create command for repoctl-managed workspaces.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- `npm create icebreaker@latest`
|
|
7
|
+
Existing automation may continue to use:
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- asks for the project directory (defaults to `icebreaker-monorepo`)
|
|
14
|
-
- asks which templates to keep (default keeps none)
|
|
15
|
-
- updates the root `package.json` name
|
|
16
|
-
|
|
17
|
-
## Next Steps
|
|
18
|
-
|
|
19
|
-
After scaffolding finishes:
|
|
20
|
-
|
|
21
|
-
```sh
|
|
22
|
-
cd <your-project>
|
|
23
|
-
pnpm install
|
|
24
|
-
pnpm setup
|
|
25
|
-
pnpm new my-package
|
|
26
|
-
pnpm check
|
|
9
|
+
```bash
|
|
10
|
+
npm create icebreaker@latest
|
|
11
|
+
pnpm create icebreaker
|
|
27
12
|
```
|
|
28
13
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
- `pnpm setup`: bootstrap recommended workspace metadata and tooling
|
|
32
|
-
- `pnpm new`: create the next package or app through the guided flow
|
|
33
|
-
- `pnpm check`: run recommended local verification
|
|
34
|
-
- `pnpm dlx repo@latest clean --yes`: optional zero-install cleanup on a fresh clone
|
|
35
|
-
|
|
36
|
-
## Templates
|
|
37
|
-
|
|
38
|
-
Available template keys:
|
|
39
|
-
|
|
40
|
-
- `tsdown`
|
|
41
|
-
- `vue-lib`
|
|
42
|
-
- `vue-hono`
|
|
43
|
-
- `hono-server`
|
|
44
|
-
- `vitepress`
|
|
45
|
-
- `cli`
|
|
14
|
+
New projects should prefer `npm create repoctl@latest` or `pnpm create repoctl`. Both entrypoints use the same maintained scaffold engine and lead into the `repo init`, `repo doctor`, `repo new`, and `repo check` workflow.
|
|
46
15
|
|
|
47
|
-
|
|
16
|
+
Output is English by default. Pass `--lang zh-CN` or set `REPOCTL_LANG=zh-CN` for Simplified Chinese.
|
|
48
17
|
|
|
49
|
-
|
|
50
|
-
- `--force`: overwrite a non-empty target directory
|
|
18
|
+
Documentation: https://repo.icebreaker.top
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# create-icebreaker
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 简体中文
|
|
4
|
+
|
|
5
|
+
repoctl 管理工作区的兼容 create 命令。
|
|
6
|
+
|
|
7
|
+
已有自动化可以继续使用:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm create icebreaker@latest
|
|
11
|
+
pnpm create icebreaker
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
新项目推荐使用 `npm create repoctl@latest` 或 `pnpm create repoctl`。两个入口共享同一套维护中的 scaffold engine,并进入 `repo init`、`repo doctor`、`repo new` 和 `repo check` 工作流。
|
|
15
|
+
|
|
16
|
+
默认输出英文。传入 `--lang zh-CN` 或设置 `REPOCTL_LANG=zh-CN` 可切换为简体中文。
|
|
17
|
+
|
|
18
|
+
文档:https://repo.icebreaker.top
|
package/dist/index.mjs
CHANGED
|
@@ -1,178 +1,10 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
1
|
import process from "node:process";
|
|
3
|
-
import {
|
|
4
|
-
import fs from "node:fs/promises";
|
|
5
|
-
//#region src/constants.ts
|
|
6
|
-
const DEFAULT_TARGET = "icebreaker-monorepo";
|
|
7
|
-
//#endregion
|
|
8
|
-
//#region src/next-steps.ts
|
|
9
|
-
function formatNextSteps(targetDir) {
|
|
10
|
-
return [
|
|
11
|
-
"",
|
|
12
|
-
"All set! Next steps:",
|
|
13
|
-
` cd ${path.relative(process.cwd(), targetDir) || "."}`,
|
|
14
|
-
" pnpm install",
|
|
15
|
-
" pnpm setup",
|
|
16
|
-
" pnpm new my-package",
|
|
17
|
-
" pnpm check",
|
|
18
|
-
""
|
|
19
|
-
].join("\n");
|
|
20
|
-
}
|
|
21
|
-
//#endregion
|
|
22
|
-
//#region src/package-json.ts
|
|
23
|
-
const repoctlPackageName = "repoctl";
|
|
24
|
-
const legacyToolPackageName = "@icebreakers/monorepo";
|
|
25
|
-
const publishedRepoctlVersion = "latest";
|
|
26
|
-
async function updateRootPackageJson(targetDir, projectName) {
|
|
27
|
-
const pkgPath = path.join(targetDir, "package.json");
|
|
28
|
-
const raw = await fs.readFile(pkgPath, "utf8");
|
|
29
|
-
const pkg = JSON.parse(raw);
|
|
30
|
-
pkg.name = projectName;
|
|
31
|
-
const devDependencies = pkg.devDependencies;
|
|
32
|
-
if (devDependencies && devDependencies[legacyToolPackageName]) delete devDependencies[legacyToolPackageName];
|
|
33
|
-
if (devDependencies && devDependencies[repoctlPackageName]?.startsWith("workspace:")) devDependencies[repoctlPackageName] = publishedRepoctlVersion;
|
|
34
|
-
if (devDependencies && Object.keys(devDependencies).length === 0) delete pkg.devDependencies;
|
|
35
|
-
const scripts = pkg.scripts;
|
|
36
|
-
if (scripts) {
|
|
37
|
-
const nextScripts = {};
|
|
38
|
-
for (const [key, value] of Object.entries(scripts)) {
|
|
39
|
-
if (typeof value === "string" && value.includes("monorepo")) continue;
|
|
40
|
-
nextScripts[key] = value;
|
|
41
|
-
}
|
|
42
|
-
if (Object.keys(nextScripts).length) pkg.scripts = nextScripts;
|
|
43
|
-
else delete pkg.scripts;
|
|
44
|
-
}
|
|
45
|
-
await fs.writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf8");
|
|
46
|
-
}
|
|
47
|
-
//#endregion
|
|
48
|
-
//#region src/templates.ts
|
|
49
|
-
const templateTokenSplitPattern = /[,\s]+/;
|
|
50
|
-
const numericTemplatePattern = /^\d+$/;
|
|
51
|
-
function parseTemplateInput(input) {
|
|
52
|
-
const selections = /* @__PURE__ */ new Set();
|
|
53
|
-
const unknown = [];
|
|
54
|
-
const tokens = input.split(templateTokenSplitPattern).map((token) => token.trim()).filter(Boolean);
|
|
55
|
-
for (const token of tokens) {
|
|
56
|
-
const normalized = token.toLowerCase();
|
|
57
|
-
if (numericTemplatePattern.test(normalized)) {
|
|
58
|
-
const choice = templateChoices[Number(normalized) - 1];
|
|
59
|
-
if (choice) selections.add(choice.key);
|
|
60
|
-
else unknown.push(token);
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
const match = templateChoices.find((item) => item.key === normalized);
|
|
64
|
-
if (match) selections.add(match.key);
|
|
65
|
-
else unknown.push(token);
|
|
66
|
-
}
|
|
67
|
-
return {
|
|
68
|
-
selections: [...selections],
|
|
69
|
-
unknown
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
function resolveTemplateSelections(input) {
|
|
73
|
-
if (!input) return {
|
|
74
|
-
selections: [],
|
|
75
|
-
unknown: []
|
|
76
|
-
};
|
|
77
|
-
return parseTemplateInput(input);
|
|
78
|
-
}
|
|
79
|
-
//#endregion
|
|
80
|
-
//#region src/prompts.ts
|
|
81
|
-
async function promptTargetDir(defaultDir) {
|
|
82
|
-
return ((await input({
|
|
83
|
-
message: `项目创建目录(默认 ${defaultDir}):`,
|
|
84
|
-
default: defaultDir
|
|
85
|
-
}))?.trim?.() ?? defaultDir) || defaultDir;
|
|
86
|
-
}
|
|
87
|
-
async function promptTemplates() {
|
|
88
|
-
return await checkbox({
|
|
89
|
-
message: "请选择要保留的模板(默认不选择):",
|
|
90
|
-
choices: templateChoices.map((item) => ({
|
|
91
|
-
name: `${item.key} - ${item.label}`,
|
|
92
|
-
value: item.key
|
|
93
|
-
}))
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
//#endregion
|
|
97
|
-
//#region src/source-npm.ts
|
|
98
|
-
async function scaffoldFromNpm(targetDir, selectedTemplates, force = false) {
|
|
99
|
-
await scaffoldWorkspace({
|
|
100
|
-
targetDir,
|
|
101
|
-
templateKeys: selectedTemplates,
|
|
102
|
-
targetMode: "prepare",
|
|
103
|
-
force
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
//#endregion
|
|
107
|
-
//#region src/tsconfig.ts
|
|
108
|
-
const workspaceRoots = [
|
|
109
|
-
"apps",
|
|
110
|
-
"packages",
|
|
111
|
-
"templates"
|
|
112
|
-
];
|
|
113
|
-
async function pathExists(targetPath) {
|
|
114
|
-
try {
|
|
115
|
-
await fs.access(targetPath);
|
|
116
|
-
return true;
|
|
117
|
-
} catch {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
async function readWorkspaceReferences(targetDir, workspaceRoot) {
|
|
122
|
-
const rootDir = path.join(targetDir, workspaceRoot);
|
|
123
|
-
if (!await pathExists(rootDir)) return [];
|
|
124
|
-
const entries = await fs.readdir(rootDir, { withFileTypes: true });
|
|
125
|
-
const references = [];
|
|
126
|
-
for (const entry of entries) {
|
|
127
|
-
if (!entry.isDirectory()) continue;
|
|
128
|
-
const workspaceDir = path.join(rootDir, entry.name);
|
|
129
|
-
const [hasPackageJson, hasTsconfig] = await Promise.all([pathExists(path.join(workspaceDir, "package.json")), pathExists(path.join(workspaceDir, "tsconfig.json"))]);
|
|
130
|
-
if (hasPackageJson && hasTsconfig) references.push({ path: `./${workspaceRoot}/${entry.name}` });
|
|
131
|
-
}
|
|
132
|
-
return references;
|
|
133
|
-
}
|
|
134
|
-
async function updateRootTsconfigReferences(targetDir) {
|
|
135
|
-
const tsconfigPath = path.join(targetDir, "tsconfig.json");
|
|
136
|
-
const raw = await fs.readFile(tsconfigPath, "utf8");
|
|
137
|
-
const tsconfig = JSON.parse(raw);
|
|
138
|
-
const references = (await Promise.all(workspaceRoots.map((workspaceRoot) => readWorkspaceReferences(targetDir, workspaceRoot)))).flat();
|
|
139
|
-
tsconfig.extends = tsconfig.extends ?? "repoctl/tsconfig.json";
|
|
140
|
-
tsconfig.files = [];
|
|
141
|
-
tsconfig.references = references;
|
|
142
|
-
await fs.writeFile(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}\n`, "utf8");
|
|
143
|
-
}
|
|
144
|
-
//#endregion
|
|
2
|
+
import { createCreateRepoctlProgram } from "create-repoctl";
|
|
145
3
|
//#region src/index.ts
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
151
|
-
let targetInput = targetDirInput || "icebreaker-monorepo";
|
|
152
|
-
if (isInteractive) targetInput = await promptTargetDir(targetInput);
|
|
153
|
-
let selectedTemplates = [];
|
|
154
|
-
if (options.templates) {
|
|
155
|
-
const { selections, unknown } = resolveTemplateSelections(options.templates);
|
|
156
|
-
if (unknown.length) process.stderr.write(`忽略未知模板:${unknown.join(", ")}\n`);
|
|
157
|
-
selectedTemplates = selections;
|
|
158
|
-
} else if (isInteractive) selectedTemplates = await promptTemplates();
|
|
159
|
-
const targetDir = path.resolve(process.cwd(), targetInput);
|
|
160
|
-
const projectName = path.basename(targetDir) || targetInput;
|
|
161
|
-
await scaffoldFromNpm(targetDir, selectedTemplates, Boolean(options.force));
|
|
162
|
-
await updateRootPackageJson(targetDir, projectName);
|
|
163
|
-
await updateRootTsconfigReferences(targetDir);
|
|
164
|
-
printNextSteps(targetDir);
|
|
165
|
-
}
|
|
166
|
-
const program = new Command();
|
|
167
|
-
program.name("create-icebreaker").description("Bootstrap the icebreaker monorepo template").argument("[dir]", "Target directory", DEFAULT_TARGET).option("-t, --templates <list>", "Comma-separated template keys or indexes to keep").option("-f, --force", "Remove existing target directory before scaffolding", false).action(async (dir, options) => {
|
|
168
|
-
try {
|
|
169
|
-
await runCreate(dir, options);
|
|
170
|
-
} catch (error) {
|
|
171
|
-
process.stderr.write(`[create-icebreaker] ${error instanceof Error ? error.message : error}\n`);
|
|
172
|
-
process.exitCode = 1;
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
program.parseAsync(process.argv).catch((error) => {
|
|
4
|
+
createCreateRepoctlProgram({
|
|
5
|
+
commandName: "create-icebreaker",
|
|
6
|
+
defaultTarget: "icebreaker-monorepo"
|
|
7
|
+
}).parseAsync(process.argv).catch((error) => {
|
|
176
8
|
process.stderr.write(`[create-icebreaker] ${error instanceof Error ? error.message : error}\n`);
|
|
177
9
|
process.exitCode = 1;
|
|
178
10
|
});
|
package/package.json
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-icebreaker",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.1.
|
|
5
|
-
"description": "
|
|
4
|
+
"version": "1.1.2",
|
|
5
|
+
"description": "Compatibility create command for repoctl-managed workspaces",
|
|
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/
|
|
11
|
+
"url": "git+https://github.com/sonofmagic/repoctl.git",
|
|
11
12
|
"directory": "packages/create-icebreaker"
|
|
12
13
|
},
|
|
13
14
|
"bugs": {
|
|
14
|
-
"url": "https://github.com/sonofmagic/
|
|
15
|
+
"url": "https://github.com/sonofmagic/repoctl/issues"
|
|
15
16
|
},
|
|
16
17
|
"keywords": [
|
|
18
|
+
"repoctl",
|
|
17
19
|
"monorepo",
|
|
18
20
|
"create-app",
|
|
19
21
|
"template",
|
|
@@ -41,7 +43,7 @@
|
|
|
41
43
|
"node": ">=18.0.0"
|
|
42
44
|
},
|
|
43
45
|
"dependencies": {
|
|
44
|
-
"
|
|
46
|
+
"create-repoctl": "0.1.1"
|
|
45
47
|
},
|
|
46
48
|
"publishConfig": {
|
|
47
49
|
"access": "public"
|
|
@@ -49,7 +51,7 @@
|
|
|
49
51
|
"scripts": {
|
|
50
52
|
"dev": "tsdown --watch --sourcemap",
|
|
51
53
|
"build": "tsdown",
|
|
52
|
-
"typecheck": "pnpm --filter
|
|
54
|
+
"typecheck": "pnpm --filter create-repoctl build && node ../../node_modules/typescript/bin/tsc -p tsconfig.json",
|
|
53
55
|
"lint": "eslint .",
|
|
54
56
|
"test": "vitest run --passWithNoTests"
|
|
55
57
|
}
|