Skip to content

Commit

Permalink
Release v5.17.0
Browse files Browse the repository at this point in the history
Co-authored-by: o.drapeza <[email protected]>
GitOrigin-RevId: c2599edbcdd7473efcf7e47349f8e3ea56df34af
  • Loading branch information
tramvaijsorg and o.drapeza committed Jan 14, 2025
1 parent fe2670b commit 86e4f02
Show file tree
Hide file tree
Showing 21 changed files with 1,802 additions and 24 deletions.
65 changes: 65 additions & 0 deletions docs/03-features/06-app-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,68 @@ _For what_: To update meta information on the current page
When `spaMode` is `after`, page actions will be executed at this stage.

_For what_: This is more of an internal stage and should not be used in ordinary cases.

## CommandLineRunner Hooks

CommandLineRunner provides a set of hooks to monitor or modify execution of commands:
- `CommandLineRunner.runLineHook` - async hook for line execution, for example when `CommandLineRunner.run('server', 'customer')` or `CommandLineRunner.run('client', 'spa')` is called.
- `CommandLineRunner.runCommandHook` - nested async hook for every command in the line, for example when `customer` line executed, `runCommandHook` will be called **in sequence** for every line command - `customerStart`, `resolveUserDeps`, `resolvePageDeps`, `generatePage` and `clear`.
- `CommandLineRunner.runCommandFnHook` - nested async hook for all command functions, resolved from DI by specific `commandLineListTokens` token, will be called **in parallel** for all functions in this command.

Hooks relations are as follows:

![CommandLineRunner hooks](/img/commands/hooks.drawio.svg)

### How to use hooks

First, you need to create a CommandLineRunner Plugin and provide it with `COMMAND_LINE_RUNNER_PLUGIN` token:

```ts
import { COMMAND_LINE_RUNNER_PLUGIN } from '@tramvai/tokens-common';

const provider = provide({
provide: COMMAND_LINE_RUNNER_PLUGIN,
useFactory: () => {
return {
apply(commandLineRunner) {},
};
},
});
```

Method `apply` will be called right in the end of CommandLineRunner initialization.

Then, you can use hooks, for example to monitor line and specific commands execution:

```ts
const plugin = {
apply(commandLineRunner) {
commandLineRunner.runLineHook.wrap(async (_, payload, next) => {
const { line } = payload;
let start = Date.now();

console.log(`line "${line}" is started`);

const result = await next(payload);

console.log(`Line "${line}" is finished, duration:`, Date.now() - start);

return result;
});

commandLineRunner.runCommandHook.wrap(async (_, payload, next) => {
const { command } = payload;
const commandName = command.toString();
let start = Date.now();

console.log(`Command "${commandName}" is started`);

const result = await next(payload);

console.log(`Command "${commandName}" is finished, duration:`, Date.now() - start);

return result;
});
},
};
```
2 changes: 1 addition & 1 deletion packages-versions.json
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@

{"@tramvai/cli":"5.16.2","@tramvai/swc-integration":"5.16.2","@tinkoff/browser-timings":"0.13.2","@tinkoff/browserslist-config":"0.5.3","@tinkoff/browser-cookies":"5.0.2","@tinkoff/dippy":"0.11.3","@tinkoff/env-validators":"0.4.2","@tinkoff/error-handlers":"0.8.2","@tinkoff/errors":"0.6.2","@tinkoff/eslint-plugin-tramvai":"0.9.2","@tinkoff/hook-runner":"0.7.2","@tramvai/http-client":"0.5.2","@tinkoff/is-modern-lib":"5.0.2","@tramvai/react-lazy-hydration-render":"0.5.2","@tinkoff/logger":"0.10.505","@tinkoff/pack-polyfills":"0.7.3","@tinkoff/measure-express-requests":"5.0.2","@tinkoff/measure-fastify-requests":"0.4.2","@tinkoff/meta-tags-generate":"0.8.3","@tinkoff/metrics-noop":"5.0.2","@tinkoff/minicss-class-generator":"0.5.2","@tinkoff/mocker":"5.0.3","@tinkoff/module-loader-client":"0.7.3","@tinkoff/module-loader-server":"0.8.4","@tinkoff/monkeypatch":"5.0.2","@tinkoff/package-manager-wrapper":"0.4.2","@tinkoff/htmlpagebuilder":"0.8.2","prettier-config-tinkoff":"0.5.2","@tinkoff/pubsub":"0.8.2","@tinkoff/react-hooks":"0.4.2","@tinkoff/router":"0.5.54","@tramvai/safe-strings":"0.8.4","@tinkoff/terminus":"0.4.2","@tinkoff/layout-factory":"0.6.2","@tramvai/tinkoff-request-http-client-adapter":"0.12.54","@tinkoff/url":"0.11.2","@tinkoff/user-agent":"0.7.54","@tinkoff/webpack-dedupe-plugin":"4.0.2","@tramvai/module-autoscroll":"5.16.2","@tramvai/module-cache-warmup":"5.16.2","@tramvai/module-child-app":"5.16.2","@tramvai/module-client-hints":"5.16.2","@tramvai/module-common":"5.16.2","@tramvai/module-cookie":"5.16.2","@tramvai/module-deps-graph":"5.16.2","@tramvai/module-dns-cache":"5.16.2","@tramvai/module-environment":"5.16.2","@tramvai/module-error-interceptor":"5.16.2","@tramvai/module-http-client":"5.16.2","@tramvai/module-http-proxy-agent":"5.16.2","@tramvai/module-log":"5.16.2","@tramvai/module-metrics":"5.16.2","@tramvai/module-micro-sentry":"5.16.2","@tramvai/module-mocker":"5.16.2","@tramvai/module-opentelemetry":"5.16.2","@tramvai/module-page-render-mode":"5.16.2","@tramvai/module-progressive-web-app":"5.16.2","@tramvai/module-react-query":"5.16.2","@tramvai/module-render":"5.16.2","@tramvai/module-request-limiter":"5.16.2","@tramvai/module-router":"5.16.2","@tramvai/module-sentry":"5.16.2","@tramvai/module-seo":"5.16.2","@tramvai/module-server":"5.16.2","@tramvai/tokens-child-app":"5.16.2","@tramvai/tokens-common":"5.16.2","@tramvai/tokens-cookie":"5.16.2","@tramvai/tokens-core":"5.16.2","@tramvai/tokens-core-private":"5.16.2","@tramvai/tokens-http-client":"5.16.2","@tramvai/tokens-metrics":"5.16.2","@tramvai/tokens-react-query":"5.16.2","@tramvai/tokens-render":"5.16.2","@tramvai/tokens-router":"5.16.2","@tramvai/tokens-server":"5.16.2","@tramvai/tokens-server-private":"5.16.2","@tramvai/child-app-core":"5.16.2","@tramvai/core":"5.16.2","@tramvai/experiments":"5.16.2","@tramvai/papi":"5.16.2","@tramvai/pwa-recipes":"5.16.2","@tramvai/react":"5.16.2","@tramvai/react-query":"5.16.2","@tramvai/state":"5.16.2","@tramvai/storybook-addon":"5.16.2","@tramvai/types-actions-state-context":"5.16.2","@tramvai/test-child-app":"5.16.2","@tramvai/test-helpers":"5.16.2","@tramvai/test-integration":"5.16.2","@tramvai/test-integration-jest":"5.16.2","@tramvai/test-jsdom":"5.16.2","@tramvai/test-mocks":"5.16.2","@tramvai/test-pw":"5.16.2","@tramvai/test-puppeteer":"5.16.2","@tramvai/test-react":"5.16.2","@tramvai/test-unit":"5.16.2","@tramvai/test-unit-jest":"5.16.2","@tramvai/build":"6.1.1","@tramvai/tools-check-versions":"0.7.5","@tramvai/create":"5.16.2","@tramvai/tools-generate-schema":"0.4.2","@tramvai/tools-migrate":"0.9.5","@tinkoff-monorepo/depscheck":"3.101.7","@tinkoff-monorepo/fix-ts-references":"3.101.7","@tinkoff-monorepo/pkgs-collector":"3.101.7","@tinkoff-monorepo/pkgs-collector-dir":"3.101.7","@tinkoff-monorepo/pkgs-collector-workspaces":"3.101.7"}
{"@tramvai/cli":"5.17.0","@tramvai/swc-integration":"5.17.0","@tinkoff/browser-timings":"0.13.2","@tinkoff/browserslist-config":"0.5.3","@tinkoff/browser-cookies":"5.0.2","@tinkoff/dippy":"0.11.3","@tinkoff/env-validators":"0.4.2","@tinkoff/error-handlers":"0.8.2","@tinkoff/errors":"0.6.2","@tinkoff/eslint-plugin-tramvai":"0.9.2","@tinkoff/hook-runner":"0.7.3","@tramvai/http-client":"0.5.2","@tinkoff/is-modern-lib":"5.0.2","@tramvai/react-lazy-hydration-render":"0.5.2","@tinkoff/logger":"0.10.505","@tinkoff/pack-polyfills":"0.7.3","@tinkoff/measure-express-requests":"5.0.2","@tinkoff/measure-fastify-requests":"0.4.2","@tinkoff/meta-tags-generate":"0.8.3","@tinkoff/metrics-noop":"5.0.2","@tinkoff/minicss-class-generator":"0.5.2","@tinkoff/mocker":"5.0.3","@tinkoff/module-loader-client":"0.7.3","@tinkoff/module-loader-server":"0.8.4","@tinkoff/monkeypatch":"5.0.2","@tinkoff/package-manager-wrapper":"0.4.2","@tinkoff/htmlpagebuilder":"0.8.2","prettier-config-tinkoff":"0.5.2","@tinkoff/pubsub":"0.8.2","@tinkoff/react-hooks":"0.4.2","@tinkoff/router":"0.5.55","@tramvai/safe-strings":"0.8.4","@tinkoff/terminus":"0.4.2","@tinkoff/layout-factory":"0.6.2","@tramvai/tinkoff-request-http-client-adapter":"0.12.55","@tinkoff/url":"0.11.2","@tinkoff/user-agent":"0.7.55","@tinkoff/webpack-dedupe-plugin":"4.0.2","@tramvai/module-autoscroll":"5.17.0","@tramvai/module-cache-warmup":"5.17.0","@tramvai/module-child-app":"5.17.0","@tramvai/module-client-hints":"5.17.0","@tramvai/module-common":"5.17.0","@tramvai/module-cookie":"5.17.0","@tramvai/module-deps-graph":"5.17.0","@tramvai/module-dns-cache":"5.17.0","@tramvai/module-environment":"5.17.0","@tramvai/module-error-interceptor":"5.17.0","@tramvai/module-http-client":"5.17.0","@tramvai/module-http-proxy-agent":"5.17.0","@tramvai/module-log":"5.17.0","@tramvai/module-metrics":"5.17.0","@tramvai/module-micro-sentry":"5.17.0","@tramvai/module-mocker":"5.17.0","@tramvai/module-opentelemetry":"5.17.0","@tramvai/module-page-render-mode":"5.17.0","@tramvai/module-progressive-web-app":"5.17.0","@tramvai/module-react-query":"5.17.0","@tramvai/module-render":"5.17.0","@tramvai/module-request-limiter":"5.17.0","@tramvai/module-router":"5.17.0","@tramvai/module-sentry":"5.17.0","@tramvai/module-seo":"5.17.0","@tramvai/module-server":"5.17.0","@tramvai/tokens-child-app":"5.17.0","@tramvai/tokens-common":"5.17.0","@tramvai/tokens-cookie":"5.17.0","@tramvai/tokens-core":"5.17.0","@tramvai/tokens-core-private":"5.17.0","@tramvai/tokens-http-client":"5.17.0","@tramvai/tokens-metrics":"5.17.0","@tramvai/tokens-react-query":"5.17.0","@tramvai/tokens-render":"5.17.0","@tramvai/tokens-router":"5.17.0","@tramvai/tokens-server":"5.17.0","@tramvai/tokens-server-private":"5.17.0","@tramvai/child-app-core":"5.17.0","@tramvai/core":"5.17.0","@tramvai/experiments":"5.17.0","@tramvai/papi":"5.17.0","@tramvai/pwa-recipes":"5.17.0","@tramvai/react":"5.17.0","@tramvai/react-query":"5.17.0","@tramvai/state":"5.17.0","@tramvai/storybook-addon":"5.17.0","@tramvai/types-actions-state-context":"5.17.0","@tramvai/test-child-app":"5.17.0","@tramvai/test-helpers":"5.17.0","@tramvai/test-integration":"5.17.0","@tramvai/test-integration-jest":"5.17.0","@tramvai/test-jsdom":"5.17.0","@tramvai/test-mocks":"5.17.0","@tramvai/test-pw":"5.17.0","@tramvai/test-puppeteer":"5.17.0","@tramvai/test-react":"5.17.0","@tramvai/test-unit":"5.17.0","@tramvai/test-unit-jest":"5.17.0","@tramvai/build":"6.1.1","@tramvai/tools-check-versions":"0.7.5","@tramvai/create":"5.17.0","@tramvai/tools-generate-schema":"0.4.2","@tramvai/tools-migrate":"0.9.5","@tinkoff-monorepo/depscheck":"3.101.7","@tinkoff-monorepo/fix-ts-references":"3.101.7","@tinkoff-monorepo/pkgs-collector":"3.101.7","@tinkoff-monorepo/pkgs-collector-dir":"3.101.7","@tinkoff-monorepo/pkgs-collector-workspaces":"3.101.7"}
6 changes: 6 additions & 0 deletions packages/libs/hooks/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
export { Hooks, Hook } from './hooks';
export {
TapableHooks,
SyncTapableHookInstance,
AsyncTapableHookInstance,
AsyncParallelTapableHookInstance,
} from './tapable';
282 changes: 282 additions & 0 deletions packages/libs/hooks/src/tapable.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
import { TapableHooks } from './tapable';

describe('libs/hooks/tapable', () => {
it('sync hook and plugin', () => {
const factory = new TapableHooks();

const hook = factory.createSync<number, number>('test');

hook.tap('test', (context, payload, result) => payload + 1);

const result = hook.call(1);

expect(result).toBe(2);
});

it('plugin untap callback', () => {
const factory = new TapableHooks();

const hook = factory.createSync<number, number>('test');

const untap = hook.tap('test', (context, payload, result) => payload + 1);

let result = hook.call(1);

expect(result).toBe(2);

untap();

result = hook.call(1);

expect(result).toBe(undefined);
});

it('async hook and sync plugin', async () => {
const factory = new TapableHooks();
const hook = factory.createAsync<number, number>('test');

hook.tap('test', (context, payload, result) => payload + 1);

const result = await hook.callPromise(1);

expect(result).toBe(2);
});

it('async hook and promise plugin', async () => {
const factory = new TapableHooks();
const hook = factory.createAsync<number, number>('test');

hook.tapPromise('test', async (context, payload, result) => payload + 1);

const result = await hook.callPromise(1);

expect(result).toBe(2);
});

it('async hook and async plugin', async () => {
const factory = new TapableHooks();
const hook = factory.createAsync<number, number>('test');

hook.tapAsync('test', (context, payload, result, done) => {
done(undefined, payload + 1);
});

const result = await hook.callPromise(1);

expect(result).toBe(2);
});

it('async hook and mix plugins payload', async () => {
const factory = new TapableHooks();
const hook = factory.createAsync<number, number>('test');

hook.tap('test-1', (context, payload, result) => payload + 1);
hook.tapPromise('test-2', async (context, payload, result) => result + 1);
hook.tapAsync('test-3', (context, payload, result, done) => {
done(undefined, result + 1);
});

const result = await hook.callPromise(1);

expect(result).toBe(4);
});

it('async hook and mix plugins order', async () => {
const factory = new TapableHooks();
const hook = factory.createAsync<string, string>('test');

hook.tap('test-1', (context, payload, result) => `${payload}2`);
hook.tapPromise('test-2', async (context, payload, result) => `${result}3`);
hook.tapAsync('test-3', (context, payload, result, done) => {
done(undefined, `${result}4`);
});

const result = await hook.callPromise('1');

expect(result).toBe('1234');
});

it('parallel hook and mix plugins', async () => {
const factory = new TapableHooks();
const hook = factory.createAsyncParallel<string[]>('test');

hook.tapPromise('test-1', async (context, payload) => {
payload.push('p1');
});
hook.tapAsync('test-2', (context, payload, result, done) => {
payload.push('p2');
done(undefined);
});
hook.tapPromise('test-3', async (context, payload) => {
payload.push('p3');
});
hook.tapAsync('test-4', (context, payload, result, done) => {
payload.push('p4');
done(undefined);
});
hook.tap('test-5', (context, payload) => {
payload.push('p5');
});

const list: string[] = [];

await hook.callPromise(list);

expect(list).toEqual(['p1', 'p2', 'p3', 'p4', 'p5']);
});

it('wrap sync hook and sync plugin', () => {
const factory = new TapableHooks();
const hook = factory.createSync<number, number>('test');
let pre: number = 0;
let post: number = 0;

hook.tap('test', (context, payload, result) => payload + 1);

hook.wrap((context, payload, next) => {
pre = payload;
const r = next(payload);
post = r;
return r;
});

const result = hook.call(1);

expect(result).toBe(2);
expect(pre).toBe(1);
expect(post).toBe(2);
});

it('multiple wrap order sync hook', () => {
const factory = new TapableHooks();
const hook = factory.createSync<string, string>('test');

hook.tap('test', (context, payload, result) => `${payload}3`);

hook.wrap((context, payload, next) => {
return `2${next(`-w1${payload}w1-`)}4`;
});

hook.wrap((context, payload, next) => {
return `1${next(`-w2${payload}w2-`)}5`;
});

const result = hook.call('-payload-');

expect(result).toBe('12-w1-w2-payload-w2-w1-345');
});

it('wrap async hook and promise plugin', async () => {
const factory = new TapableHooks();
const hook = factory.createAsync<number, number>('test');
let pre: number = 0;
let post: number = 0;

hook.tap('test', (context, payload) => payload + 1);

hook.wrap(async (context, payload, next) => {
pre = payload;
const r = await next(payload);
post = r;
return r;
});

const result = await hook.callPromise(1);

expect(result).toBe(2);
expect(pre).toBe(1);
expect(post).toBe(2);
});

it('wrap parallel hook and mix plugin', async () => {
const factory = new TapableHooks();
const hook = factory.createAsyncParallel<number[]>('test');
let pre: string = '';
let post: string = '';

hook.tap('test-1', (context, payload, result) => {
payload.push(1);
});
hook.tapPromise('test-2', async (context, payload, result) => {
payload.push(2);
});
hook.tapAsync('test-3', (context, payload, result, done) => {
payload.push(3);
done(undefined);
});

hook.wrap(async (context, payload, next) => {
pre = payload.toString();
await next(payload);
post = payload.toString();
});

const list: number[] = [];

await hook.callPromise(list);

expect(list).toEqual([1, 2, 3]);
expect(pre).toBe('');
expect(post).toBe('1,2,3');
});

it('multiple wrap order async hook', async () => {
const factory = new TapableHooks();
const hook = factory.createAsync<string, string>('test');

hook.tapPromise('test', async (context, payload, result) => {
return `${payload}3`;
});

hook.wrap(async (context, payload, next) => {
return `2${await next(`-w1${payload}w1-`)}4`;
});

hook.wrap(async (context, payload, next) => {
return `1${await next(`-w2${payload}w2-`)}5`;
});

const result = await hook.callPromise('-payload-');

expect(result).toBe('12-w1-w2-payload-w2-w1-345');
});

it('multiple wrap order parallel hook', async () => {
const factory = new TapableHooks();
const hook = factory.createAsyncParallel<number[]>('test');
let pre1: string = '';
let post1: string = '';
let pre2: string = '';
let post2: string = '';

hook.tapPromise('test', async (context, payload, result) => {
payload.push(3);
});

hook.wrap(async (context, payload, next) => {
pre1 = payload.toString();
payload.push(2);
await next(payload);
payload.push(4);
post1 = payload.toString();
});

hook.wrap(async (context, payload, next) => {
pre2 = payload.toString();
payload.push(1);
await next(payload);
payload.push(5);
post2 = payload.toString();
});

const list: number[] = [];

await hook.callPromise(list);

expect(list).toEqual([1, 2, 3, 4, 5]);
expect(pre1).toBe('1');
expect(post1).toBe('1,2,3,4');
expect(pre2).toBe('');
expect(post2).toBe('1,2,3,4,5');
});
});
Loading

0 comments on commit 86e4f02

Please sign in to comment.