feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
256
frontend/packages/arch/web-context/README.md
Normal file
256
frontend/packages/arch/web-context/README.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# @coze-arch/web-context
|
||||
|
||||
> Global Context for whole hot studio web app. You should keep using this package instead of call `window.xxx` directly
|
||||
|
||||
A centralized context management package that provides global state, event handling, and navigation utilities for Coze Studio web applications. This package helps you avoid direct window object manipulation and provides a structured way to handle global application state.
|
||||
|
||||
## Features
|
||||
|
||||
- **Global Variables Storage**: Type-safe proxy-based global variable management
|
||||
- **Event Bus System**: Centralized event handling with buffering capabilities
|
||||
- **Navigation Utilities**: Safe URL redirection with future validation support
|
||||
- **Application Constants**: Predefined enums for app sections and error codes
|
||||
- **Community Helpers**: Specialized utilities for community bot store scenarios
|
||||
|
||||
## Get Started
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Add to package.json
|
||||
"@coze-arch/web-context": "workspace:*"
|
||||
|
||||
# Then run
|
||||
rush update
|
||||
```
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```typescript
|
||||
import {
|
||||
globalVars,
|
||||
GlobalEventBus,
|
||||
redirect,
|
||||
BaseEnum,
|
||||
SpaceAppEnum,
|
||||
COZE_TOKEN_INSUFFICIENT_ERROR_CODE
|
||||
} from '@coze-arch/web-context';
|
||||
|
||||
// Use global variables
|
||||
globalVars.LAST_EXECUTE_ID = 'some-execution-id';
|
||||
console.log(globalVars.LAST_EXECUTE_ID);
|
||||
|
||||
// Create and use event bus
|
||||
const eventBus = GlobalEventBus.create('myEventBus');
|
||||
eventBus.on('dataChanged', (data) => {
|
||||
console.log('Data changed:', data);
|
||||
});
|
||||
eventBus.emit('dataChanged', { newValue: 'test' });
|
||||
|
||||
// Navigate safely
|
||||
redirect('/new-page');
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Global Variables
|
||||
|
||||
#### `globalVars`
|
||||
|
||||
A proxy-based storage for global variables that provides type-safe access to shared state.
|
||||
|
||||
```typescript
|
||||
import { globalVars } from '@coze-arch/web-context';
|
||||
|
||||
// Set a global variable
|
||||
globalVars.LAST_EXECUTE_ID = 'abc123';
|
||||
globalVars.MY_CUSTOM_VAR = { key: 'value' };
|
||||
|
||||
// Get a global variable
|
||||
const executeId = globalVars.LAST_EXECUTE_ID; // string
|
||||
const customVar = globalVars.MY_CUSTOM_VAR; // unknown
|
||||
|
||||
// Undefined for unset variables
|
||||
const unsetVar = globalVars.NONEXISTENT; // undefined
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Type-safe access through TypeScript interfaces
|
||||
- Automatic storage in internal Map
|
||||
- Returns `undefined` for unset properties
|
||||
- Support for any serializable value type
|
||||
|
||||
### Event Bus
|
||||
|
||||
#### `GlobalEventBus.create<T>(key: string)`
|
||||
|
||||
Creates or retrieves a singleton event bus instance for the given key.
|
||||
|
||||
```typescript
|
||||
import { GlobalEventBus } from '@coze-arch/web-context';
|
||||
|
||||
// Define event types
|
||||
interface MyEvents {
|
||||
userLogin: [userId: string];
|
||||
dataUpdate: [data: { id: string; value: unknown }];
|
||||
error: [error: Error];
|
||||
}
|
||||
|
||||
// Create event bus
|
||||
const eventBus = GlobalEventBus.create<MyEvents>('app');
|
||||
|
||||
// Subscribe to events
|
||||
eventBus.on('userLogin', (userId) => {
|
||||
console.log(`User ${userId} logged in`);
|
||||
});
|
||||
|
||||
// Emit events
|
||||
eventBus.emit('userLogin', 'user123');
|
||||
|
||||
// Unsubscribe
|
||||
const handler = (data) => console.log(data);
|
||||
eventBus.on('dataUpdate', handler);
|
||||
eventBus.off('dataUpdate', handler);
|
||||
```
|
||||
|
||||
#### Event Bus Control Methods
|
||||
|
||||
```typescript
|
||||
// Stop event processing (events will be buffered)
|
||||
eventBus.stop();
|
||||
|
||||
// Events emitted while stopped are buffered
|
||||
eventBus.emit('userLogin', 'user456'); // Buffered
|
||||
|
||||
// Start processing (flushes buffer)
|
||||
eventBus.start(); // Now 'user456' login event fires
|
||||
|
||||
// Clear buffered events
|
||||
eventBus.clear();
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Singleton pattern per key
|
||||
- Event buffering when stopped
|
||||
- Type-safe event definitions
|
||||
- Standard EventEmitter3 API
|
||||
|
||||
### Navigation
|
||||
|
||||
#### `redirect(href: string)`
|
||||
|
||||
Safely redirects to a new URL with potential for future validation logic.
|
||||
|
||||
```typescript
|
||||
import { redirect } from '@coze-arch/web-context';
|
||||
|
||||
// Redirect to external URL
|
||||
redirect('https://example.com');
|
||||
|
||||
// Redirect to internal route
|
||||
redirect('/dashboard');
|
||||
|
||||
// Redirect with query parameters
|
||||
redirect('/search?q=test');
|
||||
```
|
||||
|
||||
### Constants
|
||||
|
||||
#### Application Enums
|
||||
|
||||
```typescript
|
||||
import { BaseEnum, SpaceAppEnum } from '@coze-arch/web-context';
|
||||
|
||||
// Base application sections
|
||||
console.log(BaseEnum.Home); // 'home'
|
||||
console.log(BaseEnum.Store); // 'store'
|
||||
console.log(BaseEnum.Workspace); // 'space'
|
||||
|
||||
// Space-specific applications
|
||||
console.log(SpaceAppEnum.BOT); // 'bot'
|
||||
console.log(SpaceAppEnum.WORKFLOW); // 'workflow'
|
||||
console.log(SpaceAppEnum.PLUGIN); // 'plugin'
|
||||
```
|
||||
|
||||
#### Error Codes
|
||||
|
||||
```typescript
|
||||
import { COZE_TOKEN_INSUFFICIENT_ERROR_CODE } from '@coze-arch/web-context';
|
||||
|
||||
// Check for token insufficient errors
|
||||
const isTokenError = COZE_TOKEN_INSUFFICIENT_ERROR_CODE.includes(errorCode);
|
||||
if (isTokenError) {
|
||||
// Handle token insufficient error - stop streaming
|
||||
}
|
||||
```
|
||||
|
||||
#### Community Constants
|
||||
|
||||
```typescript
|
||||
import {
|
||||
defaultConversationKey,
|
||||
defaultConversationUniqId
|
||||
} from '@coze-arch/web-context';
|
||||
|
||||
// Use in community bot store scenarios
|
||||
const conversationId = defaultConversationKey; // -1
|
||||
const uniqueId = defaultConversationUniqId; // Unique string
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── const/ # Application constants
|
||||
│ ├── app.ts # Base and space app enums
|
||||
│ ├── community.ts # Community-specific constants
|
||||
│ └── custom.ts # Error codes and custom constants
|
||||
├── event-bus.ts # Global event bus implementation
|
||||
├── global-var.ts # Global variables storage
|
||||
├── location.ts # Navigation utilities
|
||||
└── index.ts # Main exports
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run tests
|
||||
rush test --to @coze-arch/web-context
|
||||
|
||||
# Run tests with coverage
|
||||
rush test:cov --to @coze-arch/web-context
|
||||
```
|
||||
|
||||
### Linting
|
||||
|
||||
```bash
|
||||
# Lint the package
|
||||
rush lint --to @coze-arch/web-context
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Runtime Dependencies
|
||||
|
||||
- **eventemitter3** (^5.0.1): High-performance event emitter for the event bus system
|
||||
- **lodash-es** (^4.17.21): Utility functions (used for `uniqueId` generation)
|
||||
|
||||
### Development Dependencies
|
||||
|
||||
- **@coze-arch/eslint-config**: Shared ESLint configuration
|
||||
- **@coze-arch/ts-config**: Shared TypeScript configuration
|
||||
- **vitest**: Testing framework with coverage support
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use typed event buses**: Always define event interfaces for better type safety
|
||||
2. **Avoid direct window access**: Use this package instead of direct `window.xxx` calls
|
||||
3. **Manage event subscriptions**: Remember to unsubscribe from events to prevent memory leaks
|
||||
4. **Use singleton pattern**: Create event buses with meaningful keys and reuse them
|
||||
5. **Handle buffered events**: Consider using `stop()`/`start()` pattern for controlling event flow
|
||||
|
||||
## License
|
||||
|
||||
Copyright © ByteDance Ltd. All rights reserved.
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 { BaseEnum, SpaceAppEnum } from '../../src/const/app';
|
||||
|
||||
describe('const/app', () => {
|
||||
describe('BaseEnum', () => {
|
||||
test('should have correct values for all enum members', () => {
|
||||
expect(BaseEnum.Home).toBe('home');
|
||||
expect(BaseEnum.Explore).toBe('explore');
|
||||
expect(BaseEnum.Store).toBe('store');
|
||||
expect(BaseEnum.Model).toBe('model');
|
||||
expect(BaseEnum.Space).toBe('space');
|
||||
expect(BaseEnum.Workflow).toBe('work_flow');
|
||||
expect(BaseEnum.Invite).toBe('invite');
|
||||
expect(BaseEnum.Token).toBe('token');
|
||||
expect(BaseEnum.Open).toBe('open');
|
||||
expect(BaseEnum.PluginMockSet).toBe('plugin_mock_set');
|
||||
expect(BaseEnum.Search).toBe('search');
|
||||
expect(BaseEnum.Premium).toBe('premium');
|
||||
expect(BaseEnum.User).toBe('user');
|
||||
});
|
||||
|
||||
test('should have the expected number of enum values', () => {
|
||||
const enumValues = Object.values(BaseEnum);
|
||||
expect(enumValues.length).toBe(14);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SpaceAppEnum', () => {
|
||||
test('should have correct values for all enum members', () => {
|
||||
expect(SpaceAppEnum.BOT).toBe('bot');
|
||||
expect(SpaceAppEnum.DEVELOP).toBe('develop');
|
||||
expect(SpaceAppEnum.LIBRARY).toBe('library');
|
||||
expect(SpaceAppEnum.PLUGIN).toBe('plugin');
|
||||
expect(SpaceAppEnum.WORKFLOW).toBe('workflow');
|
||||
expect(SpaceAppEnum.KNOWLEDGE).toBe('knowledge');
|
||||
expect(SpaceAppEnum.TEAM).toBe('team');
|
||||
expect(SpaceAppEnum.PERSONAL).toBe('personal');
|
||||
expect(SpaceAppEnum.WIDGET).toBe('widget');
|
||||
expect(SpaceAppEnum.EVALUATION).toBe('evaluation');
|
||||
expect(SpaceAppEnum.SOCIAL_SCENE).toBe('social-scene');
|
||||
expect(SpaceAppEnum.IMAGEFLOW).toBe('imageflow');
|
||||
});
|
||||
|
||||
test('should have the expected number of enum values', () => {
|
||||
const enumValues = Object.values(SpaceAppEnum);
|
||||
expect(enumValues.length).toBe(19);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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 { uniqueId } from 'lodash-es';
|
||||
|
||||
import {
|
||||
defaultConversationKey,
|
||||
defaultConversationUniqId,
|
||||
} from '../../src/const/community';
|
||||
|
||||
// 模拟 lodash-es 的 uniqueId 函数
|
||||
vi.mock('lodash-es', () => ({
|
||||
uniqueId: vi.fn().mockReturnValue('mocked-unique-id'),
|
||||
}));
|
||||
|
||||
describe('const/community', () => {
|
||||
test('defaultConversationKey should be -1', () => {
|
||||
expect(defaultConversationKey).toBe(-1);
|
||||
});
|
||||
|
||||
test('defaultConversationUniqId should be a unique ID generated by lodash-es', () => {
|
||||
// 验证 uniqueId 函数被调用
|
||||
expect(uniqueId).toHaveBeenCalled();
|
||||
// 验证 defaultConversationUniqId 的值是由模拟的 uniqueId 函数返回的
|
||||
expect(defaultConversationUniqId).toBe('mocked-unique-id');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 { COZE_TOKEN_INSUFFICIENT_ERROR_CODE } from '../../src/const/custom';
|
||||
|
||||
describe('const/custom', () => {
|
||||
describe('COZE_TOKEN_INSUFFICIENT_ERROR_CODE', () => {
|
||||
test('should be an array', () => {
|
||||
expect(Array.isArray(COZE_TOKEN_INSUFFICIENT_ERROR_CODE)).toBe(true);
|
||||
});
|
||||
|
||||
test('should contain exactly 2 error codes', () => {
|
||||
expect(COZE_TOKEN_INSUFFICIENT_ERROR_CODE.length).toBe(2);
|
||||
});
|
||||
|
||||
test('should contain the BOT error code', () => {
|
||||
expect(COZE_TOKEN_INSUFFICIENT_ERROR_CODE).toContain('702082020');
|
||||
});
|
||||
|
||||
test('should contain the WORKFLOW error code', () => {
|
||||
expect(COZE_TOKEN_INSUFFICIENT_ERROR_CODE).toContain('702095072');
|
||||
});
|
||||
|
||||
// 移除失败的测试用例
|
||||
// 原因:在 JavaScript 中,即使使用 const 声明数组,其内容仍然是可变的
|
||||
// 只有数组引用是不可变的,而不是数组内容
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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 { GlobalEventBus } from '../src/event-bus';
|
||||
|
||||
vi.mock('eventemitter3', () => ({
|
||||
default: class MockEventEmitter {
|
||||
eventsMap: Map<string, (() => void)[]> = new Map();
|
||||
emit(e: string) {
|
||||
const cbs = this.eventsMap.get(e) ?? [];
|
||||
cbs.forEach(cb => {
|
||||
cb();
|
||||
});
|
||||
}
|
||||
on(e: string, cb: () => void) {
|
||||
const cbs = this.eventsMap.get(e) ?? [];
|
||||
if (!cbs.find(cb)) {
|
||||
cbs.push(cb);
|
||||
this.eventsMap.set(e, cbs);
|
||||
}
|
||||
}
|
||||
off(e: string, cb: () => void) {
|
||||
const cbs = this.eventsMap.get(e) ?? [];
|
||||
const idx = cbs.findIndex(c => c === cb);
|
||||
if (idx !== -1) {
|
||||
cbs.splice(idx, 1);
|
||||
this.eventsMap.set(e, cbs);
|
||||
}
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
describe('event-bus', () => {
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test('A single key corresponds to a unique instance', () => {
|
||||
const testGlobalEventBus1 = GlobalEventBus.create('test');
|
||||
const testGlobalEventBus2 = GlobalEventBus.create('test');
|
||||
expect(testGlobalEventBus1).equal(testGlobalEventBus2);
|
||||
});
|
||||
|
||||
test('Should not trigger events if not started or calling `clear`', () => {
|
||||
const testGlobalEventBus = GlobalEventBus.create('test2');
|
||||
testGlobalEventBus.stop();
|
||||
const testCb = vi.fn();
|
||||
testGlobalEventBus.on('test_event', testCb);
|
||||
testGlobalEventBus.emit('test_event');
|
||||
expect(testCb).not.toHaveBeenCalled();
|
||||
|
||||
// Clear the buffer
|
||||
testGlobalEventBus.clear();
|
||||
testGlobalEventBus.start();
|
||||
expect(testCb).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Should trigger events if started', () => {
|
||||
const testGlobalEventBus = GlobalEventBus.create('test3');
|
||||
const testCb1 = vi.fn();
|
||||
testGlobalEventBus.on('test_event', testCb1);
|
||||
testGlobalEventBus.emit('test_event');
|
||||
expect(testCb1).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Should trigger events if started', () => {
|
||||
const testGlobalEventBus = GlobalEventBus.create('test4');
|
||||
const testCb1 = vi.fn();
|
||||
testGlobalEventBus.on('test_event', testCb1);
|
||||
testGlobalEventBus.off('test_event', testCb1);
|
||||
testGlobalEventBus.emit('test_event');
|
||||
expect(testCb1).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 { globalVars } from '../src/global-var';
|
||||
|
||||
describe('global-var', () => {
|
||||
test('should be able to set and get a property', () => {
|
||||
const testValue = 'Hello, World';
|
||||
// 设置一个属性
|
||||
globalVars.TEST_PROP = testValue;
|
||||
|
||||
// 确保我们能获取到相同的属性
|
||||
expect(globalVars.TEST_PROP).toBe(testValue);
|
||||
});
|
||||
|
||||
test('should return undefined for unset property', () => {
|
||||
expect(globalVars.UNSET_PROP).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should allow to overwrite an existing property', () => {
|
||||
const firstValue = 'First Value';
|
||||
const secondValue = 'Second Value';
|
||||
|
||||
// 先设置一个属性
|
||||
globalVars.OVERWRITE_PROP = firstValue;
|
||||
expect(globalVars.OVERWRITE_PROP).toBe(firstValue);
|
||||
|
||||
// 再覆盖这个属性
|
||||
globalVars.OVERWRITE_PROP = secondValue;
|
||||
expect(globalVars.OVERWRITE_PROP).toBe(secondValue);
|
||||
});
|
||||
});
|
||||
74
frontend/packages/arch/web-context/__tests__/index.test.ts
Normal file
74
frontend/packages/arch/web-context/__tests__/index.test.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 { redirect as originalRedirect } from '../src/location';
|
||||
import {
|
||||
redirect,
|
||||
GlobalEventBus,
|
||||
globalVars,
|
||||
COZE_TOKEN_INSUFFICIENT_ERROR_CODE,
|
||||
BaseEnum,
|
||||
SpaceAppEnum,
|
||||
defaultConversationKey,
|
||||
defaultConversationUniqId,
|
||||
} from '../src/index';
|
||||
import { globalVars as originalGlobalVars } from '../src/global-var';
|
||||
import { GlobalEventBus as originalGlobalEventBus } from '../src/event-bus';
|
||||
import { COZE_TOKEN_INSUFFICIENT_ERROR_CODE as originalCOZE_TOKEN_INSUFFICIENT_ERROR_CODE } from '../src/const/custom';
|
||||
import {
|
||||
defaultConversationKey as originalDefaultConversationKey,
|
||||
defaultConversationUniqId as originalDefaultConversationUniqId,
|
||||
} from '../src/const/community';
|
||||
import {
|
||||
BaseEnum as originalBaseEnum,
|
||||
SpaceAppEnum as originalSpaceAppEnum,
|
||||
} from '../src/const/app';
|
||||
|
||||
describe('index', () => {
|
||||
test('should export redirect from location', () => {
|
||||
expect(redirect).toBe(originalRedirect);
|
||||
});
|
||||
|
||||
test('should export GlobalEventBus from event-bus', () => {
|
||||
expect(GlobalEventBus).toBe(originalGlobalEventBus);
|
||||
});
|
||||
|
||||
test('should export globalVars from global-var', () => {
|
||||
expect(globalVars).toBe(originalGlobalVars);
|
||||
});
|
||||
|
||||
test('should export COZE_TOKEN_INSUFFICIENT_ERROR_CODE from const/custom', () => {
|
||||
expect(COZE_TOKEN_INSUFFICIENT_ERROR_CODE).toBe(
|
||||
originalCOZE_TOKEN_INSUFFICIENT_ERROR_CODE,
|
||||
);
|
||||
});
|
||||
|
||||
test('should export BaseEnum from const/app', () => {
|
||||
expect(BaseEnum).toBe(originalBaseEnum);
|
||||
});
|
||||
|
||||
test('should export SpaceAppEnum from const/app', () => {
|
||||
expect(SpaceAppEnum).toBe(originalSpaceAppEnum);
|
||||
});
|
||||
|
||||
test('should export defaultConversationKey from const/community', () => {
|
||||
expect(defaultConversationKey).toBe(originalDefaultConversationKey);
|
||||
});
|
||||
|
||||
test('should export defaultConversationUniqId from const/community', () => {
|
||||
expect(defaultConversationUniqId).toBe(originalDefaultConversationUniqId);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 { redirect } from '../src/location';
|
||||
|
||||
const viHrefSetter = vi.fn();
|
||||
vi.stubGlobal('location', {
|
||||
_href: '',
|
||||
set href(v: string) {
|
||||
viHrefSetter(v);
|
||||
(this as any)._href = v;
|
||||
},
|
||||
get href() {
|
||||
return (this as any)._href;
|
||||
},
|
||||
});
|
||||
|
||||
describe('location', () => {
|
||||
test('redirect', () => {
|
||||
redirect('test');
|
||||
expect(viHrefSetter).toHaveBeenCalledWith('test');
|
||||
expect(location.href).equal('test');
|
||||
});
|
||||
});
|
||||
12
frontend/packages/arch/web-context/config/rush-project.json
Normal file
12
frontend/packages/arch/web-context/config/rush-project.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"operationSettings": [
|
||||
{
|
||||
"operationName": "test:cov",
|
||||
"outputFolderNames": ["coverage"]
|
||||
},
|
||||
{
|
||||
"operationName": "ts-check",
|
||||
"outputFolderNames": ["./dist"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"codecov": {
|
||||
"incrementCoverage": 100
|
||||
}
|
||||
}
|
||||
7
frontend/packages/arch/web-context/eslint.config.js
Normal file
7
frontend/packages/arch/web-context/eslint.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const { defineConfig } = require('@coze-arch/eslint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
packageRoot: __dirname,
|
||||
preset: 'web',
|
||||
rules: {},
|
||||
});
|
||||
26
frontend/packages/arch/web-context/package.json
Normal file
26
frontend/packages/arch/web-context/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@coze-arch/web-context",
|
||||
"version": "0.0.1",
|
||||
"description": "Global Context for whole hot studio web app. You should keep using this package instead of call `window.xxx` directly",
|
||||
"author": "fanwenjie.fe@bytedance.com",
|
||||
"main": "./src/index.ts",
|
||||
"scripts": {
|
||||
"build": "exit 0",
|
||||
"lint": "eslint ./ --cache",
|
||||
"test": "NODE_OPTIONS='--max_old_space_size=2048' NODE_ENV=test vitest --run --passWithNoTests",
|
||||
"test:cov": "npm run test -- --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"eventemitter3": "^5.0.1",
|
||||
"lodash-es": "^4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@coze-arch/eslint-config": "workspace:*",
|
||||
"@coze-arch/ts-config": "workspace:*",
|
||||
"@vitest/coverage-v8": "~3.0.5",
|
||||
"sucrase": "^3.32.0",
|
||||
"tsconfig-paths": "4.1.0",
|
||||
"vitest": "~3.0.5"
|
||||
}
|
||||
}
|
||||
|
||||
55
frontend/packages/arch/web-context/src/const/app.ts
Normal file
55
frontend/packages/arch/web-context/src/const/app.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// extract from apps/bot/src/constant/app.ts
|
||||
export enum BaseEnum {
|
||||
Home = 'home', //首页
|
||||
Explore = 'explore', //探索
|
||||
Store = 'store', // 商店
|
||||
Model = 'model', //模型竞技场
|
||||
Space = 'space', //空间内
|
||||
Workflow = 'work_flow', //兼容 workflow 编辑页
|
||||
Invite = 'invite', // 邀请链接
|
||||
Token = 'token', // token
|
||||
Open = 'open', // 开放平台
|
||||
PluginMockSet = 'plugin_mock_set',
|
||||
Search = 'search', // 搜索
|
||||
Premium = 'premium', // 订阅服务
|
||||
User = 'user', // 个人主页
|
||||
Enterprise = 'enterprise', // 企业管理
|
||||
}
|
||||
|
||||
export enum SpaceAppEnum {
|
||||
BOT = 'bot',
|
||||
DOUYIN_BOT = 'douyin-bot',
|
||||
DEVELOP = 'develop',
|
||||
LIBRARY = 'library',
|
||||
MODEL = 'model',
|
||||
PLUGIN = 'plugin',
|
||||
OCEAN_PROJECT = 'ocean-project',
|
||||
WORKFLOW = 'workflow',
|
||||
KNOWLEDGE = 'knowledge',
|
||||
TEAM = 'team',
|
||||
PERSONAL = 'personal',
|
||||
WIDGET = 'widget',
|
||||
EVALUATION = 'evaluation',
|
||||
EVALUATE = 'evaluate',
|
||||
SOCIAL_SCENE = 'social-scene',
|
||||
IMAGEFLOW = 'imageflow',
|
||||
DATABASE = 'database',
|
||||
PROJECT_IDE = 'project-ide',
|
||||
PUBLISH = 'publish',
|
||||
}
|
||||
20
frontend/packages/arch/web-context/src/const/community.ts
Normal file
20
frontend/packages/arch/web-context/src/const/community.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 { uniqueId } from 'lodash-es';
|
||||
|
||||
export const defaultConversationKey = -1;
|
||||
export const defaultConversationUniqId = uniqueId();
|
||||
30
frontend/packages/arch/web-context/src/const/custom.ts
Normal file
30
frontend/packages/arch/web-context/src/const/custom.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// extract from apps/bot/src/constant/custom.ts
|
||||
|
||||
const enum CozeTokenInsufficientErrorCode {
|
||||
WORKFLOW = '702095072',
|
||||
BOT = '702082020',
|
||||
}
|
||||
/**
|
||||
* Coze Token不足错误码
|
||||
* 当出现该错误码的时候,需要额外进行停止拉流操作
|
||||
*/
|
||||
export const COZE_TOKEN_INSUFFICIENT_ERROR_CODE = [
|
||||
CozeTokenInsufficientErrorCode.BOT,
|
||||
CozeTokenInsufficientErrorCode.WORKFLOW,
|
||||
];
|
||||
113
frontend/packages/arch/web-context/src/event-bus.ts
Normal file
113
frontend/packages/arch/web-context/src/event-bus.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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 EventEmitter from 'eventemitter3';
|
||||
|
||||
interface EventWithData<T extends EventEmitter.ValidEventTypes> {
|
||||
event: EventEmitter.EventNames<T>;
|
||||
args: Parameters<EventEmitter.EventListener<T, EventEmitter.EventNames<T>>>;
|
||||
}
|
||||
|
||||
type ValidEventTypes = EventEmitter.ValidEventTypes;
|
||||
|
||||
export class GlobalEventBus<T extends ValidEventTypes> {
|
||||
private eventEmitter = new EventEmitter<T>();
|
||||
|
||||
private started = true;
|
||||
|
||||
private buffer: EventWithData<T>[] = [];
|
||||
|
||||
private static instances = new Map<string, GlobalEventBus<ValidEventTypes>>();
|
||||
|
||||
static create<T extends ValidEventTypes>(key: string): GlobalEventBus<T> {
|
||||
if (GlobalEventBus.instances.has(key)) {
|
||||
return GlobalEventBus.instances.get(key) as unknown as GlobalEventBus<T>;
|
||||
}
|
||||
const instance = new GlobalEventBus<T>();
|
||||
GlobalEventBus.instances.set(
|
||||
key,
|
||||
instance as unknown as GlobalEventBus<ValidEventTypes>,
|
||||
);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
* @param event 事件名称
|
||||
* @param args 参数
|
||||
*/
|
||||
emit<P extends EventEmitter.EventNames<T>>(
|
||||
event: P,
|
||||
...args: Parameters<EventEmitter.EventListener<T, P>>
|
||||
) {
|
||||
if (!this.started) {
|
||||
this.buffer.push({
|
||||
event,
|
||||
args,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.eventEmitter.emit(event, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅事件
|
||||
* @param event 事件名称
|
||||
* @param fn 事件回调
|
||||
*/
|
||||
on<P extends EventEmitter.EventNames<T>>(
|
||||
event: P,
|
||||
fn: EventEmitter.EventListener<T, P>,
|
||||
) {
|
||||
this.eventEmitter.on(event, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订阅事件
|
||||
* @param event 事件名称
|
||||
* @param fn 事件回调
|
||||
*/
|
||||
off<P extends EventEmitter.EventNames<T>>(
|
||||
event: P,
|
||||
fn: EventEmitter.EventListener<T, P>,
|
||||
) {
|
||||
this.eventEmitter.off(event, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启缓存事件订阅器,开启时会将关闭时收到的事件对应的回调按顺序逐一触发
|
||||
*/
|
||||
start() {
|
||||
this.started = true;
|
||||
for (const { event, args } of this.buffer) {
|
||||
this.emit(event, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭缓存事件订阅器,在关闭时收到的事件会被缓存并延迟到下次开启时触发
|
||||
*/
|
||||
stop() {
|
||||
this.started = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存事件订阅器缓存的事件,使得在重新开启(start)时不会触发在关闭(stop)时收到的事件对应的回调
|
||||
*/
|
||||
clear() {
|
||||
this.buffer = [];
|
||||
}
|
||||
}
|
||||
50
frontend/packages/arch/web-context/src/global-var.ts
Normal file
50
frontend/packages/arch/web-context/src/global-var.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
// 需要全局共享的值,可以提前到这里注册类型
|
||||
interface GlobalVars {
|
||||
/**
|
||||
* Last Execute ID that extracts from apps/bot/src/store/bot-detail/utils/execute-draft-bot-request-id.ts
|
||||
*
|
||||
* 用于 debug 记录对话接口的 log id,不需要响应式,所以直接存 const 里了
|
||||
*/
|
||||
LAST_EXECUTE_ID: string;
|
||||
[key: string | symbol]: unknown;
|
||||
}
|
||||
|
||||
const createGlobalVarsStorage = () => {
|
||||
const storage = new Map();
|
||||
|
||||
return new Proxy<GlobalVars>(Object.create(null), {
|
||||
get<T extends keyof GlobalVars>(_: unknown, prop: T): GlobalVars[T] {
|
||||
if (storage.has(prop)) {
|
||||
return storage.get(prop as string);
|
||||
}
|
||||
// add more logic for dev mode
|
||||
return undefined;
|
||||
},
|
||||
set<T extends keyof GlobalVars>(_: unknown, prop: T, value: GlobalVars[T]) {
|
||||
storage.set(prop, value);
|
||||
return true;
|
||||
},
|
||||
}) as GlobalVars;
|
||||
};
|
||||
|
||||
/**
|
||||
* 通用全局变量
|
||||
*/
|
||||
export const globalVars = createGlobalVarsStorage();
|
||||
27
frontend/packages/arch/web-context/src/index.ts
Normal file
27
frontend/packages/arch/web-context/src/index.ts
Normal 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.
|
||||
*/
|
||||
|
||||
export { redirect } from './location';
|
||||
export { GlobalEventBus } from './event-bus';
|
||||
export { globalVars } from './global-var';
|
||||
export { COZE_TOKEN_INSUFFICIENT_ERROR_CODE } from './const/custom';
|
||||
export { BaseEnum, SpaceAppEnum } from './const/app';
|
||||
|
||||
// community bot store detail业务场景专用key值
|
||||
export {
|
||||
defaultConversationKey,
|
||||
defaultConversationUniqId,
|
||||
} from './const/community';
|
||||
25
frontend/packages/arch/web-context/src/location.ts
Normal file
25
frontend/packages/arch/web-context/src/location.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// The redirect function is designed to redirect the user to a new URL.
|
||||
// It takes a single argument href which is a string representing the URL.
|
||||
// Upon invocation, it sets location.href to the provided URL, thereby navigating to the webpage.
|
||||
// While no validation logic is currently implemented prior to redirection,
|
||||
// there is the potential for such checks to be included in the future as per your requirements.
|
||||
export const redirect = (href: string) => {
|
||||
// 这里后续可以补充一些校验逻辑
|
||||
location.href = href;
|
||||
};
|
||||
21
frontend/packages/arch/web-context/tsconfig.build.json
Normal file
21
frontend/packages/arch/web-context/tsconfig.build.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "@coze-arch/ts-config/tsconfig.node.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
||||
"useUnknownInCatchVariables": false,
|
||||
"types": ["vitest/globals"],
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo"
|
||||
},
|
||||
"include": ["./src"],
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../config/eslint-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../config/ts-config/tsconfig.build.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
frontend/packages/arch/web-context/tsconfig.json
Normal file
15
frontend/packages/arch/web-context/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"composite": true
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.misc.json"
|
||||
}
|
||||
],
|
||||
"exclude": ["**/*"]
|
||||
}
|
||||
18
frontend/packages/arch/web-context/tsconfig.misc.json
Normal file
18
frontend/packages/arch/web-context/tsconfig.misc.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"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": {
|
||||
"jsx": "preserve",
|
||||
"rootDir": "./",
|
||||
"outDir": "./dist",
|
||||
"useUnknownInCatchVariables": false,
|
||||
"types": ["vitest/globals"]
|
||||
}
|
||||
}
|
||||
32
frontend/packages/arch/web-context/vitest.config.ts
Normal file
32
frontend/packages/arch/web-context/vitest.config.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// FIXME: Unable to resolve path to module 'vitest/config'
|
||||
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
mockReset: false,
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['cobertura', 'text', 'html', 'clover', 'json'],
|
||||
exclude: ['src/const', '.eslintrc.js', 'src/index.ts'],
|
||||
include: ['src/**/*.ts', 'src/**/*.tsx'],
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user