feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,239 @@
# @coze-arch/fs-enhance
> Enhanced file system utilities for improved developer experience
## Project Overview
`@coze-arch/fs-enhance` is a lightweight TypeScript utility library that provides enhanced file system operations with modern async/await API. It offers convenient wrappers around Node.js file system operations with built-in support for JSON5 and YAML parsing, making it easier to work with configuration files and common file operations in your projects.
## Features
-**Async/Await Support** - Modern promise-based API for all file operations
-**Type Safe** - Full TypeScript support with generic types for parsed content
-**JSON5 Support** - Read JSON files with comments and relaxed syntax
-**YAML Support** - Parse YAML configuration files
-**File/Directory Checks** - Convenient existence checks for files and directories
-**Directory Creation** - Recursive directory creation with existence checks
-**Line Counting** - Utility to count lines in text files
-**Zero Dependencies** - Minimal external dependencies (only json5 and yaml)
## Get Started
### Installation
Since this is a workspace package, install it using the workspace protocol:
```bash
# Add to your package.json dependencies
"@coze-arch/fs-enhance": "workspace:*"
# Then run rush update
rush update
```
### Basic Usage
```typescript
import {
isFileExists,
isDirExists,
readJsonFile,
readYamlFile,
writeJsonFile,
ensureDir,
readFileLineCount
} from '@coze-arch/fs-enhance';
// Check if file exists
const exists = await isFileExists('./config.json');
// Read JSON with type safety
interface Config {
name: string;
version: string;
}
const config = await readJsonFile<Config>('./config.json');
// Create directory if it doesn't exist
await ensureDir('./dist/output');
```
## API Reference
### File Existence Checks
#### `isFileExists(file: string): Promise<boolean>`
Checks if a file exists and is actually a file (not a directory).
```typescript
const exists = await isFileExists('./package.json');
if (exists) {
console.log('Package.json found!');
}
```
#### `isDirExists(file: string): Promise<boolean>`
Checks if a directory exists and is actually a directory (not a file).
```typescript
const exists = await isDirExists('./src');
if (exists) {
console.log('Source directory found!');
}
```
### File Reading Operations
#### `readJsonFile<T>(file: string): Promise<T>`
Reads and parses a JSON file with JSON5 support (allows comments and relaxed syntax). Returns a typed result.
```typescript
interface PackageJson {
name: string;
version: string;
dependencies?: Record<string, string>;
}
const pkg = await readJsonFile<PackageJson>('./package.json');
console.log(`Package: ${pkg.name}@${pkg.version}`);
```
#### `readYamlFile<T extends object>(filePath: string): Promise<T>`
Reads and parses a YAML file. Returns a typed result.
```typescript
interface DockerCompose {
version: string;
services: Record<string, any>;
}
const compose = await readYamlFile<DockerCompose>('./docker-compose.yml');
console.log(`Docker Compose version: ${compose.version}`);
```
#### `readFileLineCount(file: string): Promise<number>`
Counts the number of lines in a text file.
```typescript
const lineCount = await readFileLineCount('./src/index.ts');
console.log(`File has ${lineCount} lines`);
```
### File Writing Operations
#### `writeJsonFile(file: string, content: unknown): Promise<void>`
Writes an object to a JSON file with pretty formatting (2-space indentation).
```typescript
const config = {
name: 'my-app',
version: '1.0.0',
features: ['json5', 'yaml']
};
await writeJsonFile('./config.json', config);
```
### Directory Operations
#### `ensureDir(dir: string): Promise<void>`
Creates a directory and any necessary parent directories if they don't exist. Does nothing if the directory already exists.
```typescript
// Creates ./dist/assets/images and any missing parent directories
await ensureDir('./dist/assets/images');
// Safe to call multiple times
await ensureDir('./dist/assets/images'); // No error, does nothing
```
## Development
### Project Structure
```
fs-enhance/
├── src/
│ └── index.ts # Main implementation
├── __tests__/
│ └── file-enhance.test.ts # Test suite
├── package.json
├── tsconfig.json
└── README.md
```
### Running Tests
```bash
# Run tests
rush test --to @coze-arch/fs-enhance
# Run tests with coverage
rush test:cov --to @coze-arch/fs-enhance
```
### Building
```bash
# Type check
rush build --to @coze-arch/fs-enhance
# Lint code
rush lint --to @coze-arch/fs-enhance
```
## Dependencies
### Runtime Dependencies
- **json5** (^2.2.1) - JSON5 parsing support for relaxed JSON syntax
- **yaml** (^2.2.2) - YAML parsing and stringifying support
### Development Dependencies
- **@coze-arch/eslint-config** - Shared ESLint configuration
- **@coze-arch/ts-config** - Shared TypeScript configuration
- **@coze-arch/vitest-config** - Shared Vitest testing configuration
- **vitest** - Fast unit testing framework
- **@types/node** - TypeScript definitions for Node.js
## Error Handling
All functions handle errors gracefully:
- File existence checks (`isFileExists`, `isDirExists`) return `false` instead of throwing when files don't exist
- `ensureDir` safely handles existing directories without errors
- Parse operations will throw meaningful errors for invalid JSON/YAML syntax
## TypeScript Support
This package is written in TypeScript and provides full type definitions. Generic types are supported for parsing operations:
```typescript
// Strongly typed configuration
interface AppConfig {
database: {
host: string;
port: number;
};
features: string[];
}
const config = await readJsonFile<AppConfig>('./app.config.json');
// config is fully typed as AppConfig
```
## License
Apache-2.0
---
**Note**: This package was extracted from rush-x utilities to provide reusable file system enhancements across the monorepo.

View File

@@ -0,0 +1,148 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fs from 'fs/promises';
import { parse as parseYaml } from 'yaml';
import { type Mock } from 'vitest';
import { parse } from 'json5';
import {
readFileLineCount,
isFileExists,
isDirExists,
ensureDir,
readYamlFile,
} from '../src/index';
// eslint-disable-next-line @coze-arch/no-batch-import-or-export
import * as fileEnhance from '../src/index';
vi.mock('yaml', () => ({ parse: vi.fn() }));
vi.mock('fs/promises');
vi.mock('json5', () => ({
parse: vi.fn(),
}));
describe('file-enhance', () => {
afterEach(() => {
vi.clearAllMocks();
});
it('should return the number of lines in the file by calling `readFileLineCount`', async () => {
// Arrange
const file = 'test-file.txt';
const content = 'Line 1\nLine 2\nLine 3\n';
(fs.readFile as Mock).mockResolvedValue(content);
// Act
const result = await readFileLineCount(file);
// Assert
expect(fs.readFile).toHaveBeenCalledWith(file, 'utf-8');
expect(result).toEqual(4);
});
it('should return true if the file exists by calling `isFileExists`', async () => {
(fs.stat as Mock).mockResolvedValue({ isFile: () => true });
const file = 'path/to/your/file.txt';
const result = await isFileExists(file);
expect(result).toBe(true);
expect(fs.stat).toHaveBeenCalledWith(file);
});
it('should return false if the file does not exist by calling `isFileExists`', async () => {
(fs.stat as Mock).mockRejectedValue(new Error('File not found'));
const file = 'path/to/nonexistent/file.txt';
const result = await isFileExists(file);
expect(result).toBe(false);
expect(fs.stat).toHaveBeenCalledWith(file);
});
it('should return true if the dir exists by calling `isDirExists`', async () => {
(fs.stat as Mock).mockResolvedValue({ isDirectory: () => true });
const file = 'path/to/your/dir';
const result = await isDirExists(file);
expect(result).toBe(true);
expect(fs.stat).toHaveBeenCalledWith(file);
});
it('should return true if the dir does not exist by calling `isDirExists`', async () => {
(fs.stat as Mock).mockRejectedValue(new Error('Dir not found'));
const file = 'path/to/nonexistent/dir';
const result = await isDirExists(file);
expect(result).toBe(false);
expect(fs.stat).toHaveBeenCalledWith(file);
});
it('should create a dir if it does not exist by calling `ensureDir`', async () => {
vi.spyOn(fileEnhance, 'isDirExists').mockResolvedValue(false);
(fs.mkdir as Mock).mockReturnValue('');
const file = 'path/to/new/dir';
const result = await ensureDir(file);
expect(result).toBe(undefined);
expect(fs.mkdir).toHaveBeenCalledWith(file, { recursive: true });
});
it('should not create a dir if it exists', async () => {
vi.spyOn(fileEnhance, 'isDirExists').mockResolvedValue(true);
const file = 'path/to/existed/dir';
const result = await ensureDir(file);
expect(result).toBe(undefined);
expect(fs.mkdir).not.toHaveBeenCalledWith();
});
it('readJsonFile', async () => {
(parse as Mock).mockReturnValue({});
(fs.readFile as Mock).mockResolvedValueOnce('');
const file = 'path/json/file';
const result = await fileEnhance.readJsonFile(file);
expect(fs.readFile).toHaveBeenCalledWith(file, 'utf-8');
expect(result).toStrictEqual({});
});
it('readYamlFile', async () => {
(parseYaml as Mock).mockReturnValue({});
(fs.readFile as Mock).mockResolvedValueOnce('');
const file = 'path/json/file';
const result = await readYamlFile(file);
expect(fs.readFile).toHaveBeenCalledWith(file, 'utf-8');
expect(result).toStrictEqual({});
});
it('writeJsonFile', async () => {
const file = 'path/to/write';
await fileEnhance.writeJsonFile(file, {});
expect(fs.writeFile).toHaveBeenCalledWith(file, '{}');
});
});

View File

@@ -0,0 +1,12 @@
{
"operationSettings": [
{
"operationName": "test:cov",
"outputFolderNames": ["coverage"]
},
{
"operationName": "ts-check",
"outputFolderNames": ["./dist"]
}
]
}

View File

@@ -0,0 +1,7 @@
const { defineConfig } = require('@coze-arch/eslint-config');
module.exports = defineConfig({
packageRoot: __dirname,
preset: 'node',
rules: {},
});

View File

@@ -0,0 +1,29 @@
{
"name": "@coze-arch/fs-enhance",
"version": "0.0.1",
"description": "Utils to enhance fs dx",
"license": "Apache-2.0",
"author": "fanwenjie.fe@bytedance.com",
"maintainers": [],
"main": "src/index.ts",
"scripts": {
"build": "exit 0",
"lint": "eslint ./ --cache",
"test": "vitest --run --passWithNoTests",
"test:cov": "npm run test -- --coverage"
},
"dependencies": {
"json5": "^2.2.1",
"yaml": "^2.2.2"
},
"devDependencies": {
"@coze-arch/eslint-config": "workspace:*",
"@coze-arch/ts-config": "workspace:*",
"@coze-arch/vitest-config": "workspace:*",
"@types/node": "^18",
"@vitest/coverage-v8": "~3.0.5",
"sucrase": "^3.32.0",
"vitest": "~3.0.5"
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fs from 'fs/promises';
import { parse as parseYaml } from 'yaml';
import { parse } from 'json5';
export const readFileLineCount = async (file: string): Promise<number> => {
const content = await fs.readFile(file, 'utf-8');
return content.split('\n').length;
};
export const isFileExists = async (file: string): Promise<boolean> => {
try {
const stat = await fs.stat(file);
return stat.isFile();
} catch (e) {
return false;
}
};
export const isDirExists = async (file: string): Promise<boolean> => {
try {
const stat = await fs.stat(file);
return stat.isDirectory();
} catch (e) {
return false;
}
};
export const readJsonFile = async <T>(file: string): Promise<T> =>
parse(await fs.readFile(file, 'utf-8'));
export const readYamlFile = async <T extends object>(
filePath: string,
): Promise<T> => parseYaml(await fs.readFile(filePath, 'utf-8'));
export const writeJsonFile = async (
file: string,
content: unknown,
): Promise<void> => {
await fs.writeFile(file, JSON.stringify(content, null, ' '));
};
export const ensureDir = async (dir: string) => {
if (!(await isDirExists(dir))) {
await fs.mkdir(dir, { recursive: true });
}
};

View File

@@ -0,0 +1,22 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@coze-arch/ts-config/tsconfig.node.json",
"compilerOptions": {
"types": ["node"],
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo"
},
"include": ["src"],
"references": [
{
"path": "../../../config/eslint-config/tsconfig.build.json"
},
{
"path": "../../../config/ts-config/tsconfig.build.json"
},
{
"path": "../../../config/vitest-config/tsconfig.build.json"
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"composite": true
},
"references": [
{
"path": "./tsconfig.build.json"
},
{
"path": "./tsconfig.misc.json"
}
],
"exclude": ["**/*"]
}

View File

@@ -0,0 +1,16 @@
{
"extends": "@coze-arch/ts-config/tsconfig.node.json",
"$schema": "https://json.schemastore.org/tsconfig",
"include": ["__tests__", "vitest.config.ts"],
"exclude": ["./dist"],
"references": [
{
"path": "./tsconfig.build.json"
}
],
"compilerOptions": {
"rootDir": "./",
"outDir": "./dist",
"types": ["vitest/globals", "node"]
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineConfig } from '@coze-arch/vitest-config';
export default defineConfig({
dirname: __dirname,
preset: 'node',
test: {
coverage: {
all: true,
},
},
});