mirror of
https://github.com/robonen/tools.git
synced 2026-03-20 10:54:44 +00:00
Compare commits
136 Commits
feat/templ
...
09fe8079c0
| Author | SHA1 | Date | |
|---|---|---|---|
| 09fe8079c0 | |||
| ab9f45f908 | |||
| 49b9f2aa79 | |||
| 2a5412c3b8 | |||
| 5f9e0dc72d | |||
| 6565fa3de8 | |||
| 7dce7ed482 | |||
| df13f0b827 | |||
| 3da393ed08 | |||
| efadb5fe28 | |||
|
|
07e6d3eadc | ||
|
|
6fcc9d5a51 | ||
|
|
289d0d5af1 | ||
| 4bade839e7 | |||
|
|
c4321a2039 | ||
| f6b3bfbca6 | |||
|
|
7541e6aad4 | ||
|
|
a4d9b4c88a | ||
|
|
3b39f64734 | ||
|
|
6ab2d5cebf | ||
|
|
54f1facc4f | ||
|
|
717c41ef88 | ||
|
|
3747f5213e | ||
| daf18871a0 | |||
|
|
8bf9943e9e | ||
|
|
0e67715d9e | ||
|
|
3e43e4db3d | ||
| b8308e383c | |||
|
|
93c878cc35 | ||
| 7653975fa4 | |||
|
|
e2cb3f5a75 | ||
|
|
67fbad8930 | ||
|
|
e49c49e320 | ||
|
|
43cdc3b5e6 | ||
| a9a6c04176 | |||
| a6d3e8971f | |||
| 40dfdabd08 | |||
|
|
876a815fd3 | ||
| b1b9889ad2 | |||
|
|
9d2a393372 | ||
|
|
4071e49ad6 | ||
| 88bd87f9b0 | |||
|
|
ac265c05a8 | ||
| 69e5ebc085 | |||
| 48a85dbae2 | |||
| 0cfdce7456 | |||
| e035d1abca | |||
| 1851d5c80c | |||
| 48626a9fe5 | |||
| 04aa9e4721 | |||
| d55e3989f3 | |||
|
|
acee7e4167 | ||
|
|
a633bd8da0 | ||
| e194ba3883 | |||
|
|
d7c978bf9e | ||
|
|
5674095073 | ||
| 77bab6055c | |||
| 7fcafae467 | |||
| 52a5add405 | |||
| bd5fdab6a0 | |||
| e8d7cccfe0 | |||
| be13ec7079 | |||
| 55438b63f9 | |||
| 1e9859da83 | |||
|
|
aa8a0f00f3 | ||
|
|
e1e879ebbb | ||
|
|
6339b21f56 | ||
|
|
1d4f5c5512 | ||
| 2cc0efd556 | |||
| bef0aea14c | |||
| 40d1d6962b | |||
|
|
eb8514fe89 | ||
| b6200aa7a3 | |||
| a67322ca66 | |||
| 8297e47086 | |||
| 95e1bcd0c4 | |||
| 6d68246d16 | |||
| 049b5b351a | |||
| 890d984aad | |||
| 9d01b12160 | |||
| 6f2311afeb | |||
| f7312b1060 | |||
| 3d15f7b3b2 | |||
| 32bf20899f | |||
| 8355477e0e | |||
| 968cf26fd0 | |||
| 78fb4da82a | |||
| d55737df2f | |||
| 39ce28a5ef | |||
|
|
3d813d22b9 | ||
| 4f558270ce | |||
| 4d6922e06a | |||
| fa726eecc4 | |||
| c5f34efe05 | |||
| ead9c019cd | |||
| 8ee6970674 | |||
| 27c80d24ef | |||
| c596e8aa29 | |||
| f8b37cacd3 | |||
| 40d8194134 | |||
|
|
11d1ac232e | ||
| 7c1d801c8e | |||
| de391fa80d | |||
| 8ab58078ba | |||
| 88f6cec9b2 | |||
| 09e72d904c | |||
| 695647470b | |||
| b2beb6a5fc | |||
| c7048be9fb | |||
| 4ead7fb18c | |||
| 3994f349f4 | |||
| 8d6f08c332 | |||
| 3a2837c1a1 | |||
|
|
82a0c0f746 | ||
|
|
e8667d6a0a | ||
| 6ed7d39a11 | |||
|
|
74c170e853 | ||
| fa96b9ddee | |||
| ff4a88b896 | |||
|
|
871e0cfad2 | ||
|
|
849d444172 | ||
|
|
cea221ed57 | ||
|
|
49dacf071f | ||
| a07ac35db9 | |||
| 8c5252986e | |||
| fad1284cd3 | |||
| ca0a63ea38 | |||
| 7bfbb8e52a | |||
| 30b72fb2f0 | |||
| 5594cef31e | |||
| caa7c4221a | |||
| 6ae3c939d8 | |||
| 1bada217e9 | |||
|
|
c813bd174c | ||
|
|
a2f49b6286 | ||
|
|
987b8d4abd |
16
.github/workflows/ci.yaml
vendored
16
.github/workflows/ci.yaml
vendored
@@ -1,7 +1,9 @@
|
|||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
- pull_request
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NODE_VERSION: 22.x
|
NODE_VERSION: 22.x
|
||||||
@@ -14,14 +16,14 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
@@ -29,5 +31,11 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: pnpm lint
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm all:build && pnpm all:test
|
run: pnpm test
|
||||||
78
.github/workflows/publish.yaml
vendored
Normal file
78
.github/workflows/publish.yaml
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
name: Publish to NPM
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
env:
|
||||||
|
NODE_VERSION: 22.x
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-and-publish:
|
||||||
|
name: Check version changes and publish
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
cache: pnpm
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build & Test
|
||||||
|
run: pnpm build && pnpm test
|
||||||
|
|
||||||
|
- name: Check for version changes and publish
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
run: |
|
||||||
|
# Find all package.json files (excluding node_modules)
|
||||||
|
PACKAGE_FILES=$(find . -path "*/package.json" -not -path "*/node_modules/*")
|
||||||
|
|
||||||
|
for file in $PACKAGE_FILES; do
|
||||||
|
PACKAGE_DIR=$(dirname $file)
|
||||||
|
echo "Checking $PACKAGE_DIR for version changes..."
|
||||||
|
|
||||||
|
# Get package details
|
||||||
|
PACKAGE_NAME=$(node -p "require('$file').name")
|
||||||
|
CURRENT_VERSION=$(node -p "require('$file').version")
|
||||||
|
IS_PRIVATE=$(node -p "require('$file').private || false")
|
||||||
|
|
||||||
|
# Skip private packages
|
||||||
|
if [ "$IS_PRIVATE" == "true" ]; then
|
||||||
|
echo "Skipping private package $PACKAGE_NAME"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Skip root package
|
||||||
|
if [ "$PACKAGE_DIR" == "." ]; then
|
||||||
|
echo "Skipping root package"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if package exists on npm
|
||||||
|
NPM_VERSION=$(npm view $PACKAGE_NAME version 2>/dev/null || echo "0.0.0")
|
||||||
|
|
||||||
|
# Compare versions
|
||||||
|
if [ "$CURRENT_VERSION" != "$NPM_VERSION" ]; then
|
||||||
|
echo "Version changed for $PACKAGE_NAME: $NPM_VERSION → $CURRENT_VERSION"
|
||||||
|
echo "Publishing $PACKAGE_NAME@$CURRENT_VERSION"
|
||||||
|
cd $PACKAGE_DIR
|
||||||
|
pnpm publish --access public --no-git-checks
|
||||||
|
cd -
|
||||||
|
else
|
||||||
|
echo "No version change detected for $PACKAGE_NAME"
|
||||||
|
fi
|
||||||
|
done
|
||||||
57
CHANGELOG.md
57
CHANGELOG.md
@@ -1,57 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
|
|
||||||
## v0.0.1
|
|
||||||
|
|
||||||
|
|
||||||
### 🚀 Enhancements
|
|
||||||
|
|
||||||
- **repo:** Cli tool, base tscofig ([3fcc42e](https://github.com/robonen/tools/commit/3fcc42e))
|
|
||||||
- **repo:** Drop node_modules ([7dba5ac](https://github.com/robonen/tools/commit/7dba5ac))
|
|
||||||
- **repo:** Global gitignore ([00c2736](https://github.com/robonen/tools/commit/00c2736))
|
|
||||||
- **packages/tsconfig:** Readme ([afa15cd](https://github.com/robonen/tools/commit/afa15cd))
|
|
||||||
- **docs:** Add auto generated doc based on readme ([3960f86](https://github.com/robonen/tools/commit/3960f86))
|
|
||||||
- **packages/stdlib:** Create stdlib ([c985b95](https://github.com/robonen/tools/commit/c985b95))
|
|
||||||
- **packages/stdlib:** Base vite config ([0434725](https://github.com/robonen/tools/commit/0434725))
|
|
||||||
- **packages/stdlib:** Math/clamp util ([8515bff](https://github.com/robonen/tools/commit/8515bff))
|
|
||||||
- **packages/stdlib:** MapRange util ([d8a9a62](https://github.com/robonen/tools/commit/d8a9a62))
|
|
||||||
- **packages/stdlib:** Levenshtein distance util ([0022153](https://github.com/robonen/tools/commit/0022153))
|
|
||||||
- **packages/stdlib:** Add trigram distance utill ([5045852](https://github.com/robonen/tools/commit/5045852))
|
|
||||||
|
|
||||||
### 🩹 Fixes
|
|
||||||
|
|
||||||
- **repo:** Workspaces -> workspace ([80b87d7](https://github.com/robonen/tools/commit/80b87d7))
|
|
||||||
|
|
||||||
### 💅 Refactors
|
|
||||||
|
|
||||||
- **repo:** Cleanup ([bc2ebfc](https://github.com/robonen/tools/commit/bc2ebfc))
|
|
||||||
- **packages/tsconfig:** Readme remove extra spaces ([565e7d8](https://github.com/robonen/tools/commit/565e7d8))
|
|
||||||
- **docs:** Drop docs cache and dist ([03f755d](https://github.com/robonen/tools/commit/03f755d))
|
|
||||||
- **repo:** Add vitepress to gitignore ([cf71b8e](https://github.com/robonen/tools/commit/cf71b8e))
|
|
||||||
- **repo:** Add pathe lib to cli tool ([d7a2d15](https://github.com/robonen/tools/commit/d7a2d15))
|
|
||||||
- **packages/tsconfig:** Add description and publishConfig ([37d25bf](https://github.com/robonen/tools/commit/37d25bf))
|
|
||||||
- **repo:** Change cli generated exports in package.json ([a5d33ea](https://github.com/robonen/tools/commit/a5d33ea))
|
|
||||||
- **packages/tsconfig:** Disable declaration and source maps ([3f1d16b](https://github.com/robonen/tools/commit/3f1d16b))
|
|
||||||
- **packages/stdlib:** Add doc, update tests ([5280ace](https://github.com/robonen/tools/commit/5280ace))
|
|
||||||
- **packages/stdlib:** Add comments for math utils ([65ba312](https://github.com/robonen/tools/commit/65ba312))
|
|
||||||
- **packages/stdlib:** Levensthein fn replate to module export ([92721b3](https://github.com/robonen/tools/commit/92721b3))
|
|
||||||
- **packages/stdlib:** Rename arguments to left and right ([7d8f5be](https://github.com/robonen/tools/commit/7d8f5be))
|
|
||||||
- **packages/stdlib:** Reformat test files ([9031430](https://github.com/robonen/tools/commit/9031430))
|
|
||||||
- **packages/tsconfig:** Add exclude for .output and coverage folders ([769476d](https://github.com/robonen/tools/commit/769476d))
|
|
||||||
- **packages/stdlib:** Remove private from package.json ([5dadb50](https://github.com/robonen/tools/commit/5dadb50))
|
|
||||||
|
|
||||||
### 🏡 Chore
|
|
||||||
|
|
||||||
- **packages/stdlib:** Add bench script, add vscode workspace ([e9b8b0c](https://github.com/robonen/tools/commit/e9b8b0c))
|
|
||||||
- **release:** V0.0.1 ([725b73d](https://github.com/robonen/tools/commit/725b73d))
|
|
||||||
- **packages/stdlib:** Set 0.0.1 version ([c65113e](https://github.com/robonen/tools/commit/c65113e))
|
|
||||||
- **release:** V0.0.1 ([f77716a](https://github.com/robonen/tools/commit/f77716a))
|
|
||||||
|
|
||||||
### ✅ Tests
|
|
||||||
|
|
||||||
- **packages/stdlib:** Trigram distance tests ([4c10d38](https://github.com/robonen/tools/commit/4c10d38))
|
|
||||||
|
|
||||||
### ❤️ Contributors
|
|
||||||
|
|
||||||
- Robonen ([@robonen](http://github.com/robonen))
|
|
||||||
|
|
||||||
2
apps/vhs/.gitignore
vendored
2
apps/vhs/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
bin/**
|
|
||||||
!bin/.gitkeep
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# @robonen/vhs
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@robonen/vhs",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"exports": "./src/index.ts"
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@robonen/vhs",
|
|
||||||
"private": true,
|
|
||||||
"version": "0.0.1",
|
|
||||||
"license": "UNLICENSED",
|
|
||||||
"description": "",
|
|
||||||
"keywords": [],
|
|
||||||
"author": "Robonen Andrew <robonenandrew@gmail.com>",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/robonen/tools.git",
|
|
||||||
"directory": "./apps/vhs"
|
|
||||||
},
|
|
||||||
"packageManager": "pnpm@9.15.4",
|
|
||||||
"engines": {
|
|
||||||
"bun": ">=1.1.27"
|
|
||||||
},
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"start": "bun run src/index.ts"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@robonen/tsconfig": "workspace:*",
|
|
||||||
"@types/bun": "^1.1.17"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { version } from '../package.json';
|
|
||||||
import { resolve } from 'path';
|
|
||||||
import { $, Glob } from 'bun';
|
|
||||||
|
|
||||||
async function ffmpegMergeAndTranscodeAvi(files: Set<string>) {
|
|
||||||
const ffmpeg = resolve('bin/ffmpeg');
|
|
||||||
const output = resolve('output.mp4');
|
|
||||||
const input = Array.from(files).toSorted((a, b) => a.localeCompare(b)).join('|');
|
|
||||||
|
|
||||||
const shell = $`${ffmpeg} -i "concat:${input}" -stats -c:v libx264 -crf 23 -preset veryfast -c:a aac ${output}`;
|
|
||||||
|
|
||||||
for await (const line of shell.lines()) {
|
|
||||||
console.log(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const path = Bun.argv[2];
|
|
||||||
|
|
||||||
if (!path) {
|
|
||||||
console.error('Please provide a path to a file or directory');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.info(`Welcome to VHS v${version} 📼`);
|
|
||||||
console.info(`Scanning ${path}...`);
|
|
||||||
|
|
||||||
const glob = new Glob(resolve(path));
|
|
||||||
const files = new Set<string>();
|
|
||||||
|
|
||||||
for await (const file of glob.scan({ followSymlinks: false })) {
|
|
||||||
files.add(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.info(`Found ${files.size} files`);
|
|
||||||
|
|
||||||
console.info(await ffmpegMergeAndTranscodeAvi(files));
|
|
||||||
@@ -1,16 +1,53 @@
|
|||||||
import { mkdir, writeFile } from 'node:fs/promises';
|
import { mkdir, writeFile } from 'node:fs/promises';
|
||||||
import { defineCommand, runMain } from 'citty';
|
import { defineCommand, runMain } from 'citty';
|
||||||
import { resolve } from 'pathe';
|
import { resolve } from 'node:path';
|
||||||
import { splitByCase } from 'scule';
|
import { splitByCase } from 'scule';
|
||||||
|
|
||||||
const PACKAGE_MANAGER = 'pnpm@9.11.0';
|
async function getLatestPackageVersion(packageName: string) {
|
||||||
const NODE_VERSION = '>=20.17.0';
|
try {
|
||||||
const VITE_VERSION = '^5.4.8';
|
const response = await fetch(`https://registry.npmjs.org/${packageName}`);
|
||||||
const VITE_DTS_VERSION = '^4.2.2';
|
const data = await response.json();
|
||||||
const PATHE_VERSION = '^1.1.2'
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.warn(`Failed to fetch latest version for ${packageName}, using fallback`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestVersion = data['dist-tags']?.latest as string | undefined;
|
||||||
|
|
||||||
|
if (!latestVersion)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: latestVersion,
|
||||||
|
versionRange: `^${latestVersion}`,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Error fetching version for ${packageName}: ${error.message}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PACKAGE_MANAGER_DEFAULT = 'pnpm@10.10.0';
|
||||||
|
const NODE_VERSION = '>=22.15.0';
|
||||||
|
const VITE_VERSION_DEFAULT = '^5.4.8';
|
||||||
|
const VITE_DTS_VERSION_DEFAULT = '^4.2.2';
|
||||||
|
const PATHE_VERSION_DEFAULT = '^1.1.2';
|
||||||
const DEFAULT_DIR = 'packages';
|
const DEFAULT_DIR = 'packages';
|
||||||
|
|
||||||
const generatePackageJson = (name: string, path: string, hasVite: boolean) => {
|
const generatePackageJson = async (name: string, path: string, hasVite: boolean) => {
|
||||||
|
const [
|
||||||
|
packageManagerVersion,
|
||||||
|
viteVersion,
|
||||||
|
viteDtsVersion,
|
||||||
|
patheVersion,
|
||||||
|
] = await Promise.all([
|
||||||
|
getLatestPackageVersion('pnpm').then(v => v?.version || PACKAGE_MANAGER_DEFAULT),
|
||||||
|
hasVite ? getLatestPackageVersion('vite').then(v => v?.versionRange || VITE_VERSION_DEFAULT) : VITE_VERSION_DEFAULT,
|
||||||
|
hasVite ? getLatestPackageVersion('vite-plugin-dts').then(v => v?.versionRange || VITE_DTS_VERSION_DEFAULT) : VITE_DTS_VERSION_DEFAULT,
|
||||||
|
hasVite ? getLatestPackageVersion('pathe').then(v => v?.versionRange || PATHE_VERSION_DEFAULT) : PATHE_VERSION_DEFAULT,
|
||||||
|
]);
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
name,
|
name,
|
||||||
private: true,
|
private: true,
|
||||||
@@ -24,20 +61,17 @@ const generatePackageJson = (name: string, path: string, hasVite: boolean) => {
|
|||||||
url: 'git+https://github.com/robonen/tools.git',
|
url: 'git+https://github.com/robonen/tools.git',
|
||||||
directory: path,
|
directory: path,
|
||||||
},
|
},
|
||||||
packageManager: PACKAGE_MANAGER,
|
packageManager: `pnpm@${packageManagerVersion}`,
|
||||||
engines: {
|
engines: {
|
||||||
node: NODE_VERSION,
|
node: NODE_VERSION,
|
||||||
},
|
},
|
||||||
type: 'module',
|
type: 'module',
|
||||||
files: ['dist'],
|
files: ['dist'],
|
||||||
main: './dist/index.umd.js',
|
|
||||||
module: './dist/index.js',
|
|
||||||
types: './dist/index.d.ts',
|
|
||||||
exports: {
|
exports: {
|
||||||
'.': {
|
'.': {
|
||||||
|
types: './dist/index.d.ts',
|
||||||
import: './dist/index.js',
|
import: './dist/index.js',
|
||||||
require: './dist/index.umd.js',
|
require: './dist/index.umd.js',
|
||||||
types: './dist/index.d.ts',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
scripts: {
|
scripts: {
|
||||||
@@ -51,9 +85,9 @@ const generatePackageJson = (name: string, path: string, hasVite: boolean) => {
|
|||||||
devDependencies: {
|
devDependencies: {
|
||||||
'@robonen/tsconfig': 'workspace:*',
|
'@robonen/tsconfig': 'workspace:*',
|
||||||
...(hasVite && {
|
...(hasVite && {
|
||||||
vite: VITE_VERSION,
|
vite: viteVersion,
|
||||||
'vite-plugin-dts': VITE_DTS_VERSION,
|
'vite-plugin-dts': viteDtsVersion,
|
||||||
pathe: PATHE_VERSION,
|
pathe: patheVersion,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -132,14 +166,15 @@ const createCommand = defineCommand({
|
|||||||
|
|
||||||
await mkdir(resolvedPath, { recursive: true });
|
await mkdir(resolvedPath, { recursive: true });
|
||||||
|
|
||||||
writeFile(`${resolvedPath}/package.json`, generatePackageJson(args.name, path, hasVite));
|
const packageJson = await generatePackageJson(args.name, path, hasVite);
|
||||||
writeFile(`${resolvedPath}/jsr.json`, generateJsrJson(args.name));
|
await writeFile(`${resolvedPath}/package.json`, packageJson);
|
||||||
writeFile(`${resolvedPath}/tsconfig.json`, generateTsConfig());
|
await writeFile(`${resolvedPath}/jsr.json`, generateJsrJson(args.name));
|
||||||
writeFile(`${resolvedPath}/README.md`, generateReadme(args.name));
|
await writeFile(`${resolvedPath}/tsconfig.json`, generateTsConfig());
|
||||||
|
await writeFile(`${resolvedPath}/README.md`, generateReadme(args.name));
|
||||||
|
|
||||||
if (hasVite) {
|
if (hasVite) {
|
||||||
mkdir(`${resolvedPath}/src`, { recursive: true });
|
await mkdir(`${resolvedPath}/src`, { recursive: true });
|
||||||
writeFile(`${resolvedPath}/vite.config.ts`, generateViteConfig());
|
await writeFile(`${resolvedPath}/vite.config.ts`, generateViteConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Project created successfully`);
|
console.log(`Project created successfully`);
|
||||||
54
configs/oxlint/README.md
Normal file
54
configs/oxlint/README.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# @robonen/oxlint
|
||||||
|
|
||||||
|
Composable [oxlint](https://oxc.rs/docs/guide/usage/linter.html) configuration presets.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install -D @robonen/oxlint oxlint
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Create `oxlint.config.ts` in your project root:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig } from 'oxlint';
|
||||||
|
import { compose, base, typescript, vue, vitest, imports } from '@robonen/oxlint';
|
||||||
|
|
||||||
|
export default defineConfig(
|
||||||
|
compose(base, typescript, vue, vitest, imports),
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Append custom rules after presets to override them:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
compose(base, typescript, {
|
||||||
|
rules: { 'eslint/no-console': 'off' },
|
||||||
|
ignorePatterns: ['dist'],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Presets
|
||||||
|
|
||||||
|
| Preset | Description |
|
||||||
|
| ------------ | -------------------------------------------------- |
|
||||||
|
| `base` | Core eslint, oxc, unicorn rules |
|
||||||
|
| `typescript` | TypeScript-specific rules (via overrides) |
|
||||||
|
| `vue` | Vue 3 Composition API / `<script setup>` rules |
|
||||||
|
| `vitest` | Test file rules (via overrides) |
|
||||||
|
| `imports` | Import rules (cycles, duplicates, ordering) |
|
||||||
|
| `node` | Node.js-specific rules |
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### `compose(...configs: OxlintConfig[]): OxlintConfig`
|
||||||
|
|
||||||
|
Merges multiple configs into one:
|
||||||
|
|
||||||
|
- **plugins** — union (deduplicated)
|
||||||
|
- **rules / categories** — last wins
|
||||||
|
- **overrides / ignorePatterns** — concatenated
|
||||||
|
- **env / globals** — shallow merge
|
||||||
|
- **settings** — deep merge
|
||||||
4
configs/oxlint/oxlint.config.ts
Normal file
4
configs/oxlint/oxlint.config.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { defineConfig } from 'oxlint';
|
||||||
|
import { compose, base, typescript, imports } from '@robonen/oxlint';
|
||||||
|
|
||||||
|
export default defineConfig(compose(base, typescript, imports));
|
||||||
52
configs/oxlint/package.json
Normal file
52
configs/oxlint/package.json
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"name": "@robonen/oxlint",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"description": "Composable oxlint configuration presets",
|
||||||
|
"keywords": [
|
||||||
|
"oxlint",
|
||||||
|
"oxc",
|
||||||
|
"linter",
|
||||||
|
"config",
|
||||||
|
"presets"
|
||||||
|
],
|
||||||
|
"author": "Robonen Andrew <robonenandrew@gmail.com>",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/robonen/tools.git",
|
||||||
|
"directory": "configs/oxlint"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.29.3",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.18.0"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"require": "./dist/index.cjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "oxlint -c oxlint.config.ts",
|
||||||
|
"test": "vitest run",
|
||||||
|
"dev": "vitest dev",
|
||||||
|
"build": "tsdown"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@robonen/oxlint": "workspace:*",
|
||||||
|
"@robonen/tsconfig": "workspace:*",
|
||||||
|
"oxlint": "catalog:",
|
||||||
|
"tsdown": "catalog:"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"oxlint": ">=1.0.0"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
||||||
103
configs/oxlint/src/compose.ts
Normal file
103
configs/oxlint/src/compose.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import type { OxlintConfig } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deep merge two objects. Arrays are concatenated, objects are recursively merged.
|
||||||
|
*/
|
||||||
|
function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> {
|
||||||
|
const result = { ...target };
|
||||||
|
|
||||||
|
for (const key of Object.keys(source)) {
|
||||||
|
const targetValue = target[key];
|
||||||
|
const sourceValue = source[key];
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue)
|
||||||
|
&& typeof sourceValue === 'object' && sourceValue !== null && !Array.isArray(sourceValue)
|
||||||
|
) {
|
||||||
|
result[key] = deepMerge(
|
||||||
|
targetValue as Record<string, unknown>,
|
||||||
|
sourceValue as Record<string, unknown>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result[key] = sourceValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose multiple oxlint configurations into a single config.
|
||||||
|
*
|
||||||
|
* - `plugins` — union (deduplicated)
|
||||||
|
* - `categories` — later configs override earlier
|
||||||
|
* - `rules` — later configs override earlier
|
||||||
|
* - `overrides` — concatenated
|
||||||
|
* - `env` — merged (later overrides earlier)
|
||||||
|
* - `globals` — merged (later overrides earlier)
|
||||||
|
* - `settings` — deep-merged
|
||||||
|
* - `ignorePatterns` — concatenated
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* import { compose, base, typescript, vue } from '@robonen/oxlint';
|
||||||
|
* import { defineConfig } from 'oxlint';
|
||||||
|
*
|
||||||
|
* export default defineConfig(
|
||||||
|
* compose(base, typescript, vue, {
|
||||||
|
* rules: { 'eslint/no-console': 'off' },
|
||||||
|
* }),
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function compose(...configs: OxlintConfig[]): OxlintConfig {
|
||||||
|
const result: OxlintConfig = {};
|
||||||
|
|
||||||
|
for (const config of configs) {
|
||||||
|
// Plugins — union with dedup
|
||||||
|
if (config.plugins?.length) {
|
||||||
|
result.plugins = Array.from(new Set([...(result.plugins ?? []), ...config.plugins]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Categories — shallow merge
|
||||||
|
if (config.categories) {
|
||||||
|
result.categories = { ...result.categories, ...config.categories };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rules — shallow merge (later overrides earlier)
|
||||||
|
if (config.rules) {
|
||||||
|
result.rules = { ...result.rules, ...config.rules };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overrides — concatenate
|
||||||
|
if (config.overrides?.length) {
|
||||||
|
result.overrides = [...(result.overrides ?? []), ...config.overrides];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Env — shallow merge
|
||||||
|
if (config.env) {
|
||||||
|
result.env = { ...result.env, ...config.env };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Globals — shallow merge
|
||||||
|
if (config.globals) {
|
||||||
|
result.globals = { ...result.globals, ...config.globals };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings — deep merge
|
||||||
|
if (config.settings) {
|
||||||
|
result.settings = deepMerge(
|
||||||
|
(result.settings ?? {}) as Record<string, unknown>,
|
||||||
|
config.settings as Record<string, unknown>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore patterns — concatenate
|
||||||
|
if (config.ignorePatterns?.length) {
|
||||||
|
result.ignorePatterns = [...(result.ignorePatterns ?? []), ...config.ignorePatterns];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
17
configs/oxlint/src/index.ts
Normal file
17
configs/oxlint/src/index.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/* Compose */
|
||||||
|
export { compose } from './compose';
|
||||||
|
|
||||||
|
/* Presets */
|
||||||
|
export { base, typescript, vue, vitest, imports, node } from './presets';
|
||||||
|
|
||||||
|
/* Types */
|
||||||
|
export type {
|
||||||
|
OxlintConfig,
|
||||||
|
OxlintOverride,
|
||||||
|
OxlintEnv,
|
||||||
|
OxlintGlobals,
|
||||||
|
AllowWarnDeny,
|
||||||
|
DummyRule,
|
||||||
|
DummyRuleMap,
|
||||||
|
RuleCategories,
|
||||||
|
} from './types';
|
||||||
73
configs/oxlint/src/presets/base.ts
Normal file
73
configs/oxlint/src/presets/base.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import type { OxlintConfig } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base configuration for any JavaScript/TypeScript project.
|
||||||
|
*
|
||||||
|
* Enables `correctness` category and opinionated rules from
|
||||||
|
* `eslint`, `oxc`, and `unicorn` plugins.
|
||||||
|
*/
|
||||||
|
export const base: OxlintConfig = {
|
||||||
|
plugins: ['eslint', 'oxc', 'unicorn'],
|
||||||
|
|
||||||
|
categories: {
|
||||||
|
correctness: 'error',
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
/* ── eslint core ──────────────────────────────────────── */
|
||||||
|
'eslint/eqeqeq': 'error',
|
||||||
|
'eslint/no-console': 'warn',
|
||||||
|
'eslint/no-debugger': 'error',
|
||||||
|
'eslint/no-eval': 'error',
|
||||||
|
'eslint/no-var': 'error',
|
||||||
|
'eslint/prefer-const': 'error',
|
||||||
|
'eslint/prefer-template': 'warn',
|
||||||
|
'eslint/no-useless-constructor': 'warn',
|
||||||
|
'eslint/no-useless-rename': 'warn',
|
||||||
|
'eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
||||||
|
'eslint/no-self-compare': 'error',
|
||||||
|
'eslint/no-template-curly-in-string': 'warn',
|
||||||
|
'eslint/no-throw-literal': 'error',
|
||||||
|
'eslint/no-return-assign': 'warn',
|
||||||
|
'eslint/no-else-return': 'warn',
|
||||||
|
'eslint/no-lonely-if': 'warn',
|
||||||
|
'eslint/no-unneeded-ternary': 'warn',
|
||||||
|
'eslint/prefer-object-spread': 'warn',
|
||||||
|
'eslint/prefer-exponentiation-operator': 'warn',
|
||||||
|
'eslint/no-useless-computed-key': 'warn',
|
||||||
|
'eslint/no-useless-concat': 'warn',
|
||||||
|
'eslint/curly': 'off',
|
||||||
|
|
||||||
|
/* ── unicorn ──────────────────────────────────────────── */
|
||||||
|
'unicorn/prefer-node-protocol': 'error',
|
||||||
|
'unicorn/no-instanceof-array': 'error',
|
||||||
|
'unicorn/no-new-array': 'error',
|
||||||
|
'unicorn/prefer-array-flat-map': 'warn',
|
||||||
|
'unicorn/prefer-array-flat': 'warn',
|
||||||
|
'unicorn/prefer-includes': 'warn',
|
||||||
|
'unicorn/prefer-string-slice': 'warn',
|
||||||
|
'unicorn/prefer-string-starts-ends-with': 'warn',
|
||||||
|
'unicorn/throw-new-error': 'error',
|
||||||
|
'unicorn/error-message': 'warn',
|
||||||
|
'unicorn/no-useless-spread': 'warn',
|
||||||
|
'unicorn/no-useless-undefined': 'off',
|
||||||
|
'unicorn/prefer-optional-catch-binding': 'warn',
|
||||||
|
'unicorn/prefer-type-error': 'warn',
|
||||||
|
'unicorn/no-thenable': 'error',
|
||||||
|
'unicorn/prefer-number-properties': 'warn',
|
||||||
|
'unicorn/prefer-global-this': 'warn',
|
||||||
|
|
||||||
|
/* ── oxc ──────────────────────────────────────────────── */
|
||||||
|
'oxc/no-accumulating-spread': 'warn',
|
||||||
|
'oxc/bad-comparison-sequence': 'error',
|
||||||
|
'oxc/bad-min-max-func': 'error',
|
||||||
|
'oxc/bad-object-literal-comparison': 'error',
|
||||||
|
'oxc/const-comparisons': 'error',
|
||||||
|
'oxc/double-comparisons': 'error',
|
||||||
|
'oxc/erasing-op': 'error',
|
||||||
|
'oxc/missing-throw': 'error',
|
||||||
|
'oxc/bad-bitwise-operator': 'error',
|
||||||
|
'oxc/bad-char-at-comparison': 'error',
|
||||||
|
'oxc/bad-replace-all-arg': 'error',
|
||||||
|
},
|
||||||
|
};
|
||||||
20
configs/oxlint/src/presets/imports.ts
Normal file
20
configs/oxlint/src/presets/imports.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import type { OxlintConfig } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import plugin rules for clean module boundaries.
|
||||||
|
*/
|
||||||
|
export const imports: OxlintConfig = {
|
||||||
|
plugins: ['import'],
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
'import/no-duplicates': 'error',
|
||||||
|
'import/no-self-import': 'error',
|
||||||
|
'import/no-cycle': 'warn',
|
||||||
|
'import/first': 'warn',
|
||||||
|
'import/no-mutable-exports': 'error',
|
||||||
|
'import/no-amd': 'error',
|
||||||
|
'import/no-commonjs': 'warn',
|
||||||
|
'import/no-empty-named-blocks': 'warn',
|
||||||
|
'import/consistent-type-specifier-style': ['warn', 'prefer-top-level'],
|
||||||
|
},
|
||||||
|
};
|
||||||
6
configs/oxlint/src/presets/index.ts
Normal file
6
configs/oxlint/src/presets/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export { base } from './base';
|
||||||
|
export { typescript } from './typescript';
|
||||||
|
export { vue } from './vue';
|
||||||
|
export { vitest } from './vitest';
|
||||||
|
export { imports } from './imports';
|
||||||
|
export { node } from './node';
|
||||||
17
configs/oxlint/src/presets/node.ts
Normal file
17
configs/oxlint/src/presets/node.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import type { OxlintConfig } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Node.js-specific rules.
|
||||||
|
*/
|
||||||
|
export const node: OxlintConfig = {
|
||||||
|
plugins: ['node'],
|
||||||
|
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
'node/no-exports-assign': 'error',
|
||||||
|
'node/no-new-require': 'error',
|
||||||
|
},
|
||||||
|
};
|
||||||
39
configs/oxlint/src/presets/typescript.ts
Normal file
39
configs/oxlint/src/presets/typescript.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import type { OxlintConfig } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TypeScript-specific rules.
|
||||||
|
*
|
||||||
|
* Applied via `overrides` for `*.ts`, `*.tsx`, `*.mts`, `*.cts` files.
|
||||||
|
*/
|
||||||
|
export const typescript: OxlintConfig = {
|
||||||
|
plugins: ['typescript'],
|
||||||
|
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
|
||||||
|
rules: {
|
||||||
|
'typescript/consistent-type-imports': 'error',
|
||||||
|
'typescript/no-explicit-any': 'off',
|
||||||
|
'typescript/no-non-null-assertion': 'off',
|
||||||
|
'typescript/prefer-as-const': 'error',
|
||||||
|
'typescript/no-empty-object-type': 'warn',
|
||||||
|
'typescript/no-wrapper-object-types': 'error',
|
||||||
|
'typescript/no-duplicate-enum-values': 'error',
|
||||||
|
'typescript/no-unsafe-declaration-merging': 'error',
|
||||||
|
'typescript/no-import-type-side-effects': 'error',
|
||||||
|
'typescript/no-useless-empty-export': 'warn',
|
||||||
|
'typescript/no-inferrable-types': 'warn',
|
||||||
|
'typescript/prefer-function-type': 'warn',
|
||||||
|
'typescript/ban-tslint-comment': 'error',
|
||||||
|
'typescript/consistent-type-definitions': ['warn', 'interface'],
|
||||||
|
'typescript/prefer-for-of': 'warn',
|
||||||
|
'typescript/no-unnecessary-type-constraint': 'warn',
|
||||||
|
'typescript/adjacent-overload-signatures': 'warn',
|
||||||
|
'typescript/array-type': ['warn', { default: 'array-simple' }],
|
||||||
|
'typescript/no-this-alias': 'error',
|
||||||
|
'typescript/triple-slash-reference': 'error',
|
||||||
|
'typescript/no-namespace': 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
35
configs/oxlint/src/presets/vitest.ts
Normal file
35
configs/oxlint/src/presets/vitest.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import type { OxlintConfig } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vitest rules for test files.
|
||||||
|
*
|
||||||
|
* Applied via `overrides` for common test file patterns.
|
||||||
|
*/
|
||||||
|
export const vitest: OxlintConfig = {
|
||||||
|
plugins: ['vitest'],
|
||||||
|
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
'**/*.test.{ts,tsx,js,jsx}',
|
||||||
|
'**/*.spec.{ts,tsx,js,jsx}',
|
||||||
|
'**/test/**/*.{ts,tsx,js,jsx}',
|
||||||
|
'**/__tests__/**/*.{ts,tsx,js,jsx}',
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
'vitest/no-conditional-tests': 'warn',
|
||||||
|
'vitest/no-import-node-test': 'error',
|
||||||
|
'vitest/prefer-to-be-truthy': 'warn',
|
||||||
|
'vitest/prefer-to-be-falsy': 'warn',
|
||||||
|
'vitest/prefer-to-be-object': 'warn',
|
||||||
|
'vitest/prefer-to-have-length': 'warn',
|
||||||
|
'vitest/consistent-test-filename': 'warn',
|
||||||
|
'vitest/prefer-describe-function-title': 'warn',
|
||||||
|
|
||||||
|
/* relax strict rules in tests */
|
||||||
|
'eslint/no-unused-vars': 'off',
|
||||||
|
'typescript/no-explicit-any': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
26
configs/oxlint/src/presets/vue.ts
Normal file
26
configs/oxlint/src/presets/vue.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { OxlintConfig } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vue.js-specific rules.
|
||||||
|
*
|
||||||
|
* Enforces Composition API with `<script setup>` and type-based declarations.
|
||||||
|
*/
|
||||||
|
export const vue: OxlintConfig = {
|
||||||
|
plugins: ['vue'],
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
'vue/no-arrow-functions-in-watch': 'error',
|
||||||
|
'vue/no-deprecated-destroyed-lifecycle': 'error',
|
||||||
|
'vue/no-export-in-script-setup': 'error',
|
||||||
|
'vue/no-lifecycle-after-await': 'error',
|
||||||
|
'vue/no-multiple-slot-args': 'error',
|
||||||
|
'vue/no-import-compiler-macros': 'error',
|
||||||
|
'vue/define-emits-declaration': ['error', 'type-based'],
|
||||||
|
'vue/define-props-declaration': ['error', 'type-based'],
|
||||||
|
'vue/prefer-import-from-vue': 'error',
|
||||||
|
'vue/no-required-prop-with-default': 'warn',
|
||||||
|
'vue/valid-define-emits': 'error',
|
||||||
|
'vue/valid-define-props': 'error',
|
||||||
|
'vue/require-typed-ref': 'warn',
|
||||||
|
},
|
||||||
|
};
|
||||||
18
configs/oxlint/src/types.ts
Normal file
18
configs/oxlint/src/types.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Re-exported configuration types from `oxlint`.
|
||||||
|
*
|
||||||
|
* Keeps the preset API in sync with the oxlint CLI without
|
||||||
|
* maintaining a separate copy of the types.
|
||||||
|
*
|
||||||
|
* @see https://oxc.rs/docs/guide/usage/linter/config-file-reference.html
|
||||||
|
*/
|
||||||
|
export type {
|
||||||
|
OxlintConfig,
|
||||||
|
OxlintOverride,
|
||||||
|
OxlintEnv,
|
||||||
|
OxlintGlobals,
|
||||||
|
AllowWarnDeny,
|
||||||
|
DummyRule,
|
||||||
|
DummyRuleMap,
|
||||||
|
RuleCategories,
|
||||||
|
} from 'oxlint';
|
||||||
146
configs/oxlint/test/compose.test.ts
Normal file
146
configs/oxlint/test/compose.test.ts
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { compose } from '../src/compose';
|
||||||
|
import type { OxlintConfig } from '../src/types';
|
||||||
|
|
||||||
|
describe('compose', () => {
|
||||||
|
it('should return empty config when no configs provided', () => {
|
||||||
|
expect(compose()).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the same config when one config provided', () => {
|
||||||
|
const config: OxlintConfig = {
|
||||||
|
plugins: ['eslint'],
|
||||||
|
rules: { 'eslint/no-console': 'warn' },
|
||||||
|
};
|
||||||
|
const result = compose(config);
|
||||||
|
expect(result.plugins).toEqual(['eslint']);
|
||||||
|
expect(result.rules).toEqual({ 'eslint/no-console': 'warn' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge plugins with dedup', () => {
|
||||||
|
const a: OxlintConfig = { plugins: ['eslint', 'oxc'] };
|
||||||
|
const b: OxlintConfig = { plugins: ['oxc', 'typescript'] };
|
||||||
|
|
||||||
|
const result = compose(a, b);
|
||||||
|
expect(result.plugins).toEqual(['eslint', 'oxc', 'typescript']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should override rules from later configs', () => {
|
||||||
|
const a: OxlintConfig = { rules: { 'eslint/no-console': 'error', 'eslint/eqeqeq': 'warn' } };
|
||||||
|
const b: OxlintConfig = { rules: { 'eslint/no-console': 'off' } };
|
||||||
|
|
||||||
|
const result = compose(a, b);
|
||||||
|
expect(result.rules).toEqual({
|
||||||
|
'eslint/no-console': 'off',
|
||||||
|
'eslint/eqeqeq': 'warn',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should override categories from later configs', () => {
|
||||||
|
const a: OxlintConfig = { categories: { correctness: 'error', suspicious: 'warn' } };
|
||||||
|
const b: OxlintConfig = { categories: { suspicious: 'off' } };
|
||||||
|
|
||||||
|
const result = compose(a, b);
|
||||||
|
expect(result.categories).toEqual({
|
||||||
|
correctness: 'error',
|
||||||
|
suspicious: 'off',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should concatenate overrides', () => {
|
||||||
|
const a: OxlintConfig = {
|
||||||
|
overrides: [{ files: ['**/*.ts'], rules: { 'typescript/no-explicit-any': 'warn' } }],
|
||||||
|
};
|
||||||
|
const b: OxlintConfig = {
|
||||||
|
overrides: [{ files: ['**/*.test.ts'], rules: { 'eslint/no-unused-vars': 'off' } }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = compose(a, b);
|
||||||
|
expect(result.overrides).toHaveLength(2);
|
||||||
|
expect(result.overrides?.[0]?.files).toEqual(['**/*.ts']);
|
||||||
|
expect(result.overrides?.[1]?.files).toEqual(['**/*.test.ts']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge env', () => {
|
||||||
|
const a: OxlintConfig = { env: { browser: true } };
|
||||||
|
const b: OxlintConfig = { env: { node: true } };
|
||||||
|
|
||||||
|
const result = compose(a, b);
|
||||||
|
expect(result.env).toEqual({ browser: true, node: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge globals', () => {
|
||||||
|
const a: OxlintConfig = { globals: { MY_VAR: 'readonly' } };
|
||||||
|
const b: OxlintConfig = { globals: { ANOTHER: 'writable' } };
|
||||||
|
|
||||||
|
const result = compose(a, b);
|
||||||
|
expect(result.globals).toEqual({ MY_VAR: 'readonly', ANOTHER: 'writable' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deep merge settings', () => {
|
||||||
|
const a: OxlintConfig = {
|
||||||
|
settings: {
|
||||||
|
react: { version: '18.2.0' },
|
||||||
|
next: { rootDir: 'apps/' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const b: OxlintConfig = {
|
||||||
|
settings: {
|
||||||
|
react: { linkComponents: [{ name: 'Link', linkAttribute: 'to', attributes: ['to'] }] },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = compose(a, b);
|
||||||
|
expect(result.settings).toEqual({
|
||||||
|
react: {
|
||||||
|
version: '18.2.0',
|
||||||
|
linkComponents: [{ name: 'Link', linkAttribute: 'to', attributes: ['to'] }],
|
||||||
|
},
|
||||||
|
next: { rootDir: 'apps/' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should concatenate ignorePatterns', () => {
|
||||||
|
const a: OxlintConfig = { ignorePatterns: ['dist'] };
|
||||||
|
const b: OxlintConfig = { ignorePatterns: ['node_modules', 'coverage'] };
|
||||||
|
|
||||||
|
const result = compose(a, b);
|
||||||
|
expect(result.ignorePatterns).toEqual(['dist', 'node_modules', 'coverage']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle composing all presets together', () => {
|
||||||
|
const base: OxlintConfig = {
|
||||||
|
plugins: ['eslint', 'oxc'],
|
||||||
|
categories: { correctness: 'error' },
|
||||||
|
rules: { 'eslint/no-console': 'warn' },
|
||||||
|
};
|
||||||
|
const ts: OxlintConfig = {
|
||||||
|
plugins: ['typescript'],
|
||||||
|
overrides: [{ files: ['**/*.ts'], rules: { 'typescript/no-explicit-any': 'warn' } }],
|
||||||
|
};
|
||||||
|
const custom: OxlintConfig = {
|
||||||
|
rules: { 'eslint/no-console': 'off' },
|
||||||
|
ignorePatterns: ['dist'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = compose(base, ts, custom);
|
||||||
|
|
||||||
|
expect(result.plugins).toEqual(['eslint', 'oxc', 'typescript']);
|
||||||
|
expect(result.categories).toEqual({ correctness: 'error' });
|
||||||
|
expect(result.rules).toEqual({ 'eslint/no-console': 'off' });
|
||||||
|
expect(result.overrides).toHaveLength(1);
|
||||||
|
expect(result.ignorePatterns).toEqual(['dist']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip undefined/empty fields', () => {
|
||||||
|
const a: OxlintConfig = { plugins: ['eslint'] };
|
||||||
|
const b: OxlintConfig = { rules: { 'eslint/no-console': 'warn' } };
|
||||||
|
|
||||||
|
const result = compose(a, b);
|
||||||
|
expect(result.plugins).toEqual(['eslint']);
|
||||||
|
expect(result.rules).toEqual({ 'eslint/no-console': 'warn' });
|
||||||
|
expect(result.overrides).toBeUndefined();
|
||||||
|
expect(result.env).toBeUndefined();
|
||||||
|
expect(result.settings).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "@robonen/tsconfig/tsconfig.json",
|
"extends": "@robonen/tsconfig/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["DOM"]
|
"rootDir": "."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
9
configs/oxlint/tsdown.config.ts
Normal file
9
configs/oxlint/tsdown.config.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineConfig } from 'tsdown';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
entry: ['src/index.ts'],
|
||||||
|
format: ['esm', 'cjs'],
|
||||||
|
dts: true,
|
||||||
|
clean: true,
|
||||||
|
hash: false,
|
||||||
|
});
|
||||||
@@ -2,6 +2,6 @@ import { defineConfig } from 'vitest/config';
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
test: {
|
test: {
|
||||||
environment: 'jsdom',
|
environment: 'node',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
27
configs/tsconfig/README.md
Normal file
27
configs/tsconfig/README.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# @robonen/tsconfig
|
||||||
|
|
||||||
|
Shared base TypeScript configuration.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install -D @robonen/tsconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Extend from it in your `tsconfig.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"extends": "@robonen/tsconfig/tsconfig.json"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## What's Included
|
||||||
|
|
||||||
|
- **Target / Module**: ESNext with Bundler resolution
|
||||||
|
- **Strict mode**: `strict`, `noUncheckedIndexedAccess`
|
||||||
|
- **Module safety**: `verbatimModuleSyntax`, `isolatedModules`
|
||||||
|
- **Declarations**: `declaration` enabled
|
||||||
|
- **Interop**: `esModuleInterop`, `allowJs`, `resolveJsonModule`
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@robonen/tsconfig",
|
"name": "@robonen/tsconfig",
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Base typescript configuration for projects",
|
"description": "Base typescript configuration for projects",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -15,9 +15,9 @@
|
|||||||
"url": "git+https://github.com/robonen/tools.git",
|
"url": "git+https://github.com/robonen/tools.git",
|
||||||
"directory": "packages/tsconfig"
|
"directory": "packages/tsconfig"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.15.4",
|
"packageManager": "pnpm@10.29.3",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.13.0"
|
"node": ">=24.13.1"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"**tsconfig.json"
|
"**tsconfig.json"
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
23
core/platform/README.md
Normal file
23
core/platform/README.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# @robonen/platform
|
||||||
|
|
||||||
|
Platform-dependent utilities for browser & multi-runtime environments.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install @robonen/platform
|
||||||
|
```
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
| Entry | Utilities | Description |
|
||||||
|
| ------------------ | ------------- | -------------------------------- |
|
||||||
|
| `@robonen/platform/browsers` | `focusGuard` | Browser-specific helpers |
|
||||||
|
| `@robonen/platform/multi` | `global` | Cross-runtime (Node/Bun/Deno) utilities |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { focusGuard } from '@robonen/platform/browsers';
|
||||||
|
import { global } from '@robonen/platform/multi';
|
||||||
|
```
|
||||||
15
core/platform/oxlint.config.ts
Normal file
15
core/platform/oxlint.config.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { defineConfig } from 'oxlint';
|
||||||
|
import { compose, base, typescript, imports } from '@robonen/oxlint';
|
||||||
|
|
||||||
|
export default defineConfig(
|
||||||
|
compose(base, typescript, imports, {
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['src/multi/global/index.ts'],
|
||||||
|
rules: {
|
||||||
|
'unicorn/prefer-global-this': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
"url": "git+https://github.com/robonen/tools.git",
|
"url": "git+https://github.com/robonen/tools.git",
|
||||||
"directory": "packages/platform"
|
"directory": "packages/platform"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.15.4",
|
"packageManager": "pnpm@10.29.3",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.13.0"
|
"node": ">=24.13.1"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
@@ -28,25 +28,26 @@
|
|||||||
],
|
],
|
||||||
"exports": {
|
"exports": {
|
||||||
"./browsers": {
|
"./browsers": {
|
||||||
"import": "./dist/browsers.mjs",
|
"types": "./dist/browsers.d.ts",
|
||||||
"require": "./dist/browsers.cjs",
|
"import": "./dist/browsers.js",
|
||||||
"types": "./dist/browsers.d.ts"
|
"require": "./dist/browsers.cjs"
|
||||||
},
|
},
|
||||||
"./multi": {
|
"./multi": {
|
||||||
"import": "./dist/multi.mjs",
|
"types": "./dist/multi.d.ts",
|
||||||
"require": "./dist/multi.cjs",
|
"import": "./dist/multi.js",
|
||||||
"types": "./dist/multi.d.ts"
|
"require": "./dist/multi.cjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"lint": "oxlint -c oxlint.config.ts",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"dev": "vitest dev",
|
"dev": "vitest dev",
|
||||||
"build": "unbuild"
|
"build": "tsdown"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@robonen/oxlint": "workspace:*",
|
||||||
"@robonen/tsconfig": "workspace:*",
|
"@robonen/tsconfig": "workspace:*",
|
||||||
"jsdom": "catalog:",
|
"oxlint": "catalog:",
|
||||||
"unbuild": "catalog:",
|
"tsdown": "catalog:"
|
||||||
"vitest": "catalog:"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
*
|
*
|
||||||
* @since 0.0.3
|
* @since 0.0.3
|
||||||
*/
|
*/
|
||||||
export function focusGuard(namespace: string = 'focus-guard') {
|
export function focusGuard(namespace = 'focus-guard') {
|
||||||
const guardAttr = `data-${namespace}`;
|
const guardAttr = `data-${namespace}`;
|
||||||
|
|
||||||
const createGuard = () => {
|
const createGuard = () => {
|
||||||
@@ -39,7 +39,7 @@ export function focusGuard(namespace: string = 'focus-guard') {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createGuardAttrs(namespace: string) {
|
export function createGuardAttrs(namespace = 'focus-guard') {
|
||||||
const element = document.createElement('span');
|
const element = document.createElement('span');
|
||||||
|
|
||||||
element.setAttribute(namespace, '');
|
element.setAttribute(namespace, '');
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// eslint-disable
|
||||||
|
|
||||||
export interface DebounceOptions {
|
export interface DebounceOptions {
|
||||||
/**
|
/**
|
||||||
* Call the function on the leading edge of the timeout, instead of waiting for the trailing edge
|
* Call the function on the leading edge of the timeout, instead of waiting for the trailing edge
|
||||||
12
core/platform/tsdown.config.ts
Normal file
12
core/platform/tsdown.config.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { defineConfig } from 'tsdown';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
entry: {
|
||||||
|
browsers: 'src/browsers/index.ts',
|
||||||
|
multi: 'src/multi/index.ts',
|
||||||
|
},
|
||||||
|
format: ['esm', 'cjs'],
|
||||||
|
dts: true,
|
||||||
|
clean: true,
|
||||||
|
hash: false,
|
||||||
|
});
|
||||||
@@ -5,3 +5,4 @@ export default defineConfig({
|
|||||||
environment: 'jsdom',
|
environment: 'jsdom',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
32
core/stdlib/README.md
Normal file
32
core/stdlib/README.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# @robonen/stdlib
|
||||||
|
|
||||||
|
Standard library of platform-independent utilities for TypeScript.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install @robonen/stdlib
|
||||||
|
```
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
| Module | Utilities |
|
||||||
|
| --------------- | --------------------------------------------------------------- |
|
||||||
|
| **arrays** | `cluster`, `first`, `last`, `sum`, `unique` |
|
||||||
|
| **async** | `sleep`, `tryIt` |
|
||||||
|
| **bits** | `flags` |
|
||||||
|
| **collections** | `get` |
|
||||||
|
| **math** | `clamp`, `lerp`, `remap` + BigInt variants |
|
||||||
|
| **objects** | `omit`, `pick` |
|
||||||
|
| **patterns** | `pubsub` |
|
||||||
|
| **structs** | `stack` |
|
||||||
|
| **sync** | `mutex` |
|
||||||
|
| **text** | `levenshteinDistance`, `trigramDistance` |
|
||||||
|
| **types** | JS & TS type utilities |
|
||||||
|
| **utils** | `timestamp`, `noop` |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { first, sleep, clamp } from '@robonen/stdlib';
|
||||||
|
```
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://jsr.io/schema/config-file.v1.json",
|
"$schema": "https://jsr.io/schema/config-file.v1.json",
|
||||||
"name": "@robonen/stdlib",
|
"name": "@robonen/stdlib",
|
||||||
"version": "0.0.4",
|
"version": "0.0.7",
|
||||||
"exports": "./src/index.ts"
|
"exports": "./src/index.ts"
|
||||||
}
|
}
|
||||||
4
core/stdlib/oxlint.config.ts
Normal file
4
core/stdlib/oxlint.config.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { defineConfig } from 'oxlint';
|
||||||
|
import { compose, base, typescript, imports } from '@robonen/oxlint';
|
||||||
|
|
||||||
|
export default defineConfig(compose(base, typescript, imports));
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@robonen/stdlib",
|
"name": "@robonen/stdlib",
|
||||||
"version": "0.0.4",
|
"version": "0.0.7",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "A collection of tools, utilities, and helpers for TypeScript",
|
"description": "A collection of tools, utilities, and helpers for TypeScript",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -18,34 +18,31 @@
|
|||||||
"url": "git+https://github.com/robonen/tools.git",
|
"url": "git+https://github.com/robonen/tools.git",
|
||||||
"directory": "packages/stdlib"
|
"directory": "packages/stdlib"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.15.4",
|
"packageManager": "pnpm@10.29.3",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.13.0"
|
"node": ">=24.13.1"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"main": "./dist/index.cjs",
|
|
||||||
"module": "./dist/index.mjs",
|
|
||||||
"types": "./dist/index.d.ts",
|
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"import": "./dist/index.mjs",
|
"types": "./dist/index.d.ts",
|
||||||
"require": "./dist/index.cjs",
|
"import": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts"
|
"require": "./dist/index.cjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"lint": "oxlint -c oxlint.config.ts",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"dev": "vitest dev",
|
"dev": "vitest dev",
|
||||||
"build": "unbuild"
|
"build": "tsdown"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@robonen/oxlint": "workspace:*",
|
||||||
"@robonen/tsconfig": "workspace:*",
|
"@robonen/tsconfig": "workspace:*",
|
||||||
"@vitest/coverage-v8": "catalog:",
|
"oxlint": "catalog:",
|
||||||
"pathe": "catalog:",
|
"tsdown": "catalog:"
|
||||||
"unbuild": "catalog:",
|
|
||||||
"vitest": "catalog:"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
3
core/stdlib/src/async/pool/index.ts
Normal file
3
core/stdlib/src/async/pool/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface AsyncPoolOptions {
|
||||||
|
concurrency?: number;
|
||||||
|
}
|
||||||
39
core/stdlib/src/async/retry/index.ts
Normal file
39
core/stdlib/src/async/retry/index.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// eslint-disable
|
||||||
|
export interface RetryOptions {
|
||||||
|
times?: number;
|
||||||
|
delay?: number;
|
||||||
|
backoff: (options: RetryOptions & { count: number }) => number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name retry
|
||||||
|
* @category Async
|
||||||
|
* @description Retries a function a specified number of times with a delay between each retry
|
||||||
|
*
|
||||||
|
* @param {Promise<unknown>} fn - The function to retry
|
||||||
|
* @param {RetryOptions} options - The options for the retry
|
||||||
|
* @returns {Promise<unknown>} - The result of the function
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const result = await retry(() => {
|
||||||
|
* return fetch('https://jsonplaceholder.typicode.com/todos/1')
|
||||||
|
* .then(response => response.json())
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const result = await retry(() => {
|
||||||
|
* return fetch('https://jsonplaceholder.typicode.com/todos/1')
|
||||||
|
* .then(response => response.json())
|
||||||
|
* }, { times: 3, delay: 1000 });
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export async function retry<Return>(
|
||||||
|
fn: () => Promise<Return>,
|
||||||
|
options: RetryOptions
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
times = 3,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
}
|
||||||
@@ -13,6 +13,8 @@
|
|||||||
* sleep(1000).then(() => {
|
* sleep(1000).then(() => {
|
||||||
* console.log('Hello, World!');
|
* console.log('Hello, World!');
|
||||||
* });
|
* });
|
||||||
|
*
|
||||||
|
* @since 0.0.3
|
||||||
*/
|
*/
|
||||||
export function sleep(ms: number): Promise<void> {
|
export function sleep(ms: number): Promise<void> {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export interface BitVector {
|
export interface BitVectorLike {
|
||||||
getBit(index: number): boolean;
|
getBit(index: number): boolean;
|
||||||
setBit(index: number): void;
|
setBit(index: number): void;
|
||||||
clearBit(index: number): void;
|
clearBit(index: number): void;
|
||||||
@@ -12,7 +12,7 @@ export interface BitVector {
|
|||||||
*
|
*
|
||||||
* @since 0.0.3
|
* @since 0.0.3
|
||||||
*/
|
*/
|
||||||
export class BitVector extends Uint8Array implements BitVector {
|
export class BitVector extends Uint8Array implements BitVectorLike {
|
||||||
constructor(size: number) {
|
constructor(size: number) {
|
||||||
super(Math.ceil(size / 8));
|
super(Math.ceil(size / 8));
|
||||||
}
|
}
|
||||||
34
core/stdlib/src/collections/get/index.ts
Normal file
34
core/stdlib/src/collections/get/index.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import type { Collection, Path } from '../../types';
|
||||||
|
|
||||||
|
export type ExtractFromObject<O extends Record<PropertyKey, unknown>, K> =
|
||||||
|
K extends keyof O
|
||||||
|
? O[K]
|
||||||
|
: K extends keyof NonNullable<O>
|
||||||
|
? NonNullable<O>[K]
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type ExtractFromArray<A extends readonly any[], K> =
|
||||||
|
any[] extends A
|
||||||
|
? A extends ReadonlyArray<infer T>
|
||||||
|
? T | undefined
|
||||||
|
: undefined
|
||||||
|
: K extends keyof A
|
||||||
|
? A[K]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
export type ExtractFromCollection<O, K> =
|
||||||
|
K extends []
|
||||||
|
? O
|
||||||
|
: K extends [infer Key, ...infer Rest]
|
||||||
|
? O extends Record<PropertyKey, unknown>
|
||||||
|
? ExtractFromCollection<ExtractFromObject<O, Key>, Rest>
|
||||||
|
: O extends readonly any[]
|
||||||
|
? ExtractFromCollection<ExtractFromArray<O, Key>, Rest>
|
||||||
|
: never
|
||||||
|
: never;
|
||||||
|
|
||||||
|
type Get<O, K> = ExtractFromCollection<O, Path<K>>;
|
||||||
|
|
||||||
|
export function get<O extends Collection, K extends string>(obj: O, path: K) {
|
||||||
|
return path.split('.').reduce((acc, key) => (acc as any)?.[key], obj) as Get<O, K> | undefined;
|
||||||
|
}
|
||||||
1
core/stdlib/src/collections/index.ts
Normal file
1
core/stdlib/src/collections/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './get';
|
||||||
@@ -5,6 +5,7 @@ export * from './math';
|
|||||||
export * from './objects';
|
export * from './objects';
|
||||||
export * from './patterns';
|
export * from './patterns';
|
||||||
export * from './structs';
|
export * from './structs';
|
||||||
|
export * from './sync';
|
||||||
export * from './text';
|
export * from './text';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
@@ -46,13 +46,13 @@ describe('clamp', () => {
|
|||||||
|
|
||||||
it('handle NaN and Infinity', () => {
|
it('handle NaN and Infinity', () => {
|
||||||
// value is NaN
|
// value is NaN
|
||||||
expect(clamp(NaN, 0, 100)).toBe(NaN);
|
expect(clamp(Number.NaN, 0, 100)).toBe(Number.NaN);
|
||||||
|
|
||||||
// min is NaN
|
// min is NaN
|
||||||
expect(clamp(50, NaN, 100)).toBe(NaN);
|
expect(clamp(50, Number.NaN, 100)).toBe(Number.NaN);
|
||||||
|
|
||||||
// max is NaN
|
// max is NaN
|
||||||
expect(clamp(50, 0, NaN)).toBe(NaN);
|
expect(clamp(50, 0, Number.NaN)).toBe(Number.NaN);
|
||||||
|
|
||||||
// value is Infinity
|
// value is Infinity
|
||||||
expect(clamp(Infinity, 0, 100)).toBe(100);
|
expect(clamp(Infinity, 0, 100)).toBe(100);
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { isArray, type Arrayable } from '../../types';
|
import { isArray } from '../../types';
|
||||||
|
import type { Arrayable } from '../../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name omit
|
* @name omit
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { isArray, type Arrayable } from '../../types';
|
import { isArray } from '../../types';
|
||||||
|
import type { Arrayable } from '../../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name pick
|
* @name pick
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { AnyFunction } from '../../../types';
|
import type { AnyFunction } from '../../../types';
|
||||||
|
|
||||||
export type Subscriber = AnyFunction;
|
export type Subscriber = AnyFunction;
|
||||||
export type EventsRecord = Record<string | symbol, Subscriber>;
|
|
||||||
|
export type EventHandlerMap = Record<PropertyKey, Subscriber>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name PubSub
|
* @name PubSub
|
||||||
@@ -10,9 +11,9 @@ export type EventsRecord = Record<string | symbol, Subscriber>;
|
|||||||
*
|
*
|
||||||
* @since 0.0.2
|
* @since 0.0.2
|
||||||
*
|
*
|
||||||
* @template {EventsRecord} Events
|
* @template Events - Event map where all values are function types
|
||||||
*/
|
*/
|
||||||
export class PubSub<Events extends EventsRecord> {
|
export class PubSub<Events extends EventHandlerMap> {
|
||||||
/**
|
/**
|
||||||
* Events map
|
* Events map
|
||||||
*
|
*
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user