@yannick-z/modulo 0.2.0 → 0.3.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/.vscode/extensions.json +3 -0
- package/README.md +57 -60
- package/bin/modulo.js +29 -19
- package/biome.json +34 -0
- package/package.json +35 -35
- package/rslib.config.ts +19 -19
- package/src/args/get-framework-name.ts +7 -7
- package/src/args/index.ts +16 -74
- package/src/args/preset.ts +16 -16
- package/src/cli/init.ts +10 -6
- package/src/cli/pack-code.ts +20 -9
- package/src/config/example/example-config.ts +50 -39
- package/src/config/example/example-externals.ts +21 -37
- package/src/config/index.ts +186 -16
- package/src/config/presets.ts +100 -0
- package/src/config/type.ts +34 -12
- package/src/index.ts +102 -10
- package/src/initiator/create-config-file.ts +52 -36
- package/src/initiator/create-project.ts +249 -0
- package/src/initiator/modify-scripts.ts +42 -42
- package/src/packer/auto-external-plugin.ts +110 -0
- package/src/packer/collect-modules.ts +58 -69
- package/src/packer/get-externals-and-tags.ts +78 -55
- package/src/packer/lib.ts +76 -69
- package/src/packer/page.ts +106 -82
- package/src/packer/prepare.ts +63 -57
- package/src/tools/cli.ts +21 -0
- package/src/tools/file.ts +84 -14
- package/src/tools/find-path-root.ts +52 -25
- package/src/tools/get-framework-name.ts +7 -7
- package/src/tools/get-ui-plugin.ts +27 -13
- package/src/tools/json.ts +63 -9
- package/src/tools/log.ts +58 -0
- package/src/tools/merge-user-config.ts +17 -17
- package/src/tools/omit-root-path.ts +17 -7
- package/src/tools/panic.ts +12 -8
- package/src/tools/string.ts +13 -2
- package/src/type/guard.ts +22 -3
- package/tsconfig.json +9 -9
- package/dist/index.js +0 -773
- package/src/args/cmd.ts +0 -16
- package/src/args/mode.ts +0 -38
- package/src/args/node_env.ts +0 -15
- package/src/args/target.ts +0 -44
- package/src/config/externals.ts +0 -70
- package/src/config/generate_config.ts +0 -105
- package/src/config/preset/alias.ts +0 -3
- package/src/config/preset/dev-server.ts +0 -6
- package/src/config/preset/dirs.ts +0 -12
- package/src/config/preset/html.ts +0 -19
- package/src/config/preset/index.ts +0 -23
- package/src/config/preset/libs.ts +0 -5
- package/src/config/preset/minify.ts +0 -24
- package/src/config/preset/url.ts +0 -4
- package/src/tools/debug-log.ts +0 -37
package/src/packer/prepare.ts
CHANGED
|
@@ -2,75 +2,81 @@ import picocolors from "picocolors";
|
|
|
2
2
|
import type { ModuloArgs_Pack } from "../args/index.ts";
|
|
3
3
|
import { collect_modules } from "./collect-modules.ts";
|
|
4
4
|
import { omit_root_path_for_entries } from "../tools/omit-root-path.ts";
|
|
5
|
-
import {
|
|
5
|
+
import { getExternalsAndImportMap } from "./get-externals-and-tags.ts";
|
|
6
6
|
import type { GLOBAL_CONFIG } from "../config/type.ts";
|
|
7
7
|
|
|
8
8
|
let printed = false;
|
|
9
9
|
|
|
10
10
|
export function prepare_config(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
args: ModuloArgs_Pack,
|
|
12
|
+
kind: "page" | "module",
|
|
13
|
+
config: GLOBAL_CONFIG,
|
|
14
14
|
) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
config.externals
|
|
18
|
-
);
|
|
15
|
+
console.log(picocolors.blueBright(`\n**** 开始构建 【${kind}】 ****`));
|
|
16
|
+
const entries = collect_modules(args, kind, config);
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
if (!entries) {
|
|
19
|
+
console.log(picocolors.red(`\n没有要构建的${kind},跳过\n`));
|
|
20
|
+
} else {
|
|
21
|
+
console.log(
|
|
22
|
+
`${picocolors.blue(`\n${kind} entries:`)}\n${JSON.stringify(
|
|
23
|
+
omit_root_path_for_entries(entries),
|
|
24
|
+
null,
|
|
25
|
+
2,
|
|
26
|
+
)}\n`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
2
|
|
35
|
-
)}\n`
|
|
36
|
-
);
|
|
37
|
-
}
|
|
30
|
+
const { externals, importMap } = getExternalsAndImportMap(
|
|
31
|
+
args,
|
|
32
|
+
config.externals,
|
|
33
|
+
config.externalsType,
|
|
34
|
+
);
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
36
|
+
!printed &&
|
|
37
|
+
console.log(
|
|
38
|
+
`${picocolors.blue("\nexternals:")}\n${JSON.stringify(
|
|
39
|
+
externals,
|
|
40
|
+
null,
|
|
41
|
+
2,
|
|
42
|
+
)}\n`,
|
|
43
|
+
);
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
console.log(
|
|
51
|
-
`${picocolors.blue("\nimportmaps:")}\n${JSON.stringify(
|
|
52
|
-
importmaps,
|
|
53
|
-
null,
|
|
54
|
-
2
|
|
55
|
-
)}\n`
|
|
56
|
-
);
|
|
45
|
+
let importMapsTag: any;
|
|
57
46
|
|
|
58
|
-
|
|
47
|
+
if (config.externalsType === "script") {
|
|
48
|
+
// script 注入模式,生成多个 script 标签
|
|
49
|
+
importMapsTag = Object.values(importMap).map((url) => ({
|
|
50
|
+
tag: "script",
|
|
51
|
+
attrs: { src: url },
|
|
52
|
+
append: false,
|
|
53
|
+
head: true,
|
|
54
|
+
}));
|
|
55
|
+
} else {
|
|
56
|
+
// importmap 模式
|
|
57
|
+
importMapsTag = [
|
|
58
|
+
{
|
|
59
|
+
append: false,
|
|
60
|
+
head: true,
|
|
61
|
+
tag: "script",
|
|
62
|
+
attrs: { type: "importmap" },
|
|
63
|
+
children: `{
|
|
64
|
+
"imports": ${JSON.stringify(importMap, null, 2)}
|
|
65
|
+
}`,
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
}
|
|
59
69
|
|
|
60
|
-
|
|
61
|
-
|
|
70
|
+
!printed &&
|
|
71
|
+
console.log(
|
|
72
|
+
`${picocolors.blue("\nimportmaps/scripts:")}\n${JSON.stringify(
|
|
73
|
+
importMap,
|
|
74
|
+
null,
|
|
75
|
+
2,
|
|
76
|
+
)}\n`,
|
|
77
|
+
);
|
|
62
78
|
|
|
63
|
-
|
|
64
|
-
console.log(picocolors.red(`\n没有要构建的${kind},跳过\n`));
|
|
65
|
-
} else {
|
|
66
|
-
console.log(
|
|
67
|
-
`${picocolors.blue(`\n${kind} entries:`)}\n${JSON.stringify(
|
|
68
|
-
omit_root_path_for_entries(entries),
|
|
69
|
-
null,
|
|
70
|
-
2
|
|
71
|
-
)}\n`
|
|
72
|
-
);
|
|
73
|
-
}
|
|
79
|
+
printed = true;
|
|
74
80
|
|
|
75
|
-
|
|
81
|
+
return { entries, externals, importMapsTag };
|
|
76
82
|
}
|
package/src/tools/cli.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import readline from "node:readline";
|
|
2
|
+
import picocolors from "picocolors";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CLI 交互式确认
|
|
6
|
+
* @param message 提示信息
|
|
7
|
+
* @returns Promise<boolean> 用户是否确认 (Y/y 为 true)
|
|
8
|
+
*/
|
|
9
|
+
export async function confirm(message: string): Promise<boolean> {
|
|
10
|
+
const rl = readline.createInterface({
|
|
11
|
+
input: process.stdin,
|
|
12
|
+
output: process.stdout,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
rl.question(`${picocolors.yellow(message)} (Y/n) `, (answer) => {
|
|
17
|
+
rl.close();
|
|
18
|
+
resolve(answer.toLowerCase() === "y" || answer === "");
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
package/src/tools/file.ts
CHANGED
|
@@ -1,19 +1,89 @@
|
|
|
1
|
-
import { readFileSync } from
|
|
2
|
-
import { resolve } from
|
|
3
|
-
import picocolors from
|
|
4
|
-
import { debug_log } from
|
|
1
|
+
import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";
|
|
2
|
+
import { resolve, join } from "node:path";
|
|
3
|
+
import picocolors from "picocolors";
|
|
4
|
+
import { debug_log } from "./log.ts";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
/**
|
|
7
|
+
* 读取文件内容
|
|
8
|
+
* @param path 文件绝对路径
|
|
9
|
+
* @param error_msg 自定义错误消息(可选)
|
|
10
|
+
* @param throwError 是否抛出错误(默认 false)
|
|
11
|
+
* @returns 文件内容字符串,如果失败则返回空字符串(除非 throwError 为 true)
|
|
12
|
+
*/
|
|
13
|
+
export function read_file(
|
|
14
|
+
path: string,
|
|
15
|
+
error_msg?: string,
|
|
16
|
+
throwError = false,
|
|
17
|
+
) {
|
|
18
|
+
try {
|
|
19
|
+
return readFileSync(path, "utf8");
|
|
20
|
+
} catch (error) {
|
|
21
|
+
const msg = error_msg || `文件无法访问或者不存在: ${path}`;
|
|
22
|
+
debug_log("read_file error", msg, error);
|
|
23
|
+
if (throwError) {
|
|
24
|
+
throw new Error(msg);
|
|
25
|
+
}
|
|
26
|
+
console.log(picocolors.red(msg));
|
|
27
|
+
return "";
|
|
28
|
+
}
|
|
13
29
|
}
|
|
14
30
|
|
|
31
|
+
/**
|
|
32
|
+
* 解析路径并读取文件
|
|
33
|
+
*/
|
|
15
34
|
export function resolve_and_read(root: string, name: string) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
35
|
+
const fullpath = resolve(root, name);
|
|
36
|
+
debug_log(`resolve file: ${name}`, "result is:", fullpath);
|
|
37
|
+
return read_file(fullpath);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 获取指定目录下的所有子目录名称
|
|
42
|
+
* @param path 目标目录路径
|
|
43
|
+
* @returns 子目录名称列表
|
|
44
|
+
*/
|
|
45
|
+
export function get_directories(path: string): string[] {
|
|
46
|
+
try {
|
|
47
|
+
if (!existsSync(path)) return [];
|
|
48
|
+
return readdirSync(path).filter((file) => {
|
|
49
|
+
const fullPath = join(path, file);
|
|
50
|
+
return statSync(fullPath).isDirectory();
|
|
51
|
+
});
|
|
52
|
+
} catch (error) {
|
|
53
|
+
debug_log("get_directories error", path, error);
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 在目录中查找入口文件
|
|
60
|
+
* @param dir 目标目录
|
|
61
|
+
* @param candidates 候选文件名(不含扩展名)列表,优先级按顺序
|
|
62
|
+
* @param extensions 支持的扩展名列表,优先级按顺序
|
|
63
|
+
* @returns 找到的入口文件绝对路径,如果未找到返回 undefined
|
|
64
|
+
*/
|
|
65
|
+
export function find_entry_file(
|
|
66
|
+
dir: string,
|
|
67
|
+
candidates: string[],
|
|
68
|
+
extensions: string[] = [".ts", ".tsx", ".js", ".jsx", ".vue"],
|
|
69
|
+
): string | undefined {
|
|
70
|
+
for (const name of candidates) {
|
|
71
|
+
for (const ext of extensions) {
|
|
72
|
+
const filename = `${name}${ext}`;
|
|
73
|
+
const filepath = join(dir, filename);
|
|
74
|
+
if (existsSync(filepath) && statSync(filepath).isFile()) {
|
|
75
|
+
return filepath;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 检查文件是否存在
|
|
84
|
+
*/
|
|
85
|
+
export function exists(path: string): boolean {
|
|
86
|
+
const isExist = existsSync(path);
|
|
87
|
+
debug_log(`check exists: ${path}`, isExist);
|
|
88
|
+
return isExist;
|
|
19
89
|
}
|
|
@@ -1,29 +1,56 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path, { dirname } from
|
|
3
|
-
import { fileURLToPath } from
|
|
4
|
-
import { debug_log } from
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path, { dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { debug_log } from "./log.ts";
|
|
5
5
|
|
|
6
|
-
let packageRoot =
|
|
6
|
+
let packageRoot = "";
|
|
7
7
|
|
|
8
8
|
export function get_package_root() {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
9
|
+
if (!packageRoot) {
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file
|
|
11
|
+
const __dirname = dirname(__filename); // get the name of the directory
|
|
12
|
+
|
|
13
|
+
let currentDir = path.resolve(__dirname);
|
|
14
|
+
const root = path.parse(currentDir).root;
|
|
15
|
+
|
|
16
|
+
while (currentDir !== root) {
|
|
17
|
+
const potentialPkgJson = path.join(currentDir, "package.json");
|
|
18
|
+
if (fs.existsSync(potentialPkgJson)) {
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
currentDir = path.dirname(currentDir);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
debug_log("packageRoot", currentDir);
|
|
25
|
+
packageRoot = currentDir;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return packageRoot;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function find_workspace_root(cwd: string) {
|
|
32
|
+
let currentDir = path.resolve(cwd);
|
|
33
|
+
const root = path.parse(currentDir).root;
|
|
34
|
+
|
|
35
|
+
while (currentDir !== root) {
|
|
36
|
+
const pnpmWorkspace = path.join(currentDir, "pnpm-workspace.yaml");
|
|
37
|
+
const packageJsonPath = path.join(currentDir, "package.json");
|
|
38
|
+
|
|
39
|
+
if (fs.existsSync(pnpmWorkspace)) {
|
|
40
|
+
return currentDir;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
44
|
+
try {
|
|
45
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
46
|
+
if (pkg.workspaces) {
|
|
47
|
+
return currentDir;
|
|
48
|
+
}
|
|
49
|
+
} catch {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
currentDir = path.dirname(currentDir);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return undefined;
|
|
29
56
|
}
|
|
@@ -2,13 +2,13 @@ import { get_packagejson } from "../config/index.ts";
|
|
|
2
2
|
import { PANIC_IF } from "./panic.ts";
|
|
3
3
|
|
|
4
4
|
export function get_framework_name() {
|
|
5
|
-
|
|
5
|
+
const { dependencies } = get_packagejson();
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
PANIC_IF(
|
|
8
|
+
!("vue" in dependencies || "react" in dependencies),
|
|
9
|
+
"package.json中未识别到支持的ui库信息, 当前只支持vue和react",
|
|
10
|
+
);
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
// 该项目是vue还是react项目
|
|
13
|
+
return "vue" in dependencies ? "vue" : "react";
|
|
14
14
|
}
|
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
import { type PluginReactOptions, pluginReact } from "@rsbuild/plugin-react";
|
|
2
2
|
import { type PluginVueOptions, pluginVue2 } from "@rsbuild/plugin-vue2";
|
|
3
|
-
import {
|
|
3
|
+
import { get_packagejson } from "../config/index.ts";
|
|
4
|
+
import type { GLOBAL_CONFIG } from "../config/type.ts";
|
|
4
5
|
import { get_framework_name } from "./get-framework-name.ts";
|
|
5
6
|
import { PANIC_IF } from "./panic.ts";
|
|
6
7
|
import type { ModuloArgs_Pack } from "../args/index.ts";
|
|
8
|
+
import semver from "semver";
|
|
7
9
|
|
|
8
10
|
export function framework_plugin(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
global_config: GLOBAL_CONFIG,
|
|
12
|
+
options?: PluginVueOptions | PluginReactOptions,
|
|
11
13
|
) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
const { dependencies } = get_packagejson();
|
|
15
|
+
const framework_name = get_framework_name();
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
// 必须使用指定版本号的ui库,以优化代码产出
|
|
18
|
+
const version = dependencies[framework_name];
|
|
19
|
+
|
|
20
|
+
const allowed_versions = [
|
|
21
|
+
global_config.ui_lib.vue2,
|
|
22
|
+
global_config.ui_lib.react19,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const is_valid = allowed_versions.some((allowed) => {
|
|
26
|
+
const min_version = semver.minVersion(version);
|
|
27
|
+
return min_version && semver.satisfies(min_version, `^${allowed}`);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
PANIC_IF(
|
|
31
|
+
!is_valid,
|
|
32
|
+
`package.json中只允许使用固定版本号, 并且只支持vue-2.7.16, react-19.2.4 (当前版本: ${version})`,
|
|
33
|
+
);
|
|
34
|
+
return framework_name === "vue"
|
|
35
|
+
? pluginVue2(options as any)
|
|
36
|
+
: pluginReact(options as any);
|
|
23
37
|
}
|
package/src/tools/json.ts
CHANGED
|
@@ -1,11 +1,65 @@
|
|
|
1
|
-
import picocolors from
|
|
1
|
+
import picocolors from "picocolors";
|
|
2
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
/**
|
|
5
|
+
* 解析 JSON 字符串
|
|
6
|
+
* @param input JSON 字符串
|
|
7
|
+
* @param defaultValue 解析失败或输入为空时的默认值(可选)
|
|
8
|
+
* @returns 解析后的对象或默认值
|
|
9
|
+
*/
|
|
10
|
+
export function jsonparse<T>(input: string, defaultValue?: T): T | undefined {
|
|
11
|
+
try {
|
|
12
|
+
if (input) {
|
|
13
|
+
return JSON.parse(input) as T;
|
|
14
|
+
}
|
|
15
|
+
return defaultValue;
|
|
16
|
+
} catch (e) {
|
|
17
|
+
console.error(picocolors.red(`JSON.parse failed\n${e}`));
|
|
18
|
+
return defaultValue;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 更新 JSON 文件
|
|
24
|
+
* @param path 文件路径
|
|
25
|
+
* @param updater 更新函数,接收当前数据并返回新数据
|
|
26
|
+
* @param createIfNotExist 如果文件不存在是否创建(默认 false)
|
|
27
|
+
* @returns 是否更新成功
|
|
28
|
+
*/
|
|
29
|
+
export function update_json_file<T = any>(
|
|
30
|
+
path: string,
|
|
31
|
+
updater: (data: T) => T,
|
|
32
|
+
createIfNotExist = false,
|
|
33
|
+
): boolean {
|
|
34
|
+
try {
|
|
35
|
+
let data: T;
|
|
36
|
+
try {
|
|
37
|
+
const content = readFileSync(path, "utf-8");
|
|
38
|
+
const parsed = jsonparse<T>(content);
|
|
39
|
+
if (!parsed) {
|
|
40
|
+
if (createIfNotExist) {
|
|
41
|
+
data = {} as T;
|
|
42
|
+
} else {
|
|
43
|
+
console.error(picocolors.red(`Failed to parse JSON file: ${path}`));
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
data = parsed;
|
|
48
|
+
}
|
|
49
|
+
} catch (error: any) {
|
|
50
|
+
if (error.code === "ENOENT" && createIfNotExist) {
|
|
51
|
+
data = {} as T;
|
|
52
|
+
} else {
|
|
53
|
+
console.error(picocolors.red(`Failed to read file: ${path}`));
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const newData = updater(data);
|
|
59
|
+
writeFileSync(path, JSON.stringify(newData, null, 2) + "\n");
|
|
60
|
+
return true;
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.error(picocolors.red(`Failed to update JSON file: ${path}\n${e}`));
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
11
65
|
}
|
package/src/tools/log.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import picocolors from "picocolors";
|
|
4
|
+
|
|
5
|
+
const logFile = path.join(process.cwd(), "modulo.debug.log");
|
|
6
|
+
let index = 0;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 调试日志函数
|
|
10
|
+
*
|
|
11
|
+
* 仅在 process.env.DEBUG 开启或 CLI 参数包含 --debug/--verbose 时输出。
|
|
12
|
+
* - --verbose: 直接输出到控制台
|
|
13
|
+
* - --debug: 输出日志文件到当前目录的 modulo.debug.log,并在控制台打印序号
|
|
14
|
+
*
|
|
15
|
+
* @param hint 日志标题或提示信息
|
|
16
|
+
* @param params 日志内容(可以是任意对象)
|
|
17
|
+
*/
|
|
18
|
+
export function debug_log(hint: string, ...params: unknown[]) {
|
|
19
|
+
const argv_debug = process.env.DEBUG || process.argv.includes("--debug");
|
|
20
|
+
const argv_verbose =
|
|
21
|
+
process.argv.includes("--verbose") || process.argv.includes("-v");
|
|
22
|
+
|
|
23
|
+
if (!argv_debug && !argv_verbose) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const timestamp = new Date().toISOString();
|
|
28
|
+
const sn = String(index++).padStart(3, "0");
|
|
29
|
+
const logEntry = `--------------\n${sn} [${timestamp}] ${hint}\n${params
|
|
30
|
+
.map((p: unknown) =>
|
|
31
|
+
typeof p === "object" ? JSON.stringify(p, null, 2) : String(p),
|
|
32
|
+
)
|
|
33
|
+
.join("\n")}\n---------------\n\n`;
|
|
34
|
+
|
|
35
|
+
if (argv_verbose) {
|
|
36
|
+
console.log(logEntry);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (argv_debug) {
|
|
40
|
+
// 打印序列号方便debug
|
|
41
|
+
console.log(picocolors.blue(`\ndebug log ${sn}`));
|
|
42
|
+
|
|
43
|
+
fs.appendFileSync(logFile, logEntry);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Logger utility
|
|
48
|
+
export const logger = {
|
|
49
|
+
info: (msg: string) => console.log(picocolors.cyan(msg)),
|
|
50
|
+
success: (msg: string) => console.log(picocolors.green(msg)),
|
|
51
|
+
warn: (msg: string) => console.log(picocolors.yellow(msg)),
|
|
52
|
+
error: (msg: string) => console.log(picocolors.red(msg)),
|
|
53
|
+
debug: (msg: string) => {
|
|
54
|
+
if (process.env.DEBUG) {
|
|
55
|
+
console.log(picocolors.gray(`[DEBUG] ${msg}`));
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { PANIC_IF } from "./panic.ts";
|
|
2
2
|
|
|
3
3
|
export function merge_user_config(target: any, input: any) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
4
|
+
for (const key in input) {
|
|
5
|
+
const from = input[key];
|
|
6
|
+
const to = target[key];
|
|
7
|
+
if (typeof from !== typeof to || !(key in target)) {
|
|
8
|
+
target[key] = from;
|
|
9
|
+
continue;
|
|
10
|
+
} else {
|
|
11
|
+
if (Array.isArray(to)) {
|
|
12
|
+
PANIC_IF(!Array.isArray(from));
|
|
13
|
+
target[key] = [...to, ...from];
|
|
14
|
+
} else if (typeof to === "object") {
|
|
15
|
+
merge_user_config(to, from);
|
|
16
|
+
} else {
|
|
17
|
+
target[key] = from;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
21
|
}
|
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { relative } from "node:path";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 获取相对于根目录的路径
|
|
5
|
+
* @param path 绝对路径
|
|
6
|
+
* @returns 相对路径(以 / 开头)
|
|
7
|
+
*/
|
|
8
|
+
export function omit_root_path(path: string): string {
|
|
9
|
+
const rel = relative(process.cwd(), path);
|
|
10
|
+
// 保持输出格式一致,添加前导 /
|
|
11
|
+
return rel.startsWith("/") ? rel : `/${rel}`;
|
|
5
12
|
}
|
|
6
13
|
|
|
14
|
+
/**
|
|
15
|
+
* 批量处理入口文件路径
|
|
16
|
+
*/
|
|
7
17
|
export function omit_root_path_for_entries(entries: Record<string, string>) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
18
|
+
return Object.fromEntries(
|
|
19
|
+
Object.entries(entries).map(([key, value]) => [key, omit_root_path(value)]),
|
|
20
|
+
);
|
|
11
21
|
}
|
package/src/tools/panic.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { exit } from
|
|
2
|
-
import pc from
|
|
1
|
+
import { exit } from "node:process";
|
|
2
|
+
import pc from "picocolors";
|
|
3
3
|
|
|
4
|
-
const alert =
|
|
4
|
+
const alert = "! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !";
|
|
5
5
|
|
|
6
|
-
export function PANIC_IF(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
export function PANIC_IF(
|
|
7
|
+
status = false,
|
|
8
|
+
msg = `SOMETHING'S WRONG`,
|
|
9
|
+
halt = true,
|
|
10
|
+
): asserts status is false {
|
|
11
|
+
if (status) {
|
|
12
|
+
console.log(pc.bgRed(pc.white(`\n${alert}\n\n${msg}\n\n${alert}`)), "\n");
|
|
13
|
+
halt && exit(1);
|
|
14
|
+
}
|
|
11
15
|
}
|
package/src/tools/string.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 首字母大写,其余小写
|
|
3
|
+
* @param str 输入字符串
|
|
4
|
+
* @returns 格式化后的字符串
|
|
5
|
+
*/
|
|
6
|
+
export function capitalize(str: string): string {
|
|
7
|
+
if (!str) return "";
|
|
8
|
+
return `${str.charAt(0).toUpperCase()}${str.slice(1).toLowerCase()}`;
|
|
3
9
|
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @deprecated Use capitalize instead
|
|
13
|
+
*/
|
|
14
|
+
export const first_letter_upper = capitalize;
|