mirror of
https://github.com/robonen/tools.git
synced 2026-03-20 10:54:44 +00:00
28
.gitignore
vendored
Normal file
28
.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# ide
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
# temp
|
||||
node_modules
|
||||
**/*~
|
||||
**/*.log
|
||||
**/.DS_Store
|
||||
**/Thumbs.db
|
||||
|
||||
# build
|
||||
.vitepress/dist
|
||||
.vitepress/cache
|
||||
|
||||
.output
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
out
|
||||
build
|
||||
dist
|
||||
|
||||
# test
|
||||
coverage
|
||||
|
||||
# env
|
||||
.env*
|
||||
20
.vitepress/config.ts
Normal file
20
.vitepress/config.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { defineConfig } from 'vitepress';
|
||||
|
||||
export default defineConfig({
|
||||
lang: 'ru-RU',
|
||||
title: "Tools",
|
||||
description: "A set of tools and utilities for web development",
|
||||
rewrites: {
|
||||
'packages/:pkg/README.md': 'packages/:pkg/index.md'
|
||||
},
|
||||
themeConfig: {
|
||||
sidebar: [
|
||||
{
|
||||
text: 'Пакеты',
|
||||
items: [
|
||||
{ text: '@robonen/tsconfig', link: '/packages/tsconfig/' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
# toolkit
|
||||
My most frequently used tools in programming
|
||||
|
||||
- [Plural](/plural) (TS, PHP)
|
||||
132
cli.ts
Normal file
132
cli.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import { defineCommand, runMain } from 'citty';
|
||||
import { resolve } from 'pathe';
|
||||
import { splitByCase } from 'scule';
|
||||
|
||||
const PACKAGE_MANAGER = 'pnpm@8.15.6';
|
||||
const NODE_VERSION = '>=18.0.0';
|
||||
const VITE_VERSION = '^5.2.8';
|
||||
const VITE_DTS_VERSION = '^3.8.1';
|
||||
const DEFAULT_DIR = 'packages';
|
||||
|
||||
const generatePackageJson = (name: string, path: string, hasVite: boolean) => {
|
||||
const data = {
|
||||
name,
|
||||
private: true,
|
||||
version: '1.0.0',
|
||||
license: 'UNLICENSED',
|
||||
description: '',
|
||||
keywords: [],
|
||||
author: 'Robonen Andrew <robonenandrew@gmail.com>',
|
||||
repository: {
|
||||
type: 'git',
|
||||
url: 'git+https://github.com/robonen/tools.git',
|
||||
directory: path,
|
||||
},
|
||||
packageManager: PACKAGE_MANAGER,
|
||||
engines: {
|
||||
node: NODE_VERSION,
|
||||
},
|
||||
type: 'module',
|
||||
files: ['dist'],
|
||||
main: './dist/index.cjs',
|
||||
module: './dist/index.js',
|
||||
types: './dist/index.d.ts',
|
||||
exports: {
|
||||
'.': {
|
||||
import: './dist/index.js',
|
||||
require: './dist/index.cjs',
|
||||
types: './dist/index.d.ts',
|
||||
},
|
||||
},
|
||||
scripts: {
|
||||
test: 'echo \"Error: no test specified\" && exit 1',
|
||||
...(hasVite && {
|
||||
dev: 'vite',
|
||||
build: 'vite build',
|
||||
preview: 'vite preview',
|
||||
}),
|
||||
},
|
||||
devDependencies: {
|
||||
'@robonen/tsconfig': 'workspace:*',
|
||||
...(hasVite && {
|
||||
vite: VITE_VERSION,
|
||||
'vite-plugin-dts': VITE_DTS_VERSION,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
return JSON.stringify(data, null, 2);
|
||||
};
|
||||
|
||||
const generateViteConfig = () => `import { defineConfig } from 'vite';
|
||||
import dts from 'vite-plugin-dts';
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
dts({ insertTypesEntry: true }),
|
||||
],
|
||||
});
|
||||
`;
|
||||
|
||||
const generateTsConfig = () => {
|
||||
const data = {
|
||||
extends: '@robonen/tsconfig/tsconfig.json',
|
||||
};
|
||||
|
||||
return JSON.stringify(data, null, 2);
|
||||
};
|
||||
|
||||
const generateReadme = (name: string) => `# ${name}`;
|
||||
|
||||
const createCommand = defineCommand({
|
||||
meta: {
|
||||
name: "create",
|
||||
version: "1.0.0",
|
||||
description: "Command to create a new project",
|
||||
},
|
||||
args: {
|
||||
name: {
|
||||
type: 'positional',
|
||||
description: "Name of the project",
|
||||
required: true,
|
||||
},
|
||||
path: {
|
||||
type: 'positional',
|
||||
description: "Relative path to the project folder",
|
||||
required: false,
|
||||
},
|
||||
vite: {
|
||||
type: 'boolean',
|
||||
description: "Add Vite to the project",
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
async run({ args }) {
|
||||
const path = args.path ?? `./${DEFAULT_DIR}/${splitByCase(args.name).at(-1)}`;
|
||||
const resolvedPath = resolve(path);
|
||||
const hasVite = args.vite ?? false;
|
||||
|
||||
console.log(`Creating project in ${resolvedPath}`);
|
||||
|
||||
await mkdir(resolvedPath, { recursive: true });
|
||||
|
||||
writeFile(`${resolvedPath}/package.json`, generatePackageJson(args.name, path, hasVite));
|
||||
writeFile(`${resolvedPath}/tsconfig.json`, generateTsConfig());
|
||||
writeFile(`${resolvedPath}/README.md`, generateReadme(args.name));
|
||||
|
||||
if (hasVite) {
|
||||
mkdir(`${resolvedPath}/src`, { recursive: true });
|
||||
writeFile(`${resolvedPath}/vite.config.ts`, generateViteConfig());
|
||||
}
|
||||
|
||||
console.log(`Project created successfully`);
|
||||
},
|
||||
});
|
||||
|
||||
runMain(createCommand);
|
||||
37
package.json
Normal file
37
package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "tools",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "UNLICENSED",
|
||||
"description": "Set of useful tools for web development",
|
||||
"keywords": [
|
||||
"tools",
|
||||
"web",
|
||||
"ui",
|
||||
"utilities"
|
||||
],
|
||||
"author": "Robonen Andrew <robonenandrew@gmail.com>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/robonen/tools.git"
|
||||
},
|
||||
"packageManager": "pnpm@8.15.6",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.12.6",
|
||||
"citty": "^0.1.6",
|
||||
"jiti": "^1.21.0",
|
||||
"pathe": "^1.1.2",
|
||||
"scule": "^1.3.0",
|
||||
"vitepress": "^1.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"create": "jiti ./cli.ts",
|
||||
"docs:dev": "vitepress dev .",
|
||||
"docs:build": "vitepress build .",
|
||||
"docs:preview": "vitepress preview ."
|
||||
}
|
||||
}
|
||||
45
packages/tsconfig/README.md
Normal file
45
packages/tsconfig/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# @robonen/tsconfig
|
||||
|
||||
Базовый конфигурационный файл для TypeScript
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
pnpm install -D @robonen/tsconfig
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"extends": "@robonen/tsconfig/tsconfig.json"
|
||||
}
|
||||
```
|
||||
|
||||
## Описание основных параметров
|
||||
|
||||
```json
|
||||
{
|
||||
"module": "Preserve", // использовать ту же версию модуля, что и сборщик
|
||||
"noEmit": true, // не генерировать файлы
|
||||
"moduleResolution": "Bundler", // разрешение модулей на основе сборщика
|
||||
"target": "ESNext", // целевая версия JavaScript
|
||||
|
||||
|
||||
"skipLibCheck": true, // не проверять типы, заданные во всех файлах описания типов (*.d.ts)
|
||||
"esModuleInterop": true, // создать хелперы __importStar и __importDefault для обеспечения совместимости с экосистемой Babel и включить allowSyntheticDefaultImports для совместимости с системой типов
|
||||
"allowSyntheticDefaultImports": true, // разрешить импортировать модули не имеющие внутри себя "import default"
|
||||
"allowJs": true, // разрешить импортировать файлы JavaScript
|
||||
"resolveJsonModule": true, // разрешить импортировать файлы JSON
|
||||
"moduleDetection": "force", // заставляет TypeScript рассматривать все файлы как модули. Это помогает избежать ошибок cannot redeclare block-scoped variable»
|
||||
"isolatedModules": true, // орабатывать каждый файл, как отдельный изолированный модуль
|
||||
"removeComments": true, // удалять комментарии из исходного кода
|
||||
"verbatimModuleSyntax": true, // сохранять синтаксис модулей в исходном коде (важно при импорте типов)
|
||||
"useDefineForClassFields": true, // использование классов стандарта TC39, а не TypeScript
|
||||
"strict": true, // включить все строгие проверки (noImplicitAny, noImplicitThis, alwaysStrict, strictNullChecks, strictFunctionTypes, strictPropertyInitialization)
|
||||
"noUncheckedIndexedAccess": true, // запрещает доступ к массиву или объекту без предварительной проверки того, определен ли он
|
||||
"declaration": true, // генерировать файлы описания типов (*.d.ts)
|
||||
|
||||
"composite": true, // указывает TypeScript создавать файлы .tsbuildinfo. Это сообщает TypeScript, что ваш проект является частью монорепозитория, а также помогает кэшировать сборки для более быстрой работы
|
||||
"sourceMap": true, // генерировать карту исходного кода
|
||||
"declarationMap": true // генерировать карту исходного кода для файлов описания типов (*.d.ts)
|
||||
}
|
||||
```
|
||||
26
packages/tsconfig/package.json
Normal file
26
packages/tsconfig/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@robonen/tsconfig",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "UNLICENSED",
|
||||
"description": "",
|
||||
"keywords": [
|
||||
"tsconfig",
|
||||
"typescript",
|
||||
"ts",
|
||||
"config"
|
||||
],
|
||||
"author": "Robonen Andrew <robonenandrew@gmail.com>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/robonen/tools.git",
|
||||
"directory": "packages/tsconfig"
|
||||
},
|
||||
"packageManager": "pnpm@8.15.6",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"files": [
|
||||
"**tsconfig.json"
|
||||
]
|
||||
}
|
||||
36
packages/tsconfig/tsconfig.json
Normal file
36
packages/tsconfig/tsconfig.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"display": "Base TypeScript Configuration",
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
"module": "Preserve",
|
||||
"noEmit": true,
|
||||
"moduleResolution": "Bundler",
|
||||
"sourceMap": true,
|
||||
"target": "ESNext",
|
||||
|
||||
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"allowJs": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleDetection": "force",
|
||||
"isolatedModules": true,
|
||||
"removeComments": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"useDefineForClassFields": true,
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
|
||||
/* Library transpiling */
|
||||
"declaration": true,
|
||||
|
||||
/* Library in monorepo */
|
||||
"composite": true,
|
||||
"declarationMap": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
# Функция изменения формы слов в зависимости от числительного
|
||||
|
||||
Часто встречается задача вывода правильного окончания слова с предшествующим ему числом.
|
||||
|
||||
Например:
|
||||
|
||||
- 1 депутат
|
||||
- 2–4 депутат**а**
|
||||
- 0, 5-9 или 10 депутат**ов**
|
||||
|
||||
## Примеры исользования
|
||||
|
||||
### Typescript
|
||||
```typescript
|
||||
import { plural } from 'plural';
|
||||
|
||||
const totalOrders = 2;
|
||||
|
||||
const words = ['заказ', 'заказа', 'заказов'];
|
||||
|
||||
const result = `${totalOrders} ${plural(totalOrders, words)}`;
|
||||
|
||||
console.log(result); // 2 заказа
|
||||
```
|
||||
|
||||
### PHP
|
||||
```php
|
||||
include 'plural.php';
|
||||
|
||||
$totalOrders = 2;
|
||||
|
||||
$words = ['заказ', 'заказа', 'заказов'];
|
||||
|
||||
$result = "{$totalOrders} {plural($totalOrders, $words)}";
|
||||
|
||||
echo result; // 2 заказа
|
||||
```
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
function plural(int $count, array $words)
|
||||
{
|
||||
$cases = [2, 0, 1, 1, 1, 2];
|
||||
return $words[
|
||||
$count % 100 > 4 && $count % 100 < 20 ? 2 : $cases[min($count % 10, 5)]
|
||||
];
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export type WordForms = [string, string, string];
|
||||
|
||||
export const plural = (count: number, words: WordForms): string => {
|
||||
const cases = [2, 0, 1, 1, 1, 2];
|
||||
return words[
|
||||
count % 100 > 4 && count % 100 < 20 ? 2 : cases[Math.min(count % 10, 5)]
|
||||
];
|
||||
};
|
||||
1138
pnpm-lock.yaml
generated
Normal file
1138
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
pnpm-workspaces.yaml
Normal file
2
pnpm-workspaces.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
- packages/*
|
||||
@@ -1,66 +0,0 @@
|
||||
type Split<S extends string, D extends string> = S extends ''
|
||||
? []
|
||||
: S extends `${infer T}${D}${infer U}`
|
||||
? [T, ...Split<U, D>]
|
||||
: [S];
|
||||
|
||||
type UnwrapNumbers<T extends string | string[] | number | number[]> = T extends number | number[]
|
||||
? T
|
||||
: T extends string
|
||||
? T extends `${infer N extends number}`
|
||||
? N
|
||||
: never
|
||||
: T extends [infer H extends string, ...infer T extends string[]]
|
||||
? UnwrapNumbers<H> extends never
|
||||
? never
|
||||
: [UnwrapNumbers<H>, ...UnwrapNumbers<T>]
|
||||
: [];
|
||||
|
||||
type IPv4Octets = [number, number, number, number];
|
||||
type IPv6Octets = string[];
|
||||
|
||||
type IPv4<CIDR extends string> = Split<CIDR, '/'> extends [
|
||||
infer IP extends string,
|
||||
infer SUBNET extends string,
|
||||
]
|
||||
? UnwrapNumbers<Split<IP, '.'>> extends infer OCTETS
|
||||
? OCTETS extends never
|
||||
? never
|
||||
: OCTETS extends IPv4Octets
|
||||
? UnwrapNumbers<SUBNET> extends never
|
||||
? never
|
||||
: CIDR
|
||||
: never
|
||||
: never
|
||||
: never;
|
||||
|
||||
type IPv6<CIDR extends string> = Split<CIDR, '/'> extends [
|
||||
infer IP extends string,
|
||||
infer SUBNET extends string,
|
||||
]
|
||||
? Split<IP, ':'> extends IPv6Octets
|
||||
? UnwrapNumbers<SUBNET> extends never
|
||||
? never
|
||||
: CIDR
|
||||
: never
|
||||
: never;
|
||||
|
||||
type Test_1 = IPv4<'0.0.0.0/0'>;
|
||||
type Test_2 = IPv4<'255.255.255.255/32'>;
|
||||
type Test_3 = IPv4<'127.0.0.0.1/32'>;
|
||||
type Test_4 = IPv4<'a'>;
|
||||
type Test_5 = IPv4<'1.1.1.a/32'>;
|
||||
type Test_6 = IPv4<'1.1.1.1'>;
|
||||
type Test_7 = IPv4<'1.1.1.1/'>;
|
||||
type Test_8 = IPv4<'1.1.1.1/a'>;
|
||||
|
||||
type Test_9 = IPv6<'::1/0'>;
|
||||
type Test_10 = IPv6<'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128'>;
|
||||
type Test_11 = IPv6<'::1/128'>;
|
||||
type Test_12 = IPv6<'a'>;
|
||||
type Test_13 = IPv6<'1:1:1:1:1:1:1:1/128'>;
|
||||
type Test_14 = IPv6<'1:1:1:1:1:1:1:1'>;
|
||||
type Test_15 = IPv6<'1:1:1:1:1:1:1:1/'>;
|
||||
type Test_16 = IPv6<'1:1:1:1:1:1:1:1/a'>;
|
||||
|
||||
// TODO: fully-typed ipv6 (unwrap hex, full and abbreviated address representations)
|
||||
Reference in New Issue
Block a user