mirror of
https://github.com/robonen/tools.git
synced 2026-03-20 19:04:46 +00:00
Compare commits
2 Commits
df9fff0377
...
feat/templ
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d111fcacd | |||
| c29cefdaac |
10
.github/workflows/ci.yaml
vendored
10
.github/workflows/ci.yaml
vendored
@@ -1,9 +1,7 @@
|
|||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
- pull_request
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NODE_VERSION: 22.x
|
NODE_VERSION: 22.x
|
||||||
@@ -16,14 +14,14 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- 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@v6
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
@@ -32,4 +30,4 @@ jobs:
|
|||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm build && pnpm test
|
run: pnpm all:build && pnpm all:test
|
||||||
78
.github/workflows/publish.yaml
vendored
78
.github/workflows/publish.yaml
vendored
@@ -1,78 +0,0 @@
|
|||||||
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
Normal file
57
CHANGELOG.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# 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
Normal file
2
apps/vhs/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
bin/**
|
||||||
|
!bin/.gitkeep
|
||||||
1
apps/vhs/README.md
Normal file
1
apps/vhs/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# @robonen/vhs
|
||||||
0
apps/vhs/bin/.gitkeep
Normal file
0
apps/vhs/bin/.gitkeep
Normal file
5
apps/vhs/jsr.json
Normal file
5
apps/vhs/jsr.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "@robonen/vhs",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"exports": "./src/index.ts"
|
||||||
|
}
|
||||||
26
apps/vhs/package.json
Normal file
26
apps/vhs/package.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
36
apps/vhs/src/index.ts
Normal file
36
apps/vhs/src/index.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
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,53 +1,16 @@
|
|||||||
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 'node:path';
|
import { resolve } from 'pathe';
|
||||||
import { splitByCase } from 'scule';
|
import { splitByCase } from 'scule';
|
||||||
|
|
||||||
async function getLatestPackageVersion(packageName: string) {
|
const PACKAGE_MANAGER = 'pnpm@9.11.0';
|
||||||
try {
|
const NODE_VERSION = '>=20.17.0';
|
||||||
const response = await fetch(`https://registry.npmjs.org/${packageName}`);
|
const VITE_VERSION = '^5.4.8';
|
||||||
const data = await response.json();
|
const VITE_DTS_VERSION = '^4.2.2';
|
||||||
|
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 = async (name: string, path: string, hasVite: boolean) => {
|
const generatePackageJson = (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,
|
||||||
@@ -61,17 +24,20 @@ const generatePackageJson = async (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: `pnpm@${packageManagerVersion}`,
|
packageManager: PACKAGE_MANAGER,
|
||||||
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: {
|
||||||
@@ -85,9 +51,9 @@ const generatePackageJson = async (name: string, path: string, hasVite: boolean)
|
|||||||
devDependencies: {
|
devDependencies: {
|
||||||
'@robonen/tsconfig': 'workspace:*',
|
'@robonen/tsconfig': 'workspace:*',
|
||||||
...(hasVite && {
|
...(hasVite && {
|
||||||
vite: viteVersion,
|
vite: VITE_VERSION,
|
||||||
'vite-plugin-dts': viteDtsVersion,
|
'vite-plugin-dts': VITE_DTS_VERSION,
|
||||||
pathe: patheVersion,
|
pathe: PATHE_VERSION,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -166,15 +132,14 @@ const createCommand = defineCommand({
|
|||||||
|
|
||||||
await mkdir(resolvedPath, { recursive: true });
|
await mkdir(resolvedPath, { recursive: true });
|
||||||
|
|
||||||
const packageJson = await generatePackageJson(args.name, path, hasVite);
|
writeFile(`${resolvedPath}/package.json`, generatePackageJson(args.name, path, hasVite));
|
||||||
await writeFile(`${resolvedPath}/package.json`, packageJson);
|
writeFile(`${resolvedPath}/jsr.json`, generateJsrJson(args.name));
|
||||||
await writeFile(`${resolvedPath}/jsr.json`, generateJsrJson(args.name));
|
writeFile(`${resolvedPath}/tsconfig.json`, generateTsConfig());
|
||||||
await writeFile(`${resolvedPath}/tsconfig.json`, generateTsConfig());
|
writeFile(`${resolvedPath}/README.md`, generateReadme(args.name));
|
||||||
await writeFile(`${resolvedPath}/README.md`, generateReadme(args.name));
|
|
||||||
|
|
||||||
if (hasVite) {
|
if (hasVite) {
|
||||||
await mkdir(`${resolvedPath}/src`, { recursive: true });
|
mkdir(`${resolvedPath}/src`, { recursive: true });
|
||||||
await writeFile(`${resolvedPath}/vite.config.ts`, generateViteConfig());
|
writeFile(`${resolvedPath}/vite.config.ts`, generateViteConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Project created successfully`);
|
console.log(`Project created successfully`);
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
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,
|
|
||||||
});
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './mutex';
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
||||||
import { SyncMutex } from '.';
|
|
||||||
|
|
||||||
describe('SyncMutex', () => {
|
|
||||||
let mutex: SyncMutex;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mutex = new SyncMutex();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('unlocked by default', () => {
|
|
||||||
expect(mutex.isLocked).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('lock the mutex', () => {
|
|
||||||
mutex.lock();
|
|
||||||
|
|
||||||
expect(mutex.isLocked).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('remain locked when locked multiple times', () => {
|
|
||||||
mutex.lock();
|
|
||||||
mutex.lock();
|
|
||||||
|
|
||||||
expect(mutex.isLocked).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('unlock a locked mutex', () => {
|
|
||||||
mutex.lock();
|
|
||||||
mutex.unlock();
|
|
||||||
|
|
||||||
expect(mutex.isLocked).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('remain unlocked when unlocked multiple times', () => {
|
|
||||||
mutex.unlock();
|
|
||||||
mutex.unlock();
|
|
||||||
|
|
||||||
expect(mutex.isLocked).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('reflect the current lock state', () => {
|
|
||||||
expect(mutex.isLocked).toBe(false);
|
|
||||||
mutex.lock();
|
|
||||||
expect(mutex.isLocked).toBe(true);
|
|
||||||
mutex.unlock();
|
|
||||||
expect(mutex.isLocked).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('execute a callback when unlocked', async () => {
|
|
||||||
const callback = vi.fn(() => 'done');
|
|
||||||
const result = await mutex.execute(callback);
|
|
||||||
|
|
||||||
expect(result).toBe('done');
|
|
||||||
expect(callback).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('execute a promise callback when unlocked', async () => {
|
|
||||||
const callback = vi.fn(() => Promise.resolve('done'));
|
|
||||||
const result = await mutex.execute(callback);
|
|
||||||
|
|
||||||
expect(result).toBe('done');
|
|
||||||
expect(callback).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('execute concurrent callbacks only one at a time', async () => {
|
|
||||||
const callback = vi.fn(() => Promise.resolve('done'));
|
|
||||||
|
|
||||||
const result = await Promise.all([
|
|
||||||
mutex.execute(callback),
|
|
||||||
mutex.execute(callback),
|
|
||||||
mutex.execute(callback),
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(result).toEqual(['done', undefined, undefined]);
|
|
||||||
expect(callback).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not execute a callback when locked', async () => {
|
|
||||||
const callback = vi.fn(() => 'done');
|
|
||||||
mutex.lock();
|
|
||||||
const result = await mutex.execute(callback);
|
|
||||||
|
|
||||||
expect(result).toBeUndefined();
|
|
||||||
expect(callback).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('unlocks after executing a callback', async () => {
|
|
||||||
const callback = vi.fn(() => 'done');
|
|
||||||
await mutex.execute(callback);
|
|
||||||
|
|
||||||
expect(mutex.isLocked).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import type { MaybePromise } from "../../types";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name SyncMutex
|
|
||||||
* @category Utils
|
|
||||||
* @description A simple synchronous mutex to provide more readable locking and unlocking of code blocks
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const mutex = new SyncMutex();
|
|
||||||
*
|
|
||||||
* mutex.lock();
|
|
||||||
*
|
|
||||||
* mutex.unlock();
|
|
||||||
*
|
|
||||||
* const result = await mutex.execute(() => {
|
|
||||||
* // do something
|
|
||||||
* return Promise.resolve('done');
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* @since 0.0.5
|
|
||||||
*/
|
|
||||||
export class SyncMutex {
|
|
||||||
private state = false;
|
|
||||||
|
|
||||||
public get isLocked() {
|
|
||||||
return this.state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public lock() {
|
|
||||||
this.state = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public unlock() {
|
|
||||||
this.state = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async execute<T>(callback: () => T) {
|
|
||||||
if (this.isLocked)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.lock();
|
|
||||||
const result = await callback();
|
|
||||||
this.unlock();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { defineConfig } from 'tsdown';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
entry: ['src/index.ts'],
|
|
||||||
format: ['esm', 'cjs'],
|
|
||||||
dts: true,
|
|
||||||
clean: true,
|
|
||||||
hash: false,
|
|
||||||
});
|
|
||||||
22
docs/.vitepress/config.ts
Normal file
22
docs/.vitepress/config.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { defineConfig } from 'vitepress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
lang: 'ru-RU',
|
||||||
|
title: "Toolkit",
|
||||||
|
description: "A collection of typescript and javascript development tools",
|
||||||
|
rewrites: {
|
||||||
|
'packages/:pkg/README.md': 'packages/:pkg/index.md',
|
||||||
|
},
|
||||||
|
themeConfig: {
|
||||||
|
sidebar: [
|
||||||
|
{
|
||||||
|
text: 'Пакеты',
|
||||||
|
items: [
|
||||||
|
{ text: '@robonen/tsconfig', link: '/packages/tsconfig/' },
|
||||||
|
{ text: '@robonen/renovate', link: '/packages/renovate/' },
|
||||||
|
{ text: '@robonen/stdlib', link: '/packages/stdlib/' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
14
docs/index.md
Normal file
14
docs/index.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
# https://vitepress.dev/reference/default-theme-home-page
|
||||||
|
layout: home
|
||||||
|
|
||||||
|
hero:
|
||||||
|
name: Toolkit
|
||||||
|
tagline: A collection of typescript and javascript development tools
|
||||||
|
actions:
|
||||||
|
- theme: brand
|
||||||
|
text: Get Started
|
||||||
|
link: /
|
||||||
|
- theme: alt
|
||||||
|
text: View on GitHub
|
||||||
|
link: /
|
||||||
26
package.json
26
package.json
@@ -15,25 +15,25 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/robonen/tools.git"
|
"url": "git+https://github.com/robonen/tools.git"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.29.3",
|
"packageManager": "pnpm@9.15.4",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=24.13.1"
|
"node": ">=22.13.0"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24.10.13",
|
"@types/node": "^22.10.7",
|
||||||
"@vitest/coverage-v8": "catalog:",
|
"citty": "^0.1.6",
|
||||||
"@vitest/ui": "catalog:",
|
"jiti": "^2.4.2",
|
||||||
"citty": "^0.2.1",
|
"pathe": "^2.0.2",
|
||||||
"jiti": "^2.6.1",
|
|
||||||
"jsdom": "catalog:",
|
|
||||||
"scule": "^1.3.0",
|
"scule": "^1.3.0",
|
||||||
"vitest": "catalog:"
|
"vitepress": "^1.5.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm -r build",
|
"all:build": "pnpm -r build",
|
||||||
"test": "vitest run",
|
"all:test": "pnpm -r test",
|
||||||
"test:ui": "vitest --ui",
|
"create": "jiti ./cli.ts",
|
||||||
"create": "jiti ./bin/cli.ts"
|
"docs:dev": "vitepress dev .",
|
||||||
|
"docs:build": "vitepress build .",
|
||||||
|
"docs:preview": "vitepress preview ."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
packages/platform/build.config.ts
Normal file
16
packages/platform/build.config.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { defineBuildConfig } from 'unbuild';
|
||||||
|
|
||||||
|
export default defineBuildConfig({
|
||||||
|
entries: [
|
||||||
|
'src/browsers',
|
||||||
|
'src/multi',
|
||||||
|
],
|
||||||
|
clean: true,
|
||||||
|
declaration: true,
|
||||||
|
rollup: {
|
||||||
|
emitCJS: true,
|
||||||
|
esbuild: {
|
||||||
|
// minify: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -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@10.29.3",
|
"packageManager": "pnpm@9.15.4",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=24.13.1"
|
"node": ">=22.13.0"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
@@ -28,23 +28,25 @@
|
|||||||
],
|
],
|
||||||
"exports": {
|
"exports": {
|
||||||
"./browsers": {
|
"./browsers": {
|
||||||
"types": "./dist/browsers.d.ts",
|
"import": "./dist/browsers.mjs",
|
||||||
"import": "./dist/browsers.js",
|
"require": "./dist/browsers.cjs",
|
||||||
"require": "./dist/browsers.cjs"
|
"types": "./dist/browsers.d.ts"
|
||||||
},
|
},
|
||||||
"./multi": {
|
"./multi": {
|
||||||
"types": "./dist/multi.d.ts",
|
"import": "./dist/multi.mjs",
|
||||||
"import": "./dist/multi.js",
|
"require": "./dist/multi.cjs",
|
||||||
"require": "./dist/multi.cjs"
|
"types": "./dist/multi.d.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"dev": "vitest dev",
|
"dev": "vitest dev",
|
||||||
"build": "tsdown"
|
"build": "unbuild"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@robonen/tsconfig": "workspace:*",
|
"@robonen/tsconfig": "workspace:*",
|
||||||
"tsdown": "catalog:"
|
"jsdom": "catalog:",
|
||||||
|
"unbuild": "catalog:",
|
||||||
|
"vitest": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
7
packages/platform/vitest.config.ts
Normal file
7
packages/platform/vitest.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -16,9 +16,9 @@
|
|||||||
"url": "git+https://github.com/robonen/tools.git",
|
"url": "git+https://github.com/robonen/tools.git",
|
||||||
"directory": "packages/renovate"
|
"directory": "packages/renovate"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.29.3",
|
"packageManager": "pnpm@9.15.4",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=24.13.1"
|
"node": ">=22.13.0"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"default.json"
|
"default.json"
|
||||||
@@ -27,6 +27,6 @@
|
|||||||
"test": "renovate-config-validator ./default.json"
|
"test": "renovate-config-validator ./default.json"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"renovate": "^43.12.0"
|
"renovate": "^39.115.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
9
packages/stdlib/build.config.ts
Normal file
9
packages/stdlib/build.config.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineBuildConfig } from 'unbuild';
|
||||||
|
|
||||||
|
export default defineBuildConfig({
|
||||||
|
rollup: {
|
||||||
|
esbuild: {
|
||||||
|
// minify: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -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.7",
|
"version": "0.0.4",
|
||||||
"exports": "./src/index.ts"
|
"exports": "./src/index.ts"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@robonen/stdlib",
|
"name": "@robonen/stdlib",
|
||||||
"version": "0.0.7",
|
"version": "0.0.4",
|
||||||
"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,28 +18,34 @@
|
|||||||
"url": "git+https://github.com/robonen/tools.git",
|
"url": "git+https://github.com/robonen/tools.git",
|
||||||
"directory": "packages/stdlib"
|
"directory": "packages/stdlib"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.29.3",
|
"packageManager": "pnpm@9.15.4",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=24.13.1"
|
"node": ">=22.13.0"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
|
"main": "./dist/index.cjs",
|
||||||
|
"module": "./dist/index.mjs",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"types": "./dist/index.d.ts",
|
"import": "./dist/index.mjs",
|
||||||
"import": "./dist/index.js",
|
"require": "./dist/index.cjs",
|
||||||
"require": "./dist/index.cjs"
|
"types": "./dist/index.d.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"dev": "vitest dev",
|
"dev": "vitest dev",
|
||||||
"build": "tsdown"
|
"build": "unbuild"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@robonen/tsconfig": "workspace:*",
|
"@robonen/tsconfig": "workspace:*",
|
||||||
"tsdown": "catalog:"
|
"@vitest/coverage-v8": "catalog:",
|
||||||
|
"pathe": "catalog:",
|
||||||
|
"unbuild": "catalog:",
|
||||||
|
"vitest": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,8 +13,6 @@
|
|||||||
* 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));
|
||||||
@@ -29,6 +29,7 @@ export type ExtractFromCollection<O, K> =
|
|||||||
|
|
||||||
type Get<O, K> = ExtractFromCollection<O, Path<K>>;
|
type Get<O, K> = ExtractFromCollection<O, Path<K>>;
|
||||||
|
|
||||||
|
|
||||||
export function get<O extends Collection, K extends string>(obj: 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;
|
return path.split('.').reduce((acc, key) => (acc as any)?.[key], obj) as Get<O, K> | undefined;
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,6 @@ 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'
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
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
|
||||||
@@ -11,9 +10,9 @@ export type EventHandlerMap = Record<PropertyKey, Subscriber>;
|
|||||||
*
|
*
|
||||||
* @since 0.0.2
|
* @since 0.0.2
|
||||||
*
|
*
|
||||||
* @template Events - Event map where all values are function types
|
* @template {EventsRecord} Events
|
||||||
*/
|
*/
|
||||||
export class PubSub<Events extends EventHandlerMap> {
|
export class PubSub<Events extends EventsRecord> {
|
||||||
/**
|
/**
|
||||||
* Events map
|
* Events map
|
||||||
*
|
*
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expectTypeOf, it } from 'vitest';
|
import { describe, expectTypeOf, it } from 'vitest';
|
||||||
import type { ClearPlaceholder, ExtractPlaceholders } from './index';
|
import type { ClearPlaceholder, ExtractPlaceholders } from "./index";
|
||||||
|
|
||||||
describe.skip('template', () => {
|
describe.skip('template', () => {
|
||||||
describe('ClearPlaceholder', () => {
|
describe('ClearPlaceholder', () => {
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user