feat: upgrade plugin-users
This commit is contained in:
parent
bf2840b089
commit
f09991d932
@ -36,7 +36,7 @@ export class BelongsToField extends RelationField {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// inverse relation
|
// inverse relation
|
||||||
this.TargetModel.hasMany(collection.model);
|
// this.TargetModel.hasMany(collection.model);
|
||||||
|
|
||||||
// 建立关系之后从 pending 列表中删除
|
// 建立关系之后从 pending 列表中删除
|
||||||
database.removePendingField(this);
|
database.removePendingField(this);
|
||||||
@ -76,4 +76,5 @@ export class BelongsToField extends RelationField {
|
|||||||
|
|
||||||
export interface BelongsToFieldOptions extends BaseRelationFieldOptions, SequelizeBelongsToOptions {
|
export interface BelongsToFieldOptions extends BaseRelationFieldOptions, SequelizeBelongsToOptions {
|
||||||
type: 'belongsTo';
|
type: 'belongsTo';
|
||||||
|
target?: string;
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ export class HasManyField extends RelationField {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// inverse relation
|
// inverse relation
|
||||||
this.TargetModel.belongsTo(collection.model);
|
// this.TargetModel.belongsTo(collection.model);
|
||||||
|
|
||||||
// 建立关系之后从 pending 列表中删除
|
// 建立关系之后从 pending 列表中删除
|
||||||
database.removePendingField(this);
|
database.removePendingField(this);
|
||||||
|
@ -283,7 +283,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
|||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
@transaction()
|
@transaction()
|
||||||
async create(options: CreateOptions): Promise<Model> {
|
async create<M extends Model>(options: CreateOptions): Promise<M> {
|
||||||
const transaction = await this.getTransaction(options);
|
const transaction = await this.getTransaction(options);
|
||||||
|
|
||||||
const guard = UpdateGuard.fromOptions(this.model, options);
|
const guard = UpdateGuard.fromOptions(this.model, options);
|
||||||
|
1
packages/plugin-notifications/src/index.ts
Normal file
1
packages/plugin-notifications/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './server';
|
1
packages/plugin-system-settings/src/index.ts
Normal file
1
packages/plugin-system-settings/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './server';
|
@ -6,7 +6,7 @@ export default {
|
|||||||
async load() {
|
async load() {
|
||||||
const database = this.app.db;
|
const database = this.app.db;
|
||||||
|
|
||||||
database.import({
|
await database.import({
|
||||||
directory: path.resolve(__dirname, 'collections'),
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
7
packages/plugin-users/.npmignore
Normal file
7
packages/plugin-users/.npmignore
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
node_modules
|
||||||
|
*.log
|
||||||
|
docs
|
||||||
|
__tests__
|
||||||
|
tsconfig.json
|
||||||
|
src
|
||||||
|
.fatherrc.ts
|
18
packages/plugin-users/package.json
Normal file
18
packages/plugin-users/package.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "@nocobase/plugin-users",
|
||||||
|
"version": "0.6.0-alpha.0",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"build": "rimraf -rf lib esm dist && npm run build:cjs && npm run build:esm",
|
||||||
|
"build:cjs": "tsc --project tsconfig.build.json",
|
||||||
|
"build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"crypto-random-string": "^3.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nocobase/test": "^0.6.0-alpha.0"
|
||||||
|
},
|
||||||
|
"gitHead": "e7df1f93c4e23b9a666d99ee7372c02bdaec97c4"
|
||||||
|
}
|
84
packages/plugin-users/src/__tests__/fields.test.ts
Normal file
84
packages/plugin-users/src/__tests__/fields.test.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import Database from '@nocobase/database';
|
||||||
|
import { mockServer, MockServer } from '@nocobase/test';
|
||||||
|
|
||||||
|
describe('createdBy/updatedBy', () => {
|
||||||
|
let api: MockServer;
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
api = mockServer();
|
||||||
|
api.plugin(require('../server').default);
|
||||||
|
await api.load();
|
||||||
|
db = api.db;
|
||||||
|
await db.sync();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('collection definition', () => {
|
||||||
|
it('case 1', async () => {
|
||||||
|
const Post = db.collection({
|
||||||
|
name: 'posts',
|
||||||
|
createdBy: true,
|
||||||
|
updatedBy: true,
|
||||||
|
});
|
||||||
|
expect(Post.hasField('createdBy')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('case 2', async () => {
|
||||||
|
const Post = db.collection({
|
||||||
|
name: 'posts',
|
||||||
|
createdBy: true,
|
||||||
|
updatedBy: true,
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
const currentUser = await db.getCollection('users').model.create();
|
||||||
|
const p1 = await Post.repository.create({
|
||||||
|
context: {
|
||||||
|
state: {
|
||||||
|
currentUser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const p2 = await Post.repository.findOne({
|
||||||
|
appends: ['createdBy', 'updatedBy'],
|
||||||
|
});
|
||||||
|
expect(p2.get('createdBy')).toMatchObject(currentUser.toJSON());
|
||||||
|
expect(p2.get('updatedBy')).toMatchObject(currentUser.toJSON());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('case 3', async () => {
|
||||||
|
const Post = db.collection({
|
||||||
|
name: 'posts',
|
||||||
|
createdBy: true,
|
||||||
|
updatedBy: true,
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
const user1 = await db.getCollection('users').model.create();
|
||||||
|
const user2 = await db.getCollection('users').model.create();
|
||||||
|
const p1 = await Post.repository.create<any>({
|
||||||
|
context: {
|
||||||
|
state: {
|
||||||
|
currentUser: user1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await Post.repository.update({
|
||||||
|
values: {},
|
||||||
|
filterByPk: p1.id,
|
||||||
|
context: {
|
||||||
|
state: {
|
||||||
|
currentUser: user2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const p2 = await Post.repository.findOne({
|
||||||
|
appends: ['createdBy', 'updatedBy'],
|
||||||
|
});
|
||||||
|
expect(p2.get('createdBy')).toMatchObject(user1.toJSON());
|
||||||
|
expect(p2.get('updatedBy')).toMatchObject(user2.toJSON());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
156
packages/plugin-users/src/actions/users.ts
Normal file
156
packages/plugin-users/src/actions/users.ts
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import { Context, Next } from '@nocobase/actions';
|
||||||
|
import { PasswordField } from '@nocobase/database';
|
||||||
|
import cryptoRandomString from 'crypto-random-string';
|
||||||
|
|
||||||
|
export async function check(ctx: Context, next: Next) {
|
||||||
|
if (ctx.state.currentUser) {
|
||||||
|
const user = ctx.state.currentUser.toJSON();
|
||||||
|
ctx.body = user;
|
||||||
|
await next();
|
||||||
|
} else {
|
||||||
|
ctx.throw(401, 'Unauthorized');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function signin(ctx: Context, next: Next) {
|
||||||
|
const { uniqueField = 'email', values } = ctx.action.params;
|
||||||
|
console.log('signin.values', values);
|
||||||
|
if (!values[uniqueField]) {
|
||||||
|
ctx.throw(401, '请填写邮箱账号');
|
||||||
|
}
|
||||||
|
const User = ctx.db.getCollection('users');
|
||||||
|
const user = await User.model.scope('withPassword').findOne<any>({
|
||||||
|
where: {
|
||||||
|
[uniqueField]: values[uniqueField],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!user) {
|
||||||
|
ctx.throw(401, '邮箱账号未注册');
|
||||||
|
}
|
||||||
|
const pwd = User.getField<PasswordField>('password');
|
||||||
|
const isValid = await pwd.verify(values.password, user.password);
|
||||||
|
if (!isValid) {
|
||||||
|
ctx.throw(401, '密码错误,请您重新输入');
|
||||||
|
}
|
||||||
|
if (!user.token) {
|
||||||
|
user.token = cryptoRandomString({ length: 20 });
|
||||||
|
await user.save();
|
||||||
|
}
|
||||||
|
ctx.body = user.toJSON();
|
||||||
|
delete ctx.body.password;
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function signout(ctx: Context, next: Next) {
|
||||||
|
ctx.body = {};
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function signup(ctx: Context, next: Next) {
|
||||||
|
const User = ctx.db.getCollection('users');
|
||||||
|
const { values } = ctx.action.params;
|
||||||
|
try {
|
||||||
|
const user = await User.model.create(values);
|
||||||
|
ctx.body = user;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.errors) {
|
||||||
|
ctx.throw(401, error.errors.map((data) => data.message).join(', '));
|
||||||
|
} else {
|
||||||
|
ctx.throw(401, '注册失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function lostpassword(ctx: Context, next: Next) {
|
||||||
|
const {
|
||||||
|
values: { email },
|
||||||
|
} = ctx.action.params;
|
||||||
|
if (!email) {
|
||||||
|
ctx.throw(401, '请填写邮箱账号');
|
||||||
|
}
|
||||||
|
const User = ctx.db.getCollection('users');
|
||||||
|
const user = await User.model.findOne<any>({
|
||||||
|
where: {
|
||||||
|
email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!user) {
|
||||||
|
ctx.throw(401, '邮箱账号未注册');
|
||||||
|
}
|
||||||
|
user.resetToken = cryptoRandomString({ length: 20 });
|
||||||
|
await user.save();
|
||||||
|
ctx.body = user;
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resetpassword(ctx: Context, next: Next) {
|
||||||
|
const {
|
||||||
|
values: { email, password, resetToken },
|
||||||
|
} = ctx.action.params;
|
||||||
|
const User = ctx.db.getCollection('users');
|
||||||
|
const user = await User.model.findOne<any>({
|
||||||
|
where: {
|
||||||
|
email,
|
||||||
|
resetToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!user) {
|
||||||
|
ctx.throw(401, 'Unauthorized');
|
||||||
|
}
|
||||||
|
user.token = null;
|
||||||
|
user.resetToken = null;
|
||||||
|
user.password = password;
|
||||||
|
await user.save();
|
||||||
|
ctx.body = user;
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserByResetToken(ctx: Context, next: Next) {
|
||||||
|
const { token } = ctx.action.params;
|
||||||
|
const User = ctx.db.getCollection('users');
|
||||||
|
const user = await User.model.findOne({
|
||||||
|
where: {
|
||||||
|
resetToken: token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!user) {
|
||||||
|
ctx.throw(401, 'Unauthorized');
|
||||||
|
}
|
||||||
|
ctx.body = user;
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateProfile(ctx: Context, next: Next) {
|
||||||
|
const { values } = ctx.action.params;
|
||||||
|
if (!ctx.state.currentUser) {
|
||||||
|
ctx.throw(401, 'Unauthorized');
|
||||||
|
}
|
||||||
|
await ctx.state.currentUser.update(values);
|
||||||
|
ctx.body = ctx.state.currentUser;
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function changePassword(ctx: Context, next: Next) {
|
||||||
|
const {
|
||||||
|
values: { oldPassword, newPassword },
|
||||||
|
} = ctx.action.params;
|
||||||
|
if (!ctx.state.currentUser) {
|
||||||
|
ctx.throw(401, 'Unauthorized');
|
||||||
|
}
|
||||||
|
const User = ctx.db.getCollection('users');
|
||||||
|
const user = await User.model.scope('withPassword').findOne<any>({
|
||||||
|
where: {
|
||||||
|
email: ctx.state.currentUser.email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const pwd = User.getField<PasswordField>('password');
|
||||||
|
const isValid = await pwd.verify(oldPassword, user.password);
|
||||||
|
if (!isValid) {
|
||||||
|
ctx.throw(401, '密码错误,请您重新输入');
|
||||||
|
}
|
||||||
|
user.password = newPassword;
|
||||||
|
user.save();
|
||||||
|
ctx.body = ctx.state.currentUser.toJSON();
|
||||||
|
await next();
|
||||||
|
}
|
90
packages/plugin-users/src/collections/users.ts
Normal file
90
packages/plugin-users/src/collections/users.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { CollectionOptions } from '@nocobase/database';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'users',
|
||||||
|
title: '{{t("Users")}}',
|
||||||
|
sortable: 'sort',
|
||||||
|
scopes: {
|
||||||
|
withPassword: {
|
||||||
|
attributes: { include: ['password'] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultScope: {
|
||||||
|
attributes: { exclude: ['password'] },
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
interface: 'string',
|
||||||
|
type: 'string',
|
||||||
|
name: 'nickname',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Nickname")}}',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
interface: 'email',
|
||||||
|
type: 'string',
|
||||||
|
name: 'email',
|
||||||
|
unique: true,
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Email")}}',
|
||||||
|
'x-component': 'Input',
|
||||||
|
require: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
interface: 'password',
|
||||||
|
type: 'password',
|
||||||
|
name: 'password',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Password")}}',
|
||||||
|
'x-component': 'Password',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
interface: 'linkTo',
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'roles',
|
||||||
|
target: 'roles',
|
||||||
|
foreignKey: 'user_id',
|
||||||
|
otherKey: 'role_name',
|
||||||
|
sourceKey: 'id',
|
||||||
|
targetKey: 'name',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'array',
|
||||||
|
title: '{{t("Roles")}}',
|
||||||
|
'x-component': 'Select.Drawer',
|
||||||
|
'x-component-props': {
|
||||||
|
multiple: true,
|
||||||
|
fieldNames: {
|
||||||
|
label: 'title',
|
||||||
|
value: 'name',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-designable-bar': 'Select.Drawer.DesignableBar',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
interface: 'select',
|
||||||
|
type: 'string',
|
||||||
|
name: 'appLang',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'token',
|
||||||
|
unique: true,
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'resetToken',
|
||||||
|
unique: true,
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as CollectionOptions;
|
1
packages/plugin-users/src/index.ts
Normal file
1
packages/plugin-users/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './server';
|
10
packages/plugin-users/src/middlewares/check.ts
Normal file
10
packages/plugin-users/src/middlewares/check.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// TODO(usage): 拦截用户的处理暂时作为一个中间件导出,应用需要的时候可以直接使用这个中间件
|
||||||
|
export function check(options) {
|
||||||
|
return async function check(ctx, next) {
|
||||||
|
const { currentUser } = ctx.state;
|
||||||
|
if (!currentUser) {
|
||||||
|
return ctx.throw(401, 'Unauthorized');
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
};
|
||||||
|
}
|
2
packages/plugin-users/src/middlewares/index.ts
Normal file
2
packages/plugin-users/src/middlewares/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { check } from './check';
|
||||||
|
export { parseToken } from './parseToken';
|
23
packages/plugin-users/src/middlewares/parseToken.ts
Normal file
23
packages/plugin-users/src/middlewares/parseToken.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Context, Next } from '@nocobase/actions';
|
||||||
|
|
||||||
|
// TODO(feature): 表名应在 options 中配置
|
||||||
|
// 中间件默认只解决解析 token 和附加对应 user 的工作,不解决是否提前报 401 退出。
|
||||||
|
|
||||||
|
// 因为是否提供匿名访问资源是应用决定的,不是使用插件就一定不能匿名访问。
|
||||||
|
export function parseToken(options) {
|
||||||
|
return async function parseToken(ctx: Context, next: Next) {
|
||||||
|
const token = ctx.get('Authorization').replace(/^Bearer\s+/gi, '');
|
||||||
|
const User = ctx.db.getCollection('users');
|
||||||
|
const user = await User.model.findOne({
|
||||||
|
where: {
|
||||||
|
token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
ctx.state.currentUser = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
|
};
|
||||||
|
}
|
63
packages/plugin-users/src/server.ts
Normal file
63
packages/plugin-users/src/server.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import * as actions from './actions/users';
|
||||||
|
import * as middlewares from './middlewares';
|
||||||
|
import { Collection } from '@nocobase/database';
|
||||||
|
import { PluginOptions } from '@nocobase/server';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'users',
|
||||||
|
async load() {
|
||||||
|
const database = this.app.db;
|
||||||
|
const resourcer = this.app.resourcer;
|
||||||
|
|
||||||
|
this.app.on('db.init', async () => {
|
||||||
|
const User = database.getCollection('users');
|
||||||
|
await User.model.create({
|
||||||
|
nickname: 'Super Admin',
|
||||||
|
email: process.env.ADMIN_EMAIL || 'admin@nocobase.com',
|
||||||
|
password: process.env.ADMIN_PASSWORD || 'admin123',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
database.on('afterDefineCollection', (collection: Collection) => {
|
||||||
|
let { createdBy, updatedBy } = collection.options;
|
||||||
|
if (createdBy === true) {
|
||||||
|
collection.setField('createdById', {
|
||||||
|
type: 'context',
|
||||||
|
dataType: 'integer',
|
||||||
|
dataIndex: 'state.currentUser.id',
|
||||||
|
createOnly: true,
|
||||||
|
});
|
||||||
|
collection.setField('createdBy', {
|
||||||
|
type: 'belongsTo',
|
||||||
|
target: 'users',
|
||||||
|
foreignKey: 'createdById',
|
||||||
|
targetKey: 'id',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (updatedBy === true) {
|
||||||
|
collection.setField('updatedById', {
|
||||||
|
type: 'context',
|
||||||
|
dataType: 'integer',
|
||||||
|
dataIndex: 'state.currentUser.id',
|
||||||
|
});
|
||||||
|
collection.setField('updatedBy', {
|
||||||
|
type: 'belongsTo',
|
||||||
|
target: 'users',
|
||||||
|
foreignKey: 'updatedById',
|
||||||
|
targetKey: 'id',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await database.import({
|
||||||
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [key, action] of Object.entries(actions)) {
|
||||||
|
resourcer.registerActionHandler(`users:${key}`, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
resourcer.use(middlewares.parseToken({}));
|
||||||
|
},
|
||||||
|
} as PluginOptions;
|
9
packages/plugin-users/tsconfig.build.json
Normal file
9
packages/plugin-users/tsconfig.build.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.build.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./lib",
|
||||||
|
"declaration": true
|
||||||
|
},
|
||||||
|
"include": ["./src/**/*.ts", "./src/**/*.tsx"],
|
||||||
|
"exclude": ["./src/__tests__/*", "./esm/*", "./lib/*"]
|
||||||
|
}
|
5
packages/plugin-users/tsconfig.json
Normal file
5
packages/plugin-users/tsconfig.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"include": ["./src/**/*.ts", "./src/**/*.tsx"],
|
||||||
|
"exclude": ["./esm/*", "./lib/*"]
|
||||||
|
}
|
@ -4647,6 +4647,13 @@ crypto-random-string@^1.0.0:
|
|||||||
resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
|
resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
|
||||||
integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=
|
integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=
|
||||||
|
|
||||||
|
crypto-random-string@^3.3.0:
|
||||||
|
version "3.3.1"
|
||||||
|
resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-3.3.1.tgz#13cee94cac8001e4842501608ef779e0ed08f82d"
|
||||||
|
integrity sha512-5j88ECEn6h17UePrLi6pn1JcLtAiANa3KExyr9y9Z5vo2mv56Gh3I4Aja/B9P9uyMwyxNHAHWv+nE72f30T5Dg==
|
||||||
|
dependencies:
|
||||||
|
type-fest "^0.8.1"
|
||||||
|
|
||||||
css-blank-pseudo@^0.1.4:
|
css-blank-pseudo@^0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5"
|
resolved "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5"
|
||||||
|
Loading…
Reference in New Issue
Block a user