Skip to content

Commit ba3051f

Browse files
committed
feat(@unimolecule/utils): ✨ refactor output-entry-builder, support entries
1 parent 4c20525 commit ba3051f

6 files changed

Lines changed: 137 additions & 40 deletions

File tree

.changeset/real-rooms-hug.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@unimolecule/utils": patch
3+
---
4+
5+
refactor output-entry-builder, support `entries`

packages/utils/src/node/README.md

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -104,28 +104,41 @@ const hosts = getLocalhostAddress();
104104
console.log(hosts);
105105
```
106106

107+
Build tsdown entries from a source directory:
108+
109+
```ts
110+
import { outputEntryBuilder } from "@unimolecule/utils/node/output-entry-builder";
111+
112+
export default {
113+
entry: outputEntryBuilder("./src", {
114+
entries: "index",
115+
}),
116+
};
117+
```
118+
107119
## API
108120

109-
| Export | Description |
110-
| ---------------------------------------------- | ------------------------------------------------------------------------- |
111-
| `appExists(app, options?)` | Async PATH probe for an executable app. |
112-
| `appExistsSync(app, options?)` | Sync PATH probe for an executable app. |
113-
| `checkProcessDiskAccess(path?)` | Verifies read/write access for a path, defaulting to `process.cwd()`. |
114-
| `executeCommand(command, args?, options?)` | Runs a command and resolves exit details when it succeeds. |
115-
| `executeCommandSync(command, args?, options?)` | Sync command runner with the same success/error behavior. |
116-
| `formatPath(path)` | Converts backslash separators to `/` while leaving Unix paths unchanged. |
117-
| `createProcessGracefulExit(logger?)` | Creates isolated process signal registration and shutdown helpers. |
118-
| `exitSignals` | Supported graceful shutdown signals: `SIGINT`, `SIGQUIT`, `SIGTERM`. |
119-
| `getLocalhostAddress()` | Returns non-internal IPv4 addresses plus `[::]`, with duplicates removed. |
120-
| `getPackages(cwd?)` | Async export from `@manypkg/get-packages`. |
121-
| `getPackagesSync(cwd?)` | Sync export from `@manypkg/get-packages`. |
122-
| `pathExists(path)` | Async `fs.access` existence check. |
123-
| `pathExistsSync(path)` | Sync `fs.accessSync` existence check. |
124-
| `require` | `createRequire(import.meta.url)` for ESM modules that need Node require. |
125-
| `unifiedSpawn(command, args?, options?)` | Cross-platform `spawn` wrapper. |
126-
| `unifiedSpawnAsync(command, args?, options?)` | Promise wrapper resolving the child close code. |
127-
| `unifiedSpawnSync(command, args?, options?)` | Cross-platform `spawnSync` wrapper. |
128-
| `userHome` | Current `os.homedir()` value. |
121+
| Export | Description |
122+
| ---------------------------------------------- | ----------------------------------------------------------------------------------------- |
123+
| `appExists(app, options?)` | Async PATH probe for an executable app. |
124+
| `appExistsSync(app, options?)` | Sync PATH probe for an executable app. |
125+
| `checkProcessDiskAccess(path?)` | Verifies read/write access for a path, defaulting to `process.cwd()`. |
126+
| `executeCommand(command, args?, options?)` | Runs a command and resolves exit details when it succeeds. |
127+
| `executeCommandSync(command, args?, options?)` | Sync command runner with the same success/error behavior. |
128+
| `formatPath(path)` | Converts backslash separators to `/` while leaving Unix paths unchanged. |
129+
| `createProcessGracefulExit(logger?)` | Creates isolated process signal registration and shutdown helpers. |
130+
| `exitSignals` | Supported graceful shutdown signals: `SIGINT`, `SIGQUIT`, `SIGTERM`. |
131+
| `getLocalhostAddress()` | Returns non-internal IPv4 addresses plus `[::]`, with duplicates removed. |
132+
| `getPackages(cwd?)` | Async export from `@manypkg/get-packages`. |
133+
| `getPackagesSync(cwd?)` | Sync export from `@manypkg/get-packages`. |
134+
| `outputEntryBuilder(rootDir, options?)` | Builds tsdown entry objects by scanning files; `entries: "index"` keeps only index files. |
135+
| `pathExists(path)` | Async `fs.access` existence check. |
136+
| `pathExistsSync(path)` | Sync `fs.accessSync` existence check. |
137+
| `require` | `createRequire(import.meta.url)` for ESM modules that need Node require. |
138+
| `unifiedSpawn(command, args?, options?)` | Cross-platform `spawn` wrapper. |
139+
| `unifiedSpawnAsync(command, args?, options?)` | Promise wrapper resolving the child close code. |
140+
| `unifiedSpawnSync(command, args?, options?)` | Cross-platform `spawnSync` wrapper. |
141+
| `userHome` | Current `os.homedir()` value. |
129142

130143
## Runtime Notes
131144

packages/utils/src/node/README.zh-CN.md

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -104,28 +104,41 @@ const hosts = getLocalhostAddress();
104104
console.log(hosts);
105105
```
106106

107+
从源码目录生成 tsdown entries:
108+
109+
```ts
110+
import { outputEntryBuilder } from "@unimolecule/utils/node/output-entry-builder";
111+
112+
export default {
113+
entry: outputEntryBuilder("./src", {
114+
entries: "index",
115+
}),
116+
};
117+
```
118+
107119
## API
108120

109-
| Export | 说明 |
110-
| ---------------------------------------------- | --------------------------------------------------------------- |
111-
| `appExists(app, options?)` | 异步探测 PATH 中是否存在可执行命令。 |
112-
| `appExistsSync(app, options?)` | 同步探测 PATH 中是否存在可执行命令。 |
113-
| `checkProcessDiskAccess(path?)` | 验证路径读写权限,默认使用 `process.cwd()`|
114-
| `executeCommand(command, args?, options?)` | 执行命令,成功时返回退出信息。 |
115-
| `executeCommandSync(command, args?, options?)` | 同步命令执行器,行为与异步版本一致。 |
116-
| `formatPath(path)` | 将反斜杠路径分隔符转换为 `/`,Unix 路径保持不变。 |
117-
| `createProcessGracefulExit(logger?)` | 创建隔离的进程信号注册和 shutdown helper。 |
118-
| `exitSignals` | 支持的 graceful shutdown 信号:`SIGINT``SIGQUIT``SIGTERM`|
119-
| `getLocalhostAddress()` | 返回非 internal IPv4 地址和 `[::]`,并去重。 |
120-
| `getPackages(cwd?)` |`@manypkg/get-packages` 直接导出的异步函数。 |
121-
| `getPackagesSync(cwd?)` |`@manypkg/get-packages` 直接导出的同步函数。 |
122-
| `pathExists(path)` | 异步 `fs.access` 存在性检查。 |
123-
| `pathExistsSync(path)` | 同步 `fs.accessSync` 存在性检查。 |
124-
| `require` | 给 ESM 模块使用的 `createRequire(import.meta.url)`|
125-
| `unifiedSpawn(command, args?, options?)` | 跨平台 `spawn` wrapper。 |
126-
| `unifiedSpawnAsync(command, args?, options?)` | Promise wrapper,解析 child process close code。 |
127-
| `unifiedSpawnSync(command, args?, options?)` | 跨平台 `spawnSync` wrapper。 |
128-
| `userHome` | 当前 `os.homedir()` 值。 |
121+
| Export | 说明 |
122+
| ---------------------------------------------- | ---------------------------------------------------------------------- |
123+
| `appExists(app, options?)` | 异步探测 PATH 中是否存在可执行命令。 |
124+
| `appExistsSync(app, options?)` | 同步探测 PATH 中是否存在可执行命令。 |
125+
| `checkProcessDiskAccess(path?)` | 验证路径读写权限,默认使用 `process.cwd()`|
126+
| `executeCommand(command, args?, options?)` | 执行命令,成功时返回退出信息。 |
127+
| `executeCommandSync(command, args?, options?)` | 同步命令执行器,行为与异步版本一致。 |
128+
| `formatPath(path)` | 将反斜杠路径分隔符转换为 `/`,Unix 路径保持不变。 |
129+
| `createProcessGracefulExit(logger?)` | 创建隔离的进程信号注册和 shutdown helper。 |
130+
| `exitSignals` | 支持的 graceful shutdown 信号:`SIGINT``SIGQUIT``SIGTERM`|
131+
| `getLocalhostAddress()` | 返回非 internal IPv4 地址和 `[::]`,并去重。 |
132+
| `getPackages(cwd?)` |`@manypkg/get-packages` 直接导出的异步函数。 |
133+
| `getPackagesSync(cwd?)` |`@manypkg/get-packages` 直接导出的同步函数。 |
134+
| `outputEntryBuilder(rootDir, options?)` | 扫描文件生成 tsdown entry 对象;`entries: "index"` 只保留 index 文件。 |
135+
| `pathExists(path)` | 异步 `fs.access` 存在性检查。 |
136+
| `pathExistsSync(path)` | 同步 `fs.accessSync` 存在性检查。 |
137+
| `require` | 给 ESM 模块使用的 `createRequire(import.meta.url)`|
138+
| `unifiedSpawn(command, args?, options?)` | 跨平台 `spawn` wrapper。 |
139+
| `unifiedSpawnAsync(command, args?, options?)` | Promise wrapper,解析 child process close code。 |
140+
| `unifiedSpawnSync(command, args?, options?)` | 跨平台 `spawnSync` wrapper。 |
141+
| `userHome` | 当前 `os.homedir()` 值。 |
129142

130143
## 运行时说明
131144

packages/utils/src/node/output-entry-builder.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ type EntryIgnore =
1010
isDirectory: boolean;
1111
}) => boolean);
1212

13+
type EntryMode = "all" | "index";
14+
1315
interface ScanEntryOptions {
1416
cwd?: string;
17+
entries?: EntryMode;
1518
extensions?: readonly string[];
1619
ignore?: readonly EntryIgnore[];
1720
}
@@ -25,6 +28,7 @@ export function outputEntryBuilder(
2528
options: ScanEntryOptions = {},
2629
): Record<string, string> {
2730
const cwd = options.cwd ?? process.cwd();
31+
const entryMode = options.entries ?? "all";
2832
const extensions = options.extensions ?? [".ts"];
2933
const ignore = options.ignore ?? [];
3034
const root = resolve(cwd, rootDir);
@@ -72,6 +76,13 @@ export function outputEntryBuilder(
7276
continue;
7377
}
7478

79+
if (
80+
entryMode === "index" &&
81+
!relativePath.endsWith(`index${extension}`)
82+
) {
83+
continue;
84+
}
85+
7586
const entryName = relativePath.slice(0, -extension.length);
7687
entries[entryName] = `./${toPosixPath(relative(cwd, absolutePath))}`;
7788
}

packages/utils/tests/node/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ describe("index exports", () => {
1616
getLocalhostAddress: expect.any(Function),
1717
pathExists: expect.any(Function),
1818
pathExistsSync: expect.any(Function),
19+
outputEntryBuilder: expect.any(Function),
1920
require: expect.any(Function),
2021
unifiedSpawn: expect.any(Function),
2122
unifiedSpawnAsync: expect.any(Function),
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
2+
import { tmpdir } from "node:os";
3+
import { dirname, join } from "node:path";
4+
import { afterEach, describe, expect, it } from "vitest";
5+
import { outputEntryBuilder } from "../../src/node/output-entry-builder";
6+
7+
const tempDirs: string[] = [];
8+
9+
async function createTempDir() {
10+
const dir = await mkdtemp(join(tmpdir(), "utils-node-entry-"));
11+
tempDirs.push(dir);
12+
return dir;
13+
}
14+
15+
async function writeSourceFile(root: string, path: string) {
16+
const absolutePath = join(root, path);
17+
await mkdir(dirname(absolutePath), { recursive: true });
18+
await writeFile(absolutePath, "export {};\n");
19+
}
20+
21+
describe("outputEntryBuilder", () => {
22+
afterEach(async () => {
23+
await Promise.all(
24+
tempDirs
25+
.splice(0)
26+
.map((dir) => rm(dir, { force: true, recursive: true })),
27+
);
28+
});
29+
30+
it("builds entries for every matching source file by default", async () => {
31+
const cwd = await createTempDir();
32+
await writeSourceFile(cwd, "src/index.ts");
33+
await writeSourceFile(cwd, "src/feature/index.ts");
34+
await writeSourceFile(cwd, "src/feature/request.ts");
35+
36+
expect(outputEntryBuilder("./src", { cwd })).toEqual({
37+
"feature/index": "./src/feature/index.ts",
38+
"feature/request": "./src/feature/request.ts",
39+
index: "./src/index.ts",
40+
});
41+
});
42+
43+
it("can build entries only from index source files", async () => {
44+
const cwd = await createTempDir();
45+
await writeSourceFile(cwd, "src/index.ts");
46+
await writeSourceFile(cwd, "src/feature/index.ts");
47+
await writeSourceFile(cwd, "src/feature/request.ts");
48+
49+
expect(outputEntryBuilder("./src", { cwd, entries: "index" })).toEqual({
50+
"feature/index": "./src/feature/index.ts",
51+
index: "./src/index.ts",
52+
});
53+
});
54+
});

0 commit comments

Comments
 (0)