├── .husky ├── commit-msg └── pre-commit ├── pnpm-workspace.yaml ├── .npmrc ├── .prettierignore ├── packages ├── node-utils │ ├── src │ │ ├── index.ts │ │ ├── fs.ts │ │ └── pkg.ts │ ├── tsup.config.ts │ ├── package.json │ ├── test │ │ ├── pkg.spec.ts │ │ └── bundle.spec.ts │ ├── README.md │ └── CHANGELOG.md ├── eslint-config │ ├── src │ │ ├── env.d.ts │ │ ├── configs │ │ │ ├── regexp.ts │ │ │ ├── jsx.ts │ │ │ ├── next.ts │ │ │ ├── node.ts │ │ │ ├── imports.ts │ │ │ ├── markdown.ts │ │ │ ├── react.ts │ │ │ ├── typescript.ts │ │ │ ├── javascript.ts │ │ │ ├── unicorn.ts │ │ │ └── vue.ts │ │ ├── index.ts │ │ ├── shared │ │ │ ├── utils.ts │ │ │ └── prettier-config.mjs │ │ ├── types.ts │ │ ├── globs.ts │ │ ├── private-configs │ │ │ ├── tailwindcss.ts │ │ │ └── prettier.ts │ │ └── define.ts │ ├── tsup.config.ts │ ├── CHANGELOG.md │ ├── package.json │ └── README.zh-CN.md ├── bassist │ ├── src │ │ └── index.ts │ ├── README.md │ └── package.json ├── tsconfig │ ├── node.json │ ├── web.json │ ├── package.json │ ├── CHANGELOG.md │ ├── base.json │ └── README.md ├── build-config │ ├── CHANGELOG.md │ ├── tsup.config.ts │ ├── package.json │ ├── README.zh-CN.md │ ├── README.md │ └── src │ │ └── tsup.ts ├── changelog │ ├── tsup.config.ts │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ └── index.ts │ └── README.md ├── release │ ├── tsup.config.ts │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── utils.ts │ │ └── index.ts │ └── README.md ├── utils │ ├── test │ │ ├── appearance.spec.ts │ │ ├── device.spec.ts │ │ ├── runtime.spec.ts │ │ ├── random.spec.ts │ │ ├── file.spec.ts │ │ ├── storage.spec.ts │ │ ├── clipboard.spec.ts │ │ ├── index.html │ │ ├── query.spec.ts │ │ ├── performance.spec.ts │ │ ├── regexp.spec.ts │ │ ├── data.spec.ts │ │ └── format.spec.ts │ ├── tsup.config.ts │ ├── src │ │ ├── index.ts │ │ ├── storage │ │ │ ├── index.ts │ │ │ ├── fallback.ts │ │ │ └── base.ts │ │ ├── file.ts │ │ ├── clipboard │ │ │ ├── fallback.ts │ │ │ └── index.ts │ │ ├── runtime.ts │ │ ├── performance.ts │ │ ├── random.ts │ │ ├── appearance.ts │ │ ├── regexp.ts │ │ ├── query.ts │ │ ├── device.ts │ │ ├── load.ts │ │ ├── data.ts │ │ └── format.ts │ ├── package.json │ └── README.md └── progress │ ├── tsup.config.ts │ ├── package.json │ ├── src │ ├── index.ts │ └── types.ts │ ├── CHANGELOG.md │ └── README.md ├── assets └── bassist.jpg ├── commitlint.config.js ├── .prettierrc.mjs ├── vitest.config.ts ├── scripts ├── build.ts ├── publish.ts ├── changelog.cts └── utils.ts ├── .editorconfig ├── README.md ├── .gitignore ├── eslint.config.ts ├── turbo.json ├── tsconfig.json ├── LICENSE ├── .vscode └── settings.json └── package.json /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | pnpm exec commitlint --edit $1 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm exec lint-staged --concurrent false 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | auto-install-peers=true 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | lib 3 | types 4 | CHANGELOG.md 5 | -------------------------------------------------------------------------------- /packages/node-utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './fs' 2 | export * from './pkg' 3 | -------------------------------------------------------------------------------- /assets/bassist.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chengpeiquan/bassist/HEAD/assets/bassist.jpg -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | extends: ['@commitlint/config-conventional'], 3 | } 4 | -------------------------------------------------------------------------------- /packages/eslint-config/src/env.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'eslint-plugin-import' 2 | declare module '@next/eslint-plugin-next' 3 | -------------------------------------------------------------------------------- /packages/bassist/src/index.ts: -------------------------------------------------------------------------------- 1 | export default function hello() { 2 | console.log('I play bass, so enjoy the name.') 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | import prettierConfig from './packages/eslint-config/src/shared/prettier-config.mjs' 2 | 3 | export default prettierConfig 4 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | // ... 6 | }, 7 | }) 8 | -------------------------------------------------------------------------------- /packages/bassist/README.md: -------------------------------------------------------------------------------- 1 | # bassist 2 | 3 | I play bass, so enjoy the name. 4 | 5 | ## License 6 | 7 | MIT License © 2022-PRESENT [chengpeiquan](https://github.com/chengpeiquan) 8 | -------------------------------------------------------------------------------- /packages/tsconfig/node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "./base.json", 4 | "display": "Node", 5 | "compilerOptions": { 6 | "types": ["node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/node-utils/src/fs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * On the `fse` variable, the `fs-extra` APIs are provided. 3 | * 4 | * @category Fs 5 | * @see https://github.com/jprichardson/node-fs-extra 6 | */ 7 | export * as fse from 'fs-extra/esm' 8 | -------------------------------------------------------------------------------- /packages/tsconfig/web.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "./base.json", 4 | "display": "Web", 5 | "compilerOptions": { 6 | "lib": ["ESNext", "DOM", "DOM.Iterable"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/build-config/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.0 (2025-07-06) 2 | 3 | 4 | ### Features 5 | 6 | * **build-config:** add tsup configuration utilities ([b9ce9df](https://github.com/chengpeiquan/bassist/commit/b9ce9df24cec3ba55f52f7469a07a7d29e229c66)) 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/changelog/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { createBaseConfig } from '@packages/build-config/src/tsup' 2 | import { defineConfig } from 'tsup' 3 | import pkg from './package.json' 4 | 5 | const config = createBaseConfig({ pkg }) 6 | 7 | export default defineConfig(config) 8 | -------------------------------------------------------------------------------- /packages/node-utils/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { createBaseConfig } from '@packages/build-config/src/tsup' 2 | import { defineConfig } from 'tsup' 3 | import pkg from './package.json' 4 | 5 | const config = createBaseConfig({ pkg }) 6 | 7 | export default defineConfig(config) 8 | -------------------------------------------------------------------------------- /packages/release/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { createBaseConfig } from '@packages/build-config/src/tsup' 2 | import { defineConfig } from 'tsup' 3 | import pkg from './package.json' 4 | 5 | const config = createBaseConfig({ pkg }) 6 | 7 | export default defineConfig(config) 8 | -------------------------------------------------------------------------------- /packages/utils/test/appearance.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { isDark, isLight } from '..' 3 | 4 | describe('appearance', () => { 5 | it('Invalid data', () => { 6 | expect(isDark()).toBeFalsy() 7 | expect(isLight()).toBeFalsy() 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/build-config/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | import pkg from './package.json' 3 | import { createBaseConfig } from './src/tsup' 4 | 5 | const config = createBaseConfig({ 6 | pkg, 7 | entry: { 8 | tsup: 'src/tsup.ts', 9 | }, 10 | }) 11 | 12 | export default defineConfig(config) 13 | -------------------------------------------------------------------------------- /packages/utils/test/device.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { isBrowser, isServer } from '..' 3 | 4 | describe('device', () => { 5 | it('Valid data', () => { 6 | expect(isServer).toBeTruthy() 7 | }) 8 | it('Invalid data', () => { 9 | expect(isBrowser).toBeFalsy() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /scripts/build.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import { getArgv } from './utils' 3 | 4 | async function run() { 5 | const { name } = getArgv() 6 | 7 | execSync(`turbo run build --filter @bassist/${name}`, { 8 | stdio: 'inherit', 9 | }) 10 | } 11 | 12 | run().catch((e) => { 13 | console.log(e) 14 | }) 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | max_line_length = 80 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | max_line_length = 0 15 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /packages/progress/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { BundleFormat, createBaseConfig } from '@packages/build-config/src/tsup' 2 | import { defineConfig } from 'tsup' 3 | import pkg from './package.json' 4 | 5 | const config = createBaseConfig({ 6 | pkg, 7 | format: [BundleFormat.ESM, BundleFormat.CJS, BundleFormat.IIFE], 8 | }) 9 | 10 | export default defineConfig(config) 11 | -------------------------------------------------------------------------------- /packages/utils/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { BundleFormat, createBaseConfig } from '@packages/build-config/src/tsup' 2 | import { defineConfig } from 'tsup' 3 | import pkg from './package.json' 4 | 5 | const config = createBaseConfig({ 6 | pkg, 7 | format: [BundleFormat.ESM, BundleFormat.CJS, BundleFormat.IIFE], 8 | }) 9 | 10 | export default defineConfig(config) 11 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/regexp.ts: -------------------------------------------------------------------------------- 1 | import { configs } from 'eslint-plugin-regexp' 2 | import { getConfigName } from '../shared/utils' 3 | import { type FlatESLintConfig } from '../types' 4 | 5 | const recommendedConfigs = configs['flat/recommended'] 6 | 7 | export const regexp: FlatESLintConfig[] = [ 8 | { 9 | name: getConfigName('regexp'), 10 | ...recommendedConfigs, 11 | }, 12 | ] 13 | -------------------------------------------------------------------------------- /packages/eslint-config/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { createBaseConfig } from '@packages/build-config/src/tsup' 2 | import { defineConfig } from 'tsup' 3 | import pkg from './package.json' 4 | 5 | const config = createBaseConfig({ 6 | pkg, 7 | entry: { 8 | index: 'src/index.ts', 9 | 'prettier-config': 'src/shared/prettier-config.mjs', 10 | }, 11 | }) 12 | 13 | export default defineConfig(config) 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bassist 2 | 3 | I play bass, so enjoy the name. 4 | 5 | This is my commonly used tool chain and configuration, which is stable and continuously maintained and updated. 6 | 7 | You can also use them directly. 8 | 9 |

10 | Bassist 11 |

12 | 13 | ## License 14 | 15 | MIT License © 2022 [chengpeiquan](https://github.com/chengpeiquan) 16 | -------------------------------------------------------------------------------- /packages/utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './clipboard' 2 | export * from './storage' 3 | export * from './appearance' 4 | export * from './data' 5 | export * from './device' 6 | export * from './file' 7 | export * from './format' 8 | export * from './load' 9 | export * from './performance' 10 | export * from './query' 11 | export * from './random' 12 | export * from './regexp' 13 | export * from './runtime' 14 | export * from './ua' 15 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/jsx.ts: -------------------------------------------------------------------------------- 1 | import { GLOB_JSX, GLOB_TSX } from '../globs' 2 | import { getConfigName } from '../shared/utils' 3 | import { type FlatESLintConfig } from '../types' 4 | 5 | export const jsx: FlatESLintConfig[] = [ 6 | { 7 | name: getConfigName('jsx'), 8 | files: [GLOB_JSX, GLOB_TSX], 9 | languageOptions: { 10 | parserOptions: { 11 | ecmaFeatures: { 12 | jsx: true, 13 | }, 14 | }, 15 | }, 16 | }, 17 | ] 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # log files 2 | yarn.lock 3 | package-lock.json 4 | examples/*/pnpm-lock.yaml 5 | 6 | # build output 7 | dist 8 | 9 | # cache files 10 | .turbo 11 | 12 | # dependencies 13 | node_modules 14 | 15 | # logs 16 | *.log 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | pnpm-debug.log* 21 | 22 | 23 | # environment variables 24 | .env 25 | .env.production 26 | 27 | # macOS-specific files 28 | .DS_Store 29 | 30 | # others 31 | .nyc_output 32 | coverage 33 | demo 34 | *.rar 35 | *.zip 36 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/next.ts: -------------------------------------------------------------------------------- 1 | import nextPlugin from '@next/eslint-plugin-next' 2 | import { getConfigName } from '../shared/utils' 3 | import { type FlatESLintConfig } from '../types' 4 | 5 | export { nextPlugin } 6 | 7 | export const next: FlatESLintConfig[] = [ 8 | { 9 | name: getConfigName('next'), 10 | plugins: { 11 | '@next/next': nextPlugin, 12 | }, 13 | rules: { 14 | ...nextPlugin.configs.recommended.rules, 15 | ...nextPlugin.configs['core-web-vitals'].rules, 16 | }, 17 | }, 18 | ] 19 | -------------------------------------------------------------------------------- /packages/utils/test/runtime.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { 3 | getRuntimeEnv, 4 | isDevRuntime, 5 | isProdRuntime, 6 | isTestRuntime, 7 | runtimeEnv, 8 | } from '..' 9 | 10 | describe('runtime', () => { 11 | it('Valid data', () => { 12 | expect(runtimeEnv).toBe('test') 13 | expect(getRuntimeEnv()).toBe('test') 14 | expect(isTestRuntime).toBeTruthy() 15 | }) 16 | it('Invalid data', () => { 17 | expect(isDevRuntime).toBeFalsy() 18 | expect(isProdRuntime).toBeFalsy() 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /packages/utils/src/storage/index.ts: -------------------------------------------------------------------------------- 1 | import { BaseStorage } from './base' 2 | 3 | /** 4 | * LocalStorage that supports prefixes 5 | * 6 | * @category Storage 7 | */ 8 | export class LocalStorage extends BaseStorage { 9 | constructor(prefix: string) { 10 | super(prefix, 'localStorage') 11 | } 12 | } 13 | 14 | /** 15 | * SessionStorage that supports prefixes 16 | * 17 | * @category Storage 18 | */ 19 | export class SessionStorage extends BaseStorage { 20 | constructor(prefix: string) { 21 | super(prefix, 'sessionStorage') 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /eslint.config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createGetConfigNameFactory, 3 | defineFlatConfig, 4 | imports, 5 | markdown, 6 | node, 7 | typescript, 8 | } from './packages/eslint-config/src' 9 | 10 | const getConfigName = createGetConfigNameFactory('@bassist/monorepo') 11 | 12 | export default defineFlatConfig( 13 | [ 14 | ...imports, 15 | ...typescript, 16 | ...markdown, 17 | ...node, 18 | 19 | { 20 | name: getConfigName('ignore'), 21 | ignores: ['**/dist/**', '**/.build/**', '**/CHANGELOG.md'], 22 | }, 23 | ], 24 | { tailwindcssEnabled: false }, 25 | ) 26 | -------------------------------------------------------------------------------- /packages/utils/test/random.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { inRange as isInRange, randomNumber, randomUserAgent } from '..' 3 | 4 | console.log(randomUserAgent()) 5 | console.log(randomUserAgent()) 6 | console.log(randomUserAgent()) 7 | 8 | describe('randomNumber', () => { 9 | function inRange(value: number, min: number, max: number) { 10 | return isInRange({ num: value, min, max }) 11 | } 12 | 13 | it('Valid data', () => { 14 | expect(inRange(randomNumber(), 0, 100)).toBeTruthy() 15 | expect(inRange(randomNumber(-100, -50), -100, -50)).toBeTruthy() 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /packages/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bassist/tsconfig", 3 | "version": "0.1.1", 4 | "description": "Some TSConfig files for working with TypeScript projects by @chengpeiquan .", 5 | "author": "chengpeiquan ", 6 | "license": "MIT", 7 | "homepage": "https://github.com/chengpeiquan/bassist/tree/main/packages/tsconfig", 8 | "files": [ 9 | "base.json", 10 | "web.json", 11 | "node.json" 12 | ], 13 | "main": "./base.json", 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/chengpeiquan/bassist.git" 17 | }, 18 | "keywords": [ 19 | "tsconfig" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /scripts/publish.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import { getArgv } from './utils' 3 | 4 | async function run() { 5 | const { name, otp, tag } = getArgv() 6 | 7 | const publishArgs = [ 8 | `pnpm --filter ${name} publish`, 9 | `--no-git-checks`, 10 | `--access public`, 11 | `${tag ? `--tag ${tag}` : ''}`, 12 | `${otp ? `--otp=${otp}` : ''}`, 13 | `--registry https://registry.npmjs.org/`, 14 | ].filter(Boolean) 15 | 16 | const commands = [`pnpm build:lib ${name}`, publishArgs.join(' ')] 17 | const cmd = commands.join(' && ') 18 | execSync(cmd) 19 | } 20 | 21 | run().catch((e) => { 22 | console.log(e) 23 | }) 24 | -------------------------------------------------------------------------------- /scripts/changelog.cts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import { resolve } from 'path' 3 | import { getArgv } from './utils' 4 | 5 | async function run() { 6 | const { name } = getArgv() 7 | const pkgPath = resolve(__dirname, `../packages/${name}`) 8 | 9 | const changelogArgs = [ 10 | `conventional-changelog`, 11 | `--lerna-package ${name}`, 12 | `-p angular`, 13 | `-i CHANGELOG.md`, 14 | `-s`, 15 | `--commit-path=.`, 16 | ] 17 | 18 | const commands = [`cd ${pkgPath}`, changelogArgs.join(' ')] 19 | const cmd = commands.join(' && ') 20 | execSync(cmd) 21 | } 22 | 23 | run().catch((e) => { 24 | console.log(e) 25 | }) 26 | -------------------------------------------------------------------------------- /scripts/utils.ts: -------------------------------------------------------------------------------- 1 | import minimist from '@withtypes/minimist' 2 | 3 | /** 4 | * Get argv from Command Line 5 | */ 6 | export function getArgv() { 7 | const argv = minimist(process.argv.slice(2), { string: ['_'] }) 8 | const { _, otp, tag } = argv 9 | const [name] = _ 10 | 11 | if (!name) { 12 | const errArgs = [ 13 | '', 14 | '🚧 Missing package name to generate declaration files.', 15 | '', 16 | '💡 Related command arguments and options:', 17 | ' pnpm build:lib ', 18 | ' pnpm release [--otp] [--tag]', 19 | '', 20 | '', 21 | ] 22 | const errMsg = errArgs.join('\n') 23 | throw new Error(errMsg) 24 | } 25 | 26 | return { name, otp, tag } 27 | } 28 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "ui": "tui", 4 | "tasks": { 5 | "build": { 6 | "dependsOn": [ 7 | "^build" 8 | ], 9 | "inputs": [ 10 | "$TURBO_DEFAULT$", 11 | ".env*" 12 | ], 13 | "outputs": [ 14 | "dist" 15 | ], 16 | "outputLogs": "full" 17 | }, 18 | "lint": { 19 | "dependsOn": [ 20 | "^lint" 21 | ], 22 | "outputLogs": "full" 23 | }, 24 | "check-types": { 25 | "dependsOn": [ 26 | "^check-types" 27 | ], 28 | "outputLogs": "full" 29 | }, 30 | "dev": { 31 | "cache": false, 32 | "persistent": true, 33 | "outputLogs": "full" 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./packages/tsconfig/base.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "importHelpers": true, 6 | "experimentalDecorators": true, 7 | "rootDir": ".", 8 | "baseUrl": ".", 9 | "outDir": "./**/dist", 10 | "paths": { 11 | "@packages/*": ["./packages/*"], 12 | "@scripts/*": ["./scripts/*"] 13 | }, 14 | "types": ["vite/client", "node"], 15 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 16 | "declaration": true 17 | }, 18 | "include": ["packages/**/*", "scripts/**/*", "eslint.config.ts"], 19 | "exclude": [ 20 | "node_modules", 21 | "packages/**/dist", 22 | "packages/**/lib", 23 | "packages/**/types", 24 | "packages/**/.build" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/node.ts: -------------------------------------------------------------------------------- 1 | import nodePlugin from 'eslint-plugin-n' 2 | import { getConfigName } from '../shared/utils' 3 | import { type FlatESLintConfig } from '../types' 4 | 5 | export const node: FlatESLintConfig[] = [ 6 | { 7 | name: getConfigName('node'), 8 | plugins: { 9 | n: nodePlugin, 10 | }, 11 | rules: { 12 | 'n/handle-callback-err': ['error', '^(err|error)$'], 13 | 'n/no-deprecated-api': 'error', 14 | 'n/no-exports-assign': 'error', 15 | 'n/no-new-require': 'error', 16 | 'n/no-path-concat': 'error', 17 | 'n/prefer-global/buffer': ['error', 'never'], 18 | 'n/prefer-global/process': ['error', 'never'], 19 | 'n/process-exit-as-throw': 'error', 20 | }, 21 | }, 22 | ] 23 | -------------------------------------------------------------------------------- /packages/tsconfig/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.1 (2023-08-06) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **tsconfig:** remove baseUrl and paths, need to be set by the project itself ([e180a06](https://github.com/chengpeiquan/bassist/commit/e180a06e5532b061034d169556ca4c6bdd2ea8cb)) 7 | 8 | 9 | 10 | # 0.1.0 (2023-08-06) 11 | 12 | 13 | ### Features 14 | 15 | * **tsconfig:** add base config ([795665e](https://github.com/chengpeiquan/bassist/commit/795665ec2ee51e313a71b45b6917455f72b8397a)) 16 | * **tsconfig:** add node config ([71e9139](https://github.com/chengpeiquan/bassist/commit/71e91395531232314dc4a3bb665700e3530e14f1)) 17 | * **tsconfig:** add web config ([7e9d7c5](https://github.com/chengpeiquan/bassist/commit/7e9d7c552a7884cf89ebf7365fbc2cb099096eb7)) 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/tsconfig/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Base", 4 | "compilerOptions": { 5 | "target": "ES2020", 6 | "module": "ESNext", 7 | "moduleResolution": "Bundler", 8 | "jsx": "preserve", 9 | "strict": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "allowJs": false, 14 | "skipLibCheck": true, 15 | "resolveJsonModule": true, 16 | "esModuleInterop": true, 17 | "allowSyntheticDefaultImports": true, 18 | "forceConsistentCasingInFileNames": true, 19 | "useDefineForClassFields": true, 20 | "sourceMap": true, 21 | "isolatedModules": true 22 | }, 23 | "exclude": ["node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/eslint-config/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './configs/imports' 2 | export * from './configs/javascript' 3 | export * from './configs/jsx' 4 | export * from './configs/markdown' 5 | export * from './configs/next' 6 | export * from './configs/node' 7 | export * from './configs/react' 8 | export * from './configs/regexp' 9 | export * from './configs/typescript' 10 | export * from './configs/unicorn' 11 | export * from './configs/vue' 12 | 13 | export * from './define' 14 | 15 | export * from './types' 16 | 17 | export { prettierPlugin } from './private-configs/prettier' 18 | 19 | export { 20 | tailwindcssPlugin, 21 | defaultTailwindcssSettings, 22 | type TailwindcssSettings, 23 | } from './private-configs/tailwindcss' 24 | 25 | export { createGetConfigNameFactory } from './shared/utils' 26 | -------------------------------------------------------------------------------- /packages/bassist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bassist", 3 | "version": "0.0.0", 4 | "description": "I play bass, so enjoy the name.", 5 | "author": "chengpeiquan ", 6 | "license": "MIT", 7 | "homepage": "https://github.com/chengpeiquan/bassist/tree/main/packages/bassist", 8 | "files": [ 9 | "lib", 10 | "types" 11 | ], 12 | "main": "./lib/index.min.js", 13 | "module": "./lib/index.mjs", 14 | "types": "./types/index.d.ts", 15 | "exports": { 16 | ".": { 17 | "import": "./lib/index.mjs", 18 | "require": "./lib/index.cjs", 19 | "types": "./types/index.d.ts" 20 | } 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/chengpeiquan/bassist.git" 25 | }, 26 | "keywords": [ 27 | "bass", 28 | "bassist" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/changelog/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [0.3.0](https://github.com/chengpeiquan/bassist/compare/changelog@0.2.0...changelog@0.3.0) (2024-02-15) 2 | 3 | 4 | ### Features 5 | 6 | * **changelog:** let the CLI as a peer dependency to installed ([5ec81ce](https://github.com/chengpeiquan/bassist/commit/5ec81ceb6b0b322dbcbc4178cbf98fa292df04d3)) 7 | 8 | 9 | 10 | # [0.2.0](https://github.com/chengpeiquan/bassist/compare/changelog@0.1.0...changelog@0.2.0) (2024-02-15) 11 | 12 | 13 | ### Features 14 | 15 | * **changelog:** simplify the steps to use ([e0ce531](https://github.com/chengpeiquan/bassist/commit/e0ce531ca885f7a46c6816e8b130a01214f841c9)) 16 | 17 | 18 | 19 | # 0.1.0 (2024-02-14) 20 | 21 | 22 | ### Features 23 | 24 | * **changelog:** initial changelog CLI ([9b3785e](https://github.com/chengpeiquan/bassist/commit/9b3785e7c0dcfcdc1a318740dd667433a5575de9)) 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bassist/utils", 3 | "version": "0.17.0", 4 | "description": "Opinionated collection of common JavaScript / TypeScript utils by @chengpeiquan .", 5 | "author": "chengpeiquan ", 6 | "license": "MIT", 7 | "homepage": "https://jsdocs.io/package/@bassist/utils", 8 | "files": [ 9 | "dist" 10 | ], 11 | "main": "./dist/index.min.js", 12 | "module": "./dist/index.mjs", 13 | "types": "./dist/index.d.ts", 14 | "exports": { 15 | ".": { 16 | "types": "./dist/index.d.ts", 17 | "import": "./dist/index.mjs", 18 | "require": "./dist/index.cjs" 19 | } 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/chengpeiquan/bassist", 24 | "directory": "packages/utils" 25 | }, 26 | "keywords": [ 27 | "utils" 28 | ], 29 | "scripts": { 30 | "build": "tsup" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/eslint-config/src/shared/utils.ts: -------------------------------------------------------------------------------- 1 | const defaultPrefix = 'bassist' 2 | 3 | /** 4 | * A flexible tool function for generating ESLint configuration naming tools. It 5 | * helps you quickly splice configuration names, ensure consistent namespaces, 6 | * and facilitate the organization and management of complex rule sets. 7 | * 8 | * @param prefix - A string representing the prefix for your configuration 9 | * names. 10 | * @returns A function that concatenates the provided name segments with the 11 | * given prefix. 12 | */ 13 | export const createGetConfigNameFactory = (prefix: string) => { 14 | const getConfigName = (...names: string[]): string => { 15 | const finalPrefix = prefix?.trim() || defaultPrefix 16 | return `${finalPrefix}/${names.join('/')}` 17 | } 18 | 19 | return getConfigName 20 | } 21 | 22 | // Provided for internal configuration 23 | export const getConfigName = createGetConfigNameFactory(defaultPrefix) 24 | -------------------------------------------------------------------------------- /packages/utils/src/file.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get file info via `mime` package 3 | * 4 | * @category File 5 | */ 6 | export class FileInfo { 7 | mime: any 8 | 9 | constructor(theMimePackageInstance: any) { 10 | this.mime = theMimePackageInstance 11 | } 12 | 13 | getMimeType(path: string) { 14 | try { 15 | if (path.startsWith('data') && path.includes('base64')) { 16 | return path.split(',')[0].replace(/data:(.*);base64/, '$1') 17 | } 18 | return this.mime.getType(path) || '' 19 | } catch { 20 | return '' 21 | } 22 | } 23 | 24 | getExtensionFromMimeType(mimeType: string) { 25 | try { 26 | return this.mime.getExtension(mimeType) || '' 27 | } catch { 28 | return '' 29 | } 30 | } 31 | 32 | getExtension(path: string) { 33 | try { 34 | const mimeType = this.getMimeType(path) 35 | return this.getExtensionFromMimeType(mimeType) 36 | } catch { 37 | return '' 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/progress/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bassist/progress", 3 | "version": "0.2.2", 4 | "description": "Simple slim progress bars base on NProgress.", 5 | "author": "chengpeiquan ", 6 | "license": "MIT", 7 | "homepage": "https://github.com/chengpeiquan/bassist/tree/main/packages/progress", 8 | "files": [ 9 | "dist" 10 | ], 11 | "main": "./dist/index.min.js", 12 | "module": "./dist/index.mjs", 13 | "types": "./dist/index.d.ts", 14 | "exports": { 15 | ".": { 16 | "types": "./dist/index.d.ts", 17 | "import": "./dist/index.mjs", 18 | "require": "./dist/index.cjs" 19 | } 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/chengpeiquan/bassist.git" 24 | }, 25 | "keywords": [ 26 | "nprogress", 27 | "progress", 28 | "progress bar" 29 | ], 30 | "scripts": { 31 | "build": "tsup" 32 | }, 33 | "dependencies": { 34 | "@bassist/utils": "workspace:^", 35 | "nprogress": "^0.2.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/build-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bassist/build-config", 3 | "version": "0.1.0", 4 | "description": "Opinionated collection of common build config by @chengpeiquan .", 5 | "author": "chengpeiquan ", 6 | "license": "MIT", 7 | "homepage": "https://jsdocs.io/package/@bassist/build-config", 8 | "files": [ 9 | "dist" 10 | ], 11 | "exports": { 12 | "./tsup": { 13 | "types": "./dist/tsup.d.ts", 14 | "import": "./dist/tsup.mjs", 15 | "require": "./dist/tsup.cjs" 16 | } 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/chengpeiquan/bassist", 21 | "directory": "packages/build-config" 22 | }, 23 | "keywords": [ 24 | "build config", 25 | "tsup config" 26 | ], 27 | "scripts": { 28 | "build": "tsup" 29 | }, 30 | "devDependencies": { 31 | "tsup": "^8.5.0" 32 | }, 33 | "peerDependencies": { 34 | "tsup": ">=8.0.0" 35 | }, 36 | "peerDependenciesMeta": { 37 | "tsup": { 38 | "optional": true 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/utils/src/clipboard/fallback.ts: -------------------------------------------------------------------------------- 1 | import { isBrowser } from '../device' 2 | 3 | export function fallbackWriteText(text: string) { 4 | if (!isBrowser) return false 5 | 6 | try { 7 | const textArea = document.createElement('textarea') 8 | textArea.value = text 9 | 10 | textArea.style.position = 'fixed' 11 | textArea.style.top = '-9999px' 12 | textArea.style.left = '-9999px' 13 | document.body.appendChild(textArea) 14 | 15 | textArea.focus() 16 | textArea.select() 17 | 18 | const successful = document.execCommand('copy') 19 | document.body.removeChild(textArea) 20 | return successful 21 | } catch { 22 | return false 23 | } 24 | } 25 | 26 | export function fallbackReadText() { 27 | if (!isBrowser) return '' 28 | 29 | try { 30 | const textarea = document.createElement('textarea') 31 | document.body.appendChild(textarea) 32 | 33 | textarea.focus() 34 | document.execCommand('paste') 35 | 36 | const text = textarea.value 37 | textarea.remove() 38 | return text 39 | } catch { 40 | return '' 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/progress/src/index.ts: -------------------------------------------------------------------------------- 1 | import { loadRes, isBrowser, randomString } from '@bassist/utils' 2 | import nprogress from 'nprogress' 3 | import resource from 'nprogress/nprogress.css?inline' 4 | import type { Progress } from './types' 5 | 6 | loadRes({ 7 | type: 'style', 8 | id: 'bassist-nprogress', 9 | resource, 10 | }).catch((e) => { 11 | console.log(e) 12 | }) 13 | 14 | const progress = nprogress as Progress 15 | 16 | progress.setColor = function (color: string) { 17 | if (!isBrowser) return 18 | 19 | const style = ` 20 | #nprogress .bar { 21 | background: ${color} !important; 22 | } 23 | #nprogress .peg { 24 | box-shadow: 0 0 10px ${color}, 0 0 5px ${color} !important; 25 | } 26 | #nprogress .spinner .spinner-icon { 27 | border-top-color: ${color} !important; 28 | border-left-color: ${color} !important; 29 | } 30 | ` 31 | 32 | loadRes({ 33 | type: 'style', 34 | id: `bassist-nprogress-theme-${randomString()}`, 35 | resource: style, 36 | }).catch((e) => { 37 | console.log(e) 38 | }) 39 | } 40 | 41 | export default progress as Progress 42 | -------------------------------------------------------------------------------- /packages/node-utils/src/pkg.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Check the package name is valid 3 | * 4 | * @category Pkg 5 | */ 6 | export function isValidPackageName(packageName: string) { 7 | return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test( 8 | packageName, 9 | ) 10 | } 11 | 12 | /** 13 | * Format the package name to valid 14 | * 15 | * @category Pkg 16 | */ 17 | export function toValidPackageName(name: string) { 18 | return name 19 | .trim() 20 | .toLowerCase() 21 | .replace(/\s+/g, '-') 22 | .replace(/^[._]/, '') 23 | .replace(/[^a-z0-9-~]+/g, '-') 24 | } 25 | 26 | /** 27 | * Get package manager info 28 | * 29 | * @category Pkg 30 | */ 31 | export function getPackageManagerByUserAgent() { 32 | const defaultInfo = { 33 | name: '', 34 | version: '0.0.0', 35 | } 36 | 37 | try { 38 | const userAgent = process.env.npm_config_user_agent 39 | if (!userAgent) { 40 | return { ...defaultInfo } 41 | } 42 | 43 | const spec = userAgent.split(' ')[0] 44 | const [name, version] = spec.split('/') 45 | return { name, version } 46 | } catch { 47 | return { ...defaultInfo } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/utils/test/file.spec.ts: -------------------------------------------------------------------------------- 1 | import mime from '@withtypes/mime' 2 | import { describe, expect, it } from 'vitest' 3 | import { FileInfo } from '..' 4 | 5 | const file = new FileInfo(mime) 6 | 7 | describe('file', () => { 8 | it('getMimeType', () => { 9 | expect(file.getMimeType('example.txt')).toBe('text/plain') 10 | expect(file.getMimeType('example.png')).toBe('image/png') 11 | expect(file.getMimeType('example')).toBe('') 12 | expect( 13 | file.getMimeType( 14 | 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACrElEQVR4', 15 | ), 16 | ).toBe('image/png') 17 | }) 18 | 19 | it('getExtensionFromMimeType', () => { 20 | expect(file.getExtensionFromMimeType('text/plain')).toBe('txt') 21 | }) 22 | 23 | it('getExtension', () => { 24 | expect(file.getExtension('example.txt')).toBe('txt') 25 | expect(file.getExtension('example.png')).toBe('png') 26 | expect(file.getExtension('example')).toBe('') 27 | expect( 28 | file.getExtension( 29 | 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACrElEQVR4', 30 | ), 31 | ).toBe('png') 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /packages/utils/test/storage.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { LocalStorage } from '..' 3 | 4 | describe('storage', () => { 5 | it('LocalStorage Default', () => { 6 | const ls = new LocalStorage('test-ls-default') 7 | expect(ls.prefix).toBe('test-ls-default') 8 | expect(ls.count()).toBe(0) 9 | expect(ls.list()).toStrictEqual([]) 10 | }) 11 | 12 | it('LocalStorage With Data', () => { 13 | const ls = new LocalStorage('test-ls') 14 | expect(ls.prefix).toBe('test-ls') 15 | 16 | ls.set('foo', 'foo') 17 | expect(ls.count()).toBe(1) 18 | expect(ls.get('foo')).toBe('foo') 19 | expect(ls.list()).toStrictEqual(['foo']) 20 | 21 | ls.set('bar', 1) 22 | expect(ls.get('bar')).toBe(1) 23 | 24 | ls.set('baz', [1, 2, 3]) 25 | expect(ls.get('baz')).toStrictEqual([1, 2, 3]) 26 | 27 | ls.set('qux', { 28 | foo: 'foo', 29 | bar: 1, 30 | }) 31 | expect(ls.get('qux')).toStrictEqual({ 32 | foo: 'foo', 33 | bar: 1, 34 | }) 35 | 36 | ls.remove('qux') 37 | expect(ls.get('qux')).toBeNull() 38 | 39 | ls.clear() 40 | expect(ls.count()).toBe(0) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-PRESENT chengpeiquan (https://github.com/chengpeiquan) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/progress/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.2.2](https://github.com/chengpeiquan/bassist/compare/progress@0.2.1...progress@0.2.2) (2023-08-19) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **progress:** typo ([57d8a79](https://github.com/chengpeiquan/bassist/commit/57d8a7918869a24fa8f01a28d33c6a9cdeb57d3e)) 7 | 8 | 9 | 10 | ## [0.2.1](https://github.com/chengpeiquan/bassist/compare/progress@0.2.0...progress@0.2.1) (2023-03-19) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * **progress:** update the utils dep to fix a loadRes bug ([b5e1de0](https://github.com/chengpeiquan/bassist/commit/b5e1de0641210ff87e7405481903cfd00a54eee5)) 16 | 17 | 18 | 19 | # 0.2.0 (2023-02-08) 20 | 21 | 22 | ### Features 23 | 24 | * **progress:** optimize the type of package ([6b59427](https://github.com/chengpeiquan/bassist/commit/6b594271a69403292d91f04bfeff01d145935582)) 25 | 26 | 27 | 28 | # 0.1.0 (2023-02-08) 29 | 30 | 31 | ### Features 32 | 33 | * **progress:** add progress ([f906324](https://github.com/chengpeiquan/bassist/commit/f906324086ac0bfed8573f39c19f34278c25a1ea)) 34 | * **progress:** add setColor method ([fd3fcef](https://github.com/chengpeiquan/bassist/commit/fd3fcefffe242b355df05eb525a5547cccf4a2c0)) 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /packages/node-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bassist/node-utils", 3 | "version": "0.5.0", 4 | "description": "Opinionated collection of common Node.js utils by @chengpeiquan .", 5 | "author": "chengpeiquan ", 6 | "license": "MIT", 7 | "homepage": "https://jsdocs.io/package/@bassist/node-utils", 8 | "files": [ 9 | "dist" 10 | ], 11 | "main": "./dist/index.cjs", 12 | "module": "./dist/index.mjs", 13 | "types": "./dist/index.d.ts", 14 | "exports": { 15 | ".": { 16 | "types": "./dist/index.d.ts", 17 | "import": "./dist/index.mjs", 18 | "require": "./dist/index.cjs" 19 | } 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/chengpeiquan/bassist", 24 | "directory": "packages/node-utils" 25 | }, 26 | "keywords": [ 27 | "utils", 28 | "node utils", 29 | "file system utils", 30 | "file utils", 31 | "fs utils", 32 | "fs extra" 33 | ], 34 | "scripts": { 35 | "build": "tsup" 36 | }, 37 | "dependencies": { 38 | "@bassist/utils": "workspace:^", 39 | "fs-extra": "^11.3.0" 40 | }, 41 | "devDependencies": { 42 | "@types/fs-extra": "^11.0.4" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/eslint-config/src/types.ts: -------------------------------------------------------------------------------- 1 | import { type ESLint, type Linter } from 'eslint' 2 | import { 3 | type RequiredOptions as PrettierRequiredOptions, 4 | type BuiltInParserName as PrettierParser, 5 | type LiteralUnion as PrettierLiteralUnion, 6 | } from 'prettier' 7 | import { type Options as PrettierJsdocOptions } from 'prettier-plugin-jsdoc' 8 | import { type prettierLintMd } from 'prettier-plugin-lint-md' 9 | 10 | export type FlatESLintConfig = Linter.Config 11 | 12 | export type FlatESLintPlugin = ESLint.Plugin 13 | 14 | export type FlatESLintParser = Linter.Parser 15 | 16 | export type FlatESLintProcessor = Linter.Processor 17 | 18 | export type FlatESLintRules = Linter.RulesRecord 19 | 20 | /** 21 | * Prettier types 22 | */ 23 | export { type PrettierParser } 24 | 25 | export interface PrettierOptions extends PrettierRequiredOptions { 26 | parser: PrettierLiteralUnion 27 | } 28 | 29 | export type PrettierLintMdOptions = NonNullable< 30 | Parameters[0] 31 | > 32 | 33 | export { type PrettierJsdocOptions } 34 | 35 | export type PartialPrettierExtendedOptions = Partial & 36 | Partial & 37 | Partial 38 | -------------------------------------------------------------------------------- /packages/release/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.3.1](https://github.com/chengpeiquan/bassist/compare/release@0.3.0...release@0.3.1) (2025-05-22) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **release:** internal dependency upgrade ([af59e48](https://github.com/chengpeiquan/bassist/commit/af59e486710ec4d40500d8b3229e89fc4ecb9ea2)) 7 | 8 | 9 | 10 | # [0.3.0](https://github.com/chengpeiquan/bassist/compare/release@0.2.0...release@0.3.0) (2024-06-30) 11 | 12 | 13 | ### Features 14 | 15 | * **release:** use node-utils instead fs-extra ([3624f8e](https://github.com/chengpeiquan/bassist/commit/3624f8e19327f041815647ec032385561675308a)) 16 | 17 | 18 | 19 | # [0.2.0](https://github.com/chengpeiquan/bassist/compare/release@0.1.0...release@0.2.0) (2024-02-15) 20 | 21 | 22 | ### Features 23 | 24 | * **release:** supports monorepo tag format ([aca815d](https://github.com/chengpeiquan/bassist/commit/aca815d715004b9a12ebaf58d4f3118cbb09ba1b)) 25 | 26 | 27 | 28 | # [0.1.0](https://github.com/chengpeiquan/bassist/compare/release@0.1.0...release@0.1.0) (2024-02-15) 29 | 30 | 31 | ### Features 32 | 33 | * **release:** initial release CLI ([8089d04](https://github.com/chengpeiquan/bassist/commit/8089d0455ecd79df9965ce164cb0c06872e21e4e)) 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /packages/changelog/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bassist/changelog", 3 | "version": "0.3.0", 4 | "description": "Simple CHANGELOG generator by @chengpeiquan , based on conventional-changelog-cli.", 5 | "author": "chengpeiquan ", 6 | "license": "MIT", 7 | "homepage": "https://github.com/chengpeiquan/bassist/tree/main/packages/changelog", 8 | "files": [ 9 | "dist" 10 | ], 11 | "bin": { 12 | "@bassist/changelog": "./dist/index.mjs", 13 | "changelog": "./dist/index.mjs" 14 | }, 15 | "main": "./dist/index.cjs", 16 | "module": "./dist/index.mjs", 17 | "types": "./dist/index.d.ts", 18 | "exports": { 19 | ".": { 20 | "types": "./dist/index.d.ts", 21 | "import": "./dist/index.mjs", 22 | "require": "./dist/index.cjs" 23 | } 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/chengpeiquan/bassist.git" 28 | }, 29 | "keywords": [ 30 | "changelog", 31 | "changelog generator", 32 | "generate changelog", 33 | "CHANGELOG.md" 34 | ], 35 | "scripts": { 36 | "build": "tsup" 37 | }, 38 | "dependencies": { 39 | "@withtypes/minimist": "^0.1.1", 40 | "conventional-changelog-cli": "^5.0.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/release/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bassist/release", 3 | "version": "0.3.1", 4 | "description": "Simple GitHub release generator by @chengpeiquan , based on GitHub CLI.", 5 | "author": "chengpeiquan ", 6 | "license": "MIT", 7 | "homepage": "https://github.com/chengpeiquan/bassist/tree/main/packages/release", 8 | "files": [ 9 | "dist" 10 | ], 11 | "bin": { 12 | "@bassist/release": "./dist/index.mjs", 13 | "release": "./dist/index.mjs" 14 | }, 15 | "main": "./dist/index.cjs", 16 | "module": "./dist/index.mjs", 17 | "types": "./dist/index.d.ts", 18 | "exports": { 19 | ".": { 20 | "types": "./dist/index.d.ts", 21 | "import": "./dist/index.mjs", 22 | "require": "./dist/index.cjs" 23 | } 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/chengpeiquan/bassist" 28 | }, 29 | "keywords": [ 30 | "release", 31 | "release generator", 32 | "generate release", 33 | "github release" 34 | ], 35 | "scripts": { 36 | "build": "tsup" 37 | }, 38 | "dependencies": { 39 | "@bassist/node-utils": "workspace:^", 40 | "@bassist/utils": "workspace:^", 41 | "@withtypes/minimist": "^0.1.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/changelog/src/index.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'node:child_process' 2 | import minimist from '@withtypes/minimist' 3 | import pkg from '../package.json' 4 | 5 | async function run() { 6 | const argv = minimist(process.argv.slice(2), { 7 | string: ['_', 'preset', 'infile', 'lerna-package'], 8 | alias: { 9 | preset: 'p', 10 | infile: 'i', 11 | 'release-count': 'r', 12 | 'lerna-package': 'l', 13 | }, 14 | }) 15 | 16 | const { 17 | preset: presetValue, 18 | infile: infileValue, 19 | 'release-count': releaseCountValue, 20 | 'commit-path': commitPathValue, 21 | 'lerna-package': lernaPackageValue, 22 | } = argv 23 | 24 | const preset = presetValue || 'angular' 25 | const infile = infileValue || 'CHANGELOG.md' 26 | const releaseCount = releaseCountValue || 1 27 | const lernaPackage = lernaPackageValue || '' 28 | const commitPath = commitPathValue || './src' 29 | 30 | const changelogArgs = [ 31 | `conventional-changelog`, 32 | lernaPackage ? `--lerna-package ${lernaPackage}` : '', 33 | `-p ${preset}`, 34 | `-i ${infile}`, 35 | `-r ${releaseCount}`, 36 | `-s`, 37 | `--commit-path=${commitPath}`, 38 | ].filter((i) => !!i) 39 | 40 | const cmd = changelogArgs.join(' ') 41 | execSync(cmd) 42 | } 43 | 44 | run().catch((e) => { 45 | console.log(`[${pkg.name}]`, e) 46 | }) 47 | -------------------------------------------------------------------------------- /packages/utils/test/clipboard.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { clipboard } from '..' 3 | 4 | describe('clipboard', () => { 5 | it('Valid data', async () => { 6 | expect(await clipboard.read()).toBe('') 7 | }) 8 | it('Invalid data', async () => { 9 | expect(clipboard.isSupported).toBeFalsy() 10 | expect(await clipboard.write('hello')).toBeFalsy() 11 | }) 12 | }) 13 | 14 | // 27 | 28 | // 50 | -------------------------------------------------------------------------------- /packages/node-utils/test/pkg.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { 3 | isValidPackageName, 4 | toValidPackageName, 5 | getPackageManagerByUserAgent, 6 | } from '..' 7 | 8 | describe('pkg', () => { 9 | it('isValidPackageName', () => { 10 | expect(isValidPackageName('hello')).toBeTruthy() 11 | expect(isValidPackageName('hello123')).toBeTruthy() 12 | expect(isValidPackageName('113hello')).toBeTruthy() 13 | expect(isValidPackageName('hello-world')).toBeTruthy() 14 | expect(isValidPackageName('hello_world')).toBeTruthy() 15 | expect(isValidPackageName('hello.world')).toBeTruthy() 16 | expect(isValidPackageName('@hello/world')).toBeTruthy() 17 | expect(isValidPackageName('@hello/w-orld')).toBeTruthy() 18 | 19 | expect(isValidPackageName('!hello')).toBeFalsy() 20 | expect(isValidPackageName('%hello')).toBeFalsy() 21 | expect(isValidPackageName('Hello')).toBeFalsy() 22 | expect(isValidPackageName('HELLO')).toBeFalsy() 23 | expect(isValidPackageName('hello world')).toBeFalsy() 24 | }) 25 | 26 | it('toValidPackageName', () => { 27 | expect(toValidPackageName('HELLO')).toBe('hello') 28 | expect(toValidPackageName('hello world')).toBe('hello-world') 29 | }) 30 | 31 | it('getPackageManagerByUserAgent', () => { 32 | console.log('userAgent', process.env.npm_config_user_agent) 33 | expect(getPackageManagerByUserAgent().name).toBe('pnpm') 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /packages/utils/src/runtime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Common runtime environment types 3 | * 4 | * @category Runtime 5 | */ 6 | export type RuntimeEnv = 'dev' | 'development' | 'test' | 'prod' | 'production' 7 | 8 | /** 9 | * Get current runtime environment 10 | * 11 | * @category Runtime 12 | * @precondition The `cross-env` package is installed 13 | */ 14 | export function getRuntimeEnv() { 15 | try { 16 | return process.env.NODE_ENV as RuntimeEnv 17 | } catch { 18 | return undefined 19 | } 20 | } 21 | 22 | /** 23 | * Current runtime environment 24 | * 25 | * @category Runtime 26 | */ 27 | export const runtimeEnv = getRuntimeEnv() 28 | 29 | /** 30 | * Determine whether the specified runtime environment is currently 31 | * 32 | * @category Runtime 33 | * @precondition The `cross-env` package is installed 34 | */ 35 | export function checkRuntimeEnv(runtimeEnv: unknown): runtimeEnv is RuntimeEnv { 36 | try { 37 | return process.env.NODE_ENV === runtimeEnv 38 | } catch { 39 | return false 40 | } 41 | } 42 | 43 | /** 44 | * Determine whether the current runtime is development 45 | * 46 | * @category Runtime 47 | */ 48 | export const isDevRuntime = 49 | checkRuntimeEnv('dev') || checkRuntimeEnv('development') 50 | 51 | /** 52 | * Determine whether the current runtime is test 53 | * 54 | * @category Runtime 55 | */ 56 | export const isTestRuntime = checkRuntimeEnv('test') 57 | 58 | /** 59 | * Determine whether the current runtime is production 60 | * 61 | * @category Runtime 62 | */ 63 | export const isProdRuntime = 64 | checkRuntimeEnv('prod') || checkRuntimeEnv('production') 65 | -------------------------------------------------------------------------------- /packages/progress/src/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Types provided from @types/nprogress 3 | */ 4 | interface NProgressOptions { 5 | minimum: number 6 | template: string 7 | easing: string 8 | speed: number 9 | trickle: boolean 10 | trickleSpeed: number 11 | showSpinner: boolean 12 | parent: string 13 | positionUsing: string 14 | barSelector: string 15 | spinnerSelector: string 16 | } 17 | 18 | interface NProgress { 19 | version: string 20 | settings: NProgressOptions 21 | status: number | null 22 | 23 | configure(options: Partial): NProgress 24 | set(number: number): NProgress 25 | isStarted(): boolean 26 | start(): NProgress 27 | done(force?: boolean): NProgress 28 | inc(amount?: number): NProgress 29 | trickle(): NProgress 30 | 31 | /* Internal */ 32 | 33 | render(fromStart?: boolean): HTMLDivElement 34 | remove(): void 35 | isRendered(): boolean 36 | getPositioningCSS(): 'translate3d' | 'translate' | 'margin' 37 | } 38 | 39 | /** 40 | * Types of functionality that this plugin extends 41 | */ 42 | export interface Progress extends NProgress { 43 | /** 44 | * @example 45 | * use HEX 46 | * progress.setColor('#ff0000') 47 | * 48 | * @example 49 | * use RGB 50 | * progress.setColor('rgb(255, 0, 0)') 51 | * 52 | * @example 53 | * use RGBA 54 | * progress.setColor('rgba(255, 0, 0, 1)') 55 | * 56 | * @example 57 | * use CSS Variable 58 | * progress.setColor('var(--color-primary)') 59 | * 60 | * @param color - A valid CSS color value or CSS variable 61 | */ 62 | setColor: (color: string) => void 63 | } 64 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/imports.ts: -------------------------------------------------------------------------------- 1 | import importPlugin from 'eslint-plugin-import-x' 2 | import { getConfigName } from '../shared/utils' 3 | import { type FlatESLintPlugin, type FlatESLintConfig } from '../types' 4 | 5 | export { importPlugin } 6 | 7 | export const imports: FlatESLintConfig[] = [ 8 | { 9 | name: getConfigName('imports'), 10 | plugins: { 11 | import: importPlugin as unknown as FlatESLintPlugin, 12 | }, 13 | rules: { 14 | 'import/first': 'error', 15 | 'import/no-mutable-exports': 'error', 16 | 'import/no-duplicates': 'error', 17 | 18 | // Some scenes must provide a default export 19 | // e.g. Configuration files, Next.js routing, etc. 20 | 'import/no-default-export': 'off', 21 | 22 | 'import/no-named-default': 'error', 23 | 'import/no-self-import': 'error', 24 | 'import/no-webpack-loader-syntax': 'error', 25 | 26 | 'import/order': [ 27 | 'error', 28 | { 29 | groups: [ 30 | 'builtin', 31 | 'external', 32 | 'internal', 33 | 'parent', 34 | 'sibling', 35 | 'index', 36 | 'object', 37 | 'type', 38 | ], 39 | pathGroups: [{ pattern: '@/**', group: 'internal' }], 40 | pathGroupsExcludedImportTypes: ['type'], 41 | alphabetize: { 42 | order: 'asc', 43 | orderImportKind: 'asc', 44 | caseInsensitive: false, 45 | }, 46 | }, 47 | ], 48 | 49 | 'import/newline-after-import': ['error', { count: 1 }], 50 | }, 51 | }, 52 | ] 53 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/markdown.ts: -------------------------------------------------------------------------------- 1 | import markdownPlugin from '@eslint/markdown' 2 | import { 3 | GLOB_EXCLUDE, 4 | GLOB_MARKDOWN, 5 | GLOB_MARKDOWN_CODE, 6 | GLOB_VUE, 7 | } from '../globs' 8 | import { getConfigName } from '../shared/utils' 9 | import { type FlatESLintConfig } from '../types' 10 | 11 | export { markdownPlugin } 12 | 13 | export const markdown: FlatESLintConfig[] = [ 14 | ...markdownPlugin.configs.processor, 15 | ...markdownPlugin.configs.recommended, 16 | { 17 | name: getConfigName('markdown'), 18 | files: [GLOB_MARKDOWN_CODE, `${GLOB_MARKDOWN}/${GLOB_VUE}`], 19 | language: 'markdown/commonmark', 20 | rules: { 21 | '@typescript-eslint/comma-dangle': 'off', 22 | '@typescript-eslint/consistent-type-imports': 'off', 23 | '@typescript-eslint/no-extraneous-class': 'off', 24 | '@typescript-eslint/no-namespace': 'off', 25 | '@typescript-eslint/no-redeclare': 'off', 26 | '@typescript-eslint/no-require-imports': 'off', 27 | '@typescript-eslint/no-unused-expressions': 'off', 28 | '@typescript-eslint/no-unused-vars': 'off', 29 | '@typescript-eslint/no-use-before-define': 'off', 30 | 'no-alert': 'off', 31 | 'no-console': 'off', 32 | 'no-restricted-imports': 'off', 33 | 'no-undef': 'off', 34 | 'no-unused-expressions': 'off', 35 | 'no-unused-vars': 'off', 36 | 'node/prefer-global/buffer': 'off', 37 | 'node/prefer-global/process': 'off', 38 | 'unused-imports/no-unused-imports': 'off', 39 | 'unused-imports/no-unused-vars': 'off', 40 | }, 41 | ignores: [...GLOB_EXCLUDE], 42 | }, 43 | ] 44 | -------------------------------------------------------------------------------- /packages/node-utils/test/bundle.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { getBundleBanner } from '..' 3 | import pkg from '../package.json' 4 | 5 | describe('bundler', () => { 6 | it('getBundleBanner base', () => { 7 | expect(getBundleBanner(pkg)).toBe( 8 | [ 9 | `/**`, 10 | ` * name: ${pkg.name}`, 11 | ` * version: v${pkg.version}`, 12 | ` * description: ${pkg.description}`, 13 | ` * author: ${pkg.author}`, 14 | ` * homepage: ${pkg.homepage}`, 15 | ` * license: ${pkg.license}`, 16 | ` */`, 17 | ].join('\n'), 18 | ) 19 | }) 20 | 21 | it('getBundleBanner bin', () => { 22 | expect(getBundleBanner(pkg, { bin: true })).toBe( 23 | [ 24 | '#!/usr/bin/env node', 25 | '', 26 | `/**`, 27 | ` * name: ${pkg.name}`, 28 | ` * version: v${pkg.version}`, 29 | ` * description: ${pkg.description}`, 30 | ` * author: ${pkg.author}`, 31 | ` * homepage: ${pkg.homepage}`, 32 | ` * license: ${pkg.license}`, 33 | ` */`, 34 | ].join('\n'), 35 | ) 36 | }) 37 | 38 | it('getBundleBanner custom fields', () => { 39 | expect(getBundleBanner(pkg, { fields: ['name', 'version'] })).toBe( 40 | [ 41 | `/**`, 42 | ` * name: ${pkg.name}`, 43 | ` * version: v${pkg.version}`, 44 | ` */`, 45 | ].join('\n'), 46 | ) 47 | }) 48 | 49 | it('getBundleBanner bad fields', () => { 50 | expect( 51 | getBundleBanner(pkg, { fields: ['foo', 'bar', 'dependencies'] }), 52 | ).toBe([`/**`, ` */`].join('\n')) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "antfu", 4 | "apos", 5 | "Attributify", 6 | "baiduboxapp", 7 | "callees", 8 | "codemirror", 9 | "commitlint", 10 | "commonmark", 11 | "conventionalcommits", 12 | "EDITMSG", 13 | "escaperegexp", 14 | "ianvs", 15 | "IIFE", 16 | "infile", 17 | "kolorist", 18 | "middot", 19 | "mqqbrowser", 20 | "nprogress", 21 | "nuxt", 22 | "pnoop", 23 | "qqbrowser", 24 | "qzone", 25 | "rspack", 26 | "tailwindcss", 27 | "taze", 28 | "Tsup", 29 | "Weibo", 30 | "Weixin", 31 | "Windi", 32 | "windicss", 33 | "withtypes" 34 | ], 35 | "explorer.fileNesting.enabled": true, 36 | "explorer.fileNesting.patterns": { 37 | ".env": ".env.*", 38 | "tsconfig.json": "tsconfig.*.json, env.d.ts", 39 | "vite.config.*": "jsconfig*, vitest.config.*, cypress.config.*, playwright.config.*", 40 | "package.json": "package-lock.json, pnpm*, .yarnrc*, yarn*, .eslint*, eslint*, .oxlint*, oxlint*, .prettier*, prettier*, .editorconfig, postcss.config.js, tailwind.config.ts, tailwind.whitelist.js, turbo.json, commitlint.config.js" 41 | }, 42 | "editor.formatOnSave": true, 43 | "editor.codeActionsOnSave": { 44 | "source.fixAll.eslint": "always", 45 | "source.fixAll.prettier": "always" 46 | }, 47 | "editor.defaultFormatter": "esbenp.prettier-vscode", 48 | "eslint.useFlatConfig": true, 49 | "eslint.format.enable": true, 50 | "eslint.validate": [ 51 | "javascript", 52 | "javascriptreact", 53 | "typescript", 54 | "typescriptreact" 55 | ], 56 | "prettier.configPath": "./.prettierrc.mjs" 57 | } 58 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/react.ts: -------------------------------------------------------------------------------- 1 | import reactPlugin from 'eslint-plugin-react' 2 | import reactHooksConfig from 'eslint-plugin-react-hooks' 3 | import reactRefresh from 'eslint-plugin-react-refresh' 4 | import globals from 'globals' 5 | import { GLOB_EXCLUDE, GLOB_JS, GLOB_JSX, GLOB_TS, GLOB_TSX } from '../globs' 6 | import { getConfigName } from '../shared/utils' 7 | import { type FlatESLintConfig, type FlatESLintRules } from '../types' 8 | import { tsParser, tsPlugin, typescript } from './typescript' 9 | 10 | export { reactPlugin } 11 | 12 | const reactCustomRules = { 13 | 'react/jsx-uses-react': 'error', 14 | 'react/jsx-uses-vars': 'error', 15 | } as unknown as FlatESLintRules 16 | 17 | export const react: FlatESLintConfig[] = [ 18 | { 19 | name: getConfigName('react'), 20 | settings: { 21 | react: { 22 | version: 'detect', 23 | }, 24 | }, 25 | files: [GLOB_JS, GLOB_JSX, GLOB_TS, GLOB_TSX], 26 | plugins: { 27 | react: reactPlugin, 28 | 'react-hooks': reactHooksConfig, 29 | 'react-refresh': reactRefresh, 30 | '@typescript-eslint': tsPlugin, 31 | }, 32 | languageOptions: { 33 | ecmaVersion: 'latest', 34 | parser: tsParser, 35 | parserOptions: { 36 | sourceType: 'module', 37 | ecmaFeatures: { 38 | jsx: true, 39 | }, 40 | }, 41 | globals: { 42 | ...globals.browser, 43 | }, 44 | }, 45 | rules: { 46 | ...typescript[0].rules, 47 | ...reactPlugin.configs.recommended.rules, 48 | ...reactHooksConfig.configs.recommended.rules, 49 | ...reactCustomRules, 50 | }, 51 | ignores: [...GLOB_EXCLUDE], 52 | }, 53 | ] 54 | -------------------------------------------------------------------------------- /packages/utils/src/performance.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Put the program to sleep for a while 3 | * 4 | * @category Performance 5 | */ 6 | export function sleep(ms: number): Promise { 7 | return new Promise((resolve) => { 8 | setTimeout(() => { 9 | resolve() 10 | }, ms) 11 | }) 12 | } 13 | 14 | /** 15 | * When an event is triggered frequently, only execute the event processing 16 | * function once. 17 | * 18 | * @category Performance 19 | */ 20 | 21 | export function debounce void>( 22 | func: T, 23 | wait = 200, 24 | ): (...args: Parameters) => void { 25 | let timeout: ReturnType | number 26 | 27 | return function (this: ThisParameterType, ...args: Parameters) { 28 | clearTimeout(timeout) 29 | timeout = setTimeout(() => { 30 | func.apply(this, args) 31 | }, wait) 32 | } 33 | } 34 | 35 | /** 36 | * Can control how often a function is called within a specified time interval 37 | * 38 | * @category Performance 39 | */ 40 | 41 | export function throttle void>( 42 | func: T, 43 | wait: number, 44 | ) { 45 | let timeout: ReturnType | number | undefined 46 | let previous = 0 47 | 48 | return function (this: ThisParameterType, ...args: Parameters) { 49 | const now = Date.now() 50 | const remaining = wait - (now - previous) 51 | 52 | if (remaining <= 0) { 53 | clearTimeout(timeout) 54 | previous = now 55 | func.apply(this, args) 56 | } else if (!timeout) { 57 | timeout = setTimeout(() => { 58 | previous = Date.now() 59 | timeout = undefined 60 | func.apply(this, args) 61 | }, remaining) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/release/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from '@bassist/utils' 2 | 3 | const GITHUB_URL = 'https://github.com/' 4 | const DETAILS_LABEL = 'CHANGELOG' 5 | 6 | // https://docs.npmjs.com/cli/v10/configuring-npm/package-json#repository 7 | interface Repository { 8 | type: string 9 | url: string 10 | directory: string 11 | } 12 | 13 | interface RepoInfo { 14 | repo: string 15 | directory: string 16 | } 17 | 18 | export function getRepo(repository?: Repository): RepoInfo | undefined { 19 | if (!repository || !isObject(repository)) return undefined 20 | const { url = '', directory = '' } = repository 21 | if (!url) return undefined 22 | 23 | if (url.startsWith('http')) { 24 | const repo = url.endsWith('.git') ? url.replace('.git', '') : url 25 | return { repo, directory } 26 | } 27 | 28 | if (url.startsWith('github:')) { 29 | const user = url.replace('github:', '') 30 | const repo = `${GITHUB_URL}${user}` 31 | return { repo, directory } 32 | } 33 | 34 | return undefined 35 | } 36 | 37 | function getTips(label = DETAILS_LABEL) { 38 | return ['Please refer to', label, 'for details.'].join(' ') 39 | } 40 | 41 | interface GetNotesOptions { 42 | repoInfo?: RepoInfo 43 | branch: string 44 | changelog: string 45 | } 46 | 47 | export function getNotes({ repoInfo, branch, changelog }: GetNotesOptions) { 48 | if (!repoInfo) return getTips() 49 | 50 | const url = [repoInfo.repo, 'blob', branch, repoInfo.directory, changelog] 51 | .filter((i) => !!i) 52 | .join('/') 53 | 54 | const label = `[${DETAILS_LABEL}](${url})` 55 | return getTips(label) 56 | } 57 | 58 | export function getName(name: string) { 59 | if (name.startsWith('@')) { 60 | const [, scopedName] = name.split('/') 61 | return scopedName 62 | } 63 | return name 64 | } 65 | -------------------------------------------------------------------------------- /packages/release/src/index.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'node:child_process' 2 | import { resolve } from 'node:path' 3 | import { fse } from '@bassist/node-utils' 4 | import minimist from '@withtypes/minimist' 5 | import pkg from '../package.json' 6 | import { getName, getNotes, getRepo } from './utils' 7 | 8 | async function run() { 9 | const argv = minimist(process.argv.slice(2), { 10 | string: ['_', 'branch', 'changelog'], 11 | alias: { 12 | branch: 'b', 13 | changelog: 'c', 14 | }, 15 | }) 16 | 17 | const { branch = 'main', changelog = 'CHANGELOG.md' } = argv 18 | 19 | const cwd = process.cwd() 20 | const pkgPath = resolve(cwd, './package.json') 21 | const pkgJson = fse.readJsonSync(pkgPath) || {} 22 | 23 | const { name, version, repository } = pkgJson 24 | if (!name) { 25 | throw new Error(`[${pkg.name}] Missing package name`) 26 | } 27 | if (!version) { 28 | throw new Error(`[${pkg.name}] Missing package version`) 29 | } 30 | if (!repository) { 31 | throw new Error(`[${pkg.name}] Missing package repository`) 32 | } 33 | 34 | const repoInfo = getRepo(repository) 35 | if (!repoInfo) { 36 | throw new Error(`[${pkg.name}] Unsupported repository information`) 37 | } 38 | 39 | const notes = getNotes({ 40 | repoInfo, 41 | branch, 42 | changelog, 43 | }) 44 | 45 | const isMonorepo = !!repoInfo.directory 46 | const pkgName = getName(name) 47 | const tagName = isMonorepo ? `${pkgName}@${version}` : `v${version}` 48 | 49 | const releaseArgs = [ 50 | 'gh --version', 51 | `git tag -a ${tagName} -m "${tagName}"`, 52 | `git push origin ${tagName}`, 53 | `gh release create ${tagName} --title "${tagName}" --notes "${notes}"`, 54 | ] 55 | 56 | const cmd = releaseArgs.join(' && ') 57 | execSync(cmd) 58 | } 59 | run().catch((e) => { 60 | console.log(e) 61 | }) 62 | -------------------------------------------------------------------------------- /packages/eslint-config/src/globs.ts: -------------------------------------------------------------------------------- 1 | export const GLOB_SRC_EXT = '?([cm])[jt]s?(x)' 2 | export const GLOB_SRC = '**/*.?([cm])[jt]s?(x)' 3 | 4 | export const GLOB_JS = '**/*.?([cm])js' 5 | export const GLOB_JSX = '**/*.?([cm])jsx' 6 | 7 | export const GLOB_TS = '**/*.?([cm])ts' 8 | export const GLOB_TSX = '**/*.?([cm])tsx' 9 | 10 | export const GLOB_STYLE = '**/*.{c,le,sc}ss' 11 | export const GLOB_CSS = '**/*.css' 12 | export const GLOB_LESS = '**/*.less' 13 | export const GLOB_SCSS = '**/*.scss' 14 | 15 | export const GLOB_JSON = '**/*.json' 16 | export const GLOB_JSON5 = '**/*.json5' 17 | export const GLOB_JSONC = '**/*.jsonc' 18 | 19 | export const GLOB_MARKDOWN = '**/*.md' 20 | export const GLOB_MARKDOWN_CODE = `${GLOB_MARKDOWN}/${GLOB_SRC}` 21 | 22 | export const GLOB_VUE = '**/*.vue' 23 | export const GLOB_SVELTE = '**/*.svelte' 24 | export const GLOB_YAML = '**/*.y?(a)ml' 25 | export const GLOB_HTML = '**/*.htm?(l)' 26 | 27 | export const GLOB_ALL_SRC = [ 28 | GLOB_SRC, 29 | GLOB_STYLE, 30 | GLOB_JSON, 31 | GLOB_JSON5, 32 | GLOB_MARKDOWN, 33 | GLOB_VUE, 34 | GLOB_YAML, 35 | GLOB_HTML, 36 | ] as const 37 | 38 | export const GLOB_NODE_MODULES = '**/node_modules' 39 | 40 | export const GLOB_DIST = '**/dist' 41 | 42 | export const GLOB_LOCKFILE = [ 43 | '**/package-lock.json', 44 | '**/yarn.lock', 45 | '**/pnpm-lock.yaml', 46 | ] as const 47 | 48 | export const GLOB_EXCLUDE = [ 49 | GLOB_NODE_MODULES, 50 | GLOB_DIST, 51 | ...GLOB_LOCKFILE, 52 | 53 | '**/output', 54 | '**/coverage', 55 | '**/temp', 56 | '**/fixtures', 57 | '**/.vitepress/cache', 58 | '**/.nuxt', 59 | '**/.vercel', 60 | '**/.changeset', 61 | '**/.idea', 62 | '**/.output', 63 | '**/.vite-inspect', 64 | '**/.svelte-kit', 65 | 66 | '**/CHANGELOG*.md', 67 | '**/*.min.*', 68 | '**/LICENSE*', 69 | '**/__snapshots__', 70 | '**/auto-import?(s).d.ts', 71 | '**/components.d.ts', 72 | ] as const 73 | -------------------------------------------------------------------------------- /packages/tsconfig/README.md: -------------------------------------------------------------------------------- 1 | # @bassist/tsconfig 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | GitHub stars 15 | 16 |

17 | 18 | Some TSConfig files for working with TypeScript projects by [@chengpeiquan](https://github.com/chengpeiquan) . 19 | 20 | ## Usage 21 | 22 | With npm(or yarn, or pnpm): 23 | 24 | ```bash 25 | npm install -D @bassist/tsconfig 26 | ``` 27 | 28 | In the `tsconfig.json` file, use the `extends` field to extends these configuration. 29 | 30 | ```json 31 | { 32 | "extends": "@bassist/tsconfig/base.json", 33 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] 34 | } 35 | ``` 36 | 37 | ## Extendable 38 | 39 | Base: 40 | 41 | ```json 42 | { 43 | "extends": "@bassist/tsconfig/base.json" 44 | } 45 | ``` 46 | 47 | Web: 48 | 49 | ```json 50 | { 51 | "extends": "@bassist/tsconfig/web.json" 52 | } 53 | ``` 54 | 55 | Node: 56 | 57 | ```json 58 | { 59 | "extends": "@bassist/tsconfig/node.json" 60 | } 61 | ``` 62 | 63 | ## Release Notes 64 | 65 | Please refer to [CHANGELOG](https://github.com/chengpeiquan/bassist/blob/main/packages/tsconfig/CHANGELOG.md) for details. 66 | 67 | ## License 68 | 69 | MIT License © 2023-PRESENT [chengpeiquan](https://github.com/chengpeiquan) 70 | -------------------------------------------------------------------------------- /packages/utils/README.md: -------------------------------------------------------------------------------- 1 | # @bassist/utils 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | GitHub stars 15 | 16 |

17 | 18 | Opinionated collection of common JavaScript / TypeScript utils by [@chengpeiquan](https://github.com/chengpeiquan) . 19 | 20 | - 🌳 Fully tree shakeable 21 | - 💪 Type Strong 22 | - 💡 No bundler required 23 | - 🦄 SSR Friendly 24 | 25 | ## Usage 26 | 27 | With npm(or yarn, or pnpm): 28 | 29 | ```bash 30 | npm install @bassist/utils 31 | ``` 32 | 33 | In `.js` / `.ts` or other files: 34 | 35 | ```ts 36 | import { isMobile } from '@bassist/utils' 37 | 38 | if (isMobile()) { 39 | // do something... 40 | } 41 | ``` 42 | 43 | With CDN: 44 | 45 | ```html 46 | 47 | 55 | ``` 56 | 57 | ## Documentation 58 | 59 | See: [Documentation of utils](https://jsdocs.io/package/@bassist/utils) 60 | 61 | ## Release Notes 62 | 63 | Please refer to [CHANGELOG](https://github.com/chengpeiquan/bassist/blob/main/packages/utils/CHANGELOG.md) for details. 64 | 65 | ## License 66 | 67 | MIT License © 2022-PRESENT [chengpeiquan](https://github.com/chengpeiquan) 68 | -------------------------------------------------------------------------------- /packages/utils/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Browser Test 8 | 9 | 10 | 11 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /packages/node-utils/README.md: -------------------------------------------------------------------------------- 1 | # @bassist/node-utils 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | GitHub stars 15 | 16 |

17 | 18 | Opinionated collection of common Node.js utils by [@chengpeiquan](https://github.com/chengpeiquan) . 19 | 20 | - 🌳 Fully tree shakeable 21 | - 💪 Type Strong 22 | - ⚡ Simplify complex operations 23 | - 🚀 Built-in [fs-extra](https://github.com/jprichardson/node-fs-extra) 24 | 25 | > Note: This package is only for use in Node.js, don't use it in the browser. 26 | 27 | ## Usage 28 | 29 | With npm(or yarn, or pnpm): 30 | 31 | ```bash 32 | npm install @bassist/node-utils 33 | ``` 34 | 35 | In `.js` / `.ts` or other files: 36 | 37 | ```ts 38 | import { getPackageManagerByUserAgent } from '@bassist/node-utils' 39 | 40 | console.log(getPackageManagerByUserAgent()) 41 | // { name: 'pnpm', version: '7.26.0' } 42 | ``` 43 | 44 | ## Documentation 45 | 46 | See: [Documentation of node-utils](https://jsdocs.io/package/@bassist/node-utils) 47 | 48 | About `fse` API, see: [fs-extra](https://github.com/jprichardson/node-fs-extra) . 49 | 50 | ## Release Notes 51 | 52 | Please refer to [CHANGELOG](https://github.com/chengpeiquan/bassist/blob/main/packages/node-utils/CHANGELOG.md) for details. 53 | 54 | ## License 55 | 56 | MIT License © 2022-PRESENT [chengpeiquan](https://github.com/chengpeiquan) 57 | -------------------------------------------------------------------------------- /packages/utils/test/query.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { extractQueryInfo, getQuery, parseQuery, stringifyQuery } from '..' 3 | 4 | describe('query', () => { 5 | it('parseQuery', () => { 6 | expect(parseQuery('https://example.com/?a=1&b=2')).toStrictEqual({ 7 | a: '1', 8 | b: '2', 9 | }) 10 | 11 | expect(parseQuery('https://example.com/?a= &b=')).toStrictEqual({ 12 | a: ' ', 13 | b: '', 14 | }) 15 | 16 | expect( 17 | parseQuery('https://example.com/?url=https%3A%2F%2Fexample.com'), 18 | ).toStrictEqual({ 19 | url: 'https://example.com', 20 | }) 21 | 22 | expect(parseQuery('https://example.com/?nums=1,2,3')).toStrictEqual({ 23 | nums: '1,2,3', 24 | }) 25 | 26 | expect(parseQuery('https://example.com/#/foo?a=1&b=2')).toStrictEqual({ 27 | a: '1', 28 | b: '2', 29 | }) 30 | 31 | expect(parseQuery('https://example.com/?a=1&b=2#/foo')).toStrictEqual({ 32 | a: '1', 33 | b: '2', 34 | }) 35 | }) 36 | 37 | it('extractQueryInfo', () => { 38 | expect( 39 | extractQueryInfo('https://example.com/?path=%2Ffoo&a=1&b=2'), 40 | ).toStrictEqual({ 41 | path: '/foo', 42 | params: { 43 | a: '1', 44 | b: '2', 45 | }, 46 | }) 47 | 48 | expect(extractQueryInfo('https://example.com/?a=1&b=2')).toStrictEqual({ 49 | path: '', 50 | params: { 51 | a: '1', 52 | b: '2', 53 | }, 54 | }) 55 | }) 56 | 57 | it('getQuery', () => { 58 | expect(getQuery('b', 'https://example.com/?a=1&b=2')).toBe('2') 59 | 60 | expect( 61 | getQuery('url', 'https://example.com/?url=https%3A%2F%2Fexample.com'), 62 | ).toBe('https://example.com') 63 | }) 64 | 65 | it('stringifyQuery', () => { 66 | expect( 67 | stringifyQuery({ 68 | a: 1, 69 | b: 2, 70 | }), 71 | ).toBe('a=1&b=2') 72 | 73 | expect( 74 | stringifyQuery({ 75 | url: 'https://example.com', 76 | }), 77 | ).toBe('url=https%3A%2F%2Fexample.com') 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /packages/eslint-config/src/private-configs/tailwindcss.ts: -------------------------------------------------------------------------------- 1 | import tailwindcssPlugin from 'eslint-plugin-tailwindcss' 2 | import { getConfigName } from '../shared/utils' 3 | import { type FlatESLintConfig } from '../types' 4 | 5 | export { tailwindcssPlugin } 6 | 7 | const recommendedConfigs = tailwindcssPlugin.configs[ 8 | 'flat/recommended' 9 | ] as FlatESLintConfig[] 10 | 11 | export interface TailwindcssSettings { 12 | callees?: string[] 13 | config?: string 14 | cssFiles?: string[] 15 | cssFilesRefreshRate?: number 16 | removeDuplicates?: boolean 17 | skipClassAttribute?: boolean 18 | whitelist?: string[] 19 | tags?: string[] 20 | classRegex?: string 21 | } 22 | 23 | export const defaultTailwindcssSettings: TailwindcssSettings = { 24 | callees: ['cn', 'classNames', 'clsx'], 25 | 26 | // `tailwind.config.ts` is no longer supported 27 | // https://github.com/tailwindlabs/tailwindcss/discussions/15352 28 | config: 'tailwind.config.js', // returned from `loadConfig()` utility if not provided 29 | 30 | cssFiles: ['!**/node_modules', '!**/.*', '!**/dist'], 31 | cssFilesRefreshRate: 5_000, 32 | removeDuplicates: true, 33 | skipClassAttribute: false, 34 | whitelist: ['-webkit-box'], 35 | tags: [], // can be set to e.g. ['tw'] for use in tw`bg-blue` 36 | classRegex: '^class(Name)?$', // can be modified to support custom attributes. E.g. "^tw$" for `twin.macro` 37 | } 38 | 39 | const mergeSettings = (inputSettings: TailwindcssSettings) => { 40 | if (inputSettings && Object.keys(inputSettings).length > 0) { 41 | return { 42 | ...defaultTailwindcssSettings, 43 | ...inputSettings, 44 | } 45 | } 46 | 47 | return { ...defaultTailwindcssSettings } 48 | } 49 | 50 | export const createTailwindcssConfig = ( 51 | inputSettings: TailwindcssSettings = {}, 52 | ) => { 53 | const resolvedSettings = mergeSettings(inputSettings) 54 | 55 | const tailwindcss: FlatESLintConfig[] = [ 56 | ...recommendedConfigs, 57 | { 58 | name: getConfigName('tailwindcss', 'settings'), 59 | settings: { 60 | tailwindcss: resolvedSettings, 61 | }, 62 | }, 63 | ] 64 | 65 | return tailwindcss 66 | } 67 | -------------------------------------------------------------------------------- /packages/utils/src/random.ts: -------------------------------------------------------------------------------- 1 | import { desktopUserAgents, mobileUserAgents, userAgents } from './ua' 2 | 3 | /** 4 | * Generate random number 5 | * 6 | * @category Random 7 | * @param min - The minimum value in the range 8 | * @param max - The maximum value in the range 9 | * @param roundingType - Round the result 10 | */ 11 | export function randomNumber( 12 | min = 0, 13 | max = 100, 14 | roundingType: 'round' | 'ceil' | 'floor' = 'round', 15 | ) { 16 | return Math[roundingType](Math.random() * (max - min) + min) 17 | } 18 | 19 | /** 20 | * Generate random string 21 | * 22 | * @category Random 23 | * @param length - The length of the string to be returned 24 | */ 25 | export function randomString(length = 10) { 26 | // https://github.com/ai/nanoid/blob/main/url-alphabet/index.js 27 | const dict = 28 | 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' 29 | 30 | let str = '' 31 | let i = length 32 | const len = dict.length 33 | while (i--) str += dict[(Math.random() * len) | 0] 34 | return str 35 | } 36 | 37 | /** 38 | * Generate random boolean 39 | * 40 | * @category Random 41 | */ 42 | export function randomBoolean() { 43 | const index = randomNumber(0, 1) 44 | return [true, false][index] 45 | } 46 | 47 | /** 48 | * Shuffle the array and sort it randomly 49 | * 50 | * @category Random 51 | */ 52 | export function shuffle(arr: any[]): any[] { 53 | if (!Array.isArray(arr)) return arr 54 | 55 | for (let i = arr.length - 1; i > 0; i--) { 56 | const j: number = Math.floor(Math.random() * (i + 1)) 57 | const item: any = arr[i] 58 | arr[i] = arr[j] 59 | arr[j] = item 60 | } 61 | 62 | return arr 63 | } 64 | 65 | /** 66 | * Randomly returns a mocked UA 67 | * 68 | * @category Random 69 | * @param device - Get a random UA on the specified device type 70 | */ 71 | export function randomUserAgent(device?: 'desktop' | 'mobile') { 72 | const uaList = 73 | device === 'desktop' 74 | ? desktopUserAgents 75 | : device === 'mobile' 76 | ? mobileUserAgents 77 | : userAgents 78 | 79 | const index = randomNumber(0, uaList.length - 1) 80 | return uaList[index] 81 | } 82 | -------------------------------------------------------------------------------- /packages/utils/src/storage/fallback.ts: -------------------------------------------------------------------------------- 1 | import { hasKey } from '../data' 2 | 3 | // A record to store instances of FallbackStorage based on their prefix 4 | const fallbackStorageRecord: Record = {} 5 | 6 | /** 7 | * FallbackStorage class provides a fallback storage implementation when running 8 | * outside the browser environment 9 | * 10 | * @category Storage 11 | */ 12 | export class FallbackStorage { 13 | private data: Record 14 | 15 | /** 16 | * Creates an instance of FallbackStorage. 17 | * 18 | * @param prefix - The prefix to be added to the storage keys. 19 | */ 20 | constructor(prefix: string) { 21 | this.data = {} 22 | 23 | const hasRecord = hasKey(fallbackStorageRecord, prefix) 24 | 25 | // If a record with the same prefix exists, use its data 26 | // Otherwise, store the current instance in the record 27 | this.data = hasRecord ? fallbackStorageRecord[prefix].data : {} 28 | 29 | if (!hasRecord) { 30 | fallbackStorageRecord[prefix] = this 31 | } 32 | } 33 | 34 | /** 35 | * Gets the number of stored items 36 | */ 37 | get length() { 38 | return Object.keys(this.data).length 39 | } 40 | 41 | /** 42 | * Clears all stored items. 43 | */ 44 | clear() { 45 | this.data = {} 46 | } 47 | 48 | /** 49 | * Retrieves the value associated with the specified key 50 | */ 51 | getItem(key: string) { 52 | if (hasKey(this.data, key)) { 53 | return this.data[key] 54 | } 55 | return null 56 | } 57 | 58 | /** 59 | * Sets the value for the specified key. 60 | */ 61 | setItem(key: string, value: string) { 62 | this.data[key] = value 63 | } 64 | 65 | /** 66 | * Removes the item associated with the specified key. 67 | */ 68 | removeItem(key: string) { 69 | if (hasKey(this.data, key)) { 70 | // eslint-disable-next-line @typescript-eslint/no-dynamic-delete 71 | delete this.data[key] 72 | } 73 | } 74 | 75 | /** 76 | * Retrieves the key at the specified index. 77 | */ 78 | key(index: number) { 79 | const keys = Object.keys(this.data) 80 | if (index > keys.length) return null 81 | return keys[index] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/typescript.ts: -------------------------------------------------------------------------------- 1 | import _tsPlugin from '@typescript-eslint/eslint-plugin' 2 | import __tsParser from '@typescript-eslint/parser' 3 | import { GLOB_TS, GLOB_TSX } from '../globs' 4 | import { getConfigName } from '../shared/utils' 5 | import { 6 | type FlatESLintConfig, 7 | type FlatESLintParser, 8 | type FlatESLintPlugin, 9 | } from '../types' 10 | 11 | const tsParser = __tsParser as FlatESLintParser 12 | const tsPlugin = _tsPlugin as unknown as FlatESLintPlugin 13 | 14 | export { tsParser, tsPlugin } 15 | 16 | const tsPluginConfigs = _tsPlugin.configs 17 | const recommendedConfigs = tsPluginConfigs['eslint-recommended'] 18 | 19 | export const typescript: FlatESLintConfig[] = [ 20 | { 21 | name: getConfigName('typescript', 'base'), 22 | files: [GLOB_TS, GLOB_TSX], 23 | languageOptions: { 24 | parser: tsParser, 25 | parserOptions: { 26 | sourceType: 'module', 27 | }, 28 | }, 29 | plugins: { 30 | '@typescript-eslint': tsPlugin, 31 | }, 32 | rules: { 33 | ...recommendedConfigs.overrides![0].rules, 34 | ...tsPluginConfigs.strict.rules, 35 | '@typescript-eslint/no-redeclare': 'error', 36 | '@typescript-eslint/ban-ts-comment': 'off', 37 | '@typescript-eslint/ban-types': 'off', 38 | '@typescript-eslint/consistent-type-imports': [ 39 | 'error', 40 | { 41 | fixStyle: 'inline-type-imports', 42 | disallowTypeAnnotations: false, 43 | }, 44 | ], 45 | '@typescript-eslint/no-explicit-any': 'off', 46 | '@typescript-eslint/no-non-null-assertion': 'off', 47 | '@typescript-eslint/prefer-as-const': 'warn', 48 | }, 49 | }, 50 | { 51 | name: getConfigName('typescript', 'declaration'), 52 | files: ['**/*.d.ts'], 53 | rules: { 54 | 'import/no-duplicates': 'off', 55 | }, 56 | }, 57 | { 58 | name: getConfigName('typescript', 'test'), 59 | files: ['**/*.{test,spec}.ts?(x)'], 60 | rules: { 61 | 'no-unused-expressions': 'off', 62 | }, 63 | }, 64 | { 65 | name: getConfigName('typescript', 'js-compat'), 66 | files: ['**/*.js', '**/*.cjs'], 67 | rules: { 68 | '@typescript-eslint/no-var-requires': 'off', 69 | }, 70 | }, 71 | ] 72 | -------------------------------------------------------------------------------- /packages/eslint-config/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [0.3.0](https://github.com/chengpeiquan/bassist/compare/eslint-config@0.2.0...eslint-config@0.3.0) (2025-07-27) 2 | 3 | 4 | ### Features 5 | 6 | * **eslint-config:** upgrade dependencies and add pangu space formatting support for markdown ([499f888](https://github.com/chengpeiquan/bassist/commit/499f888a88f3abe8eb03c09655f36282481ec5b9)) 7 | 8 | 9 | 10 | # [0.2.0](https://github.com/chengpeiquan/bassist/compare/eslint-config@0.1.3...eslint-config@0.2.0) (2025-07-06) 11 | 12 | 13 | ### Features 14 | 15 | * **eslint-config:** add built-in Prettier rules ([0b88636](https://github.com/chengpeiquan/bassist/commit/0b8863628fd7751fb3ec810d1204f2d40a462e02)) 16 | 17 | 18 | 19 | ## [0.1.3](https://github.com/chengpeiquan/bassist/compare/eslint-config@0.1.2...eslint-config@0.1.3) (2025-05-22) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * **eslint-config:** update deps ([e6c47c7](https://github.com/chengpeiquan/bassist/commit/e6c47c70a6c51c757d9f958d9711f2da597f3ee4)) 25 | 26 | 27 | 28 | ## [0.1.2](https://github.com/chengpeiquan/bassist/compare/eslint-config@0.1.1...eslint-config@0.1.2) (2025-03-09) 29 | 30 | 31 | ### Bug Fixes 32 | 33 | * **eslint-config:** export createGetConfigNameFactory ([2967b04](https://github.com/chengpeiquan/bassist/commit/2967b0443ea7f9c787d48d857132b5ece5142dc4)) 34 | * **eslint-config:** no need for prettier sort import built-in, which will cause global lint errors ([419b065](https://github.com/chengpeiquan/bassist/commit/419b065dbcff242a742702aa9ef11fbc7266d7dd)) 35 | * **eslint-config:** use eslint-plugin-import-x instead of eslint-plugin-import ([9bafd37](https://github.com/chengpeiquan/bassist/commit/9bafd371e31795bff8b393dc2afd46b3a2901918)) 36 | 37 | 38 | 39 | ## [0.1.1](https://github.com/chengpeiquan/bassist/compare/eslint-config@0.1.0...eslint-config@0.1.1) (2025-03-09) 40 | 41 | 42 | ### Bug Fixes 43 | 44 | * **eslint-config:** missing javascript config name ([a817534](https://github.com/chengpeiquan/bassist/commit/a817534cf4f5351ee21f4fdf7a6b366ce960e49f)) 45 | 46 | 47 | 48 | # 0.1.0 (2025-03-08) 49 | 50 | 51 | ### Features 52 | 53 | * **eslint-config:** add flat configurations for ESLint v9 ([3f649fb](https://github.com/chengpeiquan/bassist/commit/3f649fb55b7eee40b1e8fe79a4820b9f3a5ffde0)) 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /packages/utils/src/appearance.ts: -------------------------------------------------------------------------------- 1 | import { isBrowser } from './device' 2 | 3 | /** 4 | * The preferred color scheme for the appearance of the user interface 5 | * 6 | * @category Appearance 7 | */ 8 | export type PrefersColorScheme = 'light' | 'dark' 9 | 10 | /** 11 | * Dark mode media query 12 | * 13 | * @category Appearance 14 | */ 15 | export const darkMediaQuery = isBrowser 16 | ? window.matchMedia('(prefers-color-scheme: dark)') 17 | : undefined 18 | 19 | /** 20 | * Checks if the user's preferred color scheme is dark 21 | * 22 | * @category Appearance 23 | */ 24 | export function isDark() { 25 | if (!isBrowser) return false 26 | return darkMediaQuery!.matches 27 | } 28 | 29 | /** 30 | * Light mode media query 31 | * 32 | * @category Appearance 33 | */ 34 | export const lightMediaQuery = isBrowser 35 | ? window.matchMedia('(prefers-color-scheme: light)') 36 | : undefined 37 | 38 | /** 39 | * Checks if the user's preferred color scheme is light 40 | * 41 | * @category Appearance 42 | */ 43 | export function isLight() { 44 | if (!isBrowser) return false 45 | return lightMediaQuery!.matches 46 | } 47 | 48 | /** 49 | * Retrieves the user's preferred color scheme 50 | * 51 | * @category Appearance 52 | */ 53 | export function getPrefersColorScheme(): PrefersColorScheme | undefined { 54 | if (isDark()) return 'dark' 55 | if (isLight()) return 'light' 56 | return undefined 57 | } 58 | 59 | /** 60 | * Portrait orientation media query 61 | * 62 | * @category Appearance 63 | */ 64 | export const portraitMediaQuery = isBrowser 65 | ? window.matchMedia('(orientation: portrait)') 66 | : undefined 67 | 68 | /** 69 | * Check whether the current screen is in portrait mode 70 | * 71 | * @category Appearance 72 | */ 73 | export function isPortrait() { 74 | if (!isBrowser) return false 75 | return portraitMediaQuery!.matches 76 | } 77 | 78 | /** 79 | * Landscape orientation media query 80 | * 81 | * @category Appearance 82 | */ 83 | export const landscapeMediaQuery = isBrowser 84 | ? window.matchMedia('(orientation: landscape)') 85 | : undefined 86 | 87 | /** 88 | * Check whether the current screen is in landscape mode 89 | * 90 | * @category Appearance 91 | */ 92 | export function isLandscape() { 93 | if (!isBrowser) return false 94 | return landscapeMediaQuery!.matches 95 | } 96 | -------------------------------------------------------------------------------- /packages/eslint-config/src/shared/prettier-config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('../types').PartialPrettierExtendedOptions} PartialPrettierExtendedOptions 3 | */ 4 | 5 | /** 6 | * Prettier Config Plugins 7 | * 8 | * @type {NonNullable} 9 | */ 10 | export const defaultPrettierPlugins = [ 11 | 'prettier-plugin-jsdoc', 12 | 'prettier-plugin-lint-md', 13 | ] 14 | 15 | /** 16 | * Prettier Config 17 | * 18 | * @type {PartialPrettierExtendedOptions} 19 | */ 20 | export default { 21 | /** 22 | * Prettier Options 23 | * 24 | * @see https://prettier.io/docs/options 25 | */ 26 | 27 | // Maximum 80 characters per line 28 | printWidth: 80, 29 | // Use 2 spaces for indentation 30 | tabWidth: 2, 31 | // Use spaces instead of tabs for indentation 32 | useTabs: false, 33 | // Add semicolons at the end of statements 34 | semi: false, 35 | // Use single quotes instead of double quotes 36 | singleQuote: true, 37 | // Only quote object properties when necessary 38 | quoteProps: 'as-needed', 39 | // Use double quotes instead of single quotes in JSX 40 | jsxSingleQuote: false, 41 | // Add trailing commas wherever possible 42 | trailingComma: 'all', 43 | // Add spaces between brackets in object literals 44 | bracketSpacing: true, 45 | // Put the > of JSX elements at the end of the last line 46 | jsxBracketSameLine: false, 47 | // Always include parentheses around arrow function parameters 48 | arrowParens: 'always', 49 | // Format the entire file content 50 | rangeStart: 0, 51 | rangeEnd: Infinity, 52 | // Don't require @prettier pragma at the beginning of files 53 | requirePragma: false, 54 | // Don't automatically insert @prettier pragma at the beginning of files 55 | insertPragma: false, 56 | // Use default prose wrapping standard 57 | proseWrap: 'preserve', 58 | // Determine HTML line breaks based on display style 59 | htmlWhitespaceSensitivity: 'css', 60 | // Use LF line endings 61 | endOfLine: 'lf', 62 | // Explicitly specify plugins here to ensure Prettier loads custom plugins 63 | // (like jsdoc and lint-md) when run directly via CLI. 64 | plugins: [...defaultPrettierPlugins], 65 | 66 | /** 67 | * JSDoc Options 68 | * 69 | * @see https://github.com/hosseinmd/prettier-plugin-jsdoc#options 70 | */ 71 | 72 | jsdocCommentLineStrategy: 'multiline', 73 | } 74 | -------------------------------------------------------------------------------- /packages/utils/src/clipboard/index.ts: -------------------------------------------------------------------------------- 1 | import { isBrowser } from '../device' 2 | import { fallbackReadText, fallbackWriteText } from './fallback' 3 | 4 | /** 5 | * @category Interactive 6 | */ 7 | export type WritableElement = HTMLInputElement | HTMLTextAreaElement 8 | 9 | /** 10 | * @category Interactive 11 | */ 12 | export type CopyableElement = HTMLElement | WritableElement 13 | 14 | /** 15 | * Extensions based on the Clipboard API and with fallback mechanism 16 | * 17 | * @category Interactive 18 | */ 19 | export class ClipboardInstance { 20 | /** 21 | * Determine whether the clipboard is supported 22 | */ 23 | isSupported: boolean 24 | 25 | constructor() { 26 | this.isSupported = !isBrowser 27 | ? false 28 | : !!navigator.clipboard || !!document.execCommand 29 | } 30 | 31 | /** 32 | * Copy the text passed in or the text of the specified DOM element 33 | * 34 | * @example 35 | * ;``` 36 | * clipboard.copy(document.querySelector('.foo')) 37 | * ``` 38 | */ 39 | async copy(el: CopyableElement) { 40 | if (!this.isSupported) return false 41 | const clipText = el.innerText || (el as WritableElement).value 42 | return await this.write(clipText) 43 | } 44 | 45 | /** 46 | * Cut the text passed in or the text of the specified DOM element 47 | * 48 | * @example 49 | * ;``` 50 | * clipboard.cut(document.querySelector('.foo')) 51 | * ``` 52 | */ 53 | async cut(el: WritableElement) { 54 | if (!this.isSupported) return false 55 | const isOk = await this.copy(el) 56 | if (!isOk) return false 57 | el.value = '' 58 | return true 59 | } 60 | 61 | /** 62 | * Read the text content of the clipboard 63 | */ 64 | async read() { 65 | if (!this.isSupported) return '' 66 | try { 67 | return await navigator!.clipboard.readText() 68 | } catch { 69 | return fallbackReadText() 70 | } 71 | } 72 | 73 | /** 74 | * Write text content to clipboard 75 | */ 76 | async write(text: string) { 77 | if (!this.isSupported) return false 78 | try { 79 | await navigator!.clipboard.writeText(text) 80 | return true 81 | } catch { 82 | return fallbackWriteText(text) 83 | } 84 | } 85 | } 86 | 87 | /** 88 | * Initialized Clipboard Instance 89 | * 90 | * @category Interactive 91 | */ 92 | export const clipboard = new ClipboardInstance() 93 | -------------------------------------------------------------------------------- /packages/build-config/README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # @bassist/build-config 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | GitHub stars 15 | 16 |

17 | 18 | 一些常用的构建工具配置,由 [@chengpeiquan](https://github.com/chengpeiquan) 精心打造。 19 | 20 | 目前提供了以下构建工具的配置: 21 | 22 | - [tsup](https://github.com/egoist/tsup): 基于 ESBuild,构建 TypeScript 库最便捷的工具 23 | 24 | ## 🤔 为什么需要这个? 25 | 26 | 单一仓库使用某些工具的配置确实已经足够方便,但如果有很多个仓库都用相似的配置,这个过程就会显得繁琐。 27 | 28 | 把一些常用的工具配置抽象出来共享,不同的项目可以更快完成配置。 29 | 30 | ## ⚡ 用法:基于 tsup 31 | 32 | - 🎯 **预设配置**: 提供开箱即用的 tsup 基础配置 33 | - 📦 **多格式支持**: 默认支持 CommonJS 和 ESM 格式 34 | - 🏷️ **自动 Banner**: 根据 package.json 自动生成文件头注释 35 | - 🧹 **自动清理**: 构建前自动清理输出目录 36 | - 📝 **类型声明**: 自动生成 TypeScript 类型声明文件 37 | - ⚡ **代码压缩**: 内置代码压缩功能 38 | 39 | ### 安装 40 | 41 | 使用常用的包管理器安装该包: 42 | 43 | ```bash 44 | npm install -D @bassist/build-config tsup 45 | ``` 46 | 47 | **注意**: 该子模块需要 tsup 作为 peer dependency ,如上方安装命令,请确保已安装 tsup 。 48 | 49 | ### 用法 50 | 51 | 统一由 `@bassist/build-config/tsup` 提供所有 API 。 52 | 53 | 通常情况下进需要直接使用 `createBaseConfig` 返回的配置,可视情况传入自定义选项: 54 | 55 | ```ts 56 | import { createBaseConfig } from '@bassist/build-config/tsup' 57 | import { defineConfig } from 'tsup' 58 | import pkg from './package.json' 59 | 60 | const config = createBaseConfig({ pkg }) 61 | 62 | export default defineConfig(config) 63 | ``` 64 | 65 | 如果配置项不满足,可以通过对象解构与合并,自行传给 `defineConfig` API 。 66 | 67 | 更多 API 和配置项请查看源码 [tsup.ts](https://github.com/chengpeiquan/bassist/blob/main/packages/build-config/src/tsup.ts) 。 68 | 69 | ## 📝 发布日志 70 | 71 | 详细更新内容请参考 [CHANGELOG](https://github.com/chengpeiquan/bassist/blob/main/packages/build-config/CHANGELOG.md) 。 72 | 73 | ## 📜 License 74 | 75 | MIT License © 2025-PRESENT [chengpeiquan](https://github.com/chengpeiquan) 76 | -------------------------------------------------------------------------------- /packages/eslint-config/src/private-configs/prettier.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'node:fs' 2 | import { join } from 'node:path' 3 | import prettierConfig from 'eslint-config-prettier' 4 | import prettierPlugin from 'eslint-plugin-prettier' 5 | import { GLOB_EXCLUDE } from '../globs' 6 | import defaultPrettierConfig, { 7 | defaultPrettierPlugins, 8 | } from '../shared/prettier-config.mjs' 9 | import { getConfigName } from '../shared/utils' 10 | import { 11 | type PartialPrettierExtendedOptions, 12 | type FlatESLintConfig, 13 | } from '../types' 14 | 15 | export { prettierPlugin } 16 | 17 | const isValidPrettierRules = ( 18 | prettierRules: unknown, 19 | ): prettierRules is PartialPrettierExtendedOptions => { 20 | return Object.prototype.toString.call(prettierRules) === '[object Object]' 21 | } 22 | 23 | const loadPrettierConfig = (cwd: string): PartialPrettierExtendedOptions => { 24 | try { 25 | const prettierrc = readFileSync(join(cwd, '.prettierrc'), 'utf-8') 26 | return prettierrc ? JSON.parse(prettierrc) : {} 27 | } catch { 28 | return { ...defaultPrettierConfig } 29 | } 30 | } 31 | 32 | const loadPrettierIgnore = (cwd: string): string[] => { 33 | try { 34 | const prettierignore = readFileSync(join(cwd, '.prettierignore'), 'utf-8') 35 | return prettierignore 36 | .split('\n') 37 | .map((line) => line.trim()) 38 | .filter((line) => line && !line.startsWith('#')) 39 | } catch { 40 | return [] 41 | } 42 | } 43 | 44 | export const createPrettierConfig = ( 45 | cwd: string, 46 | inputConfig?: PartialPrettierExtendedOptions, 47 | ) => { 48 | const resolvedConfig = isValidPrettierRules(inputConfig) 49 | ? inputConfig 50 | : loadPrettierConfig(cwd) 51 | 52 | const { plugins = [] } = resolvedConfig 53 | 54 | const finalPrettierConfig: PartialPrettierExtendedOptions = { 55 | ...resolvedConfig, 56 | 57 | // Ensure plugins are unique to avoid duplicate loading 58 | plugins: Array.from(new Set([...plugins, ...defaultPrettierPlugins])), 59 | } 60 | 61 | const resolvedIgnore = loadPrettierIgnore(cwd) 62 | 63 | const finalIgnore = Array.from(new Set([...GLOB_EXCLUDE, ...resolvedIgnore])) 64 | 65 | const prettier: FlatESLintConfig[] = [ 66 | { 67 | name: getConfigName('prettier'), 68 | plugins: { 69 | prettier: prettierPlugin, 70 | }, 71 | rules: { 72 | ...prettierConfig.rules, 73 | 'prettier/prettier': ['warn', finalPrettierConfig], 74 | 'arrow-body-style': 'off', 75 | 'prefer-arrow-callback': 'off', 76 | }, 77 | ignores: finalIgnore, 78 | }, 79 | ] 80 | 81 | return prettier 82 | } 83 | -------------------------------------------------------------------------------- /packages/utils/test/performance.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Mock, 3 | afterEach, 4 | beforeEach, 5 | describe, 6 | expect, 7 | it, 8 | vi, 9 | } from 'vitest' 10 | import { sleep, debounce, throttle } from '..' 11 | 12 | const mockFn = vi.fn() 13 | 14 | function advanceTimersByTime(ms: number) { 15 | return new Promise((resolve) => { 16 | setTimeout(resolve, ms) 17 | vi.advanceTimersByTime(ms) 18 | }) 19 | } 20 | 21 | describe('performance', () => { 22 | it('sleep', async () => { 23 | async function diff(ms: number) { 24 | const start = Date.now() 25 | await sleep(ms) 26 | const end = Date.now() 27 | return end - start > ms 28 | } 29 | 30 | expect(await diff(0)).toBeTruthy() 31 | expect(await diff(500)).toBeTruthy() 32 | expect(await diff(2000)).toBeTruthy() 33 | expect(await diff(3000)).toBeTruthy() 34 | }, 100000) 35 | 36 | it('debounce', async () => { 37 | const delayedFn = debounce(mockFn, 100) 38 | 39 | it('should delay execution by specified time', async () => { 40 | delayedFn() 41 | expect(mockFn).not.toBeCalled() 42 | await advanceTimersByTime(100) 43 | expect(mockFn).toBeCalled() 44 | }) 45 | 46 | it('should execute only the last call when called continuously', async () => { 47 | delayedFn() 48 | delayedFn() 49 | delayedFn() 50 | await advanceTimersByTime(100) 51 | expect(mockFn).toHaveBeenCalledTimes(1) 52 | }) 53 | 54 | it('should pass arguments to the debounced function', async () => { 55 | delayedFn(1, 2, 3) 56 | await advanceTimersByTime(100) 57 | expect(mockFn).toBeCalledWith(1, 2, 3) 58 | }) 59 | }) 60 | 61 | it('throttle', async () => { 62 | let testFunction: Mock 63 | let throttledFunction: (this: unknown, ...args: any) => void 64 | 65 | beforeEach(() => { 66 | testFunction = vi.fn() 67 | throttledFunction = throttle(testFunction, 1000) 68 | vi.useFakeTimers() 69 | }) 70 | 71 | afterEach(() => { 72 | vi.clearAllTimers() 73 | }) 74 | 75 | it('should call the function immediately when called for the first time', () => { 76 | throttledFunction() 77 | expect(testFunction).toHaveBeenCalledTimes(1) 78 | }) 79 | 80 | it('should not call the function again within the time limit', () => { 81 | throttledFunction() 82 | vi.advanceTimersByTime(500) 83 | throttledFunction() 84 | expect(testFunction).toHaveBeenCalledTimes(1) 85 | }) 86 | 87 | it('should call the function again after the time limit', () => { 88 | throttledFunction() 89 | vi.advanceTimersByTime(1000) 90 | throttledFunction() 91 | expect(testFunction).toHaveBeenCalledTimes(2) 92 | }) 93 | }) 94 | }) 95 | -------------------------------------------------------------------------------- /packages/eslint-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bassist/eslint-config", 3 | "version": "0.3.0", 4 | "description": "A modern flat ESLint configuration for ESLint, crafted by @chengpeiquan .", 5 | "author": "chengpeiquan ", 6 | "license": "MIT", 7 | "homepage": "https://github.com/chengpeiquan/bassist/blob/main/packages/eslint-config/README.md", 8 | "files": [ 9 | "dist" 10 | ], 11 | "main": "./dist/index.cjs", 12 | "module": "./dist/index.mjs", 13 | "types": "./dist/index.d.ts", 14 | "typesVersions": { 15 | "*": { 16 | "prettier-config": [ 17 | "./dist/prettier-config.d.ts" 18 | ], 19 | "*": [ 20 | "./dist/index.d.ts", 21 | "./dist/*" 22 | ] 23 | } 24 | }, 25 | "exports": { 26 | "./prettier-config": { 27 | "types": "./dist/prettier-config.d.ts", 28 | "import": "./dist/prettier-config.mjs", 29 | "require": "./dist/prettier-config.cjs" 30 | }, 31 | ".": { 32 | "types": "./dist/index.d.ts", 33 | "import": "./dist/index.mjs", 34 | "require": "./dist/index.cjs" 35 | } 36 | }, 37 | "repository": { 38 | "type": "git", 39 | "url": "https://github.com/chengpeiquan/bassist" 40 | }, 41 | "keywords": [ 42 | "eslint", 43 | "eslint config", 44 | "eslint flat config" 45 | ], 46 | "scripts": { 47 | "build": "tsup" 48 | }, 49 | "devDependencies": { 50 | "@lint-md/core": "^2.0.0", 51 | "@types/eslint-config-prettier": "^6.11.3", 52 | "@types/eslint-plugin-tailwindcss": "^3.17.0", 53 | "eslint": "^9.32.0", 54 | "prettier": "^3.6.2", 55 | "typescript": "^5.8.3" 56 | }, 57 | "dependencies": { 58 | "@eslint/js": "^9.32.0", 59 | "@eslint/markdown": "^7.1.0", 60 | "@next/eslint-plugin-next": "^15.4.4", 61 | "@typescript-eslint/eslint-plugin": "^8.38.0", 62 | "@typescript-eslint/parser": "^8.38.0", 63 | "eslint-config-prettier": "^10.1.8", 64 | "eslint-plugin-import-x": "^4.16.1", 65 | "eslint-plugin-n": "^17.21.1", 66 | "eslint-plugin-prettier": "^5.5.3", 67 | "eslint-plugin-react": "^7.37.5", 68 | "eslint-plugin-react-hooks": "^5.2.0", 69 | "eslint-plugin-react-refresh": "^0.4.20", 70 | "eslint-plugin-regexp": "^2.9.0", 71 | "eslint-plugin-tailwindcss": "^3.18.2", 72 | "eslint-plugin-unicorn": "^57.0.0", 73 | "eslint-plugin-vue": "^9.33.0", 74 | "globals": "^16.3.0", 75 | "prettier-plugin-jsdoc": "^1.3.3", 76 | "prettier-plugin-lint-md": "^1.0.1", 77 | "vue-eslint-parser": "^9.4.3" 78 | }, 79 | "peerDependencies": { 80 | "eslint": ">=9.0.0", 81 | "prettier": ">=3.0.0", 82 | "typescript": ">=5.0.0" 83 | }, 84 | "peerDependenciesMeta": { 85 | "typescript": { 86 | "optional": true 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bassist/monorepo", 3 | "version": "0.0.0", 4 | "description": "I play bass, so enjoy the name.", 5 | "author": "chengpeiquan ", 6 | "license": "MIT", 7 | "private": true, 8 | "packageManager": "pnpm@10.13.1", 9 | "type": "module", 10 | "scripts": { 11 | "build": "turbo run build", 12 | "build:lib": "jiti ./scripts/build", 13 | "pkg:publish": "jiti ./scripts/publish", 14 | "pkg:changelog": "jiti ./scripts/changelog.cts", 15 | "test": "vitest", 16 | "lint": "eslint packages --ext .js,.ts,.jsx,.tsx,.md", 17 | "lint:inspector": "npx @eslint/config-inspector", 18 | "format": "pnpm exec prettier --write packages", 19 | "mirror:get": "npm config get registry", 20 | "mirror:set": "npm config set registry https://registry.npmmirror.com", 21 | "mirror:rm": "npm config rm registry", 22 | "up": "npx taze minor -r -f -w -i", 23 | "backup": "git add . && git commit -m \"chore: backup\" && git push", 24 | "prepare": "husky install" 25 | }, 26 | "dependencies": { 27 | "nprogress": "^0.2.0" 28 | }, 29 | "devDependencies": { 30 | "@commitlint/cli": "^19.8.1", 31 | "@commitlint/config-conventional": "^19.8.1", 32 | "@eslint/js": "^9.32.0", 33 | "@eslint/markdown": "^7.1.0", 34 | "@lint-md/core": "^2.0.0", 35 | "@next/eslint-plugin-next": "^15.4.4", 36 | "@types/eslint-config-prettier": "^6.11.3", 37 | "@types/eslint-plugin-tailwindcss": "^3.17.0", 38 | "@types/fs-extra": "^11.0.4", 39 | "@types/node": "^24.1.0", 40 | "@types/nprogress": "^0.2.3", 41 | "@typescript-eslint/eslint-plugin": "^8.38.0", 42 | "@typescript-eslint/parser": "^8.38.0", 43 | "@withtypes/mime": "^0.1.2", 44 | "@withtypes/minimist": "^0.1.1", 45 | "conventional-changelog-cli": "^5.0.0", 46 | "eslint": "^9.32.0", 47 | "eslint-config-prettier": "^10.1.8", 48 | "eslint-plugin-import-x": "^4.16.1", 49 | "eslint-plugin-n": "^17.21.1", 50 | "eslint-plugin-prettier": "^5.5.3", 51 | "eslint-plugin-react": "^7.37.5", 52 | "eslint-plugin-react-hooks": "^5.2.0", 53 | "eslint-plugin-react-refresh": "^0.4.20", 54 | "eslint-plugin-regexp": "^2.9.0", 55 | "eslint-plugin-tailwindcss": "^3.18.2", 56 | "eslint-plugin-unicorn": "^57.0.0", 57 | "eslint-plugin-vue": "^9.33.0", 58 | "fs-extra": "^11.3.0", 59 | "globals": "^16.3.0", 60 | "husky": "^9.1.7", 61 | "jiti": "^2.5.1", 62 | "lint-staged": "^15.5.2", 63 | "prettier": "^3.6.2", 64 | "prettier-plugin-jsdoc": "^1.3.3", 65 | "prettier-plugin-lint-md": "^1.0.1", 66 | "tsup": "^8.5.0", 67 | "turbo": "^2.5.5", 68 | "typescript": "^5.8.3", 69 | "vitest": "^3.2.4", 70 | "vue-eslint-parser": "^9.4.3" 71 | }, 72 | "lint-staged": { 73 | "*.{js,jsx,md,ts,tsx}": [ 74 | "prettier --write", 75 | "eslint --fix" 76 | ] 77 | } 78 | } -------------------------------------------------------------------------------- /packages/utils/src/regexp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Verify the mobile phone number format in mainland China 3 | * 4 | * @category Regexp 5 | */ 6 | export function isMob(phoneNumber: number | string) { 7 | return /^1[3456789]\d{9}$/.test(String(phoneNumber)) 8 | } 9 | 10 | /** 11 | * Verify email format 12 | * 13 | * @category Regexp 14 | */ 15 | export function isEmail(email: string) { 16 | return /^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,4}$/.test( 17 | email, 18 | ) 19 | } 20 | 21 | /** 22 | * Verify url format 23 | * 24 | * @category Regexp 25 | */ 26 | export function isUrl(url: string) { 27 | return /https?:\/\/[\w-]+(\.[\w-]+){1,2}(\/[\w-]{3,6}){0,2}(\?[\w_]{4,6}=[\w_]{4,6}(&[\w_]{4,6}=[\w_]{4,6}){0,2})?/.test( 28 | url, 29 | ) 30 | } 31 | 32 | /** 33 | * Verify the ID card number format in mainland China 34 | * 35 | * @category Regexp 36 | */ 37 | export function isIdCard(idCardNumber: string) { 38 | // 18-digit ID number 39 | const digit18 = 40 | /^([1-6][1-9]|50)\d{4}(18|19|20)\d{2}((0[1-9])|10|11|12)(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/ 41 | 42 | // 15-digit ID number 43 | const digit15 = 44 | /^([1-6][1-9]|50)\d{4}\d{2}((0[1-9])|10|11|12)(([0-2][1-9])|10|20|30|31)\d{3}$/ 45 | 46 | return digit18.test(idCardNumber) || digit15.test(idCardNumber) 47 | } 48 | 49 | /** 50 | * Verify the bank card number format in mainland China 51 | * 52 | * @category Regexp 53 | */ 54 | export function isBankCard(bankCardNumber: string) { 55 | return /^([1-9]{1})(\d{15}|\d{16}|\d{18})$/.test(bankCardNumber) 56 | } 57 | 58 | /** 59 | * Verify the IP is IPv4 60 | * 61 | * @category Regexp 62 | */ 63 | export function isIPv4(ip: string) { 64 | return /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/.test( 65 | ip, 66 | ) 67 | } 68 | 69 | /** 70 | * Verify the IP is IPv6 71 | * 72 | * @category Regexp 73 | */ 74 | export function isIPv6(ip: string) { 75 | return /^([\da-fA-F]{1,4}:){6}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^::([\da-fA-F]{1,4}:){0,4}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^([\da-fA-F]{1,4}:):([\da-fA-F]{1,4}:){0,3}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^([\da-fA-F]{1,4}:){2}:([\da-fA-F]{1,4}:){0,2}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^([\da-fA-F]{1,4}:){3}:([\da-fA-F]{1,4}:){0,1}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^([\da-fA-F]{1,4}:){4}:((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^([\da-fA-F]{1,4}:){7}[\da-fA-F]{1,4}$|^:((:[\da-fA-F]{1,4}){1,6}|:)$|^[\da-fA-F]{1,4}:((:[\da-fA-F]{1,4}){1,5}|:)$|^([\da-fA-F]{1,4}:){2}((:[\da-fA-F]{1,4}){1,4}|:)$|^([\da-fA-F]{1,4}:){3}((:[\da-fA-F]{1,4}){1,3}|:)$|^([\da-fA-F]{1,4}:){4}((:[\da-fA-F]{1,4}){1,2}|:)$|^([\da-fA-F]{1,4}:){5}:([\da-fA-F]{1,4})?$|^([\da-fA-F]{1,4}:){6}:$/.test( 76 | ip, 77 | ) 78 | } 79 | -------------------------------------------------------------------------------- /packages/progress/README.md: -------------------------------------------------------------------------------- 1 | # @bassist/progress 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | GitHub stars 15 | 16 |

17 | 18 | Simple slim progress bars base on [NProgress](https://www.npmjs.com/package/nprogress). 19 | 20 | ## Usage 21 | 22 | With npm(or yarn, or pnpm): 23 | 24 | ```bash 25 | npm install @bassist/progress 26 | ``` 27 | 28 | In `.js` / `.ts` or other files: 29 | 30 | ```ts 31 | import progress from '@bassist/progress' 32 | 33 | // Used in the framework's router hooks. 34 | // Or in other scenarios (e.g. AJAX requests). 35 | 36 | router.beforeEach(() => { 37 | progress.start() 38 | }) 39 | 40 | router.afterEach(() => { 41 | progress.done() 42 | }) 43 | ``` 44 | 45 | All configurations and options of [NProgress](https://www.npmjs.com/package/nprogress) are supported. 46 | 47 | ## Set Color 48 | 49 | This plugin extends NProgress's API and adds a `setColor` method. 50 | 51 | - Type Declarations: 52 | 53 | ```ts 54 | export interface Progress extends NProgress { 55 | /** 56 | * 57 | * @param color - A valid CSS color value or CSS variable 58 | * 59 | * @example use HEX 60 | * progress.setColor('#ff0000') 61 | * 62 | * @example use RGB 63 | * progress.setColor('rgb(255, 0, 0)') 64 | * 65 | * @example use RGBA 66 | * progress.setColor('rgba(255, 0, 0, 1)') 67 | * 68 | * @example use CSS Variable 69 | * progress.setColor('var(--color-primary)') 70 | */ 71 | setColor: (color: string) => void 72 | } 73 | ``` 74 | 75 | - Example: 76 | 77 | ```ts 78 | import progress from '@bassist/progress' 79 | 80 | // Set the color before start 81 | progress.setColor('#ff0000') 82 | 83 | // Then, used in the framework's router hooks. 84 | // Or in other scenarios (e.g. AJAX requests). 85 | 86 | router.beforeEach(() => { 87 | progress.start() 88 | }) 89 | 90 | router.afterEach(() => { 91 | progress.done() 92 | }) 93 | ``` 94 | 95 | ## Release Notes 96 | 97 | Please refer to [CHANGELOG](https://github.com/chengpeiquan/bassist/blob/main/packages/progress/CHANGELOG.md) for details. 98 | 99 | ## License 100 | 101 | MIT License © 2022-PRESENT [chengpeiquan](https://github.com/chengpeiquan) 102 | -------------------------------------------------------------------------------- /packages/utils/src/storage/base.ts: -------------------------------------------------------------------------------- 1 | import { isString } from '../data' 2 | import { isBrowser } from '../device' 3 | import { FallbackStorage } from './fallback' 4 | 5 | /** 6 | * @category Storage 7 | */ 8 | export type StorageType = 'localStorage' | 'sessionStorage' 9 | 10 | /** 11 | * BaseStorage class provides a wrapper for browser storage or fallback storage. 12 | * 13 | * @category Storage 14 | */ 15 | export class BaseStorage { 16 | prefix: string 17 | private storage: Storage | FallbackStorage 18 | 19 | /** 20 | * Creates an instance of BaseStorage 21 | * 22 | * @param prefix - The prefix to be added to the storage keys. 23 | * @param storageType - The type of storage to use (localStorage or 24 | * sessionStorage) 25 | */ 26 | constructor(prefix: string, storageType: StorageType) { 27 | this.prefix = prefix 28 | this.storage = isBrowser ? window[storageType] : new FallbackStorage(prefix) 29 | } 30 | 31 | /** 32 | * Read stored data 33 | * 34 | * @returns The data in the format before storage 35 | * @tips The `key` doesn't need to be prefixed 36 | */ 37 | get(key: string) { 38 | const localData = this.storage.getItem(`${this.prefix}-${key}`) 39 | if (!localData) return localData 40 | 41 | try { 42 | if (localData === 'true') return true 43 | if (localData === 'false') return false 44 | if (localData === 'null') return null 45 | if (localData === 'undefined') return undefined 46 | return JSON.parse(localData) 47 | } catch { 48 | return localData 49 | } 50 | } 51 | 52 | /** 53 | * Set storage data 54 | */ 55 | set(key: string, value: any) { 56 | try { 57 | const data = isString(value) ? value : JSON.stringify(value) 58 | this.storage.setItem(`${this.prefix}-${key}`, data) 59 | } catch (e) { 60 | console.error(e) 61 | } 62 | } 63 | 64 | /** 65 | * Remove the specified storage data under the current prefix 66 | */ 67 | remove(key: string) { 68 | this.storage.removeItem(`${this.prefix}-${key}`) 69 | } 70 | 71 | /** 72 | * Clear all stored data under the current prefix 73 | */ 74 | clear() { 75 | const keys = this.list() 76 | keys.forEach((key) => { 77 | this.remove(key) 78 | }) 79 | } 80 | 81 | /** 82 | * Count the number of storage related to the current prefix 83 | */ 84 | count() { 85 | return this.list().length 86 | } 87 | 88 | /** 89 | * List storage keys associated under the current prefix 90 | * 91 | * @tips All keys without prefix 92 | */ 93 | list() { 94 | if (!this.prefix) return [] 95 | const result: string[] = [] 96 | const count = this.storage.length 97 | for (let i = 0; i < count; i++) { 98 | const key = this.storage.key(i) 99 | if (key?.startsWith(this.prefix)) { 100 | result.push(key.replace(`${this.prefix}-`, '')) 101 | } 102 | } 103 | return result 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /packages/utils/src/query.ts: -------------------------------------------------------------------------------- 1 | import { isObject, isString } from './data' 2 | import { isBrowser } from './device' 3 | 4 | /** 5 | * @category Query 6 | */ 7 | type QueryInfo = Record 8 | 9 | /** 10 | * @category Query 11 | */ 12 | type QueryInfoObject = Record< 13 | string, 14 | string | number | boolean | undefined | null 15 | > 16 | 17 | /** 18 | * Parse URL Query parameters 19 | * 20 | * @category Query 21 | * @param url - By default, it is extracted from the browser URL, and this 22 | * parameter can be parsed from the specified URL 23 | * @returns Query parameter object, will convert `key1=value1&key2=value2` into 24 | * an object 25 | */ 26 | export function parseQuery(url?: string) { 27 | let queryStringify = '' 28 | 29 | if (isBrowser) { 30 | const { search } = window.location 31 | queryStringify = search 32 | } 33 | 34 | if (isString(url) && url.startsWith('http')) { 35 | const index = url.indexOf('?') 36 | queryStringify = index === -1 ? '' : url.slice(index) 37 | } 38 | 39 | if (queryStringify.includes('#')) { 40 | const index = queryStringify.indexOf('#') 41 | queryStringify = queryStringify.slice(0, index) 42 | } 43 | 44 | if (!queryStringify.length) return {} 45 | 46 | const temp: QueryInfo = {} 47 | queryStringify 48 | .slice(1) 49 | .split('&') 50 | .forEach((str) => { 51 | const [k, v] = str.split('=') 52 | temp[k] = decodeURIComponent(v) 53 | }) 54 | return temp 55 | } 56 | 57 | /** 58 | * Extract parameter information from URL Query 59 | * 60 | * @category Query 61 | * @returns An object containing the request path and parameters object `path`: 62 | * Jump path, the same as the routing name in the Web App `params`: Parameters 63 | * other than path 64 | */ 65 | export function extractQueryInfo(url?: string): { 66 | path: string 67 | params: QueryInfo 68 | } { 69 | const query = parseQuery(url) 70 | const params: QueryInfo = {} 71 | Object.keys(query).forEach((k) => { 72 | if (k === 'path') return 73 | params[k] = query[k] 74 | }) 75 | 76 | const path = query.path || '' 77 | return { path, params } 78 | } 79 | 80 | /** 81 | * Get the specified Query parameter 82 | * 83 | * @category Query 84 | * @param key - The parameter key name to get 85 | * @param url - By default, it is extracted from the browser URL, and this 86 | * parameter can be parsed from the specified URL 87 | */ 88 | export function getQuery(key: string, url?: string) { 89 | const query = parseQuery(url) 90 | return query[key] || '' 91 | } 92 | 93 | /** 94 | * Serialize Query parameters information 95 | * 96 | * @category Query 97 | * @param queryInfoObject - The object of the Query parameter to use for 98 | * serialization 99 | */ 100 | export function stringifyQuery(queryInfoObject: QueryInfoObject) { 101 | if (!isObject(queryInfoObject)) return '' 102 | return Object.keys(queryInfoObject) 103 | .map((key) => `${key}=${encodeURIComponent(String(queryInfoObject[key]))}`) 104 | .join('&') 105 | } 106 | -------------------------------------------------------------------------------- /packages/eslint-config/src/define.ts: -------------------------------------------------------------------------------- 1 | import { createPrettierConfig } from './private-configs/prettier' 2 | import { 3 | createTailwindcssConfig, 4 | type TailwindcssSettings, 5 | } from './private-configs/tailwindcss' 6 | import { 7 | type PartialPrettierExtendedOptions, 8 | type FlatESLintConfig, 9 | } from './types' 10 | 11 | export interface DefineFlatConfigOptions { 12 | /** 13 | * Specifies the working directory for loading the `.prettierrc` 14 | * configuration. 15 | * 16 | * The config file should be in JSON format. 17 | * 18 | * @default process.cwd() 19 | */ 20 | cwd?: string 21 | 22 | /** 23 | * If `prettierEnabled` is set to `false`, all Prettier-related rules and 24 | * configurations will be ignored, even if `prettierRules` are provided. 25 | * 26 | * @default true 27 | */ 28 | prettierEnabled?: boolean 29 | 30 | /** 31 | * By default, this will read `.prettierrc` from the current working 32 | * directory, and the `.prettierrc` file must be written in JSON format. 33 | * 34 | * If you are not using a config file with JSON content, or a different config 35 | * file name, you can convert it to JSON rules and pass it in. 36 | * 37 | * After reading the custom configuration, it will be merged with the default 38 | * ESLint rules. 39 | * 40 | * @default Loads from .prettierrc file, falls back to default config 41 | * 42 | * @see https://prettier.io/docs/configuration.html 43 | */ 44 | prettierRules?: PartialPrettierExtendedOptions 45 | 46 | /** 47 | * Tailwindcss rules are enabled by default. If they interfere with your 48 | * project, you can disable them with this option. 49 | * 50 | * @default true 51 | */ 52 | tailwindcssEnabled?: boolean 53 | 54 | /** 55 | * If you need to override the configuration, you can pass the corresponding 56 | * options. 57 | * 58 | * If you want to merge configurations, you can import 59 | * `defaultTailwindcssSettings`, merge them yourself, and then pass the result 60 | * in. 61 | * 62 | * If an empty object `{}` is passed, the default settings will be used. 63 | * 64 | * @see https://github.com/francoismassart/eslint-plugin-tailwindcss/tree/v3.18.2 65 | */ 66 | tailwindcssSettings?: TailwindcssSettings 67 | } 68 | 69 | /** 70 | * Define the ESLint configuration with optional Prettier integration. 71 | * 72 | * @param configs The base ESLint configurations. 73 | * @param options - Options for defining the configuration. 74 | * @returns The final ESLint configuration array. 75 | */ 76 | export const defineFlatConfig = ( 77 | configs: FlatESLintConfig[], 78 | options: DefineFlatConfigOptions = {}, 79 | ) => { 80 | const { 81 | cwd = process.cwd(), 82 | prettierEnabled = true, 83 | prettierRules, 84 | tailwindcssEnabled = true, 85 | tailwindcssSettings, 86 | } = options 87 | 88 | const prettierConfig = prettierEnabled 89 | ? createPrettierConfig(cwd, prettierRules) 90 | : [] 91 | 92 | const tailwindcssConfig = tailwindcssEnabled 93 | ? createTailwindcssConfig(tailwindcssSettings) 94 | : [] 95 | 96 | return [...prettierConfig, ...tailwindcssConfig, ...configs] 97 | } 98 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/javascript.ts: -------------------------------------------------------------------------------- 1 | import jsConfig from '@eslint/js' 2 | import globals from 'globals' 3 | import { getConfigName } from '../shared/utils' 4 | import { type FlatESLintConfig } from '../types' 5 | 6 | export const javascript: FlatESLintConfig[] = [ 7 | { 8 | ...jsConfig.configs.recommended, 9 | name: getConfigName('js', 'eslint-recommended'), 10 | }, 11 | { 12 | name: getConfigName('js', 'base'), 13 | languageOptions: { 14 | globals: { 15 | ...globals.browser, 16 | ...globals.es2021, 17 | ...globals.node, 18 | }, 19 | sourceType: 'module', 20 | }, 21 | rules: { 22 | 'no-unused-vars': ['error', { args: 'none', ignoreRestSiblings: true }], 23 | 'no-constant-condition': 'warn', 24 | 'no-debugger': 'warn', 25 | 'no-console': ['warn', { allow: ['warn', 'error'] }], 26 | 'no-restricted-syntax': [ 27 | 'error', 28 | 'ForInStatement', 29 | 'LabeledStatement', 30 | 'WithStatement', 31 | ], 32 | 'no-return-await': 'warn', 33 | 'no-empty': ['error', { allowEmptyCatch: true }], 34 | 'sort-imports': [ 35 | 'error', 36 | { 37 | ignoreCase: false, 38 | ignoreDeclarationSort: true, 39 | ignoreMemberSort: false, 40 | memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'], 41 | allowSeparatedGroups: false, 42 | }, 43 | ], 44 | 'dot-notation': 'warn', 45 | 46 | 'no-var': 'error', 47 | 'prefer-const': [ 48 | 'warn', 49 | { destructuring: 'all', ignoreReadBeforeAssign: true }, 50 | ], 51 | 'prefer-arrow-callback': [ 52 | 'error', 53 | { allowNamedFunctions: false, allowUnboundThis: true }, 54 | ], 55 | 'object-shorthand': [ 56 | 'error', 57 | 'always', 58 | { ignoreConstructors: false, avoidQuotes: true }, 59 | ], 60 | 'prefer-rest-params': 'error', 61 | 'prefer-spread': 'error', 62 | 'prefer-template': 'error', 63 | 'require-await': 'error', 64 | 65 | 'array-callback-return': 'error', 66 | 'block-scoped-var': 'error', 67 | eqeqeq: ['error', 'smart'], 68 | 'no-alert': 'warn', 69 | 'no-case-declarations': 'error', 70 | 'no-fallthrough': ['warn', { commentPattern: 'break[\\s\\w]*omitted' }], 71 | 'no-multi-str': 'error', 72 | 'no-with': 'error', 73 | 'no-void': 'error', 74 | 'no-duplicate-imports': 'error', 75 | 76 | 'no-unused-expressions': [ 77 | 'error', 78 | { 79 | allowShortCircuit: true, 80 | allowTernary: true, 81 | allowTaggedTemplates: true, 82 | }, 83 | ], 84 | 'no-lonely-if': 'error', 85 | 'prefer-exponentiation-operator': 'error', 86 | }, 87 | }, 88 | { 89 | name: getConfigName('js', 'scripts'), 90 | files: ['**/scripts/*', '**/cli.*'], 91 | rules: { 92 | 'no-console': 'off', 93 | }, 94 | }, 95 | { 96 | name: getConfigName('js', 'test'), 97 | files: ['**/*.{test,spec}.js?(x)'], 98 | rules: { 99 | 'no-unused-expressions': 'off', 100 | }, 101 | }, 102 | ] 103 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/unicorn.ts: -------------------------------------------------------------------------------- 1 | import unicornPlugin from 'eslint-plugin-unicorn' 2 | import { getConfigName } from '../shared/utils' 3 | import { type FlatESLintConfig } from '../types' 4 | 5 | export { unicornPlugin } 6 | 7 | export const unicorn: FlatESLintConfig[] = [ 8 | { 9 | name: getConfigName('unicorn'), 10 | plugins: { 11 | unicorn: unicornPlugin, 12 | }, 13 | rules: { 14 | 'unicorn/better-regex': 'error', 15 | 'unicorn/catch-error-name': 'error', 16 | 'unicorn/custom-error-definition': 'error', 17 | 'unicorn/error-message': 'error', 18 | 'unicorn/escape-case': 'error', 19 | 'unicorn/explicit-length-check': 'error', 20 | 'unicorn/filename-case': [ 21 | 'error', 22 | { 23 | cases: { kebabCase: true, pascalCase: true }, 24 | ignore: [/^[A-Z]+\..*$/], 25 | }, 26 | ], 27 | 'unicorn/new-for-builtins': 'error', 28 | 'unicorn/no-array-callback-reference': 'error', 29 | 'unicorn/no-array-method-this-argument': 'error', 30 | 'unicorn/no-array-push-push': 'error', 31 | 'unicorn/no-console-spaces': 'error', 32 | 'unicorn/no-for-loop': 'error', 33 | 'unicorn/no-hex-escape': 'error', 34 | 'unicorn/no-instanceof-array': 'error', 35 | 'unicorn/no-invalid-remove-event-listener': 'error', 36 | 'unicorn/no-lonely-if': 'error', 37 | 'unicorn/no-new-array': 'error', 38 | 'unicorn/no-new-buffer': 'error', 39 | 'unicorn/no-static-only-class': 'error', 40 | 'unicorn/no-unnecessary-await': 'error', 41 | 'unicorn/no-zero-fractions': `error`, 42 | 'unicorn/prefer-add-event-listener': 'error', 43 | 'unicorn/prefer-array-find': 'error', 44 | 'unicorn/prefer-array-flat-map': 'error', 45 | 'unicorn/prefer-array-index-of': 'error', 46 | 'unicorn/prefer-array-some': 'error', 47 | 'unicorn/prefer-at': 'error', 48 | 'unicorn/prefer-blob-reading-methods': 'error', 49 | 'unicorn/prefer-date-now': 'error', 50 | 'unicorn/prefer-dom-node-append': 'error', 51 | 'unicorn/prefer-dom-node-dataset': 'error', 52 | 'unicorn/prefer-dom-node-remove': 'error', 53 | 'unicorn/prefer-dom-node-text-content': 'error', 54 | 'unicorn/prefer-includes': 'error', 55 | 'unicorn/prefer-keyboard-event-key': 'error', 56 | 'unicorn/prefer-math-trunc': 'error', 57 | 'unicorn/prefer-modern-dom-apis': 'error', 58 | 'unicorn/prefer-modern-math-apis': 'error', 59 | 'unicorn/prefer-negative-index': 'error', 60 | 'unicorn/prefer-node-protocol': 'error', 61 | 'unicorn/prefer-number-properties': 'error', 62 | 'unicorn/prefer-optional-catch-binding': 'error', 63 | 'unicorn/prefer-prototype-methods': 'error', 64 | 'unicorn/prefer-query-selector': 'error', 65 | 'unicorn/prefer-reflect-apply': 'error', 66 | 'unicorn/prefer-regexp-test': 'error', 67 | 'unicorn/prefer-string-replace-all': 'error', 68 | 'unicorn/prefer-string-slice': 'error', 69 | 'unicorn/prefer-string-starts-ends-with': 'error', 70 | 'unicorn/prefer-string-trim-start-end': 'error', 71 | 'unicorn/prefer-top-level-await': 'error', 72 | 'unicorn/prefer-type-error': 'error', 73 | 'unicorn/throw-new-error': 'error', 74 | }, 75 | }, 76 | ] 77 | -------------------------------------------------------------------------------- /packages/build-config/README.md: -------------------------------------------------------------------------------- 1 | # @bassist/build-config 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | GitHub stars 15 | 16 |

17 | 18 | Opinionated collection of common build tool configurations, carefully crafted by [@chengpeiquan](https://github.com/chengpeiquan). 19 | 20 | Currently provides configurations for the following build tools: 21 | 22 | - [tsup](https://github.com/egoist/tsup): The most convenient tool for building TypeScript libraries based on ESBuild 23 | 24 | ## 🤔 Why do we need this? 25 | 26 | While using certain tool configurations in a single repository is convenient enough, the process becomes tedious when many repositories use similar configurations. 27 | 28 | By abstracting and sharing commonly used tool configurations, different projects can complete their setup faster. 29 | 30 | ## ⚡ Usage: Based on tsup 31 | 32 | - 🎯 **Preset Configurations**: Provides out-of-the-box tsup base configurations 33 | - 📦 **Multi-format Support**: Default support for CommonJS and ESM formats 34 | - 🏷️ **Auto Banner**: Automatically generates file header comments based on package.json 35 | - 🧹 **Auto Clean**: Automatically cleans output directory before building 36 | - 📝 **Type Declarations**: Automatically generates TypeScript type declaration files 37 | - ⚡ **Code Minification**: Built-in code compression functionality 38 | 39 | ### Installation 40 | 41 | Install the package using your preferred package manager: 42 | 43 | ```bash 44 | npm install -D @bassist/build-config tsup 45 | ``` 46 | 47 | **Note**: This submodule requires tsup as a peer dependency. As shown in the installation command above, please ensure tsup is installed. 48 | 49 | ### Usage 50 | 51 | All APIs are provided uniformly by `@bassist/build-config/tsup`. 52 | 53 | Typically, you only need to use the configuration returned by `createBaseConfig`, and you can pass custom options as needed: 54 | 55 | ```ts 56 | import { createBaseConfig } from '@bassist/build-config/tsup' 57 | import { defineConfig } from 'tsup' 58 | import pkg from './package.json' 59 | 60 | const config = createBaseConfig({ pkg }) 61 | 62 | export default defineConfig(config) 63 | ``` 64 | 65 | If the configuration options don't meet your needs, you can destructure and merge objects to pass them to the `defineConfig` API. 66 | 67 | For more APIs and configuration options, please check the source code [tsup.ts](https://github.com/chengpeiquan/bassist/blob/main/packages/build-config/src/tsup.ts). 68 | 69 | ## 📝 Release Notes 70 | 71 | Please refer to [CHANGELOG](https://github.com/chengpeiquan/bassist/blob/main/packages/build-config/CHANGELOG.md) for details. 72 | 73 | ## 📜 License 74 | 75 | MIT License © 2025-PRESENT [chengpeiquan](https://github.com/chengpeiquan) 76 | -------------------------------------------------------------------------------- /packages/node-utils/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [0.5.0](https://github.com/chengpeiquan/bassist/compare/node-utils@0.4.2...node-utils@0.5.0) (2025-07-06) 2 | 3 | 4 | ### Refactor 5 | 6 | * **node-utils:** migrate bundle.ts to @bassist/build-config package ([7e2f1c0](https://github.com/chengpeiquan/bassist/commit/7e2f1c09f6a0b2762063c8c3b328c094e0803a39)) 7 | 8 | 9 | 10 | ## [0.4.2](https://github.com/chengpeiquan/bassist/compare/node-utils@0.4.1...node-utils@0.4.2) (2025-05-22) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * **node-utils:** reexport fse apis ([f2242c1](https://github.com/chengpeiquan/bassist/commit/f2242c18b179b41bae408692e53386618b8974f7)) 16 | 17 | 18 | 19 | ## [0.4.1](https://github.com/chengpeiquan/bassist/compare/node-utils@0.4.0...node-utils@0.4.1) (2024-06-30) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * **node-utils:** missing types ([3de219a](https://github.com/chengpeiquan/bassist/commit/3de219ac8a7b95a7d6aed8342eb4e75726ba7717)) 25 | 26 | 27 | 28 | # [0.4.0](https://github.com/chengpeiquan/bassist/compare/node-utils@0.3.0...node-utils@0.4.0) (2024-06-30) 29 | 30 | 31 | ### Bug Fixes 32 | 33 | * typescript overwrite warning ([ced9c2c](https://github.com/chengpeiquan/bassist/commit/ced9c2c8a162f63b8b0ae66f65c384177eb7b0e3)) 34 | 35 | 36 | ### Features 37 | 38 | * **node-utils:** add bundle helpers ([62e3ee7](https://github.com/chengpeiquan/bassist/commit/62e3ee756743cc4b373f1dad021e1ea5e096b8a2)) 39 | * **node-utils:** built-in fs-extra ([664731f](https://github.com/chengpeiquan/bassist/commit/664731f868f4aa2e1a6177e53196f6c0adc46ecc)) 40 | * **node-utils:** use fs-extra instead of the original fs method encapsulation ([10d5cd7](https://github.com/chengpeiquan/bassist/commit/10d5cd747a9f75f2e49d39bd10efc36dfaf885fb)) 41 | 42 | 43 | 44 | # [0.3.0](https://github.com/chengpeiquan/bassist/compare/node-utils@0.2.1...node-utils@0.3.0) (2023-08-19) 45 | 46 | 47 | ### Features 48 | 49 | * **node-utils:** change build scheme (internal mechanism) ([988ebda](https://github.com/chengpeiquan/bassist/commit/988ebda007077efcd8eaa3a310ad6a946e8ab54f)) 50 | 51 | 52 | 53 | ## [0.2.1](https://github.com/chengpeiquan/bassist/compare/node-utils@0.2.0...node-utils@0.2.1) (2023-02-12) 54 | 55 | 56 | ### Bug Fixes 57 | 58 | * **node-utils:** deprecate getDirnameInEsModule ([1c3ae39](https://github.com/chengpeiquan/bassist/commit/1c3ae39fba0d7218a53868bcf156b6b71b2f3d14)) 59 | 60 | 61 | 62 | # [0.2.0](https://github.com/chengpeiquan/bassist/compare/node-utils@0.1.2...node-utils@0.2.0) (2023-02-03) 63 | 64 | 65 | ### Features 66 | 67 | * **node-utils:** add a function to get __dirname in esm ([5abcf61](https://github.com/chengpeiquan/bassist/commit/5abcf611f4c73942b7a7b140d41dd0062b0ed848)) 68 | 69 | 70 | 71 | ## [0.1.2](https://github.com/chengpeiquan/bassist/compare/node-utils@0.1.1...node-utils@0.1.2) (2023-01-30) 72 | 73 | 74 | ### Bug Fixes 75 | 76 | * **node-utils:** hide catch log ([f3c52d2](https://github.com/chengpeiquan/bassist/commit/f3c52d2c8c762403254225856da6028dec126598)) 77 | 78 | 79 | 80 | ## [0.1.1](https://github.com/chengpeiquan/bassist/compare/node-utils@0.1.0...node-utils@0.1.1) (2023-01-30) 81 | 82 | 83 | ### Bug Fixes 84 | 85 | * **node-utils:** cannot delete non-empty directory ([276a8d1](https://github.com/chengpeiquan/bassist/commit/276a8d15c6c0189f41f0108f53e06ecb97e07ea8)) 86 | 87 | 88 | 89 | ## 0.1.0 (2023-01-27) 90 | 91 | 92 | ### Features 93 | 94 | * **node-utils:** add some functional encapsulation of fs ([9137a79](https://github.com/chengpeiquan/bassist/commit/9137a7919e1a251a9a6e7bfc4c838bc86c708ebf)) 95 | * **node-utils:** add some functional encapsulation of pkg ([59beebe](https://github.com/chengpeiquan/bassist/commit/59beebee09f616095c670c3f09a2d6321af090f0)) 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /packages/build-config/src/tsup.ts: -------------------------------------------------------------------------------- 1 | import { type defineConfig, type Options } from 'tsup' 2 | 3 | /** 4 | * Bundler format based on tsup 5 | * 6 | * @category Tsup 7 | * @see https://tsup.egoist.dev/#bundle-formats 8 | */ 9 | export enum BundleFormat { 10 | CJS = 'cjs', 11 | ESM = 'esm', 12 | IIFE = 'iife', 13 | } 14 | 15 | /** 16 | * Provides the extension of the JavaScript file according to the bundle format 17 | * 18 | * @category Tsup 19 | * @see https://tsup.egoist.dev/#output-extension 20 | */ 21 | export const getBundleExtension = ({ format }: Record) => { 22 | switch (format) { 23 | case BundleFormat.CJS: { 24 | return { js: `.cjs` } 25 | } 26 | case BundleFormat.ESM: { 27 | return { js: `.mjs` } 28 | } 29 | default: { 30 | return { js: `.js` } 31 | } 32 | } 33 | } 34 | 35 | /** 36 | * @category Tsup 37 | */ 38 | export const bundleBannerFields = [ 39 | 'name', 40 | 'version', 41 | 'description', 42 | 'author', 43 | 'homepage', 44 | 'license', 45 | ] as const 46 | 47 | /** 48 | * @category Tsup 49 | */ 50 | export interface GetBundleBannerOptions { 51 | /** 52 | * Generates a shebang that lets the script be executed using the Node.js 53 | * interpreter 54 | */ 55 | bin?: boolean 56 | 57 | /** 58 | * Fields that need to be generated to Banner 59 | * 60 | * @default bundleBannerFields 61 | * @note Please make sure the value is a string 62 | */ 63 | fields?: string[] 64 | } 65 | 66 | /** 67 | * Generate Banner content based on package.json 68 | * 69 | * @category Tsup 70 | * @param pkg - Contents of package.json 71 | * @param options - Options for adjusting output results 72 | * @returns Banner content for generated chunks 73 | * @see https://www.npmjs.com/package/vite-plugin-banner 74 | */ 75 | export const getBundleBanner = ( 76 | pkg: Record, 77 | { bin, fields: _fields }: GetBundleBannerOptions = {}, 78 | ) => { 79 | if (Object.prototype.toString.call(pkg) !== '[object Object]') return '' 80 | 81 | const fields = Array.isArray(_fields) 82 | ? _fields 83 | : (bundleBannerFields as unknown as string[]) 84 | 85 | const baseBanners: string[] = [] 86 | baseBanners.push(`/**`) 87 | 88 | fields.forEach((k) => { 89 | const v = pkg[k] 90 | if (typeof v !== 'string') return 91 | const prefix = k === 'version' ? 'v' : '' 92 | baseBanners.push(` * ${k}: ${prefix}${v}`) 93 | }) 94 | 95 | baseBanners.push(` */`) 96 | 97 | const banners = bin 98 | ? ['#!/usr/bin/env node', '', ...baseBanners] 99 | : baseBanners 100 | 101 | return banners.join('\n') 102 | } 103 | 104 | /** 105 | * @category Tsup 106 | */ 107 | export type Config = ReturnType 108 | 109 | /** 110 | * @category Tsup 111 | */ 112 | export interface CreateBaseConfigOptions 113 | extends Partial> { 114 | pkg: Record 115 | } 116 | 117 | /** 118 | * Create base tsup config 119 | * 120 | * @category Tsup 121 | * @see https://tsup.egoist.dev/#bundle-formats 122 | */ 123 | export const createBaseConfig = (options: CreateBaseConfigOptions) => { 124 | const { 125 | pkg, 126 | entry = { index: 'src/index.ts' }, 127 | globalName, 128 | outDir = 'dist', 129 | format = [BundleFormat.CJS, BundleFormat.ESM], 130 | } = options || {} 131 | 132 | return { 133 | entry, 134 | target: ['es2020'], 135 | format, 136 | globalName, 137 | outExtension: (ctx) => getBundleExtension(ctx), 138 | outDir, 139 | dts: true, 140 | banner: { 141 | js: getBundleBanner(pkg, { bin: !!pkg.bin }), 142 | }, 143 | bundle: true, 144 | minify: true, 145 | clean: true, 146 | } as const satisfies Config 147 | } 148 | -------------------------------------------------------------------------------- /packages/release/README.md: -------------------------------------------------------------------------------- 1 | # @bassist/release 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | GitHub stars 15 | 16 |

17 | 18 | Simple GitHub release generator by [@chengpeiquan](https://github.com/chengpeiquan) , based on [GitHub CLI](https://cli.github.com/). 19 | 20 | If you're tired of having to release every time on GitHub web, you can use this package to make it easier and just run a single command. 21 | 22 | ## Prerequisite 23 | 24 | For the security of your account and to avoid Token leakage, you must first install GitHub CLI and complete the login on it. 25 | 26 | See: [GitHub CLI](https://cli.github.com/) 27 | 28 | And make sure you have Release permissions on the project's GitHub repository. 29 | 30 | There is another requirement, please configure the repository information of `package.json` according to the specifications of npm docs. 31 | 32 | See: [repository](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#repository) 33 | 34 | e.g. 35 | 36 | For single-package repo: 37 | 38 | ```json 39 | { 40 | "repository": { 41 | "type": "git", 42 | "url": "https://github.com/chengpeiquan/bassist" 43 | } 44 | } 45 | ``` 46 | 47 | For monorepo, you can specify the `directory` in which it lives: 48 | 49 | ```json 50 | { 51 | "repository": { 52 | "type": "git", 53 | "url": "https://github.com/chengpeiquan/bassist", 54 | "directory": "packages/utils" 55 | } 56 | } 57 | ``` 58 | 59 | Currently supported URL formats are: 60 | 61 | - `https://github.com/chengpeiquan/bassist` 62 | - `https://github.com/chengpeiquan/bassist.git` 63 | - `github:chengpeiquan/bassist` 64 | 65 | ## Usage 66 | 67 | This is a CLI tool, you can install it locally and run it through commands such as pnpm exec. 68 | 69 | Install it: 70 | 71 | ```bash 72 | pnpm add -D @bassist/release 73 | ``` 74 | 75 | In your `package.json` : 76 | 77 | ```json 78 | { 79 | "scripts": { 80 | "gen:release": "pnpm exec release" 81 | } 82 | } 83 | ``` 84 | 85 | Run on command line: 86 | 87 | ```bash 88 | pnpm gen:release 89 | ``` 90 | 91 | You can view the latest release information on the releases page of your GitHub repository. 92 | 93 | ## Options 94 | 95 | For most projects, the default settings are sufficient. If adjustments are sometimes needed, some options are provided to pass on. 96 | 97 | On the command line, options can be passed to the program, e.g. `--preset angular` by option, or `-p angular` by short flag. 98 | 99 | | Option | Short Flag | Default Value | Description | 100 | | :-------: | :--------: | :------------: | :--------------------------------------------- | 101 | | branch | b | `main` | The branch where the CHANGELOG file is located | 102 | | changelog | c | `CHANGELOG.md` | The file name of the change log | 103 | 104 | Btw: The paths are all based on `process.cwd()` , which is usually run from the root directory of the package (the directory where `package.json` is located). 105 | 106 | If there are any running problems, please provide a reproducible example in the [issue](https://github.com/chengpeiquan/bassist/issues) . 107 | 108 | ## Release Notes 109 | 110 | Please refer to [CHANGELOG](https://github.com/chengpeiquan/bassist/blob/main/packages/release/CHANGELOG.md) for details. 111 | 112 | ## License 113 | 114 | MIT License © 2023-PRESENT [chengpeiquan](https://github.com/chengpeiquan) 115 | -------------------------------------------------------------------------------- /packages/eslint-config/src/configs/vue.ts: -------------------------------------------------------------------------------- 1 | import vuePlugin from 'eslint-plugin-vue' 2 | import _vueParser from 'vue-eslint-parser' 3 | import { GLOB_EXCLUDE, GLOB_VUE } from '../globs' 4 | import { getConfigName } from '../shared/utils' 5 | import { 6 | type FlatESLintConfig, 7 | type FlatESLintParser, 8 | type FlatESLintRules, 9 | } from '../types' 10 | import { tsParser, tsPlugin, typescript } from './typescript' 11 | 12 | const vueParser = _vueParser as unknown as FlatESLintParser 13 | 14 | export { vueParser, vuePlugin } 15 | 16 | export const reactivityTransform: FlatESLintConfig[] = [ 17 | { 18 | name: getConfigName('vue', 'reactivity-transform'), 19 | languageOptions: { 20 | globals: { 21 | $: 'readonly', 22 | $$: 'readonly', 23 | $computed: 'readonly', 24 | $customRef: 'readonly', 25 | $ref: 'readonly', 26 | $shallowRef: 'readonly', 27 | $toRef: 'readonly', 28 | }, 29 | }, 30 | plugins: { 31 | vue: vuePlugin, 32 | }, 33 | rules: { 34 | 'vue/no-setup-props-destructure': 'off', 35 | }, 36 | }, 37 | ] 38 | 39 | const vueCustomRules = { 40 | 'vue/component-tags-order': [ 41 | 'off', 42 | { order: ['script', 'template', 'style'] }, 43 | ], 44 | 'vue/custom-event-name-casing': ['error', 'camelCase'], 45 | 'vue/eqeqeq': ['error', 'smart'], 46 | 'vue/html-self-closing': [ 47 | 'error', 48 | { 49 | html: { 50 | void: 'always', 51 | normal: 'always', 52 | component: 'always', 53 | }, 54 | svg: 'always', 55 | math: 'always', 56 | }, 57 | ], 58 | 'vue/max-attributes-per-line': 'off', 59 | 'vue/multi-word-component-names': 'off', 60 | 'vue/no-constant-condition': 'warn', 61 | 'vue/no-empty-pattern': 'error', 62 | 'vue/no-loss-of-precision': 'error', 63 | 'vue/no-unused-refs': 'error', 64 | 'vue/no-useless-v-bind': 'error', 65 | 'vue/no-v-html': 'off', 66 | 'vue/object-shorthand': [ 67 | 'error', 68 | 'always', 69 | { 70 | ignoreConstructors: false, 71 | avoidQuotes: true, 72 | }, 73 | ], 74 | 'vue/padding-line-between-blocks': ['error', 'always'], 75 | 'vue/prefer-template': 'error', 76 | 'vue/require-prop-types': 'off', 77 | 'vue/require-default-prop': 'off', 78 | } as unknown as FlatESLintRules 79 | 80 | const vue3Rules = { 81 | ...vuePlugin.configs.base.rules, 82 | ...vuePlugin.configs['vue3-essential'].rules, 83 | ...vuePlugin.configs['vue3-strongly-recommended'].rules, 84 | ...vuePlugin.configs['vue3-recommended'].rules, 85 | } as unknown as FlatESLintRules 86 | 87 | const vue2Rules = { 88 | ...vuePlugin.configs.base.rules, 89 | ...vuePlugin.configs.essential.rules, 90 | ...vuePlugin.configs['strongly-recommended'].rules, 91 | ...vuePlugin.configs.recommended.rules, 92 | } as unknown as FlatESLintRules 93 | 94 | const getVueConfig = (vueVersion: 'vue2' | 'vue3') => { 95 | const vueVersionRules = vueVersion === 'vue2' ? vue2Rules : vue3Rules 96 | 97 | const vueRules: FlatESLintConfig[] = [ 98 | { 99 | name: getConfigName('vue', 'base'), 100 | files: [GLOB_VUE], 101 | plugins: { 102 | vue: vuePlugin, 103 | '@typescript-eslint': tsPlugin, 104 | }, 105 | languageOptions: { 106 | ecmaVersion: 'latest', 107 | parser: vueParser, 108 | parserOptions: { 109 | parser: tsParser, 110 | sourceType: 'module', 111 | extraFileExtensions: ['.vue'], 112 | ecmaFeatures: { 113 | jsx: true, 114 | }, 115 | }, 116 | }, 117 | processor: vuePlugin.processors['.vue'], 118 | rules: { 119 | ...typescript[0].rules, 120 | }, 121 | ignores: [...GLOB_EXCLUDE], 122 | }, 123 | { 124 | name: getConfigName('vue', vueVersion), 125 | plugins: { 126 | vue: vuePlugin, 127 | }, 128 | rules: { 129 | ...vueVersionRules, 130 | ...vueCustomRules, 131 | }, 132 | ignores: [...GLOB_EXCLUDE], 133 | }, 134 | ...reactivityTransform, 135 | ] 136 | 137 | return vueRules 138 | } 139 | 140 | export const vueLegacy = getVueConfig('vue2') 141 | export const vue = getVueConfig('vue3') 142 | -------------------------------------------------------------------------------- /packages/changelog/README.md: -------------------------------------------------------------------------------- 1 | # @bassist/changelog 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | GitHub stars 15 | 16 |

17 | 18 | Simple CHANGELOG generator by [@chengpeiquan](https://github.com/chengpeiquan) , based on [conventional-changelog-cli](https://www.npmjs.com/package/conventional-changelog-cli). 19 | 20 | If you are tired of remembering long command names and command line configurations every time, you can use this package to simplify the operation. 21 | 22 | ## Usage 23 | 24 | This is a CLI tool, you can install it locally and run it through commands such as pnpm exec. 25 | 26 | Install it: 27 | 28 | ```bash 29 | pnpm add -D @bassist/changelog conventional-changelog-cli 30 | ``` 31 | 32 | In your `package.json` : 33 | 34 | ```json 35 | { 36 | "scripts": { 37 | "gen:changelog": "pnpm exec changelog" 38 | } 39 | } 40 | ``` 41 | 42 | Run on command line: 43 | 44 | ```bash 45 | pnpm gen:changelog 46 | ``` 47 | 48 | You can see a CHANGELOG.md file in the project root directory, which will generate the software's change records based on your Git Logs. 49 | 50 | ## Implementation Principle 51 | 52 | In this package, the program will run the conventional-changelog CLI command to generate CHANGELOG, so `conventional-changelog-cli`, as the peerDependency of the package, also needs to be installed together. 53 | 54 | ## Options 55 | 56 | For most projects, the default settings are sufficient. If adjustments are sometimes needed, some options are provided to pass on. 57 | 58 | On the command line, options can be passed to the program, e.g. `--preset angular` by option, or `-p angular` by short flag. 59 | 60 | | Option | Short Flag | Default Value | Description | 61 | | :-----------: | :--------: | :------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 62 | | lerna-package | l | | Generate a changelog for a specific lerna package (:pkg-name@1.0.0) | 63 | | preset | p | `angular` | Name of the preset you want to use. Must be one of the following: `angular`, `atom`, `codemirror`, `conventionalcommits`, `ember`, `eslint`, `express`, `jquery` or `jshint` | 64 | | infile | i | `CHANGELOG.md` | Read the CHANGELOG from this file, and outputting to the same file | 65 | | release-count | r | `1` | How many releases to be generated from the latest, If `0` , the whole changelog will be regenerated and the outfile will be overwritten directory | 66 | | commit-path | | `./src` | Generate a changelog scoped to a specific directory | 67 | 68 | Btw: The paths are all based on `process.cwd()` , which is usually run from the root directory of the package (the directory where `package.json` is located). 69 | 70 | If there are any running problems, please provide a reproducible example in the [issue](https://github.com/chengpeiquan/bassist/issues), or use [conventional-changelog-cli](https://www.npmjs.com/package/conventional-changelog-cli) directly (need to configure it yourself) 71 | 72 | ## Release Notes 73 | 74 | Please refer to [CHANGELOG](https://github.com/chengpeiquan/bassist/blob/main/packages/changelog/CHANGELOG.md) for details. 75 | 76 | ## License 77 | 78 | MIT License © 2023-PRESENT [chengpeiquan](https://github.com/chengpeiquan) 79 | -------------------------------------------------------------------------------- /packages/utils/src/device.ts: -------------------------------------------------------------------------------- 1 | import { getUserAgent } from './ua' 2 | 3 | /** 4 | * Checks if the code is being executed in a browser environment 5 | * 6 | * @category Device 7 | */ 8 | export const isBrowser = typeof window !== 'undefined' 9 | 10 | /** 11 | * Checks if the code is being executed in a server environment 12 | * 13 | * @category Device 14 | */ 15 | export const isServer = !isBrowser 16 | 17 | /** 18 | * Regular expression pattern to match mobile device user agents 19 | * 20 | * @category Device 21 | */ 22 | export const mobileDevicesRegExp = /iPhone|phone|android|iPod|pad|iPad/i 23 | 24 | /** 25 | * Checks if the code is being executed on a mobile device 26 | * 27 | * @category Device 28 | */ 29 | export function isMobile() { 30 | if (!isBrowser) return false 31 | return mobileDevicesRegExp.test(getUserAgent()) 32 | } 33 | 34 | /** 35 | * Regular expression pattern to match tablet device user agents 36 | * 37 | * @category Device 38 | */ 39 | export const tabletDevicesRegExp = 40 | /(ipad|tablet|(android(?!.*mobile))|(windows(?!.*phone)(.*touch))|kindle|playbook|silk|(puffin(?!.*(IP|AP|WP))))/i 41 | 42 | /** 43 | * Checks if the code is being executed on a tablet device 44 | * 45 | * @category Device 46 | */ 47 | export function isTablet() { 48 | if (!isBrowser) return false 49 | if (!isMobile()) return false 50 | return tabletDevicesRegExp.test(getUserAgent()) 51 | } 52 | 53 | /** 54 | * Checks if the code is being executed on a desktop device 55 | * 56 | * @category Device 57 | */ 58 | export function isDesktop() { 59 | if (!isBrowser) return false 60 | return !isMobile() 61 | } 62 | 63 | /** 64 | * Regular expression pattern to match apple device user agents 65 | * 66 | * @category Device 67 | */ 68 | export const appleDevicesRegExp = /(mac|iphone|ipod|ipad)/i 69 | 70 | /** 71 | * Checks if the code is being executed on an apple device 72 | * 73 | * @category Device 74 | */ 75 | export function isAppleDevice() { 76 | if (!isBrowser) return false 77 | return appleDevicesRegExp.test(getUserAgent()) 78 | } 79 | 80 | /** 81 | * Checks if the code is running on an Android device 82 | * 83 | * @category Device 84 | */ 85 | export const isAndroid = /Android/i.test(getUserAgent()) 86 | 87 | /** 88 | * Checks if the code is running on an iOS device 89 | * 90 | * @category Device 91 | */ 92 | export const isIOS = /iPhone|iPod|iPad|iOS/i.test(getUserAgent()) 93 | 94 | /** 95 | * Checks if the code is running in a Uni-App environment 96 | * 97 | * @category Device 98 | */ 99 | export const isUniApp = /uni-app|html5plus/.test(getUserAgent()) 100 | 101 | /** 102 | * Checks if the code is running in a WeChat (Weixin) environment 103 | * 104 | * @category Device 105 | */ 106 | export const isWeixin = /MicroMessenger/i.test(getUserAgent()) 107 | 108 | /** 109 | * Checks if the code is running in a QQ environment 110 | * 111 | * @category Device 112 | */ 113 | export const isQQ = /\sQQ|mqqbrowser|qzone|qqbrowser/i.test(getUserAgent()) 114 | 115 | /** 116 | * Checks if the code is running in a QQ Browser environment 117 | * 118 | * @category Device 119 | */ 120 | export const isQQBrowser = /mqqbrowser|qqbrowser/i.test(getUserAgent()) 121 | 122 | /** 123 | * Checks if the code is running in a Qzone environment 124 | * 125 | * @category Device 126 | */ 127 | export const isQzone = /qzone\/.*_qz_([\d.]+)/i.test(getUserAgent()) 128 | 129 | /** 130 | * Checks if the code is running in a Weibo environment 131 | * 132 | * @category Device 133 | */ 134 | export const isWeibo = /(weibo).*weibo__([\d.]+)/i.test(getUserAgent()) 135 | 136 | /** 137 | * Checks if the code is running in a Baidu Box App environment 138 | * 139 | * @category Device 140 | */ 141 | export const isBaidu = /(baiduboxapp)\/([\d.]+)/i.test(getUserAgent()) 142 | 143 | /** 144 | * @category Device 145 | */ 146 | interface DeviceResizeWatcherOptions { 147 | // Executed when the page load done 148 | immediate: boolean 149 | } 150 | 151 | /** 152 | * Watches for page resize or orientation change events and executes the 153 | * callback 154 | * 155 | * @category Device 156 | * @param callback - The callback function to be executed 157 | * @param options - The options for the resize watcher `immediate`: Determines 158 | * whether the callback should be immediately executed on page load 159 | */ 160 | export function watchResize( 161 | callback: () => void, 162 | { immediate }: DeviceResizeWatcherOptions = { immediate: true }, 163 | ) { 164 | if (!isBrowser) return 165 | if (immediate) { 166 | window.addEventListener('load', callback, false) 167 | } 168 | window.addEventListener( 169 | 'orientationchange' in window ? 'orientationchange' : 'resize', 170 | callback, 171 | false, 172 | ) 173 | } 174 | -------------------------------------------------------------------------------- /packages/utils/test/regexp.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { isMob, isEmail, isUrl, isIdCard, isBankCard, isIPv4, isIPv6 } from '..' 3 | 4 | describe('isMob', () => { 5 | it('Valid data', () => { 6 | expect(isMob(13800138000)).toBeTruthy() 7 | expect(isMob('13800138000')).toBeTruthy() 8 | expect(isMob('13100000000')).toBeTruthy() 9 | }) 10 | it('Invalid data', () => { 11 | expect(isMob('13800138000 ')).toBeFalsy() 12 | expect(isMob('1380013800')).toBeFalsy() 13 | expect(isMob(123456)).toBeFalsy() 14 | expect(isMob('hello')).toBeFalsy() 15 | }) 16 | }) 17 | 18 | describe('isEmail', () => { 19 | it('Valid data', () => { 20 | expect(isEmail('abc@qq.com')).toBeTruthy() 21 | expect(isEmail('123456@qq.com')).toBeTruthy() 22 | expect(isEmail('test@163.com')).toBeTruthy() 23 | }) 24 | it('Invalid data', () => { 25 | expect(isEmail('abc@qq.com ')).toBeFalsy() 26 | expect(isEmail('abc@q q.com ')).toBeFalsy() 27 | expect(isEmail('abc@qq')).toBeFalsy() 28 | expect(isEmail('@qq.com')).toBeFalsy() 29 | expect(isEmail('hello')).toBeFalsy() 30 | }) 31 | }) 32 | 33 | describe('isUrl', () => { 34 | it('Valid data', () => { 35 | expect(isUrl('http://example.com')).toBeTruthy() 36 | expect(isUrl('https://example.com')).toBeTruthy() 37 | expect(isUrl('https://example.com ')).toBeTruthy() 38 | expect(isUrl('https://example.com/')).toBeTruthy() 39 | expect(isUrl('https://foo.example.com')).toBeTruthy() 40 | expect(isUrl('https://foo.bar.example.com')).toBeTruthy() 41 | expect(isUrl('https://example.com/foo')).toBeTruthy() 42 | expect(isUrl('https://example.com/foo/bar')).toBeTruthy() 43 | expect(isUrl('https://example.com/foo/bar/baz')).toBeTruthy() 44 | expect(isUrl('https://example.com/foo?a=1')).toBeTruthy() 45 | expect(isUrl('https://example.com/foo?a=1&b=2')).toBeTruthy() 46 | expect(isUrl('https://example.com/foo?a=1#b=2')).toBeTruthy() 47 | expect(isUrl('https://example.com/foo#a=1?b=2')).toBeTruthy() 48 | }) 49 | it('Invalid data', () => { 50 | expect(isUrl('http:example.com')).toBeFalsy() 51 | expect(isUrl('http:/example.com')).toBeFalsy() 52 | expect(isUrl('https://example..com')).toBeFalsy() 53 | expect(isUrl('https://foo..example.com')).toBeFalsy() 54 | expect(isUrl('hello')).toBeFalsy() 55 | }) 56 | }) 57 | 58 | describe('isIdCard', () => { 59 | // https://www.bjcourt.gov.cn/zxxx/indexOld.htm?jbfyId=17&zxxxlx=100013002 60 | // http://legal.people.com.cn/n1/2020/0424/c42510-31687296.html 61 | it('Valid data', () => { 62 | expect(isIdCard('110223790813697')).toBeTruthy() 63 | expect(isIdCard('110225196403026127')).toBeTruthy() 64 | expect(isIdCard('152221198906101419')).toBeTruthy() 65 | }) 66 | it('Invalid data', () => { 67 | expect(isIdCard('1102237908136971')).toBeFalsy() 68 | expect(isIdCard('1102221974****4827')).toBeFalsy() 69 | expect(isIdCard('123456')).toBeFalsy() 70 | expect(isIdCard('hello')).toBeFalsy() 71 | }) 72 | }) 73 | 74 | describe('isBankCard', () => { 75 | // https://ddu1222.github.io/bankcard-validator/bcBuilder.html 76 | it('Valid data', () => { 77 | expect(isBankCard('5124255722414430')).toBeTruthy() 78 | expect(isBankCard('5149570635749446')).toBeTruthy() 79 | expect(isBankCard('4357458903454875')).toBeTruthy() 80 | expect(isBankCard('6223508057942120')).toBeTruthy() 81 | }) 82 | it('Invalid data', () => { 83 | expect(isBankCard('151242557224144301')).toBeFalsy() 84 | expect(isBankCard('123456')).toBeFalsy() 85 | expect(isBankCard('hello')).toBeFalsy() 86 | }) 87 | }) 88 | 89 | describe('isIPv4', () => { 90 | it('Valid data', () => { 91 | expect(isIPv4('0.0.0.0')).toBeTruthy() 92 | expect(isIPv4('1.2.3.4')).toBeTruthy() 93 | expect(isIPv4('127.0.0.1')).toBeTruthy() 94 | expect(isIPv4('192.168.0.1')).toBeTruthy() 95 | expect(isIPv4('10.24.3.68')).toBeTruthy() 96 | expect(isIPv4('45.150.220.38')).toBeTruthy() 97 | expect(isIPv4('255.255.255.0')).toBeTruthy() 98 | }) 99 | it('Invalid data', () => { 100 | expect(isIPv4('123')).toBeFalsy() 101 | expect(isIPv4('localhost')).toBeFalsy() 102 | expect(isIPv4('999.999.999.999')).toBeFalsy() 103 | expect(isIPv4('192.168.1')).toBeFalsy() 104 | }) 105 | }) 106 | 107 | describe('isIPv6', () => { 108 | it('Valid data', () => { 109 | expect(isIPv6('2001:0DB8:0000:0023:0008:0800:200C:417A')).toBeTruthy() 110 | expect(isIPv6('2001:DB8:0:23:8:800:200C:417A')).toBeTruthy() 111 | expect(isIPv6('FF01:0:0:0:0:0:0:1101')).toBeTruthy() 112 | expect(isIPv6('FF01::1101')).toBeTruthy() 113 | expect(isIPv6('0:0:0:0:0:0:0:1')).toBeTruthy() 114 | expect(isIPv6('::1')).toBeTruthy() 115 | expect(isIPv6('0:0:0:0:0:0:0:0')).toBeTruthy() 116 | expect(isIPv6('::')).toBeTruthy() 117 | expect(isIPv6('::192.168.0.1')).toBeTruthy() 118 | expect(isIPv6('::FFFF:192.168.0.1')).toBeTruthy() 119 | }) 120 | it('Invalid data', () => { 121 | expect(isIPv6('123')).toBeFalsy() 122 | expect(isIPv6('localhost')).toBeFalsy() 123 | expect(isIPv6('999.999.999.999')).toBeFalsy() 124 | expect(isIPv6('192.168.1')).toBeFalsy() 125 | expect(isIPv6('0.0.0.0')).toBeFalsy() 126 | expect(isIPv6('1.2.3.4')).toBeFalsy() 127 | expect(isIPv6('127.0.0.1')).toBeFalsy() 128 | expect(isIPv6('192.168.0.1')).toBeFalsy() 129 | expect(isIPv6('10.24.3.68')).toBeFalsy() 130 | expect(isIPv6('45.150.220.38')).toBeFalsy() 131 | expect(isIPv6('255.255.255.0')).toBeFalsy() 132 | }) 133 | }) 134 | -------------------------------------------------------------------------------- /packages/utils/test/data.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { 3 | getDataType, 4 | isObject, 5 | isArray, 6 | inRange, 7 | isFunction, 8 | isAsyncFunction, 9 | isPromise, 10 | isEven, 11 | isOdd, 12 | } from '..' 13 | 14 | class Foo { 15 | bar() {} 16 | } 17 | 18 | const obj = { 19 | then() {}, 20 | } 21 | 22 | describe('getDataType', () => { 23 | it('Valid data', () => { 24 | expect(getDataType('')).toBe('String') 25 | expect(getDataType(String(1))).toBe('String') 26 | expect(getDataType(new String(1))).toBe('String') 27 | expect(getDataType(null)).toBe('Null') 28 | expect(getDataType(undefined)).toBe('Undefined') 29 | expect(getDataType(new Date())).toBe('Date') 30 | expect(getDataType(/foo/)).toBe('RegExp') 31 | }) 32 | }) 33 | 34 | describe('isArray', () => { 35 | it('Valid data', () => { 36 | expect(isArray([])).toBeTruthy() 37 | expect(isArray(new Array(1))).toBeTruthy() 38 | expect(isArray(Array.from(new Set()))).toBeTruthy() 39 | }) 40 | it('Invalid data', () => { 41 | expect(isArray({})).toBeFalsy() 42 | expect(isArray(new Object())).toBeFalsy() 43 | expect(isArray(Object.create([]))).toBeFalsy() 44 | expect(isArray(Object.create({ foo: 1 }))).toBeFalsy() 45 | expect(isArray('')).toBeFalsy() 46 | expect(isArray(null)).toBeFalsy() 47 | expect(isArray(undefined)).toBeFalsy() 48 | expect(isArray(Object(1))).toBeFalsy() 49 | }) 50 | }) 51 | 52 | describe('isAsyncFunction', () => { 53 | it('Valid data', () => { 54 | expect(isAsyncFunction(async () => {})).toBeTruthy() 55 | expect(isAsyncFunction(async function () {})).toBeTruthy() 56 | }) 57 | it('Invalid data', () => { 58 | expect(isAsyncFunction(new Function())).toBeFalsy() 59 | expect(isAsyncFunction(function () {})).toBeFalsy() 60 | expect(isAsyncFunction(() => {})).toBeFalsy() 61 | expect(isAsyncFunction(Foo)).toBeFalsy() 62 | expect(isAsyncFunction(new Foo().bar)).toBeFalsy() 63 | }) 64 | }) 65 | 66 | describe('isEven', () => { 67 | it('Valid data', () => { 68 | expect(isEven(0)).toBeTruthy() 69 | expect(isEven(2)).toBeTruthy() 70 | expect(isEven(4)).toBeTruthy() 71 | }) 72 | it('Invalid data', () => { 73 | expect(isEven(-1)).toBeFalsy() 74 | expect(isEven(1)).toBeFalsy() 75 | expect(isEven(1.5)).toBeFalsy() 76 | expect(isEven(3)).toBeFalsy() 77 | expect(isEven(15)).toBeFalsy() 78 | }) 79 | }) 80 | 81 | describe('isFunction', () => { 82 | it('Valid data', () => { 83 | expect(isFunction(new Function())).toBeTruthy() 84 | expect(isFunction(function () {})).toBeTruthy() 85 | expect(isFunction(() => {})).toBeTruthy() 86 | expect(isFunction(async () => {})).toBeTruthy() 87 | expect(isFunction(async function () {})).toBeTruthy() 88 | expect(isFunction(Foo)).toBeTruthy() 89 | expect(isFunction(new Foo().bar)).toBeTruthy() 90 | }) 91 | it('Invalid data', () => { 92 | expect(isFunction(1)).toBeFalsy() 93 | expect(isFunction('1')).toBeFalsy() 94 | expect(isFunction(undefined)).toBeFalsy() 95 | expect(isFunction(null)).toBeFalsy() 96 | expect(isFunction({})).toBeFalsy() 97 | expect(isFunction([])).toBeFalsy() 98 | }) 99 | }) 100 | 101 | describe('isOdd', () => { 102 | it('Valid data', () => { 103 | expect(isOdd(-1)).toBeTruthy() 104 | expect(isOdd(1)).toBeTruthy() 105 | expect(isOdd(3)).toBeTruthy() 106 | expect(isOdd(15)).toBeTruthy() 107 | }) 108 | it('Invalid data', () => { 109 | expect(isOdd(0)).toBeFalsy() 110 | expect(isOdd(1.5)).toBeFalsy() 111 | expect(isOdd(2)).toBeFalsy() 112 | expect(isOdd(4)).toBeFalsy() 113 | }) 114 | }) 115 | 116 | describe('isObject', () => { 117 | it('Valid data', () => { 118 | expect(isObject({})).toBeTruthy() 119 | expect(isObject(new Object())).toBeTruthy() 120 | expect(isObject(Object.create({ foo: 1 }))).toBeTruthy() 121 | }) 122 | it('Invalid data', () => { 123 | expect(isObject('')).toBeFalsy() 124 | expect(isObject(null)).toBeFalsy() 125 | expect(isObject(undefined)).toBeFalsy() 126 | expect(isObject(Object(1))).toBeFalsy() 127 | }) 128 | }) 129 | 130 | describe('isPromise', () => { 131 | it('Valid data', () => { 132 | expect(isPromise(new Promise((r) => r()))).toBeTruthy() 133 | expect(isPromise(Promise.resolve())).toBeTruthy() 134 | expect(isPromise((async () => {})())).toBeTruthy() 135 | }) 136 | it('Invalid data', () => { 137 | expect(isPromise(obj)).toBeFalsy() 138 | expect(isPromise('')).toBeFalsy() 139 | expect(isPromise(null)).toBeFalsy() 140 | expect(isPromise(undefined)).toBeFalsy() 141 | expect(isPromise(Object(1))).toBeFalsy() 142 | expect(isPromise({})).toBeFalsy() 143 | expect(isPromise(new Object())).toBeFalsy() 144 | expect(isPromise(Object.create({ foo: 1 }))).toBeFalsy() 145 | }) 146 | }) 147 | 148 | describe('inRange', () => { 149 | it('Valid data', () => { 150 | expect(inRange({ num: 1, min: 0, max: 5 })).toBeTruthy() 151 | expect(inRange({ num: 1, min: -5, max: 5 })).toBeTruthy() 152 | expect(inRange({ num: 1, min: 5, max: -5 })).toBeTruthy() 153 | expect( 154 | inRange({ num: -4, min: -5, max: 5, includeMin: false }), 155 | ).toBeTruthy() 156 | expect(inRange({ num: 4, min: -5, max: 5, includeMax: false })).toBeTruthy() 157 | }) 158 | it('Invalid data', () => { 159 | expect(inRange({ num: 10, min: 0, max: 5 })).toBeFalsy() 160 | expect(inRange({ num: -99, min: -5, max: 5 })).toBeFalsy() 161 | expect(inRange({ num: NaN, min: 5, max: -5 })).toBeFalsy() 162 | expect(inRange({ num: -5, min: -5, max: 5, includeMin: false })).toBeFalsy() 163 | expect(inRange({ num: 5, min: -5, max: 5, includeMax: false })).toBeFalsy() 164 | }) 165 | }) 166 | -------------------------------------------------------------------------------- /packages/utils/src/load.ts: -------------------------------------------------------------------------------- 1 | import { pnoop } from './data' 2 | import { isBrowser } from './device' 3 | import { getQuery } from './query' 4 | import { randomString } from './random' 5 | 6 | /** 7 | * @category Network 8 | */ 9 | export type ResourcesSupportedWithLoadRes = 'js' | 'css' | 'style' 10 | 11 | type ResourcesElement = HTMLScriptElement | HTMLLinkElement | HTMLStyleElement 12 | 13 | /** 14 | * @category Network 15 | */ 16 | export interface LoadResOptions { 17 | type: ResourcesSupportedWithLoadRes 18 | id: string 19 | resource: string 20 | } 21 | 22 | /** 23 | * Dynamic loading of resources 24 | * 25 | * @category Network 26 | */ 27 | export function loadRes({ type, id, resource }: LoadResOptions) { 28 | return new Promise((resolve, reject) => { 29 | if (!isBrowser || document.querySelector(`#${id}`)) { 30 | reject() 31 | return 32 | } 33 | 34 | function bindStatus(el: ResourcesElement) { 35 | el.addEventListener('load', resolve) 36 | el.addEventListener('error', reject) 37 | el.addEventListener('abort', reject) 38 | } 39 | 40 | switch (type) { 41 | case 'js': { 42 | const script = document.createElement('script') 43 | script.id = id 44 | script.async = true 45 | script.src = resource 46 | bindStatus(script) 47 | document.head.appendChild(script) 48 | break 49 | } 50 | 51 | case 'css': { 52 | const link = document.createElement('link') 53 | link.id = id 54 | link.rel = 'stylesheet' 55 | link.href = resource 56 | bindStatus(link) 57 | document.head.appendChild(link) 58 | break 59 | } 60 | 61 | case 'style': { 62 | const style = document.createElement('style') 63 | style.id = id 64 | bindStatus(style) 65 | document.head.appendChild(style) 66 | style.appendChild(document.createTextNode(resource)) 67 | break 68 | } 69 | } 70 | }) 71 | } 72 | 73 | /** 74 | * JSON with Padding 75 | * 76 | * Note: JSONP is a method for sending JSON data without worrying about 77 | * cross-domain issues. JSONP does not use the XMLHttpRequest or Fetch, it uses 78 | * the `