Skip to content

Commit

Permalink
fix: критический баг с резолвингом путей при сборке конфига + новое r…
Browse files Browse the repository at this point in the history
…eadme
  • Loading branch information
artpani4 committed Sep 3, 2024
1 parent d6aa94c commit 323a4c2
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 125 deletions.
190 changes: 81 additions & 109 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,61 @@
# Tuner

[![deno.land/x/tuner](https://shield.deno.dev/x/tuner)](https://deno.land/x/tuner)
[![deno.land/x/tuner](https://shield.deno.dev/x/tuner)](https://deno.land/x/tuner) [![JSR Score](https://jsr.io/badges/@artpani/tuner/score)](https://jsr.io/@vseplet/reface)

Tuner - модуль для управления конфигурациями проекта. Данные конфигурации описываются в виде **.ts** файла с экспортируемым объектом, который содержит перечисление _env_ переменных и полей конфига. Конфиги могут образовывать иерархию, наследуясь от родительских и перезаписываясь дочерними.

---

## Оглавление

- [Tuner](#tuner)
- [Оглавление](#оглавление)
- [Простейший конфиг](#простейший-конфиг)
- [Конфиг с описанием env-переменных](#конфиг-с-описанием-env-переменных)
- [Объединение конфигов](#объединение-конфигов)
- [Опции при загрузке](#опции-при-загрузке)

## Простейший конфиг

Минимально конфиг может быть описан так:

```tsx
// config/myConfig.tuner.ts
import Tuner from 'https://deno.land/x/tuner/mod.ts';
// ./config/myConfig.tuner.ts
import Tuner from 'jsr:@artpani/tuner';

export default Tuner.tune(
{
config: {
data: {
field1: 'value1',
field2: 100,
field3: true,
field4: ['минималистично', 'удобно', 'не правда ли?'],
},
},
);

export default myCfg;
export type MyCFGType = typeof myCfg;
```

> Функция _tune_ заботливо подскажет структуру ожидаемого объекта
Загрузка конфига и использование происходит так:

```tsx
// main.ts
import Tuner from 'https://deno.land/x/tuner/mod.ts';
const cfg = await Tuner.use.loadConfig();
console.log(cfg.config.field2); // 100
// ./main.ts
import Tuner from 'jsr:@artpani/tuner';
import { MyCFGType } from '../config/myConfig.tuner.ts';
const cfg = await Tuner.use.loadConfig<MyCFGType>({
configDirPath: 'config',
});
console.log(cfg.data.field2); // 100
```

__При запуске обязательно наличие _env_ переменной _config_, ее значение - название файла конфига до _.tuner.ts,__ в данном примере это myConfig._
__При запуске обязательно наличие _env_ переменной _CONFIG_, ее значение - название файла конфига до _.tuner.ts,__ в данном примере это myConfig._

```bash
config=myConfig deno run --allow-all main.ts
CONFIG=myConfig deno run --allow-all main.ts
```

## Конфиг с описанием env-переменных
Expand All @@ -56,16 +70,16 @@ config=myConfig deno run --allow-all main.ts
Например, так:

```tsx
// config/myConfig.tuner.ts
import Tuner from 'https://deno.land/x/tuner/mod.ts';
// ./config/myConfig.tuner.ts
import Tuner from 'jsr:@artpani/tuner';
export default Tuner.tune(
{
env: {
// Использовать Значение по умолчанию
env1: Tuner.Env.getString.orDefault('defalut value1'),
env2: Tuner.Env.getNumber.orDefault(100),
env3: Tuner.Env.getBoolean.orDefault(true),
// Проигнорировать отсуствие переменной
// Проигнорировать отсуствие переменной(будет иметь тип значения + undefined)
env4: Tuner.Env.getString.orNothing(),
env5: Tuner.Env.getNumber.orNothing(),
env6: Tuner.Env.getBoolean.orNothing(),
Expand All @@ -88,7 +102,7 @@ export default Tuner.tune(
new Promise(() => 100)
),
},
config: {
data: {
field1: 'value1',
field2: 100,
field3: true,
Expand All @@ -108,129 +122,87 @@ Tuner позволяет “собрать” конфиг, используя
- Текущий конфиг дополнится всеми полями дочернего, при этом совпадающие поля будут переписаны значениями из дочернего конфига
- Значения-фукнции, используемые для описания env-переменных также подчиняются этим правилам

```mermaid
flowchart LR;
subgraph W[" "]
direction BT
base["base\nРодительский конфиг\n{a: 400, b: 401, c:402}"]
rab["Рабочий конфиг\n{a: 300, b: 301}\nChild:A\nparent:base"]
A["А\nДочерний конфиг рабочего\n{b: 200, e:201}\nChild:B"]
B["B\nДочерний конфиг A\n{a: 100, d: 101}"]
end
style rab stroke:#300,stroke-width:6px
B-->|"Добавить: a=100,d=101"|A-->|"Переписать a->100, b->200\nДобавить: d=101,e=201"|rab-->|"Переписать: a->100,b->200\nДобавить: d=101, e=201"|base
W-->|Результат|F["{a: 100, b: 200, c:402, d: 101, e:201}"]
```
![Пример наследования](https://artpani.sirv.com/Images/projects/tuner/cascade.png)

> При этом, например, конфигу В необязательно указывать А в качестве родительского.
Реализация:

```tsx
// config/develop.tuner.ts
import Tuner from 'https://deno.land/x/tuner/mod.ts';
export default Tuner.tune({
child: Tuner.Load.local.configDir('a.tuner.ts'),
parent: Tuner.Load.local.configDir('base.tuner.ts'),
config: {
import Tuner from 'jsr:@artpani/tuner';
import { ACfgType } from './a.tuner.ts';

const developCfg = Tuner.tune({
parent: Tuner.Load.local.configDir<ACfgType>('a.tuner.ts'),
data: {
a: 300,
b: 301,
},
});

//config/base.tuner.ts
import Tuner from 'https://deno.land/x/tuner/mod.ts';
export default Tuner.tune({
config: { a: 400, b: 401, c: 402 },
export default developCfg;
export type DevelopCFGType = typeof developCfg;

// config/base.tuner.ts
import Tuner from 'jsr:@artpani/tuner';

const baseCfg = Tuner.tune({
data: {
a: 400,
b: 401,
c: 402,
},
});

//config/a.tuner.ts
import Tuner from 'https://deno.land/x/tuner/mod.ts';
export default Tuner.tune({
export default baseCfg;
export type BaseCFGType = typeof baseCfg;

// config/a.tuner.ts
import Tuner from 'jsr:@artpani/tuner';
import { BaseCFGType } from './base.tuner.ts';

const aCfg = Tuner.tune({
parent: Tuner.Load.local.configDir<BaseCFGType>('base.tuner.ts'),
child: Tuner.Load.local.configDir('b.tuner.ts'),
config: {
data: {
b: 200,
e: 201,
},
});

//config/b.tuner.ts
import Tuner from 'https://deno.land/x/tuner/mod.ts';
export default Tuner.tune({
config: { a: 100, d: 101 },
});

//main.ts
import Tuner from 'https://deno.land/x/tuner/mod.ts';
const cfg = await Tuner.use.loadConfig();
console.log(cfg);
//{ config: { a: 100, b: 200, c: 402, e: 201, d: 101 }, env: {} }
```

_Tuner.Load_ предлагает локальный и удаленный вариант подключения конфига.
export default aCfg;
export type ACfgType = typeof aCfg;

Tuner.Load.local
// config/b.tuner.ts
import Tuner from 'jsr:@artpani/tuner';

| Функция | Вернет объект конфига из файла по … |
| ------------------------- | --------------------------------------------------- |
| absolutePath(path:string) | …указанному полному пути до него |
| configDir(path:string) | …пути, относительно директории с названием “config” |
| cwd(path:string) | …относительному пути в директории проекта |

---

Tuner.Load.remote

| Фукнция | Описание | Пример (пусть файл конфигурации лежит по адресу http://some_server/b.tuner.ts) |
| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| import(path:string) | Работает, как обычный импорт | child: Tuner.Load.remote.import(”http://some_server/b.tuner.ts”) |
| callbackReturnModule(cb: () ⇒ Promise<{default: ITunerConfig}>) | Принимает колбэк, который вернет промис с импортируемым модулем | child: Tuner.Load.remote.callbackReturnModule(() ⇒ import(”http://some_server/b.tuner.ts”)) |
| callbackReturnString((cb: () => Promise<string>)) | Принимает колбэк, который вернет промис с текстом модуля в виде строки (забираем код конфига из форм, блоков в Notion и тд) | child: Tuner.Load.remote.callbackReturnString(() ⇒ someFetchingFunctionStringReturned(options: {…})) |

Кроме того, _Tuner.Load.remote_ имеет встроенные интеграции с различными сервисами через _Tuner.Load.remote.providers:_

- notion(key:string, blockUrl:string) - отдаем ключ авторизации(_Tuner.getEnv_ поможет найти env-переменную в окружении или .env файле) и ссылку на блок в Notion, в котором описан модуль конфигурации
- github(key: string, owner: string, repo: string, filePath: string) - ключ, ник держателя репо, название репо и путь до файла.
const bCfg = Tuner.tune({
data: {
a: 100,
d: 101,
},
});

## Генерация схемы конфига
export default bCfg;
export type BCfgType = typeof bCfg;

Для удобной работы с объектом конфигурации во время разработки рекомендуется сгенерировать тип объекта.
// main.ts
import { DevelopCFGType } from './config/develop.tuner.ts';
import Tuner from 'jsr:@artpani/tuner';

_Tuner.use.generateSchema(**obj**_: ObjectType, _**variableName**_: string, _**filePath**_: string) сформирует файл по пути _**filePath**_ со схемой объекта _**obj**_ и экспортирует тип с названием _**variableName**_, переведя первую букву в заглавный регистр.
const config = await Tuner.use.loadConfig<DevelopCFGType>({
configDirPath: './config',
});

```tsx
const cfg = await Tuner.use.loadConfig();
Tuner.use.generateSchema(
cfg,
'config',
'config/configSchema.ts',
);
console.log(config.data);
// { a: 300, b: 301, c: 402, e: 201, d: 101 }
```

Файл config/configSchema.ts
## Опции при загрузке

```tsx
import { z } from 'https://deno.land/x/zod/mod.ts';

export const configSchema = z.object({
config: z.object({
a: z.number(),
b: z.number(),
c: z.number(),
e: z.number(),
d: z.number(),
}),
env: z.object({}),
});
При вызове loadConfig, передайте объект с опциями, чтобы настроить процесс загрузки конфигураций:

export type Config = z.infer<typeof configSchema>;
- _configDirName_: Имя директории, где хранятся конфигурационные файлы. **По умолчанию используется config**. Укажите это значение, если у вас другая структура каталогов.

//├─ config
//│ ├─ a
//│ ├─ b
//│ ├─ c
//│ ├─ e
//│ └─ d
//└─ env
//
```
- _configDirPath_: Путь к директории, содержащей конфигурационные файлы. **По умолчанию это текущая рабочая директория (./)**. Используйте эту опцию, если конфигурационные файлы находятся в другом месте.
5 changes: 3 additions & 2 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@artpani/tuner",
"version": "0.3.0",
"version": "0.3.1",
"exports": "./mod.ts",
"workflows": {
"UpdateSemverDeployJsr": {
Expand All @@ -16,7 +16,7 @@
},
"tasks": {
"example": "CONFIG=develop deno run --allow-all example/config/main.ts",
"example2": "CONFIG=dev deno run --allow-all example2/config/main.ts",
"example2": "CONFIG=dev deno run --allow-all example2/config_ololo/main.ts",
"check": "CONFIG=bot deno run --allow-all config/checkConfig.ts"
},
"lint": {
Expand Down Expand Up @@ -65,6 +65,7 @@
"noImplicitReturns": true,
"exactOptionalPropertyTypes": true
},

"imports": {
"@libs/std": "jsr:@libs/std@^2024.7.24",
"@std/dotenv": "jsr:@std/dotenv@^0.225.0",
Expand Down
3 changes: 1 addition & 2 deletions example/config/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import Tuner from '../../mod.ts';
import { DevelopCfgType } from './develop.tuner.ts';

const c = await Tuner.use.loadConfig<DevelopCfgType>({
configDirName: 'config',
configDirPath: './example',
configDirPath: 'example',
});
console.log(c.data);
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions example2/config/main.ts → example2/config_ololo/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Tuner from '../../mod.ts';

export const config = await Tuner.use.loadConfig<DevCFGType>({
configDirPath: 'example2',
configDirName: 'config_ololo',
});

console.log(config.env.VITE_API_URL);
File renamed without changes.
17 changes: 12 additions & 5 deletions source/loaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const log = new luminous.Logger(loggerOptions);
*/
const absolutePath = <T extends ITunerConfig>(
path: string,
): { fun: () => Promise<T>; args: string } => ({
): { fun: () => Promise<T>; args: string; type: string } => ({
fun: async (): Promise<T> => {
try {
// Get the current working directory
Expand Down Expand Up @@ -44,6 +44,7 @@ const absolutePath = <T extends ITunerConfig>(
}
},
args: path,
type: 'absolutePath',
});

/**
Expand All @@ -54,15 +55,20 @@ const absolutePath = <T extends ITunerConfig>(
*/
const configDir = <T extends ITunerConfig>(
path: string,
): { fun: () => Promise<T>; args: string } => ({
configName?: string,
): { fun: () => Promise<T>; args: string; type: string } => ({
fun: async (): Promise<T> => {
try {
const configDir = await findDirectoryInCWD('config');
const configDir = await findDirectoryInCWD(
configName || 'config',
);

if (!configDir) {
throw new Error('Config directory not found');
throw new Error(
`Config directory not found (tried to find ${configName})`,
);
}

console.log(configName, configDir, path);
const modulePath = 'file://' + resolve(configDir, path);

const module = await import(modulePath);
Expand All @@ -78,6 +84,7 @@ const configDir = <T extends ITunerConfig>(
}
},
args: path,
type: 'configDir',
});
/**
* Получает конфигурацию из файла в текущей рабочей директории.
Expand Down
Loading

0 comments on commit 323a4c2

Please sign in to comment.