@ruan-cat/utils 4.19.0 → 4.21.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 +26 -0
- package/dist/index.d.ts +5 -16
- package/dist/node-cjs/index.cjs +18 -2
- package/dist/node-cjs/index.cjs.map +1 -1
- package/dist/node-cjs/index.d.cts +15 -17
- package/dist/node-esm/index.d.ts +88 -1
- package/dist/node-esm/index.js +199 -12
- package/dist/node-esm/index.js.map +1 -1
- package/package.json +19 -15
- package/src/monorepo/index.ts +28 -1
- package/src/node-esm/index.ts +1 -0
- package/src/node-esm/scripts/move-vercel-output-to-root/index.test.ts +151 -0
- package/src/node-esm/scripts/move-vercel-output-to-root/index.ts +286 -0
- package/src/types/pnpm-workspace.yaml.shim.ts +5 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ruan-cat/utils",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.21.0",
|
|
4
4
|
"description": "阮喵喵工具集合。默认提供js文件,也直接提供ts文件。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"types": "./dist/node-esm/index.d.ts",
|
|
30
30
|
"import": "./dist/node-esm/index.js"
|
|
31
31
|
},
|
|
32
|
+
"./monorepo": "./src/monorepo/index.ts",
|
|
33
|
+
"./move-vercel-output-to-root": "./src/node-esm/scripts/move-vercel-output-to-root/index.ts",
|
|
32
34
|
"./unplugin-vue-router": "./src/unplugin-vue-router/index.ts",
|
|
33
35
|
"./vite-plugin-autogeneration-import-file": "./src/vite-plugin-autogeneration-import-file/index.ts",
|
|
34
36
|
"./vueuse": "./src/vueuse/index.ts",
|
|
@@ -65,30 +67,31 @@
|
|
|
65
67
|
],
|
|
66
68
|
"dependencies": {
|
|
67
69
|
"@vueuse/integrations": "^13.9.0",
|
|
68
|
-
"axios": "^1.13.
|
|
70
|
+
"axios": "^1.13.6",
|
|
69
71
|
"consola": "^3.4.2",
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
+
"lodash-es": "^4.17.23",
|
|
73
|
+
"pnpm-workspace-yaml": "^1.6.0",
|
|
74
|
+
"tinyglobby": "^0.2.15"
|
|
72
75
|
},
|
|
73
76
|
"devDependencies": {
|
|
74
77
|
"@antfu/utils": "^9.3.0",
|
|
75
78
|
"@types/js-yaml": "^4.0.9",
|
|
76
79
|
"@types/lodash-es": "^4.17.12",
|
|
77
|
-
"@types/node": "^22.19.
|
|
78
|
-
"@types/qs": "^6.
|
|
79
|
-
"automd": "^0.4.
|
|
80
|
+
"@types/node": "^22.19.15",
|
|
81
|
+
"@types/qs": "^6.15.0",
|
|
82
|
+
"automd": "^0.4.3",
|
|
80
83
|
"commander": "^13.1.0",
|
|
81
|
-
"js-yaml": "^4.1.
|
|
82
|
-
"qs": "^6.
|
|
83
|
-
"tsup": "^8.5.
|
|
84
|
+
"js-yaml": "^4.1.1",
|
|
85
|
+
"qs": "^6.15.0",
|
|
86
|
+
"tsup": "^8.5.1",
|
|
84
87
|
"type-plus": "^7.6.2",
|
|
85
|
-
"typedoc": "^0.28.
|
|
86
|
-
"typedoc-plugin-frontmatter": "^1.3.
|
|
87
|
-
"typedoc-plugin-markdown": "^4.
|
|
88
|
+
"typedoc": "^0.28.17",
|
|
89
|
+
"typedoc-plugin-frontmatter": "^1.3.1",
|
|
90
|
+
"typedoc-plugin-markdown": "^4.10.0",
|
|
88
91
|
"typescript": "^5.9.3",
|
|
89
|
-
"unplugin-auto-import": "^
|
|
92
|
+
"unplugin-auto-import": "^21.0.0",
|
|
90
93
|
"unplugin-vue-router": "^0.16.1",
|
|
91
|
-
"vite-plugin-autogeneration-import-file": "^3.0.
|
|
94
|
+
"vite-plugin-autogeneration-import-file": "^3.0.1",
|
|
92
95
|
"vitepress": "^1.6.4"
|
|
93
96
|
},
|
|
94
97
|
"peerDependencies": {
|
|
@@ -119,6 +122,7 @@
|
|
|
119
122
|
"scripts": {
|
|
120
123
|
"build": "tsup",
|
|
121
124
|
"prebuild": "automd",
|
|
125
|
+
"move-vercel-output-to-root": "tsx ./src/node-esm/scripts/move-vercel-output-to-root/index.ts",
|
|
122
126
|
"docs:dev": "turbo docs:dev",
|
|
123
127
|
"build:docs": "turbo do-build-docs",
|
|
124
128
|
"typedoc": "typedoc --options typedoc.config.mjs",
|
package/src/monorepo/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
1
|
+
import { dirname, join, parse, resolve } from "node:path";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import { globSync } from "tinyglobby";
|
|
4
4
|
import { load } from "js-yaml";
|
|
@@ -17,6 +17,33 @@ function pathChange(path: string) {
|
|
|
17
17
|
// return normalizePath(path);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* 从给定目录开始,向上查找 monorepo 根目录。
|
|
22
|
+
* @description
|
|
23
|
+
* 通过查找 `pnpm-workspace.yaml` 文件来定位 monorepo 根目录。
|
|
24
|
+
* 该函数适用于脚本在 monorepo 子包内运行的场景。
|
|
25
|
+
*
|
|
26
|
+
* @param startDir 起始查找目录,默认值为 `process.cwd()`
|
|
27
|
+
* @returns 找到的 monorepo 根目录绝对路径;若未找到则返回 `null`
|
|
28
|
+
*/
|
|
29
|
+
export function findMonorepoRoot(startDir: string = process.cwd()): string | null {
|
|
30
|
+
let currentDir = resolve(startDir);
|
|
31
|
+
const fileSystemRoot = parse(currentDir).root;
|
|
32
|
+
|
|
33
|
+
while (true) {
|
|
34
|
+
const workspaceConfigPath = join(currentDir, "pnpm-workspace.yaml");
|
|
35
|
+
if (fs.existsSync(workspaceConfigPath)) {
|
|
36
|
+
return currentDir;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (currentDir === fileSystemRoot) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
currentDir = dirname(currentDir);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
20
47
|
/**
|
|
21
48
|
* 判断目标项目是否是 monorepo 格式的项目
|
|
22
49
|
* @description
|
package/src/node-esm/index.ts
CHANGED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
5
|
+
import {
|
|
6
|
+
moveVercelOutputToRoot,
|
|
7
|
+
parseMoveVercelOutputToRootCliArgs,
|
|
8
|
+
resolveMoveVercelOutputToRootOptions,
|
|
9
|
+
} from "./index";
|
|
10
|
+
|
|
11
|
+
function createMonorepoFixture() {
|
|
12
|
+
const tempRoot = fs.mkdtempSync(path.join(tmpdir(), "move-vercel-output-to-root-"));
|
|
13
|
+
const packageDir = path.join(tempRoot, "packages", "demo-app");
|
|
14
|
+
const packageOutputDir = path.join(packageDir, ".vercel", "output");
|
|
15
|
+
const rootOutputDir = path.join(tempRoot, ".vercel", "output");
|
|
16
|
+
|
|
17
|
+
fs.mkdirSync(packageOutputDir, { recursive: true });
|
|
18
|
+
fs.mkdirSync(rootOutputDir, { recursive: true });
|
|
19
|
+
fs.writeFileSync(path.join(tempRoot, "pnpm-workspace.yaml"), "packages:\n - packages/*\n", "utf8");
|
|
20
|
+
fs.writeFileSync(path.join(packageDir, "package.json"), '{"name":"demo-app"}', "utf8");
|
|
21
|
+
fs.writeFileSync(path.join(packageOutputDir, "config.json"), '{"version":3}', "utf8");
|
|
22
|
+
fs.mkdirSync(path.join(packageOutputDir, "functions"), { recursive: true });
|
|
23
|
+
fs.writeFileSync(path.join(packageOutputDir, "functions", "index.func"), "hello", "utf8");
|
|
24
|
+
fs.writeFileSync(path.join(rootOutputDir, "stale.txt"), "old", "utf8");
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
tempRoot,
|
|
28
|
+
packageDir,
|
|
29
|
+
packageOutputDir,
|
|
30
|
+
rootOutputDir,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("move-vercel-output-to-root", () => {
|
|
35
|
+
const temporaryDirectories = new Set<string>();
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
temporaryDirectories.clear();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
for (const tempDirectory of temporaryDirectories) {
|
|
43
|
+
fs.rmSync(tempDirectory, {
|
|
44
|
+
force: true,
|
|
45
|
+
recursive: true,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("resolveMoveVercelOutputToRootOptions", () => {
|
|
51
|
+
test("应该能从子包目录自动解析 monorepo 根目录与默认路径", () => {
|
|
52
|
+
const fixture = createMonorepoFixture();
|
|
53
|
+
temporaryDirectories.add(fixture.tempRoot);
|
|
54
|
+
|
|
55
|
+
const resolvedOptions = resolveMoveVercelOutputToRootOptions({
|
|
56
|
+
cwd: fixture.packageDir,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(resolvedOptions.monorepoRoot).toBe(fixture.tempRoot);
|
|
60
|
+
expect(resolvedOptions.sourceDir).toBe(path.join(fixture.packageDir, ".vercel", "output"));
|
|
61
|
+
expect(resolvedOptions.targetDir).toBe(path.join(fixture.tempRoot, ".vercel", "output"));
|
|
62
|
+
expect(resolvedOptions.skipClean).toBe(false);
|
|
63
|
+
expect(resolvedOptions.dryRun).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("当 sourceDir 和 targetDir 解析到同一路径时应该抛出错误", () => {
|
|
67
|
+
const fixture = createMonorepoFixture();
|
|
68
|
+
temporaryDirectories.add(fixture.tempRoot);
|
|
69
|
+
|
|
70
|
+
expect(() =>
|
|
71
|
+
resolveMoveVercelOutputToRootOptions({
|
|
72
|
+
cwd: fixture.packageDir,
|
|
73
|
+
targetDir: path.relative(fixture.tempRoot, fixture.packageOutputDir),
|
|
74
|
+
}),
|
|
75
|
+
).toThrow("源目录和目标目录解析到了同一路径");
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("moveVercelOutputToRoot", () => {
|
|
80
|
+
test("默认应该清空根目录旧产物并复制当前子包的 vercel 输出目录内容", () => {
|
|
81
|
+
const fixture = createMonorepoFixture();
|
|
82
|
+
temporaryDirectories.add(fixture.tempRoot);
|
|
83
|
+
|
|
84
|
+
const result = moveVercelOutputToRoot({
|
|
85
|
+
cwd: fixture.packageDir,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(result.copied).toBe(true);
|
|
89
|
+
expect(fs.existsSync(path.join(fixture.rootOutputDir, "stale.txt"))).toBe(false);
|
|
90
|
+
expect(fs.readFileSync(path.join(fixture.rootOutputDir, "config.json"), "utf8")).toBe('{"version":3}');
|
|
91
|
+
expect(fs.readFileSync(path.join(fixture.rootOutputDir, "functions", "index.func"), "utf8")).toBe("hello");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("dry-run 模式下不应该修改根目录内容", () => {
|
|
95
|
+
const fixture = createMonorepoFixture();
|
|
96
|
+
temporaryDirectories.add(fixture.tempRoot);
|
|
97
|
+
|
|
98
|
+
const result = moveVercelOutputToRoot({
|
|
99
|
+
cwd: fixture.packageDir,
|
|
100
|
+
dryRun: true,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(result.copied).toBe(false);
|
|
104
|
+
expect(fs.existsSync(path.join(fixture.rootOutputDir, "stale.txt"))).toBe(true);
|
|
105
|
+
expect(fs.existsSync(path.join(fixture.rootOutputDir, "config.json"))).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("应该支持自定义 sourceDir 与 targetDir", () => {
|
|
109
|
+
const fixture = createMonorepoFixture();
|
|
110
|
+
temporaryDirectories.add(fixture.tempRoot);
|
|
111
|
+
|
|
112
|
+
const customSourceDir = path.join(fixture.packageDir, "custom-output");
|
|
113
|
+
const customTargetDir = path.join(fixture.tempRoot, "deploy-output");
|
|
114
|
+
fs.mkdirSync(customSourceDir, { recursive: true });
|
|
115
|
+
fs.writeFileSync(path.join(customSourceDir, "routes.json"), '{"routes":[]}', "utf8");
|
|
116
|
+
|
|
117
|
+
const result = moveVercelOutputToRoot({
|
|
118
|
+
cwd: fixture.packageDir,
|
|
119
|
+
sourceDir: "custom-output",
|
|
120
|
+
targetDir: "deploy-output",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(result.sourceDir).toBe(customSourceDir);
|
|
124
|
+
expect(result.targetDir).toBe(customTargetDir);
|
|
125
|
+
expect(fs.readFileSync(path.join(customTargetDir, "routes.json"), "utf8")).toBe('{"routes":[]}');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("parseMoveVercelOutputToRootCliArgs", () => {
|
|
130
|
+
test("应该正确解析命令行参数", () => {
|
|
131
|
+
const parsedOptions = parseMoveVercelOutputToRootCliArgs([
|
|
132
|
+
"--root-dir",
|
|
133
|
+
"../../..",
|
|
134
|
+
"--source-dir",
|
|
135
|
+
".vercel/output",
|
|
136
|
+
"--target-dir",
|
|
137
|
+
"deploy-output",
|
|
138
|
+
"--skip-clean",
|
|
139
|
+
"--dry-run",
|
|
140
|
+
]);
|
|
141
|
+
|
|
142
|
+
expect(parsedOptions).toEqual({
|
|
143
|
+
rootDir: "../../..",
|
|
144
|
+
sourceDir: ".vercel/output",
|
|
145
|
+
targetDir: "deploy-output",
|
|
146
|
+
skipClean: true,
|
|
147
|
+
dryRun: true,
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import consola from "consola";
|
|
5
|
+
import { findMonorepoRoot } from "../../../monorepo";
|
|
6
|
+
|
|
7
|
+
export interface MoveVercelOutputToRootOptions {
|
|
8
|
+
/**
|
|
9
|
+
* 运行目录。
|
|
10
|
+
* 默认值为 `process.cwd()`。
|
|
11
|
+
*/
|
|
12
|
+
cwd?: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* monorepo 根目录。
|
|
16
|
+
* 不传时会从 `cwd` 开始,向上查找 `pnpm-workspace.yaml`。
|
|
17
|
+
*/
|
|
18
|
+
rootDir?: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 子包内构建产物目录。
|
|
22
|
+
* 相对路径基于 `cwd` 解析。
|
|
23
|
+
*
|
|
24
|
+
* @default ".vercel/output"
|
|
25
|
+
*/
|
|
26
|
+
sourceDir?: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* monorepo 根目录内的目标输出目录。
|
|
30
|
+
* 相对路径基于 monorepo 根目录解析。
|
|
31
|
+
*
|
|
32
|
+
* @default ".vercel/output"
|
|
33
|
+
*/
|
|
34
|
+
targetDir?: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 是否跳过目标目录清理。
|
|
38
|
+
*
|
|
39
|
+
* @default false
|
|
40
|
+
*/
|
|
41
|
+
skipClean?: boolean;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 仅打印解析结果,不实际复制文件。
|
|
45
|
+
*
|
|
46
|
+
* @default false
|
|
47
|
+
*/
|
|
48
|
+
dryRun?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ResolvedMoveVercelOutputToRootOptions {
|
|
52
|
+
cwd: string;
|
|
53
|
+
monorepoRoot: string;
|
|
54
|
+
sourceDir: string;
|
|
55
|
+
targetDir: string;
|
|
56
|
+
skipClean: boolean;
|
|
57
|
+
dryRun: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface MoveVercelOutputToRootResult extends ResolvedMoveVercelOutputToRootOptions {
|
|
61
|
+
copied: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 将路径解析为绝对路径。
|
|
66
|
+
*/
|
|
67
|
+
function resolvePathFromBase(baseDir: string, inputPath: string) {
|
|
68
|
+
if (path.isAbsolute(inputPath)) {
|
|
69
|
+
return path.normalize(inputPath);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return path.resolve(baseDir, inputPath);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 解析 monorepo 根目录。
|
|
77
|
+
*/
|
|
78
|
+
function resolveMonorepoRoot(cwd: string, rootDir?: string) {
|
|
79
|
+
if (rootDir) {
|
|
80
|
+
const resolvedRoot = resolvePathFromBase(cwd, rootDir);
|
|
81
|
+
const workspaceConfigPath = path.join(resolvedRoot, "pnpm-workspace.yaml");
|
|
82
|
+
if (!fs.existsSync(workspaceConfigPath)) {
|
|
83
|
+
throw new Error(`指定的 rootDir 不是有效的 monorepo 根目录:${resolvedRoot}。缺少 pnpm-workspace.yaml。`);
|
|
84
|
+
}
|
|
85
|
+
return resolvedRoot;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const detectedRoot = findMonorepoRoot(cwd);
|
|
89
|
+
if (detectedRoot) {
|
|
90
|
+
return detectedRoot;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
throw new Error(`无法从当前目录向上定位 monorepo 根目录:${cwd}。请显式传入 --root-dir 参数。`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 解析脚本所需的全部路径。
|
|
98
|
+
*/
|
|
99
|
+
export function resolveMoveVercelOutputToRootOptions(
|
|
100
|
+
options: MoveVercelOutputToRootOptions = {},
|
|
101
|
+
): ResolvedMoveVercelOutputToRootOptions {
|
|
102
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
103
|
+
const monorepoRoot = resolveMonorepoRoot(cwd, options.rootDir);
|
|
104
|
+
const sourceDir = resolvePathFromBase(cwd, options.sourceDir ?? ".vercel/output");
|
|
105
|
+
const targetDir = resolvePathFromBase(monorepoRoot, options.targetDir ?? ".vercel/output");
|
|
106
|
+
|
|
107
|
+
if (sourceDir === targetDir) {
|
|
108
|
+
throw new Error(`源目录和目标目录解析到了同一路径:${sourceDir}。请调整 sourceDir 或 targetDir。`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
cwd,
|
|
113
|
+
monorepoRoot,
|
|
114
|
+
sourceDir,
|
|
115
|
+
targetDir,
|
|
116
|
+
skipClean: options.skipClean ?? false,
|
|
117
|
+
dryRun: options.dryRun ?? false,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 复制目录内容,而不是把源目录本身嵌套复制进去。
|
|
123
|
+
*/
|
|
124
|
+
function copyDirectoryContents(sourceDir: string, targetDir: string) {
|
|
125
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
126
|
+
|
|
127
|
+
for (const entryName of fs.readdirSync(sourceDir)) {
|
|
128
|
+
const sourceEntry = path.join(sourceDir, entryName);
|
|
129
|
+
const targetEntry = path.join(targetDir, entryName);
|
|
130
|
+
fs.cpSync(sourceEntry, targetEntry, {
|
|
131
|
+
force: true,
|
|
132
|
+
recursive: true,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 将当前子包内的 `.vercel/output` 移动到 monorepo 根目录。
|
|
139
|
+
* @description
|
|
140
|
+
* 该函数服务于 Vercel 在 monorepo 场景下的部署:
|
|
141
|
+
* 1. 构建仍在子包目录内执行
|
|
142
|
+
* 2. 产物默认出现在子包的 `.vercel/output`
|
|
143
|
+
* 3. Vercel 却要求在 monorepo 根目录下读取 `.vercel/output`
|
|
144
|
+
*/
|
|
145
|
+
export function moveVercelOutputToRoot(options: MoveVercelOutputToRootOptions = {}): MoveVercelOutputToRootResult {
|
|
146
|
+
const resolvedOptions = resolveMoveVercelOutputToRootOptions(options);
|
|
147
|
+
|
|
148
|
+
if (!fs.existsSync(resolvedOptions.sourceDir)) {
|
|
149
|
+
throw new Error(`源目录不存在,无法搬运 Vercel 构建产物:${resolvedOptions.sourceDir}。请先在子包内完成构建。`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!fs.statSync(resolvedOptions.sourceDir).isDirectory()) {
|
|
153
|
+
throw new Error(`源路径不是目录:${resolvedOptions.sourceDir}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
consola.info("move-vercel-output-to-root 解析结果");
|
|
157
|
+
consola.log(`- cwd: ${resolvedOptions.cwd}`);
|
|
158
|
+
consola.log(`- monorepoRoot: ${resolvedOptions.monorepoRoot}`);
|
|
159
|
+
consola.log(`- sourceDir: ${resolvedOptions.sourceDir}`);
|
|
160
|
+
consola.log(`- targetDir: ${resolvedOptions.targetDir}`);
|
|
161
|
+
consola.log(`- skipClean: ${resolvedOptions.skipClean}`);
|
|
162
|
+
consola.log(`- dryRun: ${resolvedOptions.dryRun}`);
|
|
163
|
+
|
|
164
|
+
if (resolvedOptions.dryRun) {
|
|
165
|
+
consola.info("dry-run 模式:仅输出路径信息,不执行复制。");
|
|
166
|
+
return {
|
|
167
|
+
...resolvedOptions,
|
|
168
|
+
copied: false,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!resolvedOptions.skipClean) {
|
|
173
|
+
fs.rmSync(resolvedOptions.targetDir, {
|
|
174
|
+
force: true,
|
|
175
|
+
recursive: true,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
copyDirectoryContents(resolvedOptions.sourceDir, resolvedOptions.targetDir);
|
|
180
|
+
|
|
181
|
+
consola.success(`已将 ${resolvedOptions.sourceDir} 搬运到 ${resolvedOptions.targetDir}`);
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
...resolvedOptions,
|
|
185
|
+
copied: true,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 解析命令行参数。
|
|
191
|
+
*/
|
|
192
|
+
export function parseMoveVercelOutputToRootCliArgs(args: string[]): MoveVercelOutputToRootOptions & { help?: boolean } {
|
|
193
|
+
const options: MoveVercelOutputToRootOptions & { help?: boolean } = {};
|
|
194
|
+
const readFlagValue = (flagName: string, currentIndex: number) => {
|
|
195
|
+
const value = args[currentIndex + 1];
|
|
196
|
+
if (!value || value.startsWith("--")) {
|
|
197
|
+
throw new Error(`参数 ${flagName} 缺少对应的值。`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return value;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
204
|
+
const currentArg = args[index];
|
|
205
|
+
|
|
206
|
+
switch (currentArg) {
|
|
207
|
+
case "--root-dir":
|
|
208
|
+
options.rootDir = readFlagValue(currentArg, index);
|
|
209
|
+
index += 1;
|
|
210
|
+
break;
|
|
211
|
+
case "--source-dir":
|
|
212
|
+
options.sourceDir = readFlagValue(currentArg, index);
|
|
213
|
+
index += 1;
|
|
214
|
+
break;
|
|
215
|
+
case "--target-dir":
|
|
216
|
+
options.targetDir = readFlagValue(currentArg, index);
|
|
217
|
+
index += 1;
|
|
218
|
+
break;
|
|
219
|
+
case "--skip-clean":
|
|
220
|
+
options.skipClean = true;
|
|
221
|
+
break;
|
|
222
|
+
case "--dry-run":
|
|
223
|
+
options.dryRun = true;
|
|
224
|
+
break;
|
|
225
|
+
case "--help":
|
|
226
|
+
case "-h":
|
|
227
|
+
options.help = true;
|
|
228
|
+
break;
|
|
229
|
+
default:
|
|
230
|
+
throw new Error(`不支持的参数:${currentArg}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return options;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* CLI 帮助信息。
|
|
239
|
+
*/
|
|
240
|
+
export function getMoveVercelOutputToRootHelpText() {
|
|
241
|
+
return [
|
|
242
|
+
"tsx @ruan-cat/utils/move-vercel-output-to-root [options]",
|
|
243
|
+
"",
|
|
244
|
+
"选项:",
|
|
245
|
+
" --root-dir <path> 显式指定 monorepo 根目录",
|
|
246
|
+
" --source-dir <path> 指定子包内构建产物目录,默认 .vercel/output",
|
|
247
|
+
" --target-dir <path> 指定根目录内目标目录,默认 .vercel/output",
|
|
248
|
+
" --skip-clean 跳过目标目录清理",
|
|
249
|
+
" --dry-run 仅打印路径解析结果,不执行复制",
|
|
250
|
+
" -h, --help 查看帮助信息",
|
|
251
|
+
].join("\n");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 执行 CLI。
|
|
256
|
+
*/
|
|
257
|
+
export function runMoveVercelOutputToRootCli(args: string[] = process.argv.slice(2)) {
|
|
258
|
+
const cliOptions = parseMoveVercelOutputToRootCliArgs(args);
|
|
259
|
+
|
|
260
|
+
if (cliOptions.help) {
|
|
261
|
+
console.log(getMoveVercelOutputToRootHelpText());
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
moveVercelOutputToRoot(cliOptions);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function isRunningAsCli() {
|
|
269
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
270
|
+
const entryPath = process.argv[1];
|
|
271
|
+
|
|
272
|
+
if (!entryPath) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return path.resolve(entryPath) === currentFilePath;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (isRunningAsCli()) {
|
|
280
|
+
try {
|
|
281
|
+
runMoveVercelOutputToRootCli();
|
|
282
|
+
} catch (error) {
|
|
283
|
+
consola.error(error instanceof Error ? error.message : String(error));
|
|
284
|
+
process.exitCode = 1;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -1,22 +1,10 @@
|
|
|
1
|
+
import type { PnpmWorkspaceYamlSchema } from "pnpm-workspace-yaml";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* pnpm-workspace.yaml 文件的类型声明
|
|
3
5
|
* @description
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* 主要是为了让该文件被解析后,能够有一个基础的类型声明
|
|
6
|
+
* 现在是 pnpm-workspace-yaml 提供的 `PnpmWorkspaceYamlSchema` 类型
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* 未来应该找到这样的类型声明库,直接复用别人的就好了,不要自己写了。
|
|
8
|
+
* @see https://github.com/antfu/pnpm-workspace-utils/blob/main/packages/pnpm-workspace-yaml/src/index.ts#L4
|
|
11
9
|
*/
|
|
12
|
-
export
|
|
13
|
-
/**
|
|
14
|
-
* 包的匹配语法字符串
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ["packages/**", "demos/**", "utils"]
|
|
18
|
-
*/
|
|
19
|
-
packages?: string[];
|
|
20
|
-
|
|
21
|
-
catalog?: string[];
|
|
22
|
-
}
|
|
10
|
+
export type PnpmWorkspace = PnpmWorkspaceYamlSchema;
|