@icebreakers/monorepo 2.1.6 → 3.0.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.
@@ -0,0 +1,1311 @@
1
+ import gitUrlParse from "git-url-parse";
2
+ import { simpleGit } from "simple-git";
3
+ import { findWorkspaceDir } from "@pnpm/find-workspace-dir";
4
+ import { findWorkspacePackages } from "@pnpm/workspace.find-packages";
5
+ import { readWorkspaceManifest } from "@pnpm/workspace.read-manifest";
6
+ import path from "pathe";
7
+ import checkbox from "@inquirer/checkbox";
8
+ import fs from "fs-extra";
9
+ import { loadConfig } from "c12";
10
+ import process from "node:process";
11
+ import pc from "picocolors";
12
+ import path$1 from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import { createConsola } from "consola";
15
+ import crypto from "node:crypto";
16
+ import "@pnpm/types";
17
+ import { parse, stringify } from "comment-json";
18
+ import os from "node:os";
19
+ import { execaCommand } from "execa";
20
+ import PQueue from "p-queue";
21
+ import klaw from "klaw";
22
+ import { Buffer as Buffer$1 } from "node:buffer";
23
+ import { coerce, gte, minVersion } from "semver";
24
+
25
+ //#region rolldown:runtime
26
+ var __create = Object.create;
27
+ var __defProp$1 = Object.defineProperty;
28
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
29
+ var __getOwnPropNames = Object.getOwnPropertyNames;
30
+ var __getProtoOf = Object.getPrototypeOf;
31
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
32
+ var __commonJS = (cb, mod) => function() {
33
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
34
+ };
35
+ var __copyProps = (to, from, except, desc) => {
36
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
37
+ key = keys[i];
38
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp$1(to, key, {
39
+ get: ((k) => from[k]).bind(null, key),
40
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
41
+ });
42
+ }
43
+ return to;
44
+ };
45
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp$1(target, "default", {
46
+ value: mod,
47
+ enumerable: true
48
+ }) : target, mod));
49
+
50
+ //#endregion
51
+ //#region ../../node_modules/.pnpm/get-value@4.0.1/node_modules/get-value/dist/index.mjs
52
+ var __defProp = Object.defineProperty;
53
+ var __name = (target, value) => __defProp(target, "name", {
54
+ value,
55
+ configurable: true
56
+ });
57
+ var isObject$2 = /* @__PURE__ */ __name((v) => v !== null && typeof v === "object", "isObject");
58
+ var join = /* @__PURE__ */ __name((segs, joinChar, options) => {
59
+ if (typeof options.join === "function") return options.join(segs);
60
+ return segs[0] + joinChar + segs[1];
61
+ }, "join");
62
+ var split$1 = /* @__PURE__ */ __name((path$2, splitChar, options) => {
63
+ if (typeof options.split === "function") return options.split(path$2);
64
+ return path$2.split(splitChar);
65
+ }, "split");
66
+ var isValid = /* @__PURE__ */ __name((key, target = {}, options) => {
67
+ if (typeof options?.isValid === "function") return options.isValid(key, target);
68
+ return true;
69
+ }, "isValid");
70
+ var isValidObject = /* @__PURE__ */ __name((v) => {
71
+ return isObject$2(v) || typeof v === "function";
72
+ }, "isValidObject");
73
+ var index_default = /* @__PURE__ */ __name((target, path$2, options = {}) => {
74
+ if (!isObject$2(options)) options = { default: options };
75
+ if (!isValidObject(target)) return typeof options.default !== "undefined" ? options.default : target;
76
+ if (typeof path$2 === "number") path$2 = String(path$2);
77
+ const pathIsArray = Array.isArray(path$2);
78
+ const pathIsString = typeof path$2 === "string";
79
+ const splitChar = options.separator || ".";
80
+ const joinChar = options.joinChar || (typeof splitChar === "string" ? splitChar : ".");
81
+ if (!pathIsString && !pathIsArray) return target;
82
+ if (target[path$2] !== void 0) return isValid(path$2, target, options) ? target[path$2] : options.default;
83
+ const segs = pathIsArray ? path$2 : split$1(path$2, splitChar, options);
84
+ const len = segs.length;
85
+ let idx = 0;
86
+ do {
87
+ let prop = segs[idx];
88
+ if (typeof prop !== "string") prop = String(prop);
89
+ while (prop && prop.slice(-1) === "\\") prop = join([prop.slice(0, -1), segs[++idx] || ""], joinChar, options);
90
+ if (target[prop] !== void 0) {
91
+ if (!isValid(prop, target, options)) return options.default;
92
+ target = target[prop];
93
+ } else {
94
+ let hasProp = false;
95
+ let n = idx + 1;
96
+ while (n < len) {
97
+ prop = join([prop, segs[n++]], joinChar, options);
98
+ if (hasProp = target[prop] !== void 0) {
99
+ if (!isValid(prop, target, options)) return options.default;
100
+ target = target[prop];
101
+ idx = n - 1;
102
+ break;
103
+ }
104
+ }
105
+ if (!hasProp) return options.default;
106
+ }
107
+ } while (++idx < len && isValidObject(target));
108
+ if (idx === len) return target;
109
+ return options.default;
110
+ }, "getValue");
111
+ /*!
112
+ * get-value <https://github.com/jonschlinkert/get-value>
113
+ *
114
+ * Copyright (c) 2014-present, Jon Schlinkert.
115
+ * Released under the MIT License.
116
+ */
117
+
118
+ //#endregion
119
+ //#region src/core/git.ts
120
+ /**
121
+ * 对 simple-git 的轻量封装,集中处理配置缓存与常用查询。
122
+ */
123
+ var GitClient = class {
124
+ client;
125
+ #config;
126
+ constructor(options = {}) {
127
+ this.client = simpleGit(options);
128
+ }
129
+ /**
130
+ * 读取 Git 的 config 列表,原样返回 simple-git 的结果。
131
+ */
132
+ listConfig() {
133
+ return this.client.listConfig();
134
+ }
135
+ /**
136
+ * 初始化配置缓存,避免多次访问 Git 产生的性能损耗。
137
+ */
138
+ async init() {
139
+ this.#config = (await this.listConfig()).all;
140
+ return this.#config;
141
+ }
142
+ /**
143
+ * 获取缓存的配置,若未初始化则自动触发 init。
144
+ */
145
+ async getConfig() {
146
+ if (this.#config) return this.#config;
147
+ else return await this.init();
148
+ }
149
+ /**
150
+ * 解析 remote.origin.url,返回 git-url-parse 的结构,便于获取仓库元信息。
151
+ */
152
+ async getGitUrl() {
153
+ const x = index_default(await this.getConfig(), "remote.origin.url");
154
+ if (x) return gitUrlParse(x);
155
+ }
156
+ /**
157
+ * 组合 owner/name,生成常用的仓库名表达。
158
+ */
159
+ async getRepoName() {
160
+ const url = await this.getGitUrl();
161
+ if (url) return `${url.owner}/${url.name}`;
162
+ }
163
+ /**
164
+ * 从 Git 配置中提取用户信息,用于填充 package.json author 字段。
165
+ */
166
+ async getUser() {
167
+ const config = await this.getConfig();
168
+ return {
169
+ name: index_default(config, "user.name"),
170
+ email: index_default(config, "user.email")
171
+ };
172
+ }
173
+ /**
174
+ * 获取当前仓库的顶层目录路径。
175
+ */
176
+ async getRepoRoot() {
177
+ try {
178
+ const root = await this.client.revparse(["--show-toplevel"]);
179
+ return typeof root === "string" ? root.trim() : void 0;
180
+ } catch {
181
+ return;
182
+ }
183
+ }
184
+ };
185
+
186
+ //#endregion
187
+ //#region ../../node_modules/.pnpm/defu@6.1.4/node_modules/defu/dist/defu.mjs
188
+ function isPlainObject$1(value) {
189
+ if (value === null || typeof value !== "object") return false;
190
+ const prototype = Object.getPrototypeOf(value);
191
+ if (prototype !== null && prototype !== Object.prototype && Object.getPrototypeOf(prototype) !== null) return false;
192
+ if (Symbol.iterator in value) return false;
193
+ if (Symbol.toStringTag in value) return Object.prototype.toString.call(value) === "[object Module]";
194
+ return true;
195
+ }
196
+ function _defu(baseObject, defaults, namespace = ".", merger) {
197
+ if (!isPlainObject$1(defaults)) return _defu(baseObject, {}, namespace, merger);
198
+ const object = Object.assign({}, defaults);
199
+ for (const key in baseObject) {
200
+ if (key === "__proto__" || key === "constructor") continue;
201
+ const value = baseObject[key];
202
+ if (value === null || value === void 0) continue;
203
+ if (merger && merger(object, key, value, namespace)) continue;
204
+ if (Array.isArray(value) && Array.isArray(object[key])) object[key] = [...value, ...object[key]];
205
+ else if (isPlainObject$1(value) && isPlainObject$1(object[key])) object[key] = _defu(value, object[key], (namespace ? `${namespace}.` : "") + key.toString(), merger);
206
+ else object[key] = value;
207
+ }
208
+ return object;
209
+ }
210
+ function createDefu(merger) {
211
+ return (...arguments_) => arguments_.reduce((p, c) => _defu(p, c, "", merger), {});
212
+ }
213
+ const defu = createDefu();
214
+ const defuFn = createDefu((object, key, currentValue) => {
215
+ if (object[key] !== void 0 && typeof currentValue === "function") {
216
+ object[key] = currentValue(object[key]);
217
+ return true;
218
+ }
219
+ });
220
+ const defuArrayFn = createDefu((object, key, currentValue) => {
221
+ if (Array.isArray(object[key]) && typeof currentValue === "function") {
222
+ object[key] = currentValue(object[key]);
223
+ return true;
224
+ }
225
+ });
226
+
227
+ //#endregion
228
+ //#region src/core/workspace.ts
229
+ /**
230
+ * 读取 pnpm workspace 下的所有包,并根据配置过滤与补充字段。
231
+ */
232
+ async function getWorkspacePackages(workspaceDir, options) {
233
+ const { ignoreRootPackage, ignorePrivatePackage, patterns } = defu(options, {
234
+ ignoreRootPackage: true,
235
+ ignorePrivatePackage: true
236
+ });
237
+ const manifest = await readWorkspaceManifest(workspaceDir);
238
+ let pkgs = (await findWorkspacePackages(workspaceDir, { patterns: patterns ?? manifest?.packages })).filter((x) => {
239
+ if (ignorePrivatePackage && x.manifest.private) return false;
240
+ return true;
241
+ }).map((project) => {
242
+ const pkgJsonPath = path.resolve(project.rootDir, "package.json");
243
+ return {
244
+ ...project,
245
+ pkgJsonPath
246
+ };
247
+ });
248
+ if (ignoreRootPackage) pkgs = pkgs.filter((x) => {
249
+ return x.rootDir !== workspaceDir;
250
+ });
251
+ return pkgs;
252
+ }
253
+ /**
254
+ * 将工作区绝对路径、包列表与当前 cwd 打包返回,方便调用方一次获取所有信息。
255
+ */
256
+ async function getWorkspaceData(cwd, options) {
257
+ const workspaceDir = await findWorkspaceDir(cwd) ?? cwd;
258
+ return {
259
+ cwd,
260
+ workspaceDir,
261
+ packages: await getWorkspacePackages(workspaceDir, options)
262
+ };
263
+ }
264
+
265
+ //#endregion
266
+ //#region ../../node_modules/.pnpm/is-primitive@3.0.1/node_modules/is-primitive/index.js
267
+ /*!
268
+ * is-primitive <https://github.com/jonschlinkert/is-primitive>
269
+ *
270
+ * Copyright (c) 2014-present, Jon Schlinkert.
271
+ * Released under the MIT License.
272
+ */
273
+ var require_is_primitive = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/is-primitive@3.0.1/node_modules/is-primitive/index.js": ((exports, module) => {
274
+ module.exports = function isPrimitive$1(val) {
275
+ if (typeof val === "object") return val === null;
276
+ return typeof val !== "function";
277
+ };
278
+ }) });
279
+
280
+ //#endregion
281
+ //#region ../../node_modules/.pnpm/isobject@3.0.1/node_modules/isobject/index.js
282
+ /*!
283
+ * isobject <https://github.com/jonschlinkert/isobject>
284
+ *
285
+ * Copyright (c) 2014-2017, Jon Schlinkert.
286
+ * Released under the MIT License.
287
+ */
288
+ var require_isobject = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/isobject@3.0.1/node_modules/isobject/index.js": ((exports, module) => {
289
+ module.exports = function isObject$3(val) {
290
+ return val != null && typeof val === "object" && Array.isArray(val) === false;
291
+ };
292
+ }) });
293
+
294
+ //#endregion
295
+ //#region ../../node_modules/.pnpm/is-plain-object@2.0.4/node_modules/is-plain-object/index.js
296
+ /*!
297
+ * is-plain-object <https://github.com/jonschlinkert/is-plain-object>
298
+ *
299
+ * Copyright (c) 2014-2017, Jon Schlinkert.
300
+ * Released under the MIT License.
301
+ */
302
+ var require_is_plain_object = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/is-plain-object@2.0.4/node_modules/is-plain-object/index.js": ((exports, module) => {
303
+ var isObject$1 = require_isobject();
304
+ function isObjectObject(o) {
305
+ return isObject$1(o) === true && Object.prototype.toString.call(o) === "[object Object]";
306
+ }
307
+ module.exports = function isPlainObject$2(o) {
308
+ var ctor, prot;
309
+ if (isObjectObject(o) === false) return false;
310
+ ctor = o.constructor;
311
+ if (typeof ctor !== "function") return false;
312
+ prot = ctor.prototype;
313
+ if (isObjectObject(prot) === false) return false;
314
+ if (prot.hasOwnProperty("isPrototypeOf") === false) return false;
315
+ return true;
316
+ };
317
+ }) });
318
+
319
+ //#endregion
320
+ //#region ../../node_modules/.pnpm/set-value@4.1.0/node_modules/set-value/index.js
321
+ /*!
322
+ * set-value <https://github.com/jonschlinkert/set-value>
323
+ *
324
+ * Copyright (c) Jon Schlinkert (https://github.com/jonschlinkert).
325
+ * Released under the MIT License.
326
+ */
327
+ var require_set_value = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/set-value@4.1.0/node_modules/set-value/index.js": ((exports, module) => {
328
+ const { deleteProperty } = Reflect;
329
+ const isPrimitive = require_is_primitive();
330
+ const isPlainObject = require_is_plain_object();
331
+ const isObject = (value) => {
332
+ return typeof value === "object" && value !== null || typeof value === "function";
333
+ };
334
+ const isUnsafeKey = (key) => {
335
+ return key === "__proto__" || key === "constructor" || key === "prototype";
336
+ };
337
+ const validateKey = (key) => {
338
+ if (!isPrimitive(key)) throw new TypeError("Object keys must be strings or symbols");
339
+ if (isUnsafeKey(key)) throw new Error(`Cannot set unsafe key: "${key}"`);
340
+ };
341
+ const toStringKey = (input) => {
342
+ return Array.isArray(input) ? input.flat().map(String).join(",") : input;
343
+ };
344
+ const createMemoKey = (input, options) => {
345
+ if (typeof input !== "string" || !options) return input;
346
+ let key = input + ";";
347
+ if (options.arrays !== void 0) key += `arrays=${options.arrays};`;
348
+ if (options.separator !== void 0) key += `separator=${options.separator};`;
349
+ if (options.split !== void 0) key += `split=${options.split};`;
350
+ if (options.merge !== void 0) key += `merge=${options.merge};`;
351
+ if (options.preservePaths !== void 0) key += `preservePaths=${options.preservePaths};`;
352
+ return key;
353
+ };
354
+ const memoize = (input, options, fn) => {
355
+ const key = toStringKey(options ? createMemoKey(input, options) : input);
356
+ validateKey(key);
357
+ const value = setValue.cache.get(key) || fn();
358
+ setValue.cache.set(key, value);
359
+ return value;
360
+ };
361
+ const splitString = (input, options = {}) => {
362
+ const sep = options.separator || ".";
363
+ const preserve = sep === "/" ? false : options.preservePaths;
364
+ if (typeof input === "string" && preserve !== false && /\//.test(input)) return [input];
365
+ const parts = [];
366
+ let part = "";
367
+ const push = (part$1) => {
368
+ let number;
369
+ if (part$1.trim() !== "" && Number.isInteger(number = Number(part$1))) parts.push(number);
370
+ else parts.push(part$1);
371
+ };
372
+ for (let i = 0; i < input.length; i++) {
373
+ const value = input[i];
374
+ if (value === "\\") {
375
+ part += input[++i];
376
+ continue;
377
+ }
378
+ if (value === sep) {
379
+ push(part);
380
+ part = "";
381
+ continue;
382
+ }
383
+ part += value;
384
+ }
385
+ if (part) push(part);
386
+ return parts;
387
+ };
388
+ const split = (input, options) => {
389
+ if (options && typeof options.split === "function") return options.split(input);
390
+ if (typeof input === "symbol") return [input];
391
+ if (Array.isArray(input)) return input;
392
+ return memoize(input, options, () => splitString(input, options));
393
+ };
394
+ const assignProp = (obj, prop, value, options) => {
395
+ validateKey(prop);
396
+ if (value === void 0) deleteProperty(obj, prop);
397
+ else if (options && options.merge) {
398
+ const merge = options.merge === "function" ? options.merge : Object.assign;
399
+ if (merge && isPlainObject(obj[prop]) && isPlainObject(value)) obj[prop] = merge(obj[prop], value);
400
+ else obj[prop] = value;
401
+ } else obj[prop] = value;
402
+ return obj;
403
+ };
404
+ const setValue = (target, path$2, value, options) => {
405
+ if (!path$2 || !isObject(target)) return target;
406
+ const keys = split(path$2, options);
407
+ let obj = target;
408
+ for (let i = 0; i < keys.length; i++) {
409
+ const key = keys[i];
410
+ const next = keys[i + 1];
411
+ validateKey(key);
412
+ if (next === void 0) {
413
+ assignProp(obj, key, value, options);
414
+ break;
415
+ }
416
+ if (typeof next === "number" && !Array.isArray(obj[key])) {
417
+ obj = obj[key] = [];
418
+ continue;
419
+ }
420
+ if (!isObject(obj[key])) obj[key] = {};
421
+ obj = obj[key];
422
+ }
423
+ return target;
424
+ };
425
+ setValue.split = split;
426
+ setValue.cache = /* @__PURE__ */ new Map();
427
+ setValue.clear = () => {
428
+ setValue.cache = /* @__PURE__ */ new Map();
429
+ };
430
+ module.exports = setValue;
431
+ }) });
432
+
433
+ //#endregion
434
+ //#region src/core/config.ts
435
+ /**
436
+ * 简单的内存缓存,避免同一次命令中重复走磁盘加载配置。
437
+ */
438
+ const cache = /* @__PURE__ */ new Map();
439
+ /**
440
+ * 基于 c12 的通用配置加载逻辑,支持多种配置文件格式。
441
+ */
442
+ async function loadConfigInternal(cwd) {
443
+ const { config, configFile } = await loadConfig({
444
+ name: "monorepo",
445
+ cwd,
446
+ rcFile: false,
447
+ defaults: {},
448
+ globalRc: false,
449
+ packageJson: false
450
+ });
451
+ return {
452
+ file: configFile ? path.resolve(configFile) : null,
453
+ config: config ?? {}
454
+ };
455
+ }
456
+ /**
457
+ * 提供类型提示的辅助函数,供外部定义配置时使用。
458
+ */
459
+ function defineMonorepoConfig(config) {
460
+ return config;
461
+ }
462
+ /**
463
+ * 加载指定目录的 monorepo 配置,并利用缓存提升性能。
464
+ */
465
+ async function loadMonorepoConfig(cwd) {
466
+ const key = path.resolve(cwd);
467
+ if (!cache.has(key)) cache.set(key, loadConfigInternal(key));
468
+ const { config } = await cache.get(key);
469
+ return config;
470
+ }
471
+ /**
472
+ * 获取命令对应的合并配置,若未配置则返回空对象,保证调用端逻辑简单。
473
+ */
474
+ async function resolveCommandConfig(name$1, cwd) {
475
+ return ((await loadMonorepoConfig(cwd)).commands ?? {})[name$1] ?? {};
476
+ }
477
+
478
+ //#endregion
479
+ //#region src/commands/clean.ts
480
+ var import_set_value$5 = /* @__PURE__ */ __toESM(require_set_value(), 1);
481
+ /**
482
+ * 交互式清理被选中的包目录,同时重写根 package.json 中的 @icebreakers/monorepo 版本。
483
+ */
484
+ async function cleanProjects(cwd) {
485
+ const cleanConfig = await resolveCommandConfig("clean", cwd);
486
+ const { packages, workspaceDir } = await getWorkspaceData(cwd, cleanConfig?.includePrivate ? { ignorePrivatePackage: false } : void 0);
487
+ const filteredPackages = packages.filter((pkg) => {
488
+ const name$2 = pkg.manifest.name ?? "";
489
+ if (!name$2) return true;
490
+ if (!cleanConfig?.ignorePackages?.length) return true;
491
+ return !cleanConfig.ignorePackages.includes(name$2);
492
+ });
493
+ let cleanDirs = [];
494
+ if (cleanConfig?.autoConfirm) cleanDirs = filteredPackages.map((pkg) => pkg.rootDir);
495
+ else cleanDirs = await checkbox({
496
+ message: "请选择需要清理的目录",
497
+ choices: filteredPackages.map((x) => {
498
+ return {
499
+ name: path.relative(workspaceDir, x.rootDir),
500
+ value: x.rootDir,
501
+ checked: true,
502
+ description: x.manifest.name
503
+ };
504
+ })
505
+ });
506
+ const candidates = Array.from(new Set(cleanDirs.filter(Boolean)));
507
+ await Promise.all(candidates.map(async (dir) => {
508
+ if (await fs.pathExists(dir)) await fs.remove(dir);
509
+ }));
510
+ const name$1 = path.resolve(workspaceDir, "package.json");
511
+ const pkgJson = await fs.readJson(name$1);
512
+ (0, import_set_value$5.default)(pkgJson, "devDependencies.@icebreakers/monorepo", cleanConfig?.pinnedVersion ?? "latest", { preservePaths: false });
513
+ await fs.outputJson(name$1, pkgJson, { spaces: 2 });
514
+ }
515
+
516
+ //#endregion
517
+ //#region package.json
518
+ var name = "@icebreakers/monorepo";
519
+ var version = "3.0.0";
520
+
521
+ //#endregion
522
+ //#region src/constants.ts
523
+ /**
524
+ * 还原出 package.json 所在的绝对路径,方便后续按目录组织资源文件。
525
+ */
526
+ const packageJsonPath = fileURLToPath(new URL("../package.json", import.meta.url));
527
+ /**
528
+ * @icebreakers/monorepo 包的根目录,所有模板与资产目录都以此为基准。
529
+ */
530
+ const packageDir = path$1.dirname(packageJsonPath);
531
+ /**
532
+ * CLI 提供的模板目录,`monorepo new` 会从这里复制目标工程骨架。
533
+ */
534
+ const templatesDir = path$1.join(packageDir, "templates");
535
+ /**
536
+ * 升级命令需要写入的静态文件集合,位于 assets 目录中。
537
+ */
538
+ const assetsDir = path$1.join(packageDir, "assets");
539
+ /**
540
+ * monorepo 根目录,方便需要跳出当前包的逻辑(例如定位工作区文件)。
541
+ */
542
+ const rootDir = path$1.resolve(packageDir, "..", "..");
543
+
544
+ //#endregion
545
+ //#region src/core/logger.ts
546
+ /**
547
+ * 统一的日志实例,便于在命令行中输出带有前缀和颜色的消息。
548
+ */
549
+ const logger = createConsola();
550
+
551
+ //#endregion
552
+ //#region src/utils/fs.ts
553
+ /**
554
+ * 判断是否为可忽略的文件系统错误。
555
+ * - ENOENT: 文件已被删除
556
+ * - EBUSY: Windows 中资源被系统占用
557
+ */
558
+ function isIgnorableFsError(error) {
559
+ if (!error) return false;
560
+ const code = error.code;
561
+ return code === "ENOENT" || code === "EBUSY" || code === "EEXIST";
562
+ }
563
+
564
+ //#endregion
565
+ //#region src/utils/gitignore.ts
566
+ /**
567
+ * Utilities to handle renaming `.gitignore` files when packaging templates.
568
+ * pnpm publish strips `.gitignore`, so we temporarily rename them to `gitignore`
569
+ * and convert them back when writing into workspaces.
570
+ */
571
+ const publishBasename = "gitignore";
572
+ const workspaceBasename = ".gitignore";
573
+ function detectSeparator(input) {
574
+ if (input.includes("\\") && !input.includes("/")) return "\\";
575
+ return "/";
576
+ }
577
+ function replaceBasename(input, from, to) {
578
+ if (!input) return input;
579
+ const separator = detectSeparator(input);
580
+ const normalized = input.replace(/[\\/]/g, separator);
581
+ const hasTrailingSeparator = normalized.endsWith(separator);
582
+ const segments = normalized.split(separator);
583
+ if (hasTrailingSeparator && segments[segments.length - 1] === "") segments.pop();
584
+ const lastIndex = segments.length - 1;
585
+ if (lastIndex >= 0 && segments[lastIndex] === from) {
586
+ segments[lastIndex] = to;
587
+ const rebuilt = segments.join(separator);
588
+ return hasTrailingSeparator ? `${rebuilt}${separator}` : rebuilt;
589
+ }
590
+ return input;
591
+ }
592
+ /**
593
+ * Map a workspace path (containing `.gitignore`) to its packaged variant.
594
+ */
595
+ function toPublishGitignorePath(input) {
596
+ return replaceBasename(input, workspaceBasename, publishBasename);
597
+ }
598
+ /**
599
+ * Map a packaged path (containing `gitignore`) back to the workspace form.
600
+ */
601
+ function toWorkspaceGitignorePath(input) {
602
+ return replaceBasename(input, publishBasename, workspaceBasename);
603
+ }
604
+ /**
605
+ * Convenient helper to check whether a filename (with or without dot prefix)
606
+ * should be treated as a gitignore file.
607
+ */
608
+ function isGitignoreFile(name$1) {
609
+ return toPublishGitignorePath(name$1) !== name$1 || toWorkspaceGitignorePath(name$1) !== name$1;
610
+ }
611
+
612
+ //#endregion
613
+ //#region src/utils/hash.ts
614
+ /**
615
+ * 生成给定二进制内容的 md5 摘要,用于快速比较文件内容。
616
+ */
617
+ function getFileHash(data) {
618
+ const hashSum = crypto.createHash("md5");
619
+ hashSum.update(data);
620
+ return hashSum.digest("hex");
621
+ }
622
+ /**
623
+ * 对比两个文件的 md5,如果不一致则认为内容有变化。
624
+ */
625
+ function isFileChanged(src, dest) {
626
+ try {
627
+ return getFileHash(src) !== getFileHash(dest);
628
+ } catch (err) {
629
+ logger.error("Error calculating file hash:", err);
630
+ return false;
631
+ }
632
+ }
633
+
634
+ //#endregion
635
+ //#region src/utils/regexp.ts
636
+ /**
637
+ * 逃逸正则表达式中所有特殊字符,避免被当做模式解析。
638
+ */
639
+ function escapeStringRegexp(str) {
640
+ return str.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d");
641
+ }
642
+ /**
643
+ * 判断字符串是否命中任意一个正则,用于过滤要同步的资产文件。
644
+ */
645
+ function isMatch(str, arr) {
646
+ for (const reg of arr) if (reg.test(str)) return true;
647
+ return false;
648
+ }
649
+
650
+ //#endregion
651
+ //#region src/commands/create.ts
652
+ var import_set_value$4 = /* @__PURE__ */ __toESM(require_set_value(), 1);
653
+ /**
654
+ * 内置模板映射表,value 指向仓库中对应模板所在路径。
655
+ */
656
+ const templateMap = {
657
+ "tsup": "packages/tsup-template",
658
+ "tsdown": "packages/tsdown-template",
659
+ "unbuild": "packages/unbuild-template",
660
+ "vue-lib": "packages/vue-lib-template",
661
+ "hono-server": "apps/server",
662
+ "vue-hono": "apps/client",
663
+ "vitepress": "apps/website",
664
+ "cli": "apps/cli"
665
+ };
666
+ const defaultTemplate = "unbuild";
667
+ /**
668
+ * 交互式选择模板时的默认选项列表。
669
+ */
670
+ const baseChoices = [
671
+ {
672
+ name: "unbuild 打包",
673
+ value: "unbuild"
674
+ },
675
+ {
676
+ name: "tsup 打包",
677
+ value: "tsup"
678
+ },
679
+ {
680
+ name: "tsdown 打包",
681
+ value: "tsdown"
682
+ },
683
+ {
684
+ name: "vue 组件",
685
+ value: "vue-lib"
686
+ },
687
+ {
688
+ name: "vue hono 全栈",
689
+ value: "vue-hono"
690
+ },
691
+ {
692
+ name: "hono 模板",
693
+ value: "hono-server"
694
+ },
695
+ {
696
+ name: "vitepress 文档",
697
+ value: "vitepress"
698
+ },
699
+ {
700
+ name: "cli 模板",
701
+ value: "cli"
702
+ }
703
+ ];
704
+ /**
705
+ * 若配置中提供 choices 则优先使用,否则退回默认预设。
706
+ */
707
+ function getCreateChoices(choices) {
708
+ if (choices?.length) return choices;
709
+ return [...baseChoices];
710
+ }
711
+ /**
712
+ * 合并内置与自定义模板映射,允许扩展新的模板类型。
713
+ */
714
+ function getTemplateMap(extra) {
715
+ const base = { ...templateMap };
716
+ if (extra && Object.keys(extra).length) Object.assign(base, extra);
717
+ return base;
718
+ }
719
+ async function applyGitMetadata(pkgJson, repoDir, targetDir) {
720
+ try {
721
+ const git = new GitClient({ baseDir: repoDir });
722
+ const repoName = await git.getRepoName();
723
+ if (!repoName) return;
724
+ (0, import_set_value$4.default)(pkgJson, ["bugs", "url"], `https://github.com/${repoName}/issues`);
725
+ const repository = {
726
+ type: "git",
727
+ url: `git+https://github.com/${repoName}.git`
728
+ };
729
+ const directoryBase = await git.getRepoRoot() ?? repoDir;
730
+ const relative = path.relative(directoryBase, targetDir);
731
+ if (relative && relative !== ".") repository.directory = relative.split(path.sep).join("/");
732
+ (0, import_set_value$4.default)(pkgJson, "repository", repository);
733
+ const gitUser = await git.getUser();
734
+ if (gitUser?.name && gitUser?.email) (0, import_set_value$4.default)(pkgJson, "author", `${gitUser.name} <${gitUser.email}>`);
735
+ } catch {}
736
+ }
737
+ /**
738
+ * 根据提供的参数或配置生成新工程目录,并可自动改写 package.json。
739
+ */
740
+ async function createNewProject(options) {
741
+ const cwd = options?.cwd ?? process.cwd();
742
+ const createConfig = await resolveCommandConfig("create", cwd);
743
+ const renameJson = options?.renameJson ?? createConfig?.renameJson ?? false;
744
+ const rawName = options?.name ?? createConfig?.name;
745
+ const name$1 = typeof rawName === "string" ? rawName.trim() : void 0;
746
+ const requestedTemplate = options?.type ?? createConfig?.type ?? createConfig?.defaultTemplate ?? defaultTemplate;
747
+ const templateDefinitions = getTemplateMap(createConfig?.templateMap);
748
+ const templatesRoot = createConfig?.templatesDir ? path.resolve(cwd, createConfig.templatesDir) : templatesDir;
749
+ const fallbackTemplate = createConfig?.defaultTemplate ?? defaultTemplate;
750
+ const bundlerName = typeof requestedTemplate === "string" && templateDefinitions[requestedTemplate] ? requestedTemplate : fallbackTemplate;
751
+ const sourceRelative = templateDefinitions[bundlerName];
752
+ if (!sourceRelative) throw new Error(`未找到名为 ${bundlerName} 的模板,请检查 monorepo.config.ts`);
753
+ const from = path.join(templatesRoot, sourceRelative);
754
+ const targetName = name$1 && name$1.length > 0 ? name$1 : sourceRelative;
755
+ const to = path.join(cwd, targetName);
756
+ if (await fs.pathExists(to)) throw new Error(`${pc.red("目标目录已存在")}: ${path.relative(cwd, to)}`);
757
+ await fs.ensureDir(to);
758
+ const filelist = await fs.readdir(from);
759
+ const shouldSkip = (src) => path.basename(src) === ".DS_Store";
760
+ const copyTasks = filelist.filter((filename) => filename !== "package.json").map(async (filename) => {
761
+ const sourcePath = path.resolve(from, filename);
762
+ const targetPath = path.resolve(to, toWorkspaceGitignorePath(filename));
763
+ await fs.copy(sourcePath, targetPath, { filter(src) {
764
+ if (shouldSkip(src)) return false;
765
+ return true;
766
+ } });
767
+ });
768
+ await Promise.all(copyTasks);
769
+ if (filelist.includes("package.json")) {
770
+ const sourceJsonPath = path.resolve(from, "package.json");
771
+ const sourceJson = await fs.readJson(sourceJsonPath);
772
+ (0, import_set_value$4.default)(sourceJson, "version", "0.0.0");
773
+ (0, import_set_value$4.default)(sourceJson, "name", name$1?.startsWith("@") ? name$1 : path.basename(targetName));
774
+ await applyGitMetadata(sourceJson, cwd, to);
775
+ await fs.outputJson(path.resolve(to, renameJson ? "package.mock.json" : "package.json"), sourceJson, { spaces: 2 });
776
+ }
777
+ logger.success(`${pc.bgGreenBright(pc.white(`[${bundlerName}]`))} ${targetName} 项目创建成功!`);
778
+ }
779
+
780
+ //#endregion
781
+ //#region src/core/context.ts
782
+ /**
783
+ * 构建命令执行上下文,封装常用的工作区、Git、配置等信息。
784
+ */
785
+ async function createContext(cwd) {
786
+ const { packages, workspaceDir } = await getWorkspaceData(cwd);
787
+ const git = new GitClient({ baseDir: workspaceDir });
788
+ const workspaceFilepath = path.resolve(workspaceDir, "pnpm-workspace.yaml");
789
+ return {
790
+ cwd,
791
+ git,
792
+ gitUrl: await git.getGitUrl(),
793
+ gitUser: await git.getUser(),
794
+ workspaceDir,
795
+ workspaceFilepath,
796
+ packages,
797
+ config: await loadMonorepoConfig(workspaceDir)
798
+ };
799
+ }
800
+
801
+ //#endregion
802
+ //#region src/commands/init/setChangeset.ts
803
+ var import_set_value$3 = /* @__PURE__ */ __toESM(require_set_value(), 1);
804
+ /**
805
+ * 将 changeset 配置中的仓库地址指向当前项目,方便自动生成变更日志链接。
806
+ */
807
+ async function setChangeset_default(ctx) {
808
+ const { gitUrl, workspaceFilepath } = ctx;
809
+ if (gitUrl && await fs.exists(workspaceFilepath)) {
810
+ const changesetConfigPath = path.resolve(path.dirname(workspaceFilepath), ".changeset/config.json");
811
+ if (await fs.exists(changesetConfigPath)) {
812
+ const changesetConfig = await fs.readJson(changesetConfigPath);
813
+ if (gitUrl.full_name) {
814
+ (0, import_set_value$3.default)(changesetConfig, "changelog.1.repo", gitUrl.full_name);
815
+ await fs.outputJson(changesetConfigPath, changesetConfig, { spaces: 2 });
816
+ }
817
+ }
818
+ }
819
+ }
820
+
821
+ //#endregion
822
+ //#region src/commands/init/setPkgJson.ts
823
+ var import_set_value$2 = /* @__PURE__ */ __toESM(require_set_value(), 1);
824
+ /**
825
+ * 根据当前仓库信息同步 package.json 的仓库、作者等字段。
826
+ */
827
+ async function setPkgJson_default(ctx) {
828
+ const { gitUrl, gitUser, packages, cwd, workspaceFilepath } = ctx;
829
+ const workspaceExists = await fs.pathExists(workspaceFilepath);
830
+ if (gitUrl && workspaceExists) await Promise.all(packages.map(async (pkg) => {
831
+ if (!await fs.pathExists(pkg.pkgJsonPath)) return;
832
+ const pkgJson = JSON.parse(JSON.stringify(pkg.manifest));
833
+ const directory = path.relative(cwd, pkg.rootDir);
834
+ (0, import_set_value$2.default)(pkgJson, ["bugs", "url"], `https://github.com/${gitUrl.full_name}/issues`);
835
+ const repository = {
836
+ type: "git",
837
+ url: `git+https://github.com/${gitUrl.full_name}.git`
838
+ };
839
+ if (directory) repository.directory = directory;
840
+ (0, import_set_value$2.default)(pkgJson, "repository", repository);
841
+ if (gitUser?.name && gitUser?.email) (0, import_set_value$2.default)(pkgJson, "author", `${gitUser.name} <${gitUser.email}>`);
842
+ const nextContent = `${JSON.stringify(pkgJson, void 0, 2)}\n`;
843
+ if (await fs.readFile(pkg.pkgJsonPath, "utf8") !== nextContent) await fs.writeFile(pkg.pkgJsonPath, nextContent, "utf8");
844
+ }));
845
+ }
846
+
847
+ //#endregion
848
+ //#region src/commands/init/setReadme.ts
849
+ async function getRows(ctx) {
850
+ const { packages, gitUrl, gitUser, cwd } = ctx;
851
+ const rows = [];
852
+ if (gitUrl) rows.push(`# ${gitUrl.name}\n`);
853
+ rows.push("## Packages\n");
854
+ const sortedPackages = [...packages].sort((a, b) => {
855
+ const left = a.manifest.name ?? "";
856
+ const right = b.manifest.name ?? "";
857
+ return left.localeCompare(right);
858
+ });
859
+ for (const pkg of sortedPackages) {
860
+ const p = path.relative(cwd, pkg.rootDirRealPath);
861
+ if (p) {
862
+ const description = pkg.manifest.description ? `- ${pkg.manifest.description}` : "";
863
+ rows.push(`- [${pkg.manifest.name}](${p}) ${description}`);
864
+ }
865
+ }
866
+ if (gitUrl) {
867
+ rows.push("\n## Contributing\n");
868
+ rows.push("Contributions Welcome! You can contribute in the following ways.");
869
+ rows.push("");
870
+ rows.push("- Create an Issue - Propose a new feature. Report a bug.");
871
+ rows.push("- Pull Request - Fix a bug and typo. Refactor the code.");
872
+ rows.push("- Create third-party middleware - Instruct below.");
873
+ rows.push("- Share - Share your thoughts on the Blog, X, and others.");
874
+ rows.push(`- Make your application - Please try to use ${gitUrl.name}.`);
875
+ rows.push("");
876
+ rows.push("For more details, see [CONTRIBUTING.md](CONTRIBUTING.md).");
877
+ rows.push("\n## Contributors\n");
878
+ rows.push(`Thanks to [all contributors](https://github.com/${gitUrl.full_name}/graphs/contributors)!`);
879
+ }
880
+ rows.push("\n## Authors\n");
881
+ if (gitUser?.name && gitUser?.email) rows.push(`${gitUser.name} <${gitUser.email}>`);
882
+ rows.push("\n## License\n");
883
+ rows.push("Distributed under the MIT License. See [LICENSE](LICENSE) for more information.");
884
+ return rows;
885
+ }
886
+ /**
887
+ * 生成标准化的 README 草稿,列出所有子包并补充贡献者、作者信息。
888
+ */
889
+ async function setReadme_default(ctx) {
890
+ const rows = await getRows(ctx);
891
+ await fs.writeFile(path.resolve(ctx.cwd, "README.md"), `${rows.join("\n")}\n`);
892
+ }
893
+
894
+ //#endregion
895
+ //#region src/commands/init/index.ts
896
+ /**
897
+ * 初始化命令入口,根据配置逐步生成基础文件。
898
+ */
899
+ async function init(cwd) {
900
+ const ctx = await createContext(cwd);
901
+ const initConfig = ctx.config.commands?.init ?? {};
902
+ if (!initConfig.skipChangeset) await setChangeset_default(ctx);
903
+ if (!initConfig.skipPkgJson) await setPkgJson_default(ctx);
904
+ if (!initConfig.skipReadme) await setReadme_default(ctx);
905
+ }
906
+
907
+ //#endregion
908
+ //#region src/commands/mirror/sources.ts
909
+ const chinaMirrorsEnvs = {
910
+ COREPACK_NPM_REGISTRY: "https://registry.npmmirror.com",
911
+ EDGEDRIVER_CDNURL: "https://npmmirror.com/mirrors/edgedriver",
912
+ NODEJS_ORG_MIRROR: "https://cdn.npmmirror.com/binaries/node",
913
+ NVM_NODEJS_ORG_MIRROR: "https://cdn.npmmirror.com/binaries/node",
914
+ PHANTOMJS_CDNURL: "https://cdn.npmmirror.com/binaries/phantomjs",
915
+ CHROMEDRIVER_CDNURL: "https://cdn.npmmirror.com/binaries/chromedriver",
916
+ OPERADRIVER_CDNURL: "https://cdn.npmmirror.com/binaries/operadriver",
917
+ CYPRESS_DOWNLOAD_PATH_TEMPLATE: "https://cdn.npmmirror.com/binaries/cypress/${version}/${platform}-${arch}/cypress.zip",
918
+ ELECTRON_MIRROR: "https://cdn.npmmirror.com/binaries/electron/",
919
+ ELECTRON_BUILDER_BINARIES_MIRROR: "https://cdn.npmmirror.com/binaries/electron-builder-binaries/",
920
+ SASS_BINARY_SITE: "https://cdn.npmmirror.com/binaries/node-sass",
921
+ SWC_BINARY_SITE: "https://cdn.npmmirror.com/binaries/node-swc",
922
+ NWJS_URLBASE: "https://cdn.npmmirror.com/binaries/nwjs/v",
923
+ PUPPETEER_DOWNLOAD_HOST: "https://cdn.npmmirror.com/binaries/chrome-for-testing",
924
+ PUPPETEER_DOWNLOAD_BASE_URL: "https://cdn.npmmirror.com/binaries/chrome-for-testing",
925
+ PLAYWRIGHT_DOWNLOAD_HOST: "https://cdn.npmmirror.com/binaries/playwright",
926
+ SENTRYCLI_CDNURL: "https://cdn.npmmirror.com/binaries/sentry-cli",
927
+ SAUCECTL_INSTALL_BINARY_MIRROR: "https://cdn.npmmirror.com/binaries/saucectl",
928
+ RE2_DOWNLOAD_MIRROR: "https://cdn.npmmirror.com/binaries/node-re2",
929
+ RE2_DOWNLOAD_SKIP_PATH: "true",
930
+ PRISMA_ENGINES_MIRROR: "https://cdn.npmmirror.com/binaries/prisma",
931
+ npm_config_better_sqlite3_binary_host: "https://cdn.npmmirror.com/binaries/better-sqlite3",
932
+ npm_config_keytar_binary_host: "https://cdn.npmmirror.com/binaries/keytar",
933
+ npm_config_sharp_binary_host: "https://cdn.npmmirror.com/binaries/sharp",
934
+ npm_config_sharp_libvips_binary_host: "https://cdn.npmmirror.com/binaries/sharp-libvips",
935
+ npm_config_robotjs_binary_host: "https://cdn.npmmirror.com/binaries/robotjs"
936
+ };
937
+
938
+ //#endregion
939
+ //#region src/commands/mirror/utils.ts
940
+ var import_set_value$1 = /* @__PURE__ */ __toESM(require_set_value(), 1);
941
+ /**
942
+ * 在 vscode 里面设置镜像下载地址的环境变量,避免下载缓慢
943
+ */
944
+ function setMirror(obj, envs = chinaMirrorsEnvs) {
945
+ const platforms = [
946
+ "linux",
947
+ "windows",
948
+ "osx"
949
+ ];
950
+ const prefix = "terminal.integrated.env";
951
+ if (typeof obj === "object" && obj) for (const platform of platforms) (0, import_set_value$1.default)(obj, [prefix, platform].join(".").replaceAll(".", "\\."), envs);
952
+ }
953
+
954
+ //#endregion
955
+ //#region src/commands/mirror/binaryMirror.ts
956
+ /**
957
+ * 根据中国大陆镜像源,自动向 VSCode settings.json 注入终端环境变量。
958
+ */
959
+ async function setVscodeBinaryMirror(cwd) {
960
+ const mirrorConfig = await resolveCommandConfig("mirror", cwd);
961
+ const targetJsonPath = path.resolve(cwd, ".vscode/settings.json");
962
+ await fs.ensureFile(targetJsonPath);
963
+ const json = parse(await fs.readFile(targetJsonPath, "utf8"), void 0, false);
964
+ const env = mirrorConfig?.env ? {
965
+ ...chinaMirrorsEnvs,
966
+ ...mirrorConfig.env
967
+ } : chinaMirrorsEnvs;
968
+ json && typeof json === "object" && setMirror(json, env);
969
+ await fs.writeFile(targetJsonPath, `${stringify(json, void 0, 2)}\n`, "utf8");
970
+ }
971
+
972
+ //#endregion
973
+ //#region src/commands/sync.ts
974
+ function renderCommand(template, pkgName) {
975
+ return template.replaceAll("{name}", pkgName);
976
+ }
977
+ /**
978
+ * 批量执行 `cnpm sync`(或自定义命令),将所有包同步到 npmmirror。
979
+ */
980
+ async function syncNpmMirror(cwd, options) {
981
+ const { concurrency: configConcurrency, command: configCommand, packages: packageFilter, ...workspaceOverrides } = await resolveCommandConfig("sync", cwd) ?? {};
982
+ const { packages, workspaceDir } = await getWorkspaceData(cwd, {
983
+ ...workspaceOverrides,
984
+ ...options ?? {}
985
+ });
986
+ logger.info(`[当前工作区Repo]:\n${packages.map((x) => `- ${pc.green(x.manifest.name)} : ${path.relative(workspaceDir, x.rootDir)}`).join("\n")}\n`);
987
+ const set$6 = new Set(packages.map((x) => x.manifest.name));
988
+ if (packageFilter?.length) {
989
+ for (const name$1 of Array.from(set$6)) if (!name$1 || !packageFilter.includes(name$1)) set$6.delete(name$1);
990
+ }
991
+ logger.info(`[即将同步的包]:\n${Array.from(set$6).map((x) => `- ${pc.green(x ?? "")}`).join("\n")}\n`);
992
+ const queue = new PQueue({ concurrency: configConcurrency ?? Math.max(os.cpus().length, 1) });
993
+ const template = configCommand ?? "cnpm sync {name}";
994
+ const tasks = [];
995
+ for (const pkgName of set$6) {
996
+ if (!pkgName) continue;
997
+ tasks.push(queue.add(async () => {
998
+ return execaCommand(renderCommand(template, pkgName), { stdio: "inherit" });
999
+ }));
1000
+ }
1001
+ await Promise.all(tasks);
1002
+ }
1003
+
1004
+ //#endregion
1005
+ //#region src/commands/upgrade/overwrite.ts
1006
+ function asBuffer(data) {
1007
+ return typeof data === "string" ? Buffer$1.from(data) : data;
1008
+ }
1009
+ async function evaluateWriteIntent(targetPath, options) {
1010
+ const { skipOverwrite, source } = options;
1011
+ if (!await fs.pathExists(targetPath)) return {
1012
+ type: "write",
1013
+ reason: "missing"
1014
+ };
1015
+ if (skipOverwrite) return {
1016
+ type: "skip",
1017
+ reason: "skipOverwrite"
1018
+ };
1019
+ const src = asBuffer(source);
1020
+ let destSize = 0;
1021
+ try {
1022
+ destSize = (await fs.stat(targetPath)).size;
1023
+ } catch {
1024
+ return {
1025
+ type: "write",
1026
+ reason: "missing"
1027
+ };
1028
+ }
1029
+ if (destSize !== src.length) return {
1030
+ type: "prompt",
1031
+ reason: "changed"
1032
+ };
1033
+ if (!isFileChanged(src, await fs.readFile(targetPath))) return {
1034
+ type: "skip",
1035
+ reason: "identical"
1036
+ };
1037
+ return {
1038
+ type: "prompt",
1039
+ reason: "changed"
1040
+ };
1041
+ }
1042
+ async function scheduleOverwrite(intent, options) {
1043
+ const { relPath, targetPath, action, pending } = options;
1044
+ if (intent.type === "write") {
1045
+ await action();
1046
+ return;
1047
+ }
1048
+ if (intent.type === "prompt") pending.push({
1049
+ relPath,
1050
+ targetPath,
1051
+ action
1052
+ });
1053
+ }
1054
+ async function flushPendingOverwrites(pending) {
1055
+ if (!pending.length) return;
1056
+ const selected = await checkbox({
1057
+ message: "检测到以下文件内容与当前仓库不同,选择需要覆盖的文件",
1058
+ choices: pending.map((item) => ({
1059
+ name: pc.greenBright(item.relPath),
1060
+ value: item.targetPath,
1061
+ checked: false
1062
+ })),
1063
+ loop: false
1064
+ });
1065
+ const selectedSet = new Set(selected);
1066
+ for (const item of pending) if (selectedSet.has(item.targetPath)) await item.action();
1067
+ }
1068
+
1069
+ //#endregion
1070
+ //#region src/commands/upgrade/scripts.ts
1071
+ /**
1072
+ * 升级时注入到 package.json 的脚本命令集合,保证常用脚本齐全。
1073
+ */
1074
+ const scripts = {
1075
+ "script:init": "monorepo init",
1076
+ "script:sync": "monorepo sync",
1077
+ "script:clean": "monorepo clean",
1078
+ "script:mirror": "monorepo mirror",
1079
+ "commitlint": "commitlint --edit"
1080
+ };
1081
+ const scriptsEntries = Object.entries(scripts);
1082
+
1083
+ //#endregion
1084
+ //#region src/commands/upgrade/pkg-json.ts
1085
+ const NON_OVERRIDABLE_PREFIXES = ["workspace:", "catalog:"];
1086
+ function parseVersion(input) {
1087
+ if (typeof input !== "string" || input.trim().length === 0) return null;
1088
+ try {
1089
+ return minVersion(input) ?? coerce(input);
1090
+ } catch {
1091
+ return null;
1092
+ }
1093
+ }
1094
+ function hasNonOverridablePrefix(version$1) {
1095
+ if (typeof version$1 !== "string") return false;
1096
+ return NON_OVERRIDABLE_PREFIXES.some((prefix) => version$1.startsWith(prefix));
1097
+ }
1098
+ function shouldAssignVersion(currentVersion, nextVersion) {
1099
+ if (typeof currentVersion !== "string" || currentVersion.trim().length === 0) return true;
1100
+ if (currentVersion === nextVersion) return false;
1101
+ const current = parseVersion(currentVersion);
1102
+ const next = parseVersion(nextVersion);
1103
+ if (!current || !next) return true;
1104
+ return !gte(current, next);
1105
+ }
1106
+ /**
1107
+ * 将内置 package.json 内容合并进目标工程:
1108
+ * - 同步依赖(保留 workspace: 前缀的版本)
1109
+ * - 确保 @icebreakers/monorepo 使用最新版本
1110
+ * - 写入预置脚本
1111
+ */
1112
+ function setPkgJson(sourcePkgJson, targetPkgJson, options) {
1113
+ const packageManager = sourcePkgJson.packageManager ?? "";
1114
+ const sourceDeps = sourcePkgJson.dependencies ?? {};
1115
+ const sourceDevDeps = sourcePkgJson.devDependencies ?? {};
1116
+ const targetDeps = { ...targetPkgJson.dependencies ?? {} };
1117
+ const targetDevDeps = { ...targetPkgJson.devDependencies ?? {} };
1118
+ if (packageManager) targetPkgJson.packageManager = packageManager;
1119
+ for (const [depName, depVersion] of Object.entries(sourceDeps)) {
1120
+ if (typeof depVersion !== "string") continue;
1121
+ const targetVersion = targetDeps[depName];
1122
+ if (hasNonOverridablePrefix(targetVersion)) continue;
1123
+ if (shouldAssignVersion(targetVersion, depVersion)) targetDeps[depName] = depVersion;
1124
+ }
1125
+ if (Object.keys(targetDeps).length) targetPkgJson.dependencies = targetDeps;
1126
+ for (const [depName, depVersion] of Object.entries(sourceDevDeps)) {
1127
+ if (typeof depVersion !== "string") continue;
1128
+ if (depName === name) {
1129
+ const nextVersion = `^${version}`;
1130
+ const targetVersion = targetDevDeps[depName];
1131
+ if (!hasNonOverridablePrefix(targetVersion) && shouldAssignVersion(targetVersion, nextVersion)) targetDevDeps[depName] = nextVersion;
1132
+ } else {
1133
+ const targetVersion = targetDevDeps[depName];
1134
+ if (hasNonOverridablePrefix(targetVersion)) continue;
1135
+ if (shouldAssignVersion(targetVersion, depVersion)) targetDevDeps[depName] = depVersion;
1136
+ }
1137
+ }
1138
+ if (Object.keys(targetDevDeps).length) targetPkgJson.devDependencies = targetDevDeps;
1139
+ const scriptPairs = options?.scripts ? Object.entries(options.scripts) : scriptsEntries;
1140
+ if (scriptPairs.length) {
1141
+ const scripts$1 = { ...targetPkgJson.scripts ?? {} };
1142
+ for (const [scriptName, scriptCmd] of scriptPairs) scripts$1[scriptName] = scriptCmd;
1143
+ targetPkgJson.scripts = scripts$1;
1144
+ }
1145
+ }
1146
+
1147
+ //#endregion
1148
+ //#region src/commands/upgrade/targets.ts
1149
+ /**
1150
+ * 根据 core 模式返回需要同步的资产目录。
1151
+ * core=true 时仅包含最小集,便于内联到其他项目。
1152
+ */
1153
+ function getAssetTargets(core) {
1154
+ const list = [
1155
+ ".changeset",
1156
+ ".husky",
1157
+ ".vscode",
1158
+ ".editorconfig",
1159
+ ".gitattributes",
1160
+ ".gitignore",
1161
+ ".npmrc",
1162
+ "commitlint.config.ts",
1163
+ "eslint.config.js",
1164
+ "lint-staged.config.js",
1165
+ "stylelint.config.js",
1166
+ "monorepo.config.ts",
1167
+ "package.json",
1168
+ "pnpm-workspace.yaml",
1169
+ "tsconfig.json",
1170
+ "turbo.json",
1171
+ "vitest.config.ts",
1172
+ "Dockerfile",
1173
+ ".dockerignore"
1174
+ ];
1175
+ if (!core) list.push(".github", "LICENSE", "renovate.json", "SECURITY.md", "CODE_OF_CONDUCT.md", "CONTRIBUTING.md", "netlify.toml");
1176
+ return list;
1177
+ }
1178
+
1179
+ //#endregion
1180
+ //#region src/commands/upgrade/index.ts
1181
+ var import_set_value = /* @__PURE__ */ __toESM(require_set_value(), 1);
1182
+ /**
1183
+ * 将 assets 目录的模版文件同步到工程中,实现一键升级脚手架能力。
1184
+ */
1185
+ async function upgradeMonorepo(opts) {
1186
+ const cwd = opts.cwd ?? process.cwd();
1187
+ const upgradeConfig = await resolveCommandConfig("upgrade", cwd);
1188
+ const merged = {
1189
+ cwd,
1190
+ outDir: "",
1191
+ ...upgradeConfig ?? {},
1192
+ ...opts
1193
+ };
1194
+ const outDir = merged.outDir ?? "";
1195
+ const absOutDir = path.isAbsolute(outDir) ? outDir : path.join(cwd, outDir);
1196
+ const repoName = await new GitClient({ baseDir: cwd }).getRepoName();
1197
+ const useCoreAssets = merged.core ?? false;
1198
+ merged.core = useCoreAssets;
1199
+ const baseTargets = getAssetTargets(useCoreAssets);
1200
+ const configTargets = upgradeConfig?.targets ?? [];
1201
+ const mergeTargets = upgradeConfig?.mergeTargets;
1202
+ let targets = configTargets.length ? mergeTargets === false ? [...configTargets] : Array.from(new Set([...baseTargets, ...configTargets])) : baseTargets;
1203
+ if (merged.interactive) targets = await checkbox({
1204
+ message: "选择你需要的文件",
1205
+ choices: targets.map((x) => {
1206
+ return {
1207
+ value: x,
1208
+ checked: true
1209
+ };
1210
+ })
1211
+ });
1212
+ const regexpArr = targets.map((x) => {
1213
+ return /* @__PURE__ */ new RegExp(`^${escapeStringRegexp(x)}`);
1214
+ });
1215
+ const skipChangesetMarkdown = upgradeConfig?.skipChangesetMarkdown ?? true;
1216
+ const scriptOverrides = upgradeConfig?.scripts;
1217
+ const skipOverwrite = merged.skipOverwrite;
1218
+ const pendingOverwrites = [];
1219
+ for await (const file of klaw(assetsDir, { filter(p) {
1220
+ return isMatch(toWorkspaceGitignorePath(path.relative(assetsDir, p)), regexpArr);
1221
+ } })) {
1222
+ if (!file.stats.isFile()) continue;
1223
+ const relPath = toWorkspaceGitignorePath(path.relative(assetsDir, file.path));
1224
+ if (skipChangesetMarkdown && relPath.startsWith(".changeset/") && relPath.endsWith(".md")) continue;
1225
+ const targetPath = path.resolve(absOutDir, relPath);
1226
+ try {
1227
+ if (relPath === "package.json") {
1228
+ if (!await fs.pathExists(targetPath)) continue;
1229
+ const sourcePkgJson = await fs.readJson(file.path);
1230
+ const targetPkgJson = await fs.readJson(targetPath);
1231
+ setPkgJson(sourcePkgJson, targetPkgJson, { scripts: scriptOverrides });
1232
+ const data = `${JSON.stringify(targetPkgJson, void 0, 2)}\n`;
1233
+ const intent$1 = await evaluateWriteIntent(targetPath, {
1234
+ skipOverwrite,
1235
+ source: data
1236
+ });
1237
+ const action$1 = async () => {
1238
+ await fs.outputFile(targetPath, data, "utf8");
1239
+ logger.success(targetPath);
1240
+ };
1241
+ await scheduleOverwrite(intent$1, {
1242
+ relPath,
1243
+ targetPath,
1244
+ action: action$1,
1245
+ pending: pendingOverwrites
1246
+ });
1247
+ continue;
1248
+ }
1249
+ if (relPath === ".changeset/config.json" && repoName) {
1250
+ const changesetJson = await fs.readJson(file.path);
1251
+ (0, import_set_value.default)(changesetJson, "changelog.1.repo", repoName);
1252
+ const data = `${JSON.stringify(changesetJson, void 0, 2)}\n`;
1253
+ const intent$1 = await evaluateWriteIntent(targetPath, {
1254
+ skipOverwrite,
1255
+ source: data
1256
+ });
1257
+ const action$1 = async () => {
1258
+ await fs.outputFile(targetPath, data, "utf8");
1259
+ logger.success(targetPath);
1260
+ };
1261
+ await scheduleOverwrite(intent$1, {
1262
+ relPath,
1263
+ targetPath,
1264
+ action: action$1,
1265
+ pending: pendingOverwrites
1266
+ });
1267
+ continue;
1268
+ }
1269
+ if (relPath === "LICENSE") {
1270
+ const source$1 = await fs.readFile(file.path);
1271
+ const intent$1 = await evaluateWriteIntent(targetPath, {
1272
+ skipOverwrite: true,
1273
+ source: source$1
1274
+ });
1275
+ const action$1 = async () => {
1276
+ await fs.outputFile(targetPath, source$1);
1277
+ logger.success(targetPath);
1278
+ };
1279
+ await scheduleOverwrite(intent$1, {
1280
+ relPath,
1281
+ targetPath,
1282
+ action: action$1,
1283
+ pending: pendingOverwrites
1284
+ });
1285
+ continue;
1286
+ }
1287
+ const source = await fs.readFile(file.path);
1288
+ const intent = await evaluateWriteIntent(targetPath, {
1289
+ skipOverwrite,
1290
+ source
1291
+ });
1292
+ const action = async () => {
1293
+ await fs.outputFile(targetPath, source);
1294
+ logger.success(targetPath);
1295
+ };
1296
+ await scheduleOverwrite(intent, {
1297
+ relPath,
1298
+ targetPath,
1299
+ action,
1300
+ pending: pendingOverwrites
1301
+ });
1302
+ } catch (error) {
1303
+ if (isIgnorableFsError(error)) continue;
1304
+ throw error;
1305
+ }
1306
+ }
1307
+ await flushPendingOverwrites(pendingOverwrites);
1308
+ }
1309
+
1310
+ //#endregion
1311
+ export { getWorkspaceData as A, templatesDir as C, defineMonorepoConfig as D, cleanProjects as E, GitClient as M, loadMonorepoConfig as O, rootDir as S, version as T, toWorkspaceGitignorePath as _, createContext as a, assetsDir as b, getCreateChoices as c, escapeStringRegexp as d, isMatch as f, toPublishGitignorePath as g, isGitignoreFile as h, init as i, getWorkspacePackages as j, resolveCommandConfig as k, getTemplateMap as l, isFileChanged as m, syncNpmMirror as n, createNewProject as o, getFileHash as p, setVscodeBinaryMirror as r, defaultTemplate as s, upgradeMonorepo as t, templateMap as u, isIgnorableFsError as v, name as w, packageDir as x, logger as y };