@densetsuuu/docteur 0.1.1-beta.3 → 0.1.1-beta.5

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 +1 -1
  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 +1 -1
  7. package/build/src/profiler/collector.js +1 -1
  8. package/build/src/profiler/hooks.d.ts +4 -24
  9. package/build/src/profiler/hooks.js +6 -6
  10. package/build/src/profiler/loader.js +34 -34
  11. package/build/src/profiler/profiler.d.ts +1 -1
  12. package/build/src/profiler/profiler.js +2 -2
  13. package/build/src/profiler/reporters/base_reporter.d.ts +1 -1
  14. package/build/src/profiler/reporters/console_reporter.js +2 -2
  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 +1 -1
  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 +1 -1
  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 +21 -3
  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
@@ -150,7 +150,7 @@ Docteur measures cold start performance through two complementary approaches:
150
150
  ## Requirements
151
151
 
152
152
  - AdonisJS v7+
153
- - Node.js 21+
153
+ - Node.js 22+
154
154
 
155
155
  ## License
156
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;
@@ -8,7 +8,7 @@
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;
@@ -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,17 +10,17 @@
10
10
  */
11
11
  import { performance } from 'node:perf_hooks';
12
12
  let port;
13
- export function initialize(data) {
13
+ export const initialize = (data) => {
14
14
  port = data.port;
15
- }
16
- export async function resolve(specifier, context, next) {
15
+ };
16
+ export const resolve = async (specifier, context, next) => {
17
17
  const result = await next(specifier, context);
18
18
  if (context.parentURL && result.url.startsWith('file://')) {
19
19
  port.postMessage({ type: 'parent', child: result.url, parent: context.parentURL });
20
20
  }
21
21
  return result;
22
- }
23
- export async function load(url, context, next) {
22
+ };
23
+ export const load = async (url, context, next) => {
24
24
  if (!url.startsWith('file://')) {
25
25
  return next(url, context);
26
26
  }
@@ -30,4 +30,4 @@ export async function load(url, context, next) {
30
30
  port.postMessage({ type: 'timing', url, loadTime: performance.now() - start });
31
31
  }
32
32
  return result;
33
- }
33
+ };
@@ -13,14 +13,11 @@ 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
22
  port1.unref();
26
23
  port1.on('message', (msg) => {
@@ -34,54 +31,57 @@ register('./hooks.js', {
34
31
  data: { port: port2 },
35
32
  transferList: [port2],
36
33
  });
37
- // Subscribe to provider lifecycle phases
38
- // For async methods: start -> end -> asyncStart -> asyncEnd (we wait for asyncEnd)
39
- // For sync methods: start -> end (we record on end, but defer to check if async fires)
40
- function getProviderName(msg) {
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) {
41
44
  return msg.provider.constructor.name;
42
45
  }
43
- function recordPhase(name, phase, endTime) {
44
- const key = `${name}:${phase}`;
45
- const start = providerStarts.get(key);
46
+ function record(provider, phase, endTime) {
47
+ const start = starts.get(`${provider}:${phase}`);
46
48
  if (start === undefined)
47
49
  return;
48
- const phases = providerPhases.get(name) || {};
50
+ const phases = providerPhases.get(provider) ?? {};
49
51
  phases[phase] = endTime - start;
50
- providerPhases.set(name, phases);
51
- providerStarts.delete(key);
52
+ providerPhases.set(provider, phases);
53
+ starts.delete(`${provider}:${phase}`);
52
54
  }
53
- function subscribePhase(channel, phase) {
54
- channel.subscribe({
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({
55
59
  start(msg) {
56
- providerStarts.set(`${getProviderName(msg)}:${phase}`, performance.now());
60
+ starts.set(`${name(msg)}:${phase}`, performance.now());
57
61
  },
58
62
  end(msg) {
59
- const name = getProviderName(msg);
60
- const key = `${name}:${phase}`;
63
+ const provider = name(msg);
61
64
  const endTime = performance.now();
62
- // Defer to check if this becomes async (asyncStart fires before our setTimeout)
65
+ const key = `${provider}:${phase}`;
63
66
  setTimeout(() => {
64
- if (!asyncCalls.has(key))
65
- recordPhase(name, phase, endTime);
67
+ if (!asyncPhases.has(key))
68
+ record(provider, phase, endTime);
66
69
  }, 0);
67
70
  },
68
71
  asyncStart(msg) {
69
- asyncCalls.add(`${getProviderName(msg)}:${phase}`);
72
+ asyncPhases.add(`${name(msg)}:${phase}`);
70
73
  },
71
74
  asyncEnd(msg) {
72
- const name = getProviderName(msg);
73
- recordPhase(name, phase, performance.now());
74
- asyncCalls.delete(`${name}:${phase}`);
75
+ const provider = name(msg);
76
+ record(provider, phase, performance.now());
77
+ asyncPhases.delete(`${provider}:${phase}`);
75
78
  },
76
79
  error() { },
77
80
  });
78
81
  }
79
- subscribePhase(tracingChannels.providerRegister, 'register');
80
- subscribePhase(tracingChannels.providerBoot, 'boot');
81
- subscribePhase(tracingChannels.providerStart, 'start');
82
- subscribePhase(tracingChannels.providerReady, 'ready');
83
- subscribePhase(tracingChannels.providerShutdown, 'shutdown');
84
- // Send results to parent process when requested
82
+ /**
83
+ * IPC: send collected results to parent process
84
+ */
85
85
  if (process.send) {
86
86
  process.on('message', (msg) => {
87
87
  if (msg.type !== 'getResults')
@@ -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,10 +8,10 @@
8
8
  |
9
9
  */
10
10
  import { fork } from 'node:child_process';
11
- import { join } from 'node:path';
12
11
  import { existsSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
13
  import { ProfileCollector } from './collector.js';
14
- import { simplifyUrl } from './reporters/format.js';
14
+ import { simplifyUrl } from '#profiler/reporters/format';
15
15
  const TIMEOUT_MS = 30_000;
16
16
  export function findLoaderPath() {
17
17
  return import.meta.resolve('@densetsuuu/docteur/profiler/loader');
@@ -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
  /**
@@ -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: " " });
@@ -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);
@@ -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.3",
4
+ "version": "0.1.1-beta.5",
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",
@@ -30,7 +30,7 @@
30
30
  "typecheck": "tsc --noEmit",
31
31
  "lint": "eslint .",
32
32
  "format": "prettier --write .",
33
- "quick:test": "node --import=./tsnode.esm.js --enable-source-maps bin/test.ts",
33
+ "quick:test": "node --conditions=development --import=./tsnode.esm.js --enable-source-maps bin/test.ts",
34
34
  "pretest": "npm run lint",
35
35
  "test": "c8 npm run quick:test",
36
36
  "prebuild": "npm run lint && npm run clean",
@@ -92,6 +92,24 @@
92
92
  "optional": true
93
93
  }
94
94
  },
95
+ "imports": {
96
+ "#registries/*": {
97
+ "development": "./src/registries/*.js",
98
+ "default": "./build/src/registries/*.js"
99
+ },
100
+ "#types": {
101
+ "development": "./src/types.js",
102
+ "default": "./build/src/types.js"
103
+ },
104
+ "#profiler/*": {
105
+ "development": "./src/profiler/*.js",
106
+ "default": "./build/src/profiler/*.js"
107
+ },
108
+ "#xray/*": {
109
+ "development": "./src/xray/*.js",
110
+ "default": "./build/src/xray/*.js"
111
+ }
112
+ },
95
113
  "publishConfig": {
96
114
  "access": "public",
97
115
  "tag": "latest"