@astral/ui 4.83.0 → 4.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/_mocks/LoggerMock/LoggerMock.d.ts +16 -0
  2. package/_mocks/LoggerMock/LoggerMock.js +18 -0
  3. package/_mocks/LoggerMock/index.d.ts +1 -0
  4. package/_mocks/LoggerMock/index.js +1 -0
  5. package/components/ConfigProvider/ConfigProvider.d.ts +7 -1
  6. package/components/ConfigProvider/ConfigProvider.js +14 -8
  7. package/components/DashboardLayout/Header/useLogic/useLogic.d.ts +1 -1
  8. package/components/Onboarding/Onboarding.js +13 -3
  9. package/components/Onboarding/core/ExitController/ExitController.d.ts +3 -1
  10. package/components/Onboarding/core/ExitController/ExitController.js +4 -1
  11. package/components/Onboarding/core/OnboardingTour/OnboardingTour.d.ts +3 -1
  12. package/components/Onboarding/core/OnboardingTour/OnboardingTour.js +25 -4
  13. package/components/Onboarding/core/ProgressStorage/ProgressStorage.d.ts +3 -1
  14. package/components/Onboarding/core/ProgressStorage/ProgressStorage.js +8 -4
  15. package/node/_mocks/LoggerMock/LoggerMock.d.ts +16 -0
  16. package/node/_mocks/LoggerMock/LoggerMock.js +23 -0
  17. package/node/_mocks/LoggerMock/index.d.ts +1 -0
  18. package/node/_mocks/LoggerMock/index.js +17 -0
  19. package/node/components/ConfigProvider/ConfigProvider.d.ts +7 -1
  20. package/node/components/ConfigProvider/ConfigProvider.js +13 -7
  21. package/node/components/DashboardLayout/Header/useLogic/useLogic.d.ts +1 -1
  22. package/node/components/Onboarding/Onboarding.js +13 -3
  23. package/node/components/Onboarding/core/ExitController/ExitController.d.ts +3 -1
  24. package/node/components/Onboarding/core/ExitController/ExitController.js +4 -1
  25. package/node/components/Onboarding/core/OnboardingTour/OnboardingTour.d.ts +3 -1
  26. package/node/components/Onboarding/core/OnboardingTour/OnboardingTour.js +25 -4
  27. package/node/components/Onboarding/core/ProgressStorage/ProgressStorage.d.ts +3 -1
  28. package/node/components/Onboarding/core/ProgressStorage/ProgressStorage.js +8 -4
  29. package/node/services/Logger/Logger.d.ts +45 -0
  30. package/node/services/Logger/Logger.js +72 -0
  31. package/node/services/Logger/index.d.ts +10 -0
  32. package/node/services/Logger/index.js +17 -0
  33. package/package.json +1 -1
  34. package/services/Logger/Logger.d.ts +45 -0
  35. package/services/Logger/Logger.js +68 -0
  36. package/services/Logger/index.d.ts +10 -0
  37. package/services/Logger/index.js +12 -0
@@ -0,0 +1,16 @@
1
+ import { type Logger } from '../../services/Logger';
2
+ /**
3
+ * Mock логгера для тестов
4
+ */
5
+ export declare class LoggerMock {
6
+ configure: Logger['configure'];
7
+ child: Logger['child'];
8
+ isLevelEnabled: Logger['isLevelEnabled'];
9
+ success: Logger['success'];
10
+ info: Logger['info'];
11
+ warn: Logger['warn'];
12
+ debug: Logger['debug'];
13
+ error: Logger['error'];
14
+ constructor();
15
+ }
16
+ export declare const createLoggerMock: () => Logger;
@@ -0,0 +1,18 @@
1
+ import { vi } from 'vitest';
2
+ /**
3
+ * Mock логгера для тестов
4
+ */
5
+ export class LoggerMock {
6
+ constructor() {
7
+ this.configure = vi.fn(() => vi.fn());
8
+ this.child = vi.fn();
9
+ this.isLevelEnabled = vi.fn(() => false);
10
+ this.success = vi.fn();
11
+ this.info = vi.fn();
12
+ this.warn = vi.fn();
13
+ this.debug = vi.fn();
14
+ this.error = vi.fn();
15
+ vi.mocked(this.child).mockReturnValue(this);
16
+ }
17
+ }
18
+ export const createLoggerMock = () => new LoggerMock();
@@ -0,0 +1 @@
1
+ export * from './LoggerMock';
@@ -0,0 +1 @@
1
+ export * from './LoggerMock';
@@ -1,4 +1,5 @@
1
1
  import { type ReactElement, type ReactNode } from 'react';
2
+ import { type LogThreshold } from '../../services/Logger';
2
3
  import { type LanguageMap } from '../DatePicker/types';
3
4
  import { type PlaceholderImageProps } from '../placeholders/Placeholder/Image';
4
5
  import { type CaptureException } from '../services/ErrorService';
@@ -138,7 +139,12 @@ export type ConfigProviderProps = Partial<ConfigContextProps> & {
138
139
  * Переданный в компонент флаг напрямую имеет приоритет выше
139
140
  */
140
141
  nextFeatureFlags?: NextFeatureFlagsContextProps;
142
+ /**
143
+ * Порог логов ui-kit в консоли. По умолчанию: prod → `error`, dev → `warn`,
144
+ * test → `silent`. `silent` — выключить полностью.
145
+ */
146
+ logLevel?: LogThreshold;
141
147
  };
142
148
  export declare const ConfigContext: import("react").Context<ConfigContextProps>;
143
- export declare const ConfigProvider: ({ children, language, datePickerLanguageMap, captureException: captureExceptionProp, emptySymbol, imagesMap, techSup, hidePersonalData, hidePersonalDataClassname, hidePersonalInputDataClassname, components: propsComponents, nextFeatureFlags, }: Partial<ConfigProviderProps>) => import("react/jsx-runtime").JSX.Element;
149
+ export declare const ConfigProvider: ({ children, language, datePickerLanguageMap, captureException: captureExceptionProp, emptySymbol, imagesMap, techSup, hidePersonalData, hidePersonalDataClassname, hidePersonalInputDataClassname, components: propsComponents, nextFeatureFlags, logLevel, }: Partial<ConfigProviderProps>) => import("react/jsx-runtime").JSX.Element;
144
150
  export {};
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useEffect, useMemo, } from 'react';
2
+ import { createContext, useEffect, } from 'react';
3
+ import { configureLogger } from '../../services/Logger';
3
4
  import { russianMap } from '../DatePicker/constants';
4
5
  import { ErrorService } from '../services/ErrorService';
5
6
  import { NextFeatureFlagsContext, } from './NextFeatureFlagsContext';
@@ -13,6 +14,10 @@ const techSupDefault = {
13
14
  phone: '',
14
15
  email: '',
15
16
  };
17
+ const defaultCaptureException = (error) => {
18
+ // biome-ignore lint/suspicious/noConsole: Ошибка для интеграторов
19
+ console.error(error);
20
+ };
16
21
  const defaultHidePersonalDataClassname = 'ym-hide-content sentry-mask';
17
22
  const defaultHidePersonalInputDataClassname = 'ym-disable-keys sentry-mask';
18
23
  export const ConfigContext = createContext({
@@ -27,20 +32,21 @@ export const ConfigContext = createContext({
27
32
  hidePersonalInputDataClassname: defaultHidePersonalInputDataClassname,
28
33
  hidePersonalData: true,
29
34
  });
30
- export const ConfigProvider = ({ children, language = 'ru', datePickerLanguageMap = russianMap, captureException: captureExceptionProp, emptySymbol = '—', imagesMap = imagesMapDefault, techSup = techSupDefault, hidePersonalData = true, hidePersonalDataClassname = defaultHidePersonalDataClassname, hidePersonalInputDataClassname = defaultHidePersonalInputDataClassname, components: propsComponents, nextFeatureFlags = {}, }) => {
31
- const captureException =
32
- // biome-ignore lint/suspicious/noConsole: Ошибка для интеграторов
33
- captureExceptionProp || ((error) => console.error(error));
34
- useMemo(() => {
35
+ export const ConfigProvider = ({ children, language = 'ru', datePickerLanguageMap = russianMap, captureException: captureExceptionProp, emptySymbol = '—', imagesMap = imagesMapDefault, techSup = techSupDefault, hidePersonalData = true, hidePersonalDataClassname = defaultHidePersonalDataClassname, hidePersonalInputDataClassname = defaultHidePersonalInputDataClassname, components: propsComponents, nextFeatureFlags = {}, logLevel, }) => {
36
+ const captureException = captureExceptionProp ?? defaultCaptureException;
37
+ useEffect(() => {
35
38
  ErrorService.getInstance().init(captureException);
36
39
  }, [captureException]);
37
40
  useEffect(() => {
38
- if (!captureException) {
41
+ return configureLogger({ level: logLevel });
42
+ }, [logLevel]);
43
+ useEffect(() => {
44
+ if (!captureExceptionProp) {
39
45
  // biome-ignore lint/suspicious/noConsole: Ошибка для интеграторов
40
46
  console.warn('ConfigProvider: Необходимо наличие captureException, связанного с сервисом мониторинга ошибок.\n' +
41
47
  'На данный момент все ошибки, отлавливаемые в ErrorBoundary будут выводиться только в консоль');
42
48
  }
43
- }, []);
49
+ }, [captureExceptionProp]);
44
50
  const components = {
45
51
  ...propsComponents,
46
52
  layout: {
@@ -5,7 +5,7 @@ export declare const useLogic: ({ profile, menuOrganization }: HeaderProps) => {
5
5
  isShowProfile: boolean;
6
6
  isMobile: boolean;
7
7
  isFocusedMode: boolean;
8
- mobileHeaderPriorityFeature: "menuOrg" | "profile";
8
+ mobileHeaderPriorityFeature: "profile" | "menuOrg";
9
9
  isLoading: boolean;
10
10
  collapsedIn: boolean;
11
11
  onToggleSidebar: (newValue?: boolean | undefined) => void;
@@ -1,3 +1,4 @@
1
+ import { createLogger } from '../../services/Logger';
1
2
  import { ErrorService } from '../services/ErrorService';
2
3
  import { ActiveStepStore } from './core/ActiveStepStore';
3
4
  import { OnboardingTour } from './core/OnboardingTour';
@@ -18,11 +19,20 @@ export class Onboarding {
18
19
  * Создаёт тур.
19
20
  */
20
21
  this.createTour = (tourConfig) => {
22
+ const logger = createLogger('Onboarding', {
23
+ context: { tourName: tourConfig.name },
24
+ });
21
25
  // Fail-fast на некорректной конфигурации до сборки тура.
22
- validateTourConfig(tourConfig);
26
+ try {
27
+ validateTourConfig(tourConfig);
28
+ }
29
+ catch (error) {
30
+ logger.error('Некорректная конфигурация тура', error);
31
+ throw error;
32
+ }
23
33
  // Ключ хранилища уникален на тур, поэтому стратегия строится на createTour.
24
- const progressStorage = new ProgressStorage(this.saveStrategyFactory(tourConfig.name));
25
- return new OnboardingTour(tourConfig, progressStorage, this.activeStepStore, this.errorService);
34
+ const progressStorage = new ProgressStorage(this.saveStrategyFactory(tourConfig.name), logger);
35
+ return new OnboardingTour(tourConfig, progressStorage, this.activeStepStore, this.errorService, logger);
26
36
  };
27
37
  /**
28
38
  * Закрывает показанный сейчас тур.
@@ -1,3 +1,4 @@
1
+ import { type Logger } from '../../../../services/Logger';
1
2
  export type ExitControllerParams = {
2
3
  /**
3
4
  * Фактическое закрытие тура. Вызывается только после подтверждения в
@@ -20,8 +21,9 @@ export type ExitControllerParams = {
20
21
  */
21
22
  export declare class ExitController {
22
23
  private readonly params;
24
+ private readonly logger;
23
25
  private confirming;
24
- constructor(params: ExitControllerParams);
26
+ constructor(params: ExitControllerParams, logger: Logger);
25
27
  /**
26
28
  * Идёт ли запрос подтверждения выхода.
27
29
  */
@@ -6,8 +6,9 @@
6
6
  * (ConfirmExit); закрытие происходит только по confirm.
7
7
  */
8
8
  export class ExitController {
9
- constructor(params) {
9
+ constructor(params, logger) {
10
10
  this.params = params;
11
+ this.logger = logger;
11
12
  this.confirming = false;
12
13
  /**
13
14
  * Запрос на закрытие — показывает ConfirmExit. Повторные вызовы во время
@@ -17,6 +18,7 @@ export class ExitController {
17
18
  if (this.confirming) {
18
19
  return;
19
20
  }
21
+ this.logger.debug('Запрошен выход из тура, показано подтверждение');
20
22
  this.confirming = true;
21
23
  this.params.onChange();
22
24
  };
@@ -37,6 +39,7 @@ export class ExitController {
37
39
  if (!this.confirming) {
38
40
  return;
39
41
  }
42
+ this.logger.debug('Выход из тура отменён');
40
43
  this.confirming = false;
41
44
  this.params.onChange();
42
45
  };
@@ -1,3 +1,4 @@
1
+ import { type Logger } from '../../../../services/Logger';
1
2
  import { type ErrorService } from '../../../services/ErrorService';
2
3
  import { type OnboardingTourConfig, type Step, type StepHandles, type TourProgress } from '../../types';
3
4
  import { type ActiveStepStore } from '../ActiveStepStore';
@@ -13,6 +14,7 @@ export declare class OnboardingTour<TSteps extends readonly Step[] = readonly St
13
14
  private readonly progressStorage;
14
15
  private readonly activeStepStore;
15
16
  private readonly errorService;
17
+ private readonly logger;
16
18
  private readonly navigator;
17
19
  private readonly resolver;
18
20
  private readonly stepsController;
@@ -22,7 +24,7 @@ export declare class OnboardingTour<TSteps extends readonly Step[] = readonly St
22
24
  * Типобезопасный доступ к шагам по их именам.
23
25
  */
24
26
  readonly steps: StepHandles<TSteps>;
25
- constructor(config: OnboardingTourConfig<TSteps>, progressStorage: ProgressStorage, activeStepStore: ActiveStepStore, errorService: ErrorService);
27
+ constructor(config: OnboardingTourConfig<TSteps>, progressStorage: ProgressStorage, activeStepStore: ActiveStepStore, errorService: ErrorService, logger: Logger);
26
28
  /**
27
29
  * Запуск тура с первого шага.
28
30
  */
@@ -9,11 +9,12 @@ import { StepsController } from '../StepsController';
9
9
  * состояние активного шага в store своего экземпляра онбординга.
10
10
  */
11
11
  export class OnboardingTour {
12
- constructor(config, progressStorage, activeStepStore, errorService) {
12
+ constructor(config, progressStorage, activeStepStore, errorService, logger) {
13
13
  this.config = config;
14
14
  this.progressStorage = progressStorage;
15
15
  this.activeStepStore = activeStepStore;
16
16
  this.errorService = errorService;
17
+ this.logger = logger;
17
18
  this.status = 'idle';
18
19
  /**
19
20
  * Запуск тура с первого шага.
@@ -21,8 +22,10 @@ export class OnboardingTour {
21
22
  this.start = async () => {
22
23
  const persisted = await this.progressStorage.load();
23
24
  if (persisted?.isDismissed) {
25
+ this.logger.info('Тур пропущен: ранее закрыт пользователем');
24
26
  return;
25
27
  }
28
+ this.logger.info('Тур запущен');
26
29
  this.exitController.reset();
27
30
  this.navigator.reset();
28
31
  this.enterCurrentStep();
@@ -32,8 +35,10 @@ export class OnboardingTour {
32
35
  */
33
36
  this.resume = () => {
34
37
  if (this.status !== 'waiting-resume') {
38
+ this.logger.debug('resume проигнорирован: тур не на паузе');
35
39
  return;
36
40
  }
41
+ this.logger.debug('Тур возобновлён после паузы');
37
42
  this.status = 'running';
38
43
  this.publishStep();
39
44
  };
@@ -42,6 +47,7 @@ export class OnboardingTour {
42
47
  */
43
48
  this.restore = async () => {
44
49
  const persisted = await this.progressStorage.load();
50
+ this.logger.info('Тур восстановлен');
45
51
  this.exitController.reset();
46
52
  this.navigator.reset();
47
53
  // Пройденный тур переигрываем с начала; иначе продолжаем с сохранённого шага.
@@ -148,6 +154,7 @@ export class OnboardingTour {
148
154
  */
149
155
  this.resetProgress = async () => {
150
156
  await this.progressStorage.reset();
157
+ this.logger.debug('Прогресс тура сброшен');
151
158
  };
152
159
  /**
153
160
  * Возвращает сохранённый прогресс тура
@@ -167,12 +174,14 @@ export class OnboardingTour {
167
174
  isDismissed: true,
168
175
  });
169
176
  }
177
+ this.logger.info('Тур закрыт', { reason });
170
178
  this.status = 'idle';
171
179
  this.exitController.reset();
172
180
  this.releaseStep();
173
181
  this.config.onClose?.(reason);
174
182
  };
175
183
  this.handleError = (error) => {
184
+ this.logger.error('Ошибка тура', error);
176
185
  this.errorService.captureException(error, {
177
186
  place: 'Onboarding',
178
187
  tourName: this.config.name,
@@ -185,17 +194,22 @@ export class OnboardingTour {
185
194
  this.resolver = new StepResolver(config);
186
195
  this.stepsController = new StepsController(config.steps, (stepName) => this.refreshStep(stepName));
187
196
  this.steps = this.stepsController.handles;
188
- this.exitController = new ExitController({
197
+ const exitParams = {
189
198
  close: () => this.exit('dismissed'),
190
199
  onChange: () => this.publishStep(),
191
- });
200
+ };
201
+ this.exitController = new ExitController(exitParams, this.logger);
192
202
  }
193
203
  /**
194
204
  * Разрешена ли навигация сейчас: тур показан на экране и владеет активным
195
205
  * шагом store
196
206
  */
197
207
  canNavigate() {
198
- return this.status === 'running' && this.activeStepStore.isActiveTour(this);
208
+ const isNavigable = this.status === 'running' && this.activeStepStore.isActiveTour(this);
209
+ if (!isNavigable) {
210
+ this.logger.debug('Навигация проигнорирована: тур неактивен');
211
+ }
212
+ return isNavigable;
199
213
  }
200
214
  /**
201
215
  * Переход к текущему шагу. Шаг с `waitForResume` ставит тур в ожидание
@@ -204,16 +218,23 @@ export class OnboardingTour {
204
218
  enterCurrentStep() {
205
219
  // Показывать нечего: у тура нет ни одного видимого (не-пропущенного) шага.
206
220
  if (this.navigator.total === 0) {
221
+ this.logger.warn('Нет ни одного видимого шага, тур не показан');
207
222
  this.status = 'idle';
208
223
  this.releaseStep();
209
224
  return;
210
225
  }
211
226
  const step = this.navigator.current;
212
227
  if (step.waitForResume) {
228
+ this.logger.debug('Ожидание resume', { step: step.name });
213
229
  this.status = 'waiting-resume';
214
230
  this.releaseStep();
215
231
  return;
216
232
  }
233
+ this.logger.debug('Показан шаг', {
234
+ step: step.name,
235
+ position: this.navigator.position,
236
+ total: this.navigator.total,
237
+ });
217
238
  this.status = 'running';
218
239
  this.publishStep();
219
240
  }
@@ -1,3 +1,4 @@
1
+ import { type Logger } from '../../../../services/Logger';
1
2
  import { type SaveStrategy, type TourProgress } from '../../types';
2
3
  /**
3
4
  * Обёртка над переданной SaveStrategy, изолирующая тур от исключений хранилища
@@ -9,8 +10,9 @@ import { type SaveStrategy, type TourProgress } from '../../types';
9
10
  */
10
11
  export declare class ProgressStorage {
11
12
  private readonly strategy;
13
+ private readonly logger;
12
14
  private saving;
13
- constructor(strategy: SaveStrategy);
15
+ constructor(strategy: SaveStrategy, logger: Logger);
14
16
  load(): Promise<TourProgress | null>;
15
17
  save(state: TourProgress): Promise<void>;
16
18
  /**
@@ -15,8 +15,9 @@ const CLEARED_STATE = {
15
15
  * `load()` ждёт текущее сохранение, поэтому читает уже применённое состояние.
16
16
  */
17
17
  export class ProgressStorage {
18
- constructor(strategy) {
18
+ constructor(strategy, logger) {
19
19
  this.strategy = strategy;
20
+ this.logger = logger;
20
21
  this.saving = Promise.resolve();
21
22
  }
22
23
  async load() {
@@ -25,7 +26,9 @@ export class ProgressStorage {
25
26
  try {
26
27
  return await this.strategy.load();
27
28
  }
28
- catch {
29
+ catch (error) {
30
+ // Ошибка хранилища не должна ронять тур, но остаётся видимой в логах.
31
+ this.logger.warn('Ошибка чтения прогресса из хранилища', error);
29
32
  return null;
30
33
  }
31
34
  }
@@ -49,8 +52,9 @@ export class ProgressStorage {
49
52
  try {
50
53
  await write();
51
54
  }
52
- catch {
53
- // Ошибка хранилища не должна ронять тур;
55
+ catch (error) {
56
+ // Ошибка хранилища не должна ронять тур, но остаётся видимой в логах.
57
+ this.logger.warn('Ошибка записи прогресса в хранилище', error);
54
58
  }
55
59
  });
56
60
  return this.saving;
@@ -0,0 +1,16 @@
1
+ import { type Logger } from '../../services/Logger';
2
+ /**
3
+ * Mock логгера для тестов
4
+ */
5
+ export declare class LoggerMock {
6
+ configure: Logger['configure'];
7
+ child: Logger['child'];
8
+ isLevelEnabled: Logger['isLevelEnabled'];
9
+ success: Logger['success'];
10
+ info: Logger['info'];
11
+ warn: Logger['warn'];
12
+ debug: Logger['debug'];
13
+ error: Logger['error'];
14
+ constructor();
15
+ }
16
+ export declare const createLoggerMock: () => Logger;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createLoggerMock = exports.LoggerMock = void 0;
4
+ const vitest_1 = require("vitest");
5
+ /**
6
+ * Mock логгера для тестов
7
+ */
8
+ class LoggerMock {
9
+ constructor() {
10
+ this.configure = vitest_1.vi.fn(() => vitest_1.vi.fn());
11
+ this.child = vitest_1.vi.fn();
12
+ this.isLevelEnabled = vitest_1.vi.fn(() => false);
13
+ this.success = vitest_1.vi.fn();
14
+ this.info = vitest_1.vi.fn();
15
+ this.warn = vitest_1.vi.fn();
16
+ this.debug = vitest_1.vi.fn();
17
+ this.error = vitest_1.vi.fn();
18
+ vitest_1.vi.mocked(this.child).mockReturnValue(this);
19
+ }
20
+ }
21
+ exports.LoggerMock = LoggerMock;
22
+ const createLoggerMock = () => new LoggerMock();
23
+ exports.createLoggerMock = createLoggerMock;
@@ -0,0 +1 @@
1
+ export * from './LoggerMock';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./LoggerMock"), exports);
@@ -1,4 +1,5 @@
1
1
  import { type ReactElement, type ReactNode } from 'react';
2
+ import { type LogThreshold } from '../../services/Logger';
2
3
  import { type LanguageMap } from '../DatePicker/types';
3
4
  import { type PlaceholderImageProps } from '../placeholders/Placeholder/Image';
4
5
  import { type CaptureException } from '../services/ErrorService';
@@ -138,7 +139,12 @@ export type ConfigProviderProps = Partial<ConfigContextProps> & {
138
139
  * Переданный в компонент флаг напрямую имеет приоритет выше
139
140
  */
140
141
  nextFeatureFlags?: NextFeatureFlagsContextProps;
142
+ /**
143
+ * Порог логов ui-kit в консоли. По умолчанию: prod → `error`, dev → `warn`,
144
+ * test → `silent`. `silent` — выключить полностью.
145
+ */
146
+ logLevel?: LogThreshold;
141
147
  };
142
148
  export declare const ConfigContext: import("react").Context<ConfigContextProps>;
143
- export declare const ConfigProvider: ({ children, language, datePickerLanguageMap, captureException: captureExceptionProp, emptySymbol, imagesMap, techSup, hidePersonalData, hidePersonalDataClassname, hidePersonalInputDataClassname, components: propsComponents, nextFeatureFlags, }: Partial<ConfigProviderProps>) => import("react/jsx-runtime").JSX.Element;
149
+ export declare const ConfigProvider: ({ children, language, datePickerLanguageMap, captureException: captureExceptionProp, emptySymbol, imagesMap, techSup, hidePersonalData, hidePersonalDataClassname, hidePersonalInputDataClassname, components: propsComponents, nextFeatureFlags, logLevel, }: Partial<ConfigProviderProps>) => import("react/jsx-runtime").JSX.Element;
144
150
  export {};
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConfigProvider = exports.ConfigContext = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
+ const Logger_1 = require("../../services/Logger");
6
7
  const constants_1 = require("../DatePicker/constants");
7
8
  const ErrorService_1 = require("../services/ErrorService");
8
9
  const NextFeatureFlagsContext_1 = require("./NextFeatureFlagsContext");
@@ -16,6 +17,10 @@ const techSupDefault = {
16
17
  phone: '',
17
18
  email: '',
18
19
  };
20
+ const defaultCaptureException = (error) => {
21
+ // biome-ignore lint/suspicious/noConsole: Ошибка для интеграторов
22
+ console.error(error);
23
+ };
19
24
  const defaultHidePersonalDataClassname = 'ym-hide-content sentry-mask';
20
25
  const defaultHidePersonalInputDataClassname = 'ym-disable-keys sentry-mask';
21
26
  exports.ConfigContext = (0, react_1.createContext)({
@@ -30,20 +35,21 @@ exports.ConfigContext = (0, react_1.createContext)({
30
35
  hidePersonalInputDataClassname: defaultHidePersonalInputDataClassname,
31
36
  hidePersonalData: true,
32
37
  });
33
- const ConfigProvider = ({ children, language = 'ru', datePickerLanguageMap = constants_1.russianMap, captureException: captureExceptionProp, emptySymbol = '—', imagesMap = imagesMapDefault, techSup = techSupDefault, hidePersonalData = true, hidePersonalDataClassname = defaultHidePersonalDataClassname, hidePersonalInputDataClassname = defaultHidePersonalInputDataClassname, components: propsComponents, nextFeatureFlags = {}, }) => {
34
- const captureException =
35
- // biome-ignore lint/suspicious/noConsole: Ошибка для интеграторов
36
- captureExceptionProp || ((error) => console.error(error));
37
- (0, react_1.useMemo)(() => {
38
+ const ConfigProvider = ({ children, language = 'ru', datePickerLanguageMap = constants_1.russianMap, captureException: captureExceptionProp, emptySymbol = '—', imagesMap = imagesMapDefault, techSup = techSupDefault, hidePersonalData = true, hidePersonalDataClassname = defaultHidePersonalDataClassname, hidePersonalInputDataClassname = defaultHidePersonalInputDataClassname, components: propsComponents, nextFeatureFlags = {}, logLevel, }) => {
39
+ const captureException = captureExceptionProp ?? defaultCaptureException;
40
+ (0, react_1.useEffect)(() => {
38
41
  ErrorService_1.ErrorService.getInstance().init(captureException);
39
42
  }, [captureException]);
40
43
  (0, react_1.useEffect)(() => {
41
- if (!captureException) {
44
+ return (0, Logger_1.configureLogger)({ level: logLevel });
45
+ }, [logLevel]);
46
+ (0, react_1.useEffect)(() => {
47
+ if (!captureExceptionProp) {
42
48
  // biome-ignore lint/suspicious/noConsole: Ошибка для интеграторов
43
49
  console.warn('ConfigProvider: Необходимо наличие captureException, связанного с сервисом мониторинга ошибок.\n' +
44
50
  'На данный момент все ошибки, отлавливаемые в ErrorBoundary будут выводиться только в консоль');
45
51
  }
46
- }, []);
52
+ }, [captureExceptionProp]);
47
53
  const components = {
48
54
  ...propsComponents,
49
55
  layout: {
@@ -5,7 +5,7 @@ export declare const useLogic: ({ profile, menuOrganization }: HeaderProps) => {
5
5
  isShowProfile: boolean;
6
6
  isMobile: boolean;
7
7
  isFocusedMode: boolean;
8
- mobileHeaderPriorityFeature: "menuOrg" | "profile";
8
+ mobileHeaderPriorityFeature: "profile" | "menuOrg";
9
9
  isLoading: boolean;
10
10
  collapsedIn: boolean;
11
11
  onToggleSidebar: (newValue?: boolean | undefined) => void;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createOnboarding = exports.Onboarding = void 0;
4
+ const Logger_1 = require("../../services/Logger");
4
5
  const ErrorService_1 = require("../services/ErrorService");
5
6
  const ActiveStepStore_1 = require("./core/ActiveStepStore");
6
7
  const OnboardingTour_1 = require("./core/OnboardingTour");
@@ -21,11 +22,20 @@ class Onboarding {
21
22
  * Создаёт тур.
22
23
  */
23
24
  this.createTour = (tourConfig) => {
25
+ const logger = (0, Logger_1.createLogger)('Onboarding', {
26
+ context: { tourName: tourConfig.name },
27
+ });
24
28
  // Fail-fast на некорректной конфигурации до сборки тура.
25
- (0, validation_1.validateTourConfig)(tourConfig);
29
+ try {
30
+ (0, validation_1.validateTourConfig)(tourConfig);
31
+ }
32
+ catch (error) {
33
+ logger.error('Некорректная конфигурация тура', error);
34
+ throw error;
35
+ }
26
36
  // Ключ хранилища уникален на тур, поэтому стратегия строится на createTour.
27
- const progressStorage = new ProgressStorage_1.ProgressStorage(this.saveStrategyFactory(tourConfig.name));
28
- return new OnboardingTour_1.OnboardingTour(tourConfig, progressStorage, this.activeStepStore, this.errorService);
37
+ const progressStorage = new ProgressStorage_1.ProgressStorage(this.saveStrategyFactory(tourConfig.name), logger);
38
+ return new OnboardingTour_1.OnboardingTour(tourConfig, progressStorage, this.activeStepStore, this.errorService, logger);
29
39
  };
30
40
  /**
31
41
  * Закрывает показанный сейчас тур.
@@ -1,3 +1,4 @@
1
+ import { type Logger } from '../../../../services/Logger';
1
2
  export type ExitControllerParams = {
2
3
  /**
3
4
  * Фактическое закрытие тура. Вызывается только после подтверждения в
@@ -20,8 +21,9 @@ export type ExitControllerParams = {
20
21
  */
21
22
  export declare class ExitController {
22
23
  private readonly params;
24
+ private readonly logger;
23
25
  private confirming;
24
- constructor(params: ExitControllerParams);
26
+ constructor(params: ExitControllerParams, logger: Logger);
25
27
  /**
26
28
  * Идёт ли запрос подтверждения выхода.
27
29
  */
@@ -9,8 +9,9 @@ exports.ExitController = void 0;
9
9
  * (ConfirmExit); закрытие происходит только по confirm.
10
10
  */
11
11
  class ExitController {
12
- constructor(params) {
12
+ constructor(params, logger) {
13
13
  this.params = params;
14
+ this.logger = logger;
14
15
  this.confirming = false;
15
16
  /**
16
17
  * Запрос на закрытие — показывает ConfirmExit. Повторные вызовы во время
@@ -20,6 +21,7 @@ class ExitController {
20
21
  if (this.confirming) {
21
22
  return;
22
23
  }
24
+ this.logger.debug('Запрошен выход из тура, показано подтверждение');
23
25
  this.confirming = true;
24
26
  this.params.onChange();
25
27
  };
@@ -40,6 +42,7 @@ class ExitController {
40
42
  if (!this.confirming) {
41
43
  return;
42
44
  }
45
+ this.logger.debug('Выход из тура отменён');
43
46
  this.confirming = false;
44
47
  this.params.onChange();
45
48
  };
@@ -1,3 +1,4 @@
1
+ import { type Logger } from '../../../../services/Logger';
1
2
  import { type ErrorService } from '../../../services/ErrorService';
2
3
  import { type OnboardingTourConfig, type Step, type StepHandles, type TourProgress } from '../../types';
3
4
  import { type ActiveStepStore } from '../ActiveStepStore';
@@ -13,6 +14,7 @@ export declare class OnboardingTour<TSteps extends readonly Step[] = readonly St
13
14
  private readonly progressStorage;
14
15
  private readonly activeStepStore;
15
16
  private readonly errorService;
17
+ private readonly logger;
16
18
  private readonly navigator;
17
19
  private readonly resolver;
18
20
  private readonly stepsController;
@@ -22,7 +24,7 @@ export declare class OnboardingTour<TSteps extends readonly Step[] = readonly St
22
24
  * Типобезопасный доступ к шагам по их именам.
23
25
  */
24
26
  readonly steps: StepHandles<TSteps>;
25
- constructor(config: OnboardingTourConfig<TSteps>, progressStorage: ProgressStorage, activeStepStore: ActiveStepStore, errorService: ErrorService);
27
+ constructor(config: OnboardingTourConfig<TSteps>, progressStorage: ProgressStorage, activeStepStore: ActiveStepStore, errorService: ErrorService, logger: Logger);
26
28
  /**
27
29
  * Запуск тура с первого шага.
28
30
  */
@@ -12,11 +12,12 @@ const StepsController_1 = require("../StepsController");
12
12
  * состояние активного шага в store своего экземпляра онбординга.
13
13
  */
14
14
  class OnboardingTour {
15
- constructor(config, progressStorage, activeStepStore, errorService) {
15
+ constructor(config, progressStorage, activeStepStore, errorService, logger) {
16
16
  this.config = config;
17
17
  this.progressStorage = progressStorage;
18
18
  this.activeStepStore = activeStepStore;
19
19
  this.errorService = errorService;
20
+ this.logger = logger;
20
21
  this.status = 'idle';
21
22
  /**
22
23
  * Запуск тура с первого шага.
@@ -24,8 +25,10 @@ class OnboardingTour {
24
25
  this.start = async () => {
25
26
  const persisted = await this.progressStorage.load();
26
27
  if (persisted?.isDismissed) {
28
+ this.logger.info('Тур пропущен: ранее закрыт пользователем');
27
29
  return;
28
30
  }
31
+ this.logger.info('Тур запущен');
29
32
  this.exitController.reset();
30
33
  this.navigator.reset();
31
34
  this.enterCurrentStep();
@@ -35,8 +38,10 @@ class OnboardingTour {
35
38
  */
36
39
  this.resume = () => {
37
40
  if (this.status !== 'waiting-resume') {
41
+ this.logger.debug('resume проигнорирован: тур не на паузе');
38
42
  return;
39
43
  }
44
+ this.logger.debug('Тур возобновлён после паузы');
40
45
  this.status = 'running';
41
46
  this.publishStep();
42
47
  };
@@ -45,6 +50,7 @@ class OnboardingTour {
45
50
  */
46
51
  this.restore = async () => {
47
52
  const persisted = await this.progressStorage.load();
53
+ this.logger.info('Тур восстановлен');
48
54
  this.exitController.reset();
49
55
  this.navigator.reset();
50
56
  // Пройденный тур переигрываем с начала; иначе продолжаем с сохранённого шага.
@@ -151,6 +157,7 @@ class OnboardingTour {
151
157
  */
152
158
  this.resetProgress = async () => {
153
159
  await this.progressStorage.reset();
160
+ this.logger.debug('Прогресс тура сброшен');
154
161
  };
155
162
  /**
156
163
  * Возвращает сохранённый прогресс тура
@@ -170,12 +177,14 @@ class OnboardingTour {
170
177
  isDismissed: true,
171
178
  });
172
179
  }
180
+ this.logger.info('Тур закрыт', { reason });
173
181
  this.status = 'idle';
174
182
  this.exitController.reset();
175
183
  this.releaseStep();
176
184
  this.config.onClose?.(reason);
177
185
  };
178
186
  this.handleError = (error) => {
187
+ this.logger.error('Ошибка тура', error);
179
188
  this.errorService.captureException(error, {
180
189
  place: 'Onboarding',
181
190
  tourName: this.config.name,
@@ -188,17 +197,22 @@ class OnboardingTour {
188
197
  this.resolver = new StepResolver_1.StepResolver(config);
189
198
  this.stepsController = new StepsController_1.StepsController(config.steps, (stepName) => this.refreshStep(stepName));
190
199
  this.steps = this.stepsController.handles;
191
- this.exitController = new ExitController_1.ExitController({
200
+ const exitParams = {
192
201
  close: () => this.exit('dismissed'),
193
202
  onChange: () => this.publishStep(),
194
- });
203
+ };
204
+ this.exitController = new ExitController_1.ExitController(exitParams, this.logger);
195
205
  }
196
206
  /**
197
207
  * Разрешена ли навигация сейчас: тур показан на экране и владеет активным
198
208
  * шагом store
199
209
  */
200
210
  canNavigate() {
201
- return this.status === 'running' && this.activeStepStore.isActiveTour(this);
211
+ const isNavigable = this.status === 'running' && this.activeStepStore.isActiveTour(this);
212
+ if (!isNavigable) {
213
+ this.logger.debug('Навигация проигнорирована: тур неактивен');
214
+ }
215
+ return isNavigable;
202
216
  }
203
217
  /**
204
218
  * Переход к текущему шагу. Шаг с `waitForResume` ставит тур в ожидание
@@ -207,16 +221,23 @@ class OnboardingTour {
207
221
  enterCurrentStep() {
208
222
  // Показывать нечего: у тура нет ни одного видимого (не-пропущенного) шага.
209
223
  if (this.navigator.total === 0) {
224
+ this.logger.warn('Нет ни одного видимого шага, тур не показан');
210
225
  this.status = 'idle';
211
226
  this.releaseStep();
212
227
  return;
213
228
  }
214
229
  const step = this.navigator.current;
215
230
  if (step.waitForResume) {
231
+ this.logger.debug('Ожидание resume', { step: step.name });
216
232
  this.status = 'waiting-resume';
217
233
  this.releaseStep();
218
234
  return;
219
235
  }
236
+ this.logger.debug('Показан шаг', {
237
+ step: step.name,
238
+ position: this.navigator.position,
239
+ total: this.navigator.total,
240
+ });
220
241
  this.status = 'running';
221
242
  this.publishStep();
222
243
  }
@@ -1,3 +1,4 @@
1
+ import { type Logger } from '../../../../services/Logger';
1
2
  import { type SaveStrategy, type TourProgress } from '../../types';
2
3
  /**
3
4
  * Обёртка над переданной SaveStrategy, изолирующая тур от исключений хранилища
@@ -9,8 +10,9 @@ import { type SaveStrategy, type TourProgress } from '../../types';
9
10
  */
10
11
  export declare class ProgressStorage {
11
12
  private readonly strategy;
13
+ private readonly logger;
12
14
  private saving;
13
- constructor(strategy: SaveStrategy);
15
+ constructor(strategy: SaveStrategy, logger: Logger);
14
16
  load(): Promise<TourProgress | null>;
15
17
  save(state: TourProgress): Promise<void>;
16
18
  /**
@@ -18,8 +18,9 @@ const CLEARED_STATE = {
18
18
  * `load()` ждёт текущее сохранение, поэтому читает уже применённое состояние.
19
19
  */
20
20
  class ProgressStorage {
21
- constructor(strategy) {
21
+ constructor(strategy, logger) {
22
22
  this.strategy = strategy;
23
+ this.logger = logger;
23
24
  this.saving = Promise.resolve();
24
25
  }
25
26
  async load() {
@@ -28,7 +29,9 @@ class ProgressStorage {
28
29
  try {
29
30
  return await this.strategy.load();
30
31
  }
31
- catch {
32
+ catch (error) {
33
+ // Ошибка хранилища не должна ронять тур, но остаётся видимой в логах.
34
+ this.logger.warn('Ошибка чтения прогресса из хранилища', error);
32
35
  return null;
33
36
  }
34
37
  }
@@ -52,8 +55,9 @@ class ProgressStorage {
52
55
  try {
53
56
  await write();
54
57
  }
55
- catch {
56
- // Ошибка хранилища не должна ронять тур;
58
+ catch (error) {
59
+ // Ошибка хранилища не должна ронять тур, но остаётся видимой в логах.
60
+ this.logger.warn('Ошибка записи прогресса в хранилище', error);
57
61
  }
58
62
  });
59
63
  return this.saving;
@@ -0,0 +1,45 @@
1
+ export type LogLevel = 'error' | 'warn' | 'info' | 'debug';
2
+ export type LogThreshold = LogLevel | 'silent';
3
+ export type LogContext = Readonly<Record<string, unknown>>;
4
+ export type LoggerOptions = {
5
+ context?: LogContext;
6
+ };
7
+ export type CreateLoggerOptions = LoggerOptions & {
8
+ name: string;
9
+ level?: LogThreshold;
10
+ };
11
+ export type ConfigureOptions = {
12
+ level?: LogThreshold;
13
+ };
14
+ /**
15
+ * Логгер.
16
+ * Формат логов: `{пакет}: {иконка} [{scope}]: {сообщение}`.
17
+ * */
18
+ export declare class Logger {
19
+ private readonly name;
20
+ private readonly scope;
21
+ private readonly context;
22
+ private readonly parent;
23
+ private level?;
24
+ private readonly console;
25
+ private constructor();
26
+ static create: ({ name, context, level, }: CreateLoggerOptions) => Logger;
27
+ /**
28
+ * Меняет уровень пакета.
29
+ * Без `level` — сброс к env-дефолту.
30
+ * Возвращает функцию отмены — вернёт прежний уровень.
31
+ * */
32
+ configure: (options?: ConfigureOptions) => () => void;
33
+ /** Дочерний логгер: добавляет `scope`, наследует уровень корня. */
34
+ child: (scope: string, options?: LoggerOptions) => Logger;
35
+ /** Дойдёт ли лог такого уровня до консоли. */
36
+ isLevelEnabled: (level: LogLevel) => boolean;
37
+ success: (message: string, ...data: unknown[]) => void;
38
+ info: (message: string, ...data: unknown[]) => void;
39
+ warn: (message: string, ...data: unknown[]) => void;
40
+ debug: (message: string, ...data: unknown[]) => void;
41
+ error: (message: string, ...data: unknown[]) => void;
42
+ /** Свой уровень → уровень родителя по цепочке → env-дефолт. */
43
+ private effectiveLevel;
44
+ private print;
45
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ // biome-ignore-all lint/suspicious/noConsole: Logger — единственная точка вывода в консоль
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.Logger = void 0;
5
+ /** Приоритет уровней. Лог виден, если его приоритет ≤ порога. */
6
+ const LOG_LEVEL_PRIORITY = {
7
+ silent: 0,
8
+ error: 1,
9
+ warn: 2,
10
+ info: 3,
11
+ debug: 4,
12
+ };
13
+ /** Порог по умолчанию для каждого окружения. */
14
+ const DEFAULT_LOG_LEVEL_BY_ENV = {
15
+ production: 'error',
16
+ test: 'silent',
17
+ development: 'warn',
18
+ };
19
+ /** Порог для текущего окружения. */
20
+ const getEnvLevel = () => DEFAULT_LOG_LEVEL_BY_ENV[process.env.NODE_ENV ?? 'development'] ?? 'warn';
21
+ /**
22
+ * Логгер.
23
+ * Формат логов: `{пакет}: {иконка} [{scope}]: {сообщение}`.
24
+ * */
25
+ class Logger {
26
+ constructor(name, scope, context, parent, level, console = globalThis.console) {
27
+ this.name = name;
28
+ this.scope = scope;
29
+ this.context = context;
30
+ this.parent = parent;
31
+ this.level = level;
32
+ this.console = console;
33
+ /**
34
+ * Меняет уровень пакета.
35
+ * Без `level` — сброс к env-дефолту.
36
+ * Возвращает функцию отмены — вернёт прежний уровень.
37
+ * */
38
+ this.configure = (options = {}) => {
39
+ const previousLevel = this.level;
40
+ this.level = options.level;
41
+ return () => {
42
+ this.level = previousLevel;
43
+ };
44
+ };
45
+ /** Дочерний логгер: добавляет `scope`, наследует уровень корня. */
46
+ this.child = (scope, options) => {
47
+ return new Logger(this.name,
48
+ // склеиваем scope: `Onboarding` → `Onboarding Tour`.
49
+ this.scope ? `${this.scope} ${scope}` : scope, { ...this.context, ...options?.context }, this, undefined, this.console);
50
+ };
51
+ /** Дойдёт ли лог такого уровня до консоли. */
52
+ this.isLevelEnabled = (level) => LOG_LEVEL_PRIORITY[level] <= LOG_LEVEL_PRIORITY[this.effectiveLevel()];
53
+ this.success = (message, ...data) => this.print('info', '✅', this.console.log, message, data);
54
+ this.info = (message, ...data) => this.print('info', 'ℹ️', this.console.log, message, data);
55
+ this.warn = (message, ...data) => this.print('warn', '⚠️', this.console.warn, message, data);
56
+ this.debug = (message, ...data) => this.print('debug', '🐞', this.console.log, message, data);
57
+ this.error = (message, ...data) => this.print('error', '❌', this.console.error, message, data);
58
+ /** Свой уровень → уровень родителя по цепочке → env-дефолт. */
59
+ this.effectiveLevel = () => this.level ?? this.parent?.effectiveLevel() ?? getEnvLevel();
60
+ this.print = (level, icon, sink, message, data) => {
61
+ if (!this.isLevelEnabled(level)) {
62
+ return;
63
+ }
64
+ const prefix = this.name ? `${this.name}: ` : '';
65
+ const scope = this.scope ? ` [${this.scope}]:` : '';
66
+ const withContext = Object.keys(this.context).length === 0 ? data : [...data, this.context];
67
+ sink(`${prefix}${icon}${scope} ${message}`, ...withContext);
68
+ };
69
+ }
70
+ }
71
+ exports.Logger = Logger;
72
+ Logger.create = ({ name, context = {}, level, }) => new Logger(name, undefined, { ...context }, null, level);
@@ -0,0 +1,10 @@
1
+ import { Logger, type ConfigureOptions, type CreateLoggerOptions, type LogContext, type LoggerOptions, type LogLevel, type LogThreshold } from './Logger';
2
+ export { Logger, type ConfigureOptions, type CreateLoggerOptions, type LogContext, type LoggerOptions, type LogLevel, type LogThreshold, };
3
+ /**
4
+ * Фабрика Logger для `@astral/ui`: сервисы и компоненты получают инстанс через DI.
5
+ */
6
+ export declare const createLogger: (scope: string, options?: LoggerOptions) => Logger;
7
+ /**
8
+ * (Пере)-Настройка логгера пакета `@astral/ui` — вызывает `ConfigProvider`.
9
+ */
10
+ export declare const configureLogger: (options?: ConfigureOptions) => () => void;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.configureLogger = exports.createLogger = exports.Logger = void 0;
4
+ const Logger_1 = require("./Logger");
5
+ Object.defineProperty(exports, "Logger", { enumerable: true, get: function () { return Logger_1.Logger; } });
6
+ // Корневой логгер пакета @astral/ui.
7
+ const uiLogger = Logger_1.Logger.create({ name: '@astral/ui' });
8
+ /**
9
+ * Фабрика Logger для `@astral/ui`: сервисы и компоненты получают инстанс через DI.
10
+ */
11
+ const createLogger = (scope, options) => uiLogger.child(scope, options);
12
+ exports.createLogger = createLogger;
13
+ /**
14
+ * (Пере)-Настройка логгера пакета `@astral/ui` — вызывает `ConfigProvider`.
15
+ */
16
+ const configureLogger = (options) => uiLogger.configure(options);
17
+ exports.configureLogger = configureLogger;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astral/ui",
3
- "version": "4.83.0",
3
+ "version": "4.84.0",
4
4
  "browser": "./index.js",
5
5
  "main": "./node/index.js",
6
6
  "dependencies": {
@@ -0,0 +1,45 @@
1
+ export type LogLevel = 'error' | 'warn' | 'info' | 'debug';
2
+ export type LogThreshold = LogLevel | 'silent';
3
+ export type LogContext = Readonly<Record<string, unknown>>;
4
+ export type LoggerOptions = {
5
+ context?: LogContext;
6
+ };
7
+ export type CreateLoggerOptions = LoggerOptions & {
8
+ name: string;
9
+ level?: LogThreshold;
10
+ };
11
+ export type ConfigureOptions = {
12
+ level?: LogThreshold;
13
+ };
14
+ /**
15
+ * Логгер.
16
+ * Формат логов: `{пакет}: {иконка} [{scope}]: {сообщение}`.
17
+ * */
18
+ export declare class Logger {
19
+ private readonly name;
20
+ private readonly scope;
21
+ private readonly context;
22
+ private readonly parent;
23
+ private level?;
24
+ private readonly console;
25
+ private constructor();
26
+ static create: ({ name, context, level, }: CreateLoggerOptions) => Logger;
27
+ /**
28
+ * Меняет уровень пакета.
29
+ * Без `level` — сброс к env-дефолту.
30
+ * Возвращает функцию отмены — вернёт прежний уровень.
31
+ * */
32
+ configure: (options?: ConfigureOptions) => () => void;
33
+ /** Дочерний логгер: добавляет `scope`, наследует уровень корня. */
34
+ child: (scope: string, options?: LoggerOptions) => Logger;
35
+ /** Дойдёт ли лог такого уровня до консоли. */
36
+ isLevelEnabled: (level: LogLevel) => boolean;
37
+ success: (message: string, ...data: unknown[]) => void;
38
+ info: (message: string, ...data: unknown[]) => void;
39
+ warn: (message: string, ...data: unknown[]) => void;
40
+ debug: (message: string, ...data: unknown[]) => void;
41
+ error: (message: string, ...data: unknown[]) => void;
42
+ /** Свой уровень → уровень родителя по цепочке → env-дефолт. */
43
+ private effectiveLevel;
44
+ private print;
45
+ }
@@ -0,0 +1,68 @@
1
+ // biome-ignore-all lint/suspicious/noConsole: Logger — единственная точка вывода в консоль
2
+ /** Приоритет уровней. Лог виден, если его приоритет ≤ порога. */
3
+ const LOG_LEVEL_PRIORITY = {
4
+ silent: 0,
5
+ error: 1,
6
+ warn: 2,
7
+ info: 3,
8
+ debug: 4,
9
+ };
10
+ /** Порог по умолчанию для каждого окружения. */
11
+ const DEFAULT_LOG_LEVEL_BY_ENV = {
12
+ production: 'error',
13
+ test: 'silent',
14
+ development: 'warn',
15
+ };
16
+ /** Порог для текущего окружения. */
17
+ const getEnvLevel = () => DEFAULT_LOG_LEVEL_BY_ENV[process.env.NODE_ENV ?? 'development'] ?? 'warn';
18
+ /**
19
+ * Логгер.
20
+ * Формат логов: `{пакет}: {иконка} [{scope}]: {сообщение}`.
21
+ * */
22
+ export class Logger {
23
+ constructor(name, scope, context, parent, level, console = globalThis.console) {
24
+ this.name = name;
25
+ this.scope = scope;
26
+ this.context = context;
27
+ this.parent = parent;
28
+ this.level = level;
29
+ this.console = console;
30
+ /**
31
+ * Меняет уровень пакета.
32
+ * Без `level` — сброс к env-дефолту.
33
+ * Возвращает функцию отмены — вернёт прежний уровень.
34
+ * */
35
+ this.configure = (options = {}) => {
36
+ const previousLevel = this.level;
37
+ this.level = options.level;
38
+ return () => {
39
+ this.level = previousLevel;
40
+ };
41
+ };
42
+ /** Дочерний логгер: добавляет `scope`, наследует уровень корня. */
43
+ this.child = (scope, options) => {
44
+ return new Logger(this.name,
45
+ // склеиваем scope: `Onboarding` → `Onboarding Tour`.
46
+ this.scope ? `${this.scope} ${scope}` : scope, { ...this.context, ...options?.context }, this, undefined, this.console);
47
+ };
48
+ /** Дойдёт ли лог такого уровня до консоли. */
49
+ this.isLevelEnabled = (level) => LOG_LEVEL_PRIORITY[level] <= LOG_LEVEL_PRIORITY[this.effectiveLevel()];
50
+ this.success = (message, ...data) => this.print('info', '✅', this.console.log, message, data);
51
+ this.info = (message, ...data) => this.print('info', 'ℹ️', this.console.log, message, data);
52
+ this.warn = (message, ...data) => this.print('warn', '⚠️', this.console.warn, message, data);
53
+ this.debug = (message, ...data) => this.print('debug', '🐞', this.console.log, message, data);
54
+ this.error = (message, ...data) => this.print('error', '❌', this.console.error, message, data);
55
+ /** Свой уровень → уровень родителя по цепочке → env-дефолт. */
56
+ this.effectiveLevel = () => this.level ?? this.parent?.effectiveLevel() ?? getEnvLevel();
57
+ this.print = (level, icon, sink, message, data) => {
58
+ if (!this.isLevelEnabled(level)) {
59
+ return;
60
+ }
61
+ const prefix = this.name ? `${this.name}: ` : '';
62
+ const scope = this.scope ? ` [${this.scope}]:` : '';
63
+ const withContext = Object.keys(this.context).length === 0 ? data : [...data, this.context];
64
+ sink(`${prefix}${icon}${scope} ${message}`, ...withContext);
65
+ };
66
+ }
67
+ }
68
+ Logger.create = ({ name, context = {}, level, }) => new Logger(name, undefined, { ...context }, null, level);
@@ -0,0 +1,10 @@
1
+ import { Logger, type ConfigureOptions, type CreateLoggerOptions, type LogContext, type LoggerOptions, type LogLevel, type LogThreshold } from './Logger';
2
+ export { Logger, type ConfigureOptions, type CreateLoggerOptions, type LogContext, type LoggerOptions, type LogLevel, type LogThreshold, };
3
+ /**
4
+ * Фабрика Logger для `@astral/ui`: сервисы и компоненты получают инстанс через DI.
5
+ */
6
+ export declare const createLogger: (scope: string, options?: LoggerOptions) => Logger;
7
+ /**
8
+ * (Пере)-Настройка логгера пакета `@astral/ui` — вызывает `ConfigProvider`.
9
+ */
10
+ export declare const configureLogger: (options?: ConfigureOptions) => () => void;
@@ -0,0 +1,12 @@
1
+ import { Logger, } from './Logger';
2
+ export { Logger, };
3
+ // Корневой логгер пакета @astral/ui.
4
+ const uiLogger = Logger.create({ name: '@astral/ui' });
5
+ /**
6
+ * Фабрика Logger для `@astral/ui`: сервисы и компоненты получают инстанс через DI.
7
+ */
8
+ export const createLogger = (scope, options) => uiLogger.child(scope, options);
9
+ /**
10
+ * (Пере)-Настройка логгера пакета `@astral/ui` — вызывает `ConfigProvider`.
11
+ */
12
+ export const configureLogger = (options) => uiLogger.configure(options);