@densetsuuu/docteur 0.1.1-beta.2 → 0.1.1-beta.4

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 (33) hide show
  1. package/README.md +6 -4
  2. package/build/index.d.ts +1 -1
  3. package/build/index.js +1 -1
  4. package/build/src/cli/commands/diagnose.js +3 -3
  5. package/build/src/cli/commands/xray.js +3 -3
  6. package/build/src/profiler/collector.d.ts +3 -2
  7. package/build/src/profiler/collector.js +14 -3
  8. package/build/src/profiler/hooks.d.ts +4 -24
  9. package/build/src/profiler/hooks.js +8 -20
  10. package/build/src/profiler/loader.js +46 -61
  11. package/build/src/profiler/profiler.d.ts +1 -1
  12. package/build/src/profiler/profiler.js +8 -8
  13. package/build/src/profiler/reporters/base_reporter.d.ts +1 -1
  14. package/build/src/profiler/reporters/console_reporter.js +5 -5
  15. package/build/src/profiler/reporters/format.d.ts +2 -8
  16. package/build/src/profiler/reporters/format.js +1 -1
  17. package/build/src/profiler/reporters/tui_reporter.js +1 -1
  18. package/build/src/{profiler/registries → registries}/categories.d.ts +1 -1
  19. package/build/src/xray/components/ListView.js +1 -1
  20. package/build/src/xray/components/ModuleView.js +2 -2
  21. package/build/src/xray/components/ProviderListView.d.ts +1 -1
  22. package/build/src/xray/components/ProviderListView.js +1 -1
  23. package/build/src/xray/components/ProviderView.d.ts +1 -1
  24. package/build/src/xray/components/ProviderView.js +2 -4
  25. package/build/src/xray/components/XRayApp.d.ts +1 -1
  26. package/build/src/xray/tree.d.ts +2 -2
  27. package/build/src/xray/tree.js +2 -2
  28. package/package.json +8 -2
  29. /package/build/src/{profiler/registries → registries}/categories.js +0 -0
  30. /package/build/src/{profiler/registries → registries}/index.d.ts +0 -0
  31. /package/build/src/{profiler/registries → registries}/index.js +0 -0
  32. /package/build/src/{profiler/registries → registries}/symbols.d.ts +0 -0
  33. /package/build/src/{profiler/registries → registries}/symbols.js +0 -0
package/README.md CHANGED
@@ -52,11 +52,12 @@ Interactive TUI for exploring module dependencies.
52
52
  docteur xray [options]
53
53
  ```
54
54
 
55
- | Option | Description | Default |
56
- | --------- | ------------------------- | --------------- |
57
- | `--entry` | Custom entry point | `bin/server.ts` |
55
+ | Option | Description | Default |
56
+ | --------- | ------------------ | --------------- |
57
+ | `--entry` | Custom entry point | `bin/server.ts` |
58
58
 
59
59
  **Features:**
60
+
60
61
  - Browse slowest modules
61
62
  - Drill down into module dependencies
62
63
  - See why a module was loaded (import chain)
@@ -64,6 +65,7 @@ docteur xray [options]
64
65
  - Explore provider lifecycle times (register, boot, start, ready)
65
66
 
66
67
  **Keyboard shortcuts:**
68
+
67
69
  - `↑/↓` Navigate
68
70
  - `Enter` Select
69
71
  - `Tab` Switch between Modules/Providers view
@@ -148,7 +150,7 @@ Docteur measures cold start performance through two complementary approaches:
148
150
  ## Requirements
149
151
 
150
152
  - AdonisJS v7+
151
- - Node.js 21+
153
+ - Node.js 22+
152
154
 
153
155
  ## License
154
156
 
package/build/index.d.ts CHANGED
@@ -20,4 +20,4 @@ export { colorDuration, createBar, formatDuration, getCategoryIcon, getEffective
20
20
  /**
21
21
  * Export registries for customization
22
22
  */
23
- export { categories, fileIcons, symbols, type CategoryDefinition, type SymbolKey, } from './src/profiler/registries/index.js';
23
+ export { categories, fileIcons, symbols, type CategoryDefinition, type SymbolKey, } from './src/registries/index.js';
package/build/index.js CHANGED
@@ -20,4 +20,4 @@ export { colorDuration, createBar, formatDuration, getCategoryIcon, getEffective
20
20
  /**
21
21
  * Export registries for customization
22
22
  */
23
- export { categories, fileIcons, symbols, } from './src/profiler/registries/index.js';
23
+ export { categories, fileIcons, symbols, } from './src/registries/index.js';
@@ -1,7 +1,7 @@
1
1
  import { defineCommand } from 'citty';
2
- import { isAdonisProject, profile } from '../../profiler/profiler.js';
3
- import { ConsoleReporter } from '../../profiler/reporters/console_reporter.js';
4
- import { ui } from '../../profiler/reporters/format.js';
2
+ import { isAdonisProject, profile } from '#profiler/profiler';
3
+ import { ConsoleReporter } from '#profiler/reporters/console_reporter';
4
+ import { ui } from '#profiler/reporters/format';
5
5
  export const diagnoseCommand = defineCommand({
6
6
  meta: {
7
7
  name: 'diagnose',
@@ -1,7 +1,7 @@
1
1
  import { defineCommand } from 'citty';
2
- import { isAdonisProject, profile } from '../../profiler/profiler.js';
3
- import { TuiReporter } from '../../profiler/reporters/tui_reporter.js';
4
- import { ui } from '../../profiler/reporters/format.js';
2
+ import { isAdonisProject, profile } from '#profiler/profiler';
3
+ import { TuiReporter } from '#profiler/reporters/tui_reporter';
4
+ import { ui } from '#profiler/reporters/format';
5
5
  export const xrayCommand = defineCommand({
6
6
  meta: {
7
7
  name: 'xray',
@@ -1,4 +1,4 @@
1
- import type { AppFileGroup, ModuleTiming, ProfileResult, ProfileSummary, ProviderTiming, ResolvedConfig } from '../types.js';
1
+ import type { AppFileGroup, ModuleTiming, ProfileResult, ProfileSummary, ProviderTiming, ResolvedConfig } from '#types';
2
2
  export interface PackageGroup {
3
3
  name: string;
4
4
  totalTime: number;
@@ -6,7 +6,8 @@ export interface PackageGroup {
6
6
  }
7
7
  export declare class ProfileCollector {
8
8
  #private;
9
- constructor(modules?: ModuleTiming[], providers?: ProviderTiming[]);
9
+ constructor(modules?: ModuleTiming[], providerPhases?: Map<string, Record<string, number>>);
10
+ static buildProviderTimings(phases: Map<string, Record<string, number>>): ProviderTiming[];
10
11
  groupAppFilesByCategory(): AppFileGroup[];
11
12
  groupModulesByPackage(): PackageGroup[];
12
13
  computeSummary(): ProfileSummary;
@@ -8,18 +8,29 @@
8
8
  | category or package.
9
9
  |
10
10
  */
11
- import { categories } from './registries/index.js';
11
+ import { categories } from '#registries/index';
12
12
  export class ProfileCollector {
13
13
  #modules;
14
14
  #providers;
15
- constructor(modules = [], providers = []) {
15
+ constructor(modules = [], providerPhases = new Map()) {
16
16
  this.#modules = modules;
17
- this.#providers = providers;
17
+ this.#providers = ProfileCollector.buildProviderTimings(providerPhases);
18
18
  // Compute subtree times if not already done (skip after filtering to avoid incomplete graph)
19
19
  if (modules.length > 0 && modules[0].subtreeTime === undefined) {
20
20
  this.#populateSubtreeTimes();
21
21
  }
22
22
  }
23
+ static buildProviderTimings(phases) {
24
+ return [...phases.entries()].map(([name, t]) => ({
25
+ name,
26
+ registerTime: t.register || 0,
27
+ bootTime: t.boot || 0,
28
+ startTime: t.start || 0,
29
+ readyTime: t.ready || 0,
30
+ shutdownTime: t.shutdown || 0,
31
+ totalTime: (t.register || 0) + (t.boot || 0) + (t.start || 0) + (t.ready || 0),
32
+ }));
33
+ }
23
34
  #populateSubtreeTimes() {
24
35
  const byUrl = new Map(this.#modules.map((m) => [m.resolvedUrl, m]));
25
36
  const children = new Map();
@@ -1,27 +1,7 @@
1
+ import type { InitializeHook, LoadHook, ResolveHook } from 'node:module';
1
2
  import type { MessagePort } from 'node:worker_threads';
2
- type ResolveFn = (specifier: string, context?: {
3
- parentURL?: string;
4
- }) => Promise<{
5
- url: string;
6
- }>;
7
- type LoadFn = (url: string, context?: {
8
- format?: string;
9
- }) => Promise<{
10
- format: string;
11
- source: string | ArrayBuffer | SharedArrayBuffer;
12
- }>;
13
- export declare function initialize(data: {
3
+ export declare const initialize: InitializeHook<{
14
4
  port: MessagePort;
15
- }): void;
16
- export declare function resolve(specifier: string, context: {
17
- parentURL?: string;
18
- }, next: ResolveFn): Promise<{
19
- url: string;
20
- }>;
21
- export declare function load(url: string, context: {
22
- format?: string;
23
- }, next: LoadFn): Promise<{
24
- format: string;
25
- source: string | ArrayBuffer | SharedArrayBuffer;
26
5
  }>;
27
- export {};
6
+ export declare const resolve: ResolveHook;
7
+ export declare const load: LoadHook;
@@ -10,36 +10,24 @@
10
10
  */
11
11
  import { performance } from 'node:perf_hooks';
12
12
  let port;
13
- const queue = [];
14
- let pending = false;
15
- function send(msg) {
16
- queue.push(msg);
17
- if (!pending) {
18
- pending = true;
19
- setImmediate(() => {
20
- pending = false;
21
- port?.postMessage({ type: 'batch', messages: queue.splice(0) });
22
- });
23
- }
24
- }
25
- export function initialize(data) {
13
+ export const initialize = (data) => {
26
14
  port = data.port;
27
- }
28
- export async function resolve(specifier, context, next) {
15
+ };
16
+ export const resolve = async (specifier, context, next) => {
29
17
  const result = await next(specifier, context);
30
18
  if (context.parentURL && result.url.startsWith('file://')) {
31
- send({ type: 'parent', child: result.url, parent: context.parentURL });
19
+ port.postMessage({ type: 'parent', child: result.url, parent: context.parentURL });
32
20
  }
33
21
  return result;
34
- }
35
- export async function load(url, context, next) {
22
+ };
23
+ export const load = async (url, context, next) => {
36
24
  if (!url.startsWith('file://')) {
37
25
  return next(url, context);
38
26
  }
39
27
  const start = performance.now();
40
28
  const result = await next(url, context);
41
29
  if (result.format === 'module') {
42
- send({ type: 'timing', url, loadTime: performance.now() - start });
30
+ port.postMessage({ type: 'timing', url, loadTime: performance.now() - start });
43
31
  }
44
32
  return result;
45
- }
33
+ };
@@ -13,100 +13,85 @@ import { performance } from 'node:perf_hooks';
13
13
  import { MessageChannel } from 'node:worker_threads';
14
14
  const require = createRequire(join(process.cwd(), 'node_modules', '_'));
15
15
  const { tracingChannels } = require('@adonisjs/application');
16
- // Module timing data from hooks
16
+ /**
17
+ * Module timing data collected via ESM hooks
18
+ */
17
19
  const parents = new Map();
18
20
  const loadTimes = new Map();
19
- // Provider timing data
20
- const providerPhases = new Map();
21
- const providerStarts = new Map();
22
- const asyncCalls = new Set();
23
- // Set up message channel for hooks
24
21
  const { port1, port2 } = new MessageChannel();
25
- port1.unref?.();
22
+ port1.unref();
26
23
  port1.on('message', (msg) => {
27
- if (msg.type !== 'batch' || !msg.messages)
28
- return;
29
- for (const m of msg.messages) {
30
- if (m.type === 'parent')
31
- parents.set(m.child, m.parent);
32
- else if (m.type === 'timing')
33
- loadTimes.set(m.url, m.loadTime);
34
- }
24
+ if (msg.type === 'parent')
25
+ parents.set(msg.child, msg.parent);
26
+ else if (msg.type === 'timing')
27
+ loadTimes.set(msg.url, msg.loadTime);
35
28
  });
36
29
  register('./hooks.js', {
37
30
  parentURL: import.meta.url,
38
31
  data: { port: port2 },
39
32
  transferList: [port2],
40
33
  });
41
- // Subscribe to provider lifecycle phases
42
- // For async methods: start -> end -> asyncStart -> asyncEnd (we wait for asyncEnd)
43
- // For sync methods: start -> end (we record on end, but defer to check if async fires)
44
- function subscribePhase(channel, phase) {
45
- const getName = (msg) => msg.provider.constructor.name;
46
- channel.subscribe({
34
+ /**
35
+ * Provider lifecycle timing via tracing channels.
36
+ *
37
+ * Tracing channels emit: start → end (sync) or start → end → asyncStart → asyncEnd (async).
38
+ * We defer sync recording with setTimeout so asyncStart can claim the phase first.
39
+ */
40
+ const providerPhases = new Map();
41
+ const starts = new Map();
42
+ const asyncPhases = new Set();
43
+ function name(msg) {
44
+ return msg.provider.constructor.name;
45
+ }
46
+ function record(provider, phase, endTime) {
47
+ const start = starts.get(`${provider}:${phase}`);
48
+ if (start === undefined)
49
+ return;
50
+ const phases = providerPhases.get(provider) ?? {};
51
+ phases[phase] = endTime - start;
52
+ providerPhases.set(provider, phases);
53
+ starts.delete(`${provider}:${phase}`);
54
+ }
55
+ const phases = ['register', 'boot', 'start', 'ready', 'shutdown'];
56
+ for (const phase of phases) {
57
+ const channelKey = `provider${phase[0].toUpperCase()}${phase.slice(1)}`;
58
+ tracingChannels[channelKey].subscribe({
47
59
  start(msg) {
48
- providerStarts.set(`${getName(msg)}:${phase}`, performance.now());
60
+ starts.set(`${name(msg)}:${phase}`, performance.now());
49
61
  },
50
62
  end(msg) {
51
- const name = getName(msg);
52
- const key = `${name}:${phase}`;
63
+ const provider = name(msg);
53
64
  const endTime = performance.now();
54
- // Defer to check if this becomes async (asyncStart fires before our setTimeout)
65
+ const key = `${provider}:${phase}`;
55
66
  setTimeout(() => {
56
- if (asyncCalls.has(key))
57
- return;
58
- const start = providerStarts.get(key);
59
- if (start !== undefined) {
60
- const phases = providerPhases.get(name) || {};
61
- phases[phase] = endTime - start;
62
- providerPhases.set(name, phases);
63
- providerStarts.delete(key);
64
- }
67
+ if (!asyncPhases.has(key))
68
+ record(provider, phase, endTime);
65
69
  }, 0);
66
70
  },
67
71
  asyncStart(msg) {
68
- asyncCalls.add(`${getName(msg)}:${phase}`);
72
+ asyncPhases.add(`${name(msg)}:${phase}`);
69
73
  },
70
74
  asyncEnd(msg) {
71
- const name = getName(msg);
72
- const key = `${name}:${phase}`;
73
- const start = providerStarts.get(key);
74
- if (start !== undefined) {
75
- const phases = providerPhases.get(name) || {};
76
- phases[phase] = performance.now() - start;
77
- providerPhases.set(name, phases);
78
- providerStarts.delete(key);
79
- }
80
- asyncCalls.delete(key);
75
+ const provider = name(msg);
76
+ record(provider, phase, performance.now());
77
+ asyncPhases.delete(`${provider}:${phase}`);
81
78
  },
82
79
  error() { },
83
80
  });
84
81
  }
85
- subscribePhase(tracingChannels.providerRegister, 'register');
86
- subscribePhase(tracingChannels.providerBoot, 'boot');
87
- subscribePhase(tracingChannels.providerStart, 'start');
88
- subscribePhase(tracingChannels.providerReady, 'ready');
89
- subscribePhase(tracingChannels.providerShutdown, 'shutdown');
90
- // Send results to parent process when requested
82
+ /**
83
+ * IPC: send collected results to parent process
84
+ */
91
85
  if (process.send) {
92
86
  process.on('message', (msg) => {
93
87
  if (msg.type !== 'getResults')
94
88
  return;
95
- const providers = [...providerPhases.entries()].map(([name, t]) => ({
96
- name,
97
- registerTime: t.register || 0,
98
- bootTime: t.boot || 0,
99
- startTime: t.start || 0,
100
- readyTime: t.ready || 0,
101
- shutdownTime: t.shutdown || 0,
102
- totalTime: (t.register || 0) + (t.boot || 0) + (t.start || 0) + (t.ready || 0),
103
- }));
104
89
  process.send({
105
90
  type: 'results',
106
91
  data: {
107
92
  loadTimes: Object.fromEntries(loadTimes),
108
93
  parents: Object.fromEntries(parents),
109
- providers,
94
+ providerPhases: Object.fromEntries(providerPhases),
110
95
  },
111
96
  });
112
97
  });
@@ -1,4 +1,4 @@
1
- import type { ProfileResult } from '../types.js';
1
+ import type { ProfileResult } from '#types';
2
2
  export interface ProfileOptions {
3
3
  entryPoint?: string;
4
4
  suppressOutput?: boolean;
@@ -8,14 +8,14 @@
8
8
  |
9
9
  */
10
10
  import { fork } from 'node:child_process';
11
- import { fileURLToPath } from 'node:url';
12
- import { join } from 'node:path';
13
11
  import { existsSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ import { pathToFileURL } from 'node:url';
14
14
  import { ProfileCollector } from './collector.js';
15
- import { simplifyUrl } from './reporters/format.js';
15
+ import { simplifyUrl } from '#profiler/reporters/format';
16
16
  const TIMEOUT_MS = 30_000;
17
17
  export function findLoaderPath() {
18
- return fileURLToPath(import.meta.resolve('@densetsuuu/docteur/profiler/loader'));
18
+ return import.meta.resolve('@densetsuuu/docteur/profiler/loader');
19
19
  }
20
20
  export function findEntryPoint(cwd, entry) {
21
21
  const entryPath = join(cwd, entry || 'bin/server.ts');
@@ -37,12 +37,12 @@ function runProfiledProcess(loaderPath, entryPoint, cwd, options) {
37
37
  const suppressOutput = options.suppressOutput ?? false;
38
38
  return new Promise((resolve, reject) => {
39
39
  const state = {
40
- providers: [],
40
+ providerPhases: new Map(),
41
41
  loadTimes: new Map(),
42
42
  parents: new Map(),
43
43
  done: false,
44
44
  };
45
- const child = fork(entryPoint, [], {
45
+ const child = fork(pathToFileURL(entryPoint).href, [], {
46
46
  cwd,
47
47
  stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
48
48
  execArgv: ['--import', loaderPath, '--import', '@poppinss/ts-exec', '--no-warnings'],
@@ -86,7 +86,7 @@ function runProfiledProcess(loaderPath, entryPoint, cwd, options) {
86
86
  const data = msg.data;
87
87
  state.loadTimes = new Map(Object.entries(data.loadTimes));
88
88
  state.parents = new Map(Object.entries(data.parents || {}));
89
- state.providers = data.providers || [];
89
+ state.providerPhases = new Map(Object.entries(data.providerPhases || {}));
90
90
  complete();
91
91
  }
92
92
  });
@@ -109,7 +109,7 @@ function buildResults(state, cwd) {
109
109
  loadTime,
110
110
  parentUrl: state.parents.get(url),
111
111
  }));
112
- const collector = new ProfileCollector(modules, state.providers);
112
+ const collector = new ProfileCollector(modules, state.providerPhases);
113
113
  const bootTimeMs = state.bootDuration
114
114
  ? state.bootDuration[0] * 1000 + state.bootDuration[1] / 1_000_000
115
115
  : 0;
@@ -1,4 +1,4 @@
1
- import type { ProfileResult, ResolvedConfig } from '../../types.js';
1
+ import type { ProfileResult, ResolvedConfig } from '#types';
2
2
  /**
3
3
  * Context passed to reporters containing all data needed for rendering.
4
4
  */
@@ -6,8 +6,8 @@
6
6
  | Renders profiling results to the terminal using @poppinss/cliui.
7
7
  |
8
8
  */
9
- import { ProfileCollector } from '../collector.js';
10
- import { symbols } from '../registries/index.js';
9
+ import { ProfileCollector } from '#profiler/collector';
10
+ import { symbols } from '#registries/index';
11
11
  import { colorDuration, createBar, formatDuration, getCategoryIcon, getEffectiveTime, simplifyUrl, ui, } from './format.js';
12
12
  export class ConsoleReporter {
13
13
  /**
@@ -65,7 +65,7 @@ export class ConsoleReporter {
65
65
  ui.logger.log('');
66
66
  }
67
67
  #printSlowestModules(result, config, cwd) {
68
- const collector = new ProfileCollector(result.modules, result.providers);
68
+ const collector = new ProfileCollector(result.modules);
69
69
  const filtered = collector.filterModules(config);
70
70
  const slowest = new ProfileCollector(filtered).getTopSlowest(config.topModules);
71
71
  if (slowest.length === 0) {
@@ -102,7 +102,7 @@ export class ConsoleReporter {
102
102
  ui.logger.log('');
103
103
  }
104
104
  #printPackageGroups(result, config) {
105
- const collector = new ProfileCollector(result.modules, result.providers);
105
+ const collector = new ProfileCollector(result.modules);
106
106
  const filtered = collector.filterModules(config);
107
107
  const groups = new ProfileCollector(filtered).groupModulesByPackage();
108
108
  if (groups.length === 0) {
@@ -176,7 +176,7 @@ export class ConsoleReporter {
176
176
  if (result.summary.totalModules > 500) {
177
177
  recommendations.push(`Loading ${result.summary.totalModules} modules. Consider code splitting or lazy imports.`);
178
178
  }
179
- const collector = new ProfileCollector(result.modules, result.providers);
179
+ const collector = new ProfileCollector(result.modules);
180
180
  const filtered = collector.filterModules(config);
181
181
  const verySlowModules = filtered.filter((m) => getEffectiveTime(m) > 100);
182
182
  if (verySlowModules.length > 0) {
@@ -1,17 +1,11 @@
1
- import type { AppFileCategory, ModuleTiming } from '../../types.js';
1
+ import type { AppFileCategory, ModuleTiming } from '#types';
2
2
  /**
3
3
  * Shared UI instance for consistent styling
4
4
  */
5
5
  export declare const ui: {
6
6
  colors: import("@poppinss/colors/types").Colors;
7
7
  logger: import("@poppinss/cliui").Logger;
8
- table: (tableOptions
9
- /**
10
- * Colors a duration based on how slow it is
11
- */
12
- ? /**
13
- * Colors a duration based on how slow it is
14
- */: Partial<import("@poppinss/cliui/types").TableOptions>) => import("@poppinss/cliui").Table;
8
+ table: (tableOptions?: Partial<import("@poppinss/cliui/types").TableOptions>) => import("@poppinss/cliui").Table;
15
9
  tasks: (tasksOptions?: Partial<import("@poppinss/cliui/types").TaskManagerOptions>) => import("@poppinss/cliui").TaskManager;
16
10
  steps: () => import("@poppinss/cliui").Steps;
17
11
  icons: {
@@ -7,7 +7,7 @@
7
7
  |
8
8
  */
9
9
  import { cliui } from '@poppinss/cliui';
10
- import { categories, symbols } from '../registries/index.js';
10
+ import { categories, symbols } from '#registries/index';
11
11
  /**
12
12
  * Shared UI instance for consistent styling
13
13
  */
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import React from 'react';
11
11
  import { render } from 'ink';
12
- import { XRayApp } from '../../xray/components/XRayApp.js';
12
+ import { XRayApp } from '#xray/components/XRayApp';
13
13
  export class TuiReporter {
14
14
  /**
15
15
  * Renders an interactive TUI report using the XRay explorer.
@@ -1,4 +1,4 @@
1
- import type { AppFileCategory } from '../../types.js';
1
+ import type { AppFileCategory } from '#types';
2
2
  export interface CategoryDefinition {
3
3
  displayName: string;
4
4
  icon: string;
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
3
  import SelectInput from 'ink-select-input';
4
4
  import { formatDuration, getEffectiveTime, getFileIcon, getTimeColor, isDependency, } from '../tree.js';
5
- import { symbols } from '../../profiler/registries/index.js';
5
+ import { symbols } from '#registries/index';
6
6
  function ItemComponent({ isSelected = false, label, timeStr, timeColor, fileIcon, displayName, }) {
7
7
  // Spacer
8
8
  if (!label) {
@@ -3,7 +3,7 @@ import { useMemo } from 'react';
3
3
  import { Box, Text } from 'ink';
4
4
  import SelectInput from 'ink-select-input';
5
5
  import { formatDuration, getEffectiveTime, getImportChain, getFileIcon, getSourceIcon, getTimeColor, isDependency, } from '../tree.js';
6
- import { symbols } from '../../profiler/registries/index.js';
6
+ import { symbols } from '#registries/index';
7
7
  function ItemComponent({ isSelected = false, label }) {
8
8
  if (!label) {
9
9
  return _jsx(Text, { children: " " });
@@ -124,7 +124,7 @@ export function ModuleView({ node, tree: _tree, onNavigate, onBack }) {
124
124
  }
125
125
  onNavigate(item.value);
126
126
  };
127
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, color: "cyan", children: [' ', '\uf21e', " Module Details"] }) }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { children: _jsxs(Text, { bold: true, color: "green", children: [' ', node.displayName] }) }), _jsxs(Text, { dimColor: true, children: [" ", node.timing.resolvedUrl] })] }), _jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: " Total time: " }), _jsx(Text, { color: getTimeColor(time), children: formatDuration(time) }), _jsx(Text, { dimColor: true, children: " (with dependencies)" })] }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: " Self time: " }), _jsx(Text, { color: getTimeColor(selfTime), children: formatDuration(selfTime) }), _jsx(Text, { dimColor: true, children: " (this file only)" })] })] }), lazyImportCandidates.length > 0 && !isDependency(node.timing.resolvedUrl) && (_jsxs(Box, { marginBottom: 1, flexDirection: "column", paddingLeft: 1, children: [_jsxs(Text, { color: "yellow", bold: true, children: [symbols.lightbulb, " Optimization tip:"] }), _jsxs(Text, { dimColor: true, children: [' ', "This file imports ", lazyImportCandidates.length, " heavy package(s) that could be"] }), _jsx(Text, { dimColor: true, children: " lazy-loaded with dynamic imports to reduce cold start time:" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: " Before: import xlsx from 'xlsx'" }), _jsx(Text, { dimColor: true, children: " After: const xlsx = await import('xlsx')" })] })] })), _jsx(Box, { flexDirection: "column", children: _jsx(SelectInput, { items: items, onSelect: handleSelect, itemComponent: ItemComponent }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: " Left/ESC/Backspace: back | q: quit" }) })] }));
127
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, color: "cyan", children: [' ', '\uf21e', " Module Details"] }) }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { children: _jsxs(Text, { bold: true, color: "green", children: [' ', node.displayName] }) }), _jsxs(Text, { dimColor: true, children: [" ", node.timing.resolvedUrl] })] }), _jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: " Total time: " }), _jsx(Text, { color: getTimeColor(time), children: formatDuration(time) }), _jsx(Text, { dimColor: true, children: " (with dependencies)" })] }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: " Self time: " }), _jsx(Text, { color: getTimeColor(selfTime), children: formatDuration(selfTime) }), _jsx(Text, { dimColor: true, children: " (this file only)" })] })] }), lazyImportCandidates.length > 0 && !isDependency(node.timing.resolvedUrl) && (_jsxs(Box, { marginBottom: 1, flexDirection: "column", paddingLeft: 1, children: [_jsxs(Text, { color: "yellow", bold: true, children: [symbols.lightbulb, " Optimization tip:"] }), _jsxs(Text, { dimColor: true, children: [' ', "This file imports ", lazyImportCandidates.length, " heavy package(s) that could be"] }), _jsx(Text, { dimColor: true, children: " lazy-loaded with dynamic imports to reduce cold start time:" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: " Before: import xlsx from 'xlsx'" }), _jsx(Text, { dimColor: true, children: " After: const xlsx = await import('xlsx')" })] })] })), _jsx(Box, { flexDirection: "column", children: _jsx(SelectInput, { items: items, onSelect: handleSelect, itemComponent: ItemComponent }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: " Left/ESC/Backspace: back | q: quit" }) })] }));
128
128
  }
129
129
  function extractPackageName(url) {
130
130
  const match = url.match(/node_modules\/(@[^/]+\/[^/]+|[^/]+)/);
@@ -1,4 +1,4 @@
1
- import type { ProviderTiming } from '../../types.js';
1
+ import type { ProviderTiming } from '#types';
2
2
  interface Props {
3
3
  providers: ProviderTiming[];
4
4
  onSelect: (provider: ProviderTiming) => void;
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
3
  import SelectInput from 'ink-select-input';
4
4
  import { formatDuration, getTimeColor } from '../tree.js';
5
- import { symbols } from '../../profiler/registries/index.js';
5
+ import { symbols } from '#registries/index';
6
6
  function ItemComponent({ isSelected = false, timeStr, timeColor, name, label }) {
7
7
  // Spacer
8
8
  if (!label) {
@@ -1,4 +1,4 @@
1
- import type { ProviderTiming } from '../../types.js';
1
+ import type { ProviderTiming } from '#types';
2
2
  interface Props {
3
3
  provider: ProviderTiming;
4
4
  onBack: () => void;
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
3
  import SelectInput from 'ink-select-input';
4
4
  import { formatDuration, getTimeColor } from '../tree.js';
5
- import { symbols } from '../../profiler/registries/index.js';
5
+ import { symbols } from '#registries/index';
6
6
  function Bar({ value, max, width = 30 }) {
7
7
  const ratio = max > 0 ? Math.min(value / max, 1) : 0;
8
8
  const filled = Math.round(ratio * width);
@@ -23,9 +23,7 @@ export function ProviderView({ provider, onBack }) {
23
23
  { label: 'ready()', value: provider.readyTime },
24
24
  ];
25
25
  const maxPhaseTime = Math.max(...phases.map((p) => p.value), 1);
26
- const items = [
27
- { key: 'back', label: `${symbols.arrowLeft} Back`, value: 'back' },
28
- ];
26
+ const items = [{ key: 'back', label: `${symbols.arrowLeft} Back`, value: 'back' }];
29
27
  const handleSelect = (item) => {
30
28
  if (item.value === 'back') {
31
29
  onBack();
@@ -1,4 +1,4 @@
1
- import type { ProfileResult } from '../../types.js';
1
+ import type { ProfileResult } from '#types';
2
2
  interface Props {
3
3
  result: ProfileResult;
4
4
  cwd: string;
@@ -1,5 +1,5 @@
1
- import type { ModuleTiming } from '../types.js';
2
- import { formatDuration, getEffectiveTime } from '../profiler/reporters/format.js';
1
+ import type { ModuleTiming } from '#types';
2
+ import { formatDuration, getEffectiveTime } from '#profiler/reporters/format';
3
3
  export interface ModuleNode {
4
4
  timing: ModuleTiming;
5
5
  displayName: string;
@@ -7,8 +7,8 @@
7
7
  | Provides utilities for traversing and displaying the tree.
8
8
  |
9
9
  */
10
- import { formatDuration, getEffectiveTime, simplifyUrl } from '../profiler/reporters/format.js';
11
- import { fileIcons, symbols } from '../profiler/registries/index.js';
10
+ import { formatDuration, getEffectiveTime, simplifyUrl } from '#profiler/reporters/format';
11
+ import { fileIcons, symbols } from '#registries/index';
12
12
  export function buildDependencyTree(modules, cwd) {
13
13
  const nodeMap = new Map();
14
14
  const time = (n) => getEffectiveTime(n.timing);
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@densetsuuu/docteur",
3
3
  "description": "AdonisJS cold start profiler - analyze and optimize your application boot time",
4
- "version": "0.1.1-beta.2",
4
+ "version": "0.1.1-beta.4",
5
5
  "engines": {
6
- "node": ">=21.0.0"
6
+ "node": ">=22.0.0"
7
7
  },
8
8
  "main": "./build/index.js",
9
9
  "types": "./build/index.d.ts",
@@ -92,6 +92,12 @@
92
92
  "optional": true
93
93
  }
94
94
  },
95
+ "imports": {
96
+ "#registries/*": "./build/src/registries/*.js",
97
+ "#types": "./build/src/types.js",
98
+ "#profiler/*": "./build/src/profiler/*.js",
99
+ "#xray/*": "./build/src/xray/*.js"
100
+ },
95
101
  "publishConfig": {
96
102
  "access": "public",
97
103
  "tag": "latest"