diff --git a/types/node/inspector.d.ts b/types/node/inspector.d.ts index 3a2bb35afe..a9d0f1140a 100644 --- a/types/node/inspector.d.ts +++ b/types/node/inspector.d.ts @@ -1912,6 +1912,90 @@ declare module "inspector" { } } + namespace NodeTracing { + interface TraceConfig { + /** + * Controls how the trace buffer stores data. + */ + recordMode?: string; + /** + * Included category filters. + */ + includedCategories: string[]; + } + + interface StartParameterType { + traceConfig: TraceConfig; + } + + interface GetCategoriesReturnType { + /** + * A list of supported tracing categories. + */ + categories: string[]; + } + + interface DataCollectedEventDataType { + value: Array<{}>; + } + } + + namespace NodeWorker { + type WorkerID = string; + + /** + * Unique identifier of attached debugging session. + */ + type SessionID = string; + + interface WorkerInfo { + workerId: WorkerID; + type: string; + title: string; + url: string; + } + + interface SendMessageToWorkerParameterType { + message: string; + /** + * Identifier of the session. + */ + sessionId: SessionID; + } + + interface EnableParameterType { + /** + * Whether to new workers should be paused until the frontend sends `Runtime.runIfWaitingForDebugger` + * message to run them. + */ + waitForDebuggerOnStart: boolean; + } + + interface AttachedToWorkerEventDataType { + /** + * Identifier assigned to the session used to send/receive messages. + */ + sessionId: SessionID; + workerInfo: WorkerInfo; + waitingForDebugger: boolean; + } + + interface DetachedFromWorkerEventDataType { + /** + * Detached session identifier. + */ + sessionId: SessionID; + } + + interface ReceivedMessageFromWorkerEventDataType { + /** + * Identifier of a session which sends a message. + */ + sessionId: SessionID; + message: string; + } + } + /** * The inspector.Session is used for dispatching messages to the V8 inspector back-end and receiving message responses and notifications. */ @@ -1958,6 +2042,7 @@ declare module "inspector" { * `messageAdded` notification. */ post(method: "Console.enable", callback?: (err: Error | null) => void): void; + /** * Continues execution until specific location is reached. */ @@ -2155,6 +2240,7 @@ declare module "inspector" { * Steps over the statement. */ post(method: "Debugger.stepOver", callback?: (err: Error | null) => void): void; + /** * Enables console to refer to the node with given id via $x (see Command Line API for more details * $x functions). @@ -2193,6 +2279,7 @@ declare module "inspector" { post(method: "HeapProfiler.takeHeapSnapshot", params?: HeapProfiler.TakeHeapSnapshotParameterType, callback?: (err: Error | null) => void): void; post(method: "HeapProfiler.takeHeapSnapshot", callback?: (err: Error | null) => void): void; + post(method: "Profiler.disable", callback?: (err: Error | null) => void): void; post(method: "Profiler.enable", callback?: (err: Error | null) => void): void; @@ -2250,6 +2337,7 @@ declare module "inspector" { * @experimental */ post(method: "Profiler.takeTypeProfile", callback?: (err: Error | null, params: Profiler.TakeTypeProfileReturnType) => void): void; + /** * Add handler to promise with given promise object id. */ @@ -2360,11 +2448,47 @@ declare module "inspector" { * @experimental */ post(method: "Runtime.terminateExecution", callback?: (err: Error | null) => void): void; + /** * Returns supported domains. */ post(method: "Schema.getDomains", callback?: (err: Error | null, params: Schema.GetDomainsReturnType) => void): void; + /** + * Gets supported tracing categories. + */ + post(method: "NodeTracing.getCategories", callback?: (err: Error | null, params: NodeTracing.GetCategoriesReturnType) => void): void; + + /** + * Start trace events collection. + */ + post(method: "NodeTracing.start", params?: NodeTracing.StartParameterType, callback?: (err: Error | null) => void): void; + post(method: "NodeTracing.start", callback?: (err: Error | null) => void): void; + + /** + * Stop trace events collection. Remaining collected events will be sent as a sequence of + * dataCollected events followed by tracingComplete event. + */ + post(method: "NodeTracing.stop", callback?: (err: Error | null) => void): void; + + /** + * Sends protocol message over session with given id. + */ + post(method: "NodeWorker.sendMessageToWorker", params?: NodeWorker.SendMessageToWorkerParameterType, callback?: (err: Error | null) => void): void; + post(method: "NodeWorker.sendMessageToWorker", callback?: (err: Error | null) => void): void; + + /** + * Instructs the inspector to attach to running workers. Will also attach to new workers + * as they start + */ + post(method: "NodeWorker.enable", params?: NodeWorker.EnableParameterType, callback?: (err: Error | null) => void): void; + post(method: "NodeWorker.enable", callback?: (err: Error | null) => void): void; + + /** + * Detaches from all running workers and disables attaching to new workers as they are started. + */ + post(method: "NodeWorker.disable", callback?: (err: Error | null) => void): void; + // Events addListener(event: string, listener: (...args: any[]) => void): this; @@ -2464,6 +2588,33 @@ declare module "inspector" { */ addListener(event: "Runtime.inspectRequested", listener: (message: InspectorNotification) => void): this; + /** + * Contains an bucket of collected trace events. + */ + addListener(event: "NodeTracing.dataCollected", listener: (message: InspectorNotification) => void): this; + + /** + * Signals that tracing is stopped and there is no trace buffers pending flush, all data were + * delivered via dataCollected events. + */ + addListener(event: "NodeTracing.tracingComplete", listener: () => void): this; + + /** + * Issued when attached to a worker. + */ + addListener(event: "NodeWorker.attachedToWorker", listener: (message: InspectorNotification) => void): this; + + /** + * Issued when detached from the worker. + */ + addListener(event: "NodeWorker.detachedFromWorker", listener: (message: InspectorNotification) => void): this; + + /** + * Notifies about a new protocol message received from the session + * (session ID is provided in attachedToWorker notification). + */ + addListener(event: "NodeWorker.receivedMessageFromWorker", listener: (message: InspectorNotification) => void): this; + emit(event: string | symbol, ...args: any[]): boolean; emit(event: "inspectorNotification", message: InspectorNotification<{}>): boolean; emit(event: "Console.messageAdded", message: InspectorNotification): boolean; @@ -2486,6 +2637,11 @@ declare module "inspector" { emit(event: "Runtime.executionContextDestroyed", message: InspectorNotification): boolean; emit(event: "Runtime.executionContextsCleared"): boolean; emit(event: "Runtime.inspectRequested", message: InspectorNotification): boolean; + emit(event: "NodeTracing.dataCollected", message: InspectorNotification): boolean; + emit(event: "NodeTracing.tracingComplete"): boolean; + emit(event: "NodeWorker.attachedToWorker", message: InspectorNotification): boolean; + emit(event: "NodeWorker.detachedFromWorker", message: InspectorNotification): boolean; + emit(event: "NodeWorker.receivedMessageFromWorker", message: InspectorNotification): boolean; on(event: string, listener: (...args: any[]) => void): this; @@ -2584,6 +2740,33 @@ declare module "inspector" { */ on(event: "Runtime.inspectRequested", listener: (message: InspectorNotification) => void): this; + /** + * Contains an bucket of collected trace events. + */ + on(event: "NodeTracing.dataCollected", listener: (message: InspectorNotification) => void): this; + + /** + * Signals that tracing is stopped and there is no trace buffers pending flush, all data were + * delivered via dataCollected events. + */ + on(event: "NodeTracing.tracingComplete", listener: () => void): this; + + /** + * Issued when attached to a worker. + */ + on(event: "NodeWorker.attachedToWorker", listener: (message: InspectorNotification) => void): this; + + /** + * Issued when detached from the worker. + */ + on(event: "NodeWorker.detachedFromWorker", listener: (message: InspectorNotification) => void): this; + + /** + * Notifies about a new protocol message received from the session + * (session ID is provided in attachedToWorker notification). + */ + on(event: "NodeWorker.receivedMessageFromWorker", listener: (message: InspectorNotification) => void): this; + once(event: string, listener: (...args: any[]) => void): this; /** @@ -2681,6 +2864,33 @@ declare module "inspector" { */ once(event: "Runtime.inspectRequested", listener: (message: InspectorNotification) => void): this; + /** + * Contains an bucket of collected trace events. + */ + once(event: "NodeTracing.dataCollected", listener: (message: InspectorNotification) => void): this; + + /** + * Signals that tracing is stopped and there is no trace buffers pending flush, all data were + * delivered via dataCollected events. + */ + once(event: "NodeTracing.tracingComplete", listener: () => void): this; + + /** + * Issued when attached to a worker. + */ + once(event: "NodeWorker.attachedToWorker", listener: (message: InspectorNotification) => void): this; + + /** + * Issued when detached from the worker. + */ + once(event: "NodeWorker.detachedFromWorker", listener: (message: InspectorNotification) => void): this; + + /** + * Notifies about a new protocol message received from the session + * (session ID is provided in attachedToWorker notification). + */ + once(event: "NodeWorker.receivedMessageFromWorker", listener: (message: InspectorNotification) => void): this; + prependListener(event: string, listener: (...args: any[]) => void): this; /** @@ -2778,6 +2988,33 @@ declare module "inspector" { */ prependListener(event: "Runtime.inspectRequested", listener: (message: InspectorNotification) => void): this; + /** + * Contains an bucket of collected trace events. + */ + prependListener(event: "NodeTracing.dataCollected", listener: (message: InspectorNotification) => void): this; + + /** + * Signals that tracing is stopped and there is no trace buffers pending flush, all data were + * delivered via dataCollected events. + */ + prependListener(event: "NodeTracing.tracingComplete", listener: () => void): this; + + /** + * Issued when attached to a worker. + */ + prependListener(event: "NodeWorker.attachedToWorker", listener: (message: InspectorNotification) => void): this; + + /** + * Issued when detached from the worker. + */ + prependListener(event: "NodeWorker.detachedFromWorker", listener: (message: InspectorNotification) => void): this; + + /** + * Notifies about a new protocol message received from the session + * (session ID is provided in attachedToWorker notification). + */ + prependListener(event: "NodeWorker.receivedMessageFromWorker", listener: (message: InspectorNotification) => void): this; + prependOnceListener(event: string, listener: (...args: any[]) => void): this; /** @@ -2874,6 +3111,33 @@ declare module "inspector" { * call). */ prependOnceListener(event: "Runtime.inspectRequested", listener: (message: InspectorNotification) => void): this; + + /** + * Contains an bucket of collected trace events. + */ + prependOnceListener(event: "NodeTracing.dataCollected", listener: (message: InspectorNotification) => void): this; + + /** + * Signals that tracing is stopped and there is no trace buffers pending flush, all data were + * delivered via dataCollected events. + */ + prependOnceListener(event: "NodeTracing.tracingComplete", listener: () => void): this; + + /** + * Issued when attached to a worker. + */ + prependOnceListener(event: "NodeWorker.attachedToWorker", listener: (message: InspectorNotification) => void): this; + + /** + * Issued when detached from the worker. + */ + prependOnceListener(event: "NodeWorker.detachedFromWorker", listener: (message: InspectorNotification) => void): this; + + /** + * Notifies about a new protocol message received from the session + * (session ID is provided in attachedToWorker notification). + */ + prependOnceListener(event: "NodeWorker.receivedMessageFromWorker", listener: (message: InspectorNotification) => void): this; } // Top Level API diff --git a/types/node/node-tests.ts b/types/node/node-tests.ts index c0f80728fc..f1fd836697 100644 --- a/types/node/node-tests.ts +++ b/types/node/node-tests.ts @@ -4673,6 +4673,10 @@ import * as constants from 'constants'; const pauseReason: string = message.params.reason; }); session.on('Debugger.resumed', () => {}); + // Node Inspector events + session.on('NodeTracing.dataCollected', (message: inspector.InspectorNotification) => { + const value: Array<{}> = message.params.value; + }); } } diff --git a/types/node/scripts/generate-inspector/event-emitter.ts b/types/node/scripts/generate-inspector/event-emitter.ts index 658109d6b2..3d3410633f 100644 --- a/types/node/scripts/generate-inspector/event-emitter.ts +++ b/types/node/scripts/generate-inspector/event-emitter.ts @@ -66,10 +66,11 @@ export const createListeners = (events: Event[]): string[] => { "", ...createListenerBlockFn("prependOnceListener")(events), ].reduce((acc, next, index, arr) => { - // removes trailing and consecutive empty lines + // removes leading, trailing and consecutive empty lines + const isFirst = index === 0; const isLast = index === arr.length - 1; const followsEmptyLine = acc.length > 0 && acc[acc.length - 1] === ""; - if ((isLast || followsEmptyLine) && next === "") { + if ((isFirst || isLast || followsEmptyLine) && next === "") { return acc; } else { acc.push(next); diff --git a/types/node/scripts/generate-inspector/generate-substitute-args.ts b/types/node/scripts/generate-inspector/generate-substitute-args.ts index 0054e9a85c..cd12eff1a8 100644 --- a/types/node/scripts/generate-inspector/generate-substitute-args.ts +++ b/types/node/scripts/generate-inspector/generate-substitute-args.ts @@ -4,17 +4,21 @@ import { capitalize, createDocs, flattenArgs, hasElements, isObjectReference } f const INDENT = " "; +// Turns a type into a type representing an array of that type +const arrify = (typeString: string) => { + return typeString === '{}' ? `Array<${typeString}>` : `${typeString}[]`; +} + // Converts DevTools type to TS type const createTypeString = (type: schema.Field, domain?: string): string => { return isObjectReference(type) ? type.$ref : - type.type === "any" ? "any" : + type.type === "any" ? "any" : type.type === "integer" ? "number" : type.type === "number" ? "number" : type.type === "boolean" ? "boolean" : type.type === "string" ? "string" : - type.type === "array" ? `${createTypeString(type.items, domain)}[]` : - type.type === "object" ? "{}" // this code path is likely never exercised - : "never"; + type.type === "array" ? arrify(createTypeString(type.items, domain)) : + type.type === "object" ? "{}" : "never"; // "never" has yet to be observed }; // Helper for createInterface -- constructs a list of interface fields @@ -138,7 +142,7 @@ export const generateSubstituteArgs = (protocol: schema.Schema): { [propName: st .map(item => item.commands .map(command => createPostFunctions(command, item.domain)) .reduce(flattenArgs(""), [])) - .reduce(flattenArgs(), []); + .reduce(flattenArgs(""), []); const eventOverloads: string[] = createListeners(protocol.domains .map(item => { diff --git a/types/node/scripts/generate-inspector/index.ts b/types/node/scripts/generate-inspector/index.ts index eb5da5ef2e..f555105e37 100644 --- a/types/node/scripts/generate-inspector/index.ts +++ b/types/node/scripts/generate-inspector/index.ts @@ -2,25 +2,55 @@ // [tag] corresponds to a tag name in the node-core repository. import { execSync } from "child_process"; -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; +import { existsSync, readFileSync, writeFileSync } from "fs"; import * as https from "https"; import * as schema from "./devtools-protocol-schema"; import { generateSubstituteArgs } from "./generate-substitute-args"; -import { flattenArgs, substitute, trimRight } from "./utils"; +import { substitute, trimRight } from "./utils"; +import { string } from "parsimmon"; + +const httpsGet = (url: string) => new Promise((resolve, reject) => { + https.get(url, res => { + const frames: Buffer[] = []; + res.on("data", (data: Buffer) => { + frames.push(data); + }); + res.on("end", () => { + resolve(Buffer.concat(frames).toString("utf8")); + }); + res.on("error", (err: Error) => { + reject(err); + }); + }); +}); // Input arguments const tag = process.argv[2] || process.version; -const PROTOCOL_URL = `https://raw.githubusercontent.com/nodejs/node/${tag}/deps/v8/src/inspector/js_protocol.json`; - -const devToolsPath = `${__dirname}/../../node_modules/devtools-protocol`; - -function writeProtocolToFile(json: string) { - const protocol: schema.Schema = JSON.parse(json); +const V8_PROTOCOL_URL = `https://raw.githubusercontent.com/nodejs/node/${tag}/deps/v8/src/inspector/js_protocol.json`; +const NODE_PROTOCOL_URL = `https://raw.githubusercontent.com/nodejs/node/${tag}/src/inspector/node_protocol.pdl`; +const INSPECTOR_PROTOCOL_REMOTE = `https://chromium.googlesource.com/deps/inspector_protocol`; +const INSPECTOR_PROTOCOL_LOCAL_DIR = "/tmp/inspector_protocol"; +/** + * Given a list of Inspector protocol definitions, write an inspector.d.ts + * containing all of those definitions merged together. + * @param jsonProtocols A list of Inspector protocol definitions. + */ +function writeProtocolsToFile(jsonProtocols: string[]) { + const combinedProtocol: schema.Schema = { + version: { major: '', minor: '' }, // doesn't matter + domains: [] + }; + for (const json of jsonProtocols) { + if (json) { + const protocol: schema.Schema = JSON.parse(json); + combinedProtocol.domains.push(...protocol.domains); + } + } + const substituteArgs = generateSubstituteArgs(combinedProtocol); const template = readFileSync(`${__dirname}/inspector.d.ts.template`, "utf8"); - - const substituteArgs = generateSubstituteArgs(protocol); + const inspectorDts = substitute(template, substituteArgs).split("\n") .map(line => trimRight(line)) .join("\n"); @@ -28,12 +58,24 @@ function writeProtocolToFile(json: string) { writeFileSync("./inspector.d.ts", inspectorDts, "utf8"); } -https.get(PROTOCOL_URL, res => { - const frames: Buffer[] = []; - res.on("data", (data: Buffer) => { - frames.push(data); - }); - res.on("end", () => { - writeProtocolToFile(Buffer.concat(frames).toString("utf8")); - }); -}); +/** + * Given a PDL-formatted string, return a JSON-formatted string. + * Note that this function run blocking shell commands, as it depends on an + * external script to do the conversion. + * @param pdl A PDL-formatted string. + */ +function convertPdlToJson(pdl: string): string { + if (!existsSync(INSPECTOR_PROTOCOL_LOCAL_DIR)) { + execSync(`git clone ${INSPECTOR_PROTOCOL_REMOTE} ${INSPECTOR_PROTOCOL_LOCAL_DIR}`); + } + writeFileSync("/tmp/inspector_protocol.pdl", pdl); + execSync(`${INSPECTOR_PROTOCOL_LOCAL_DIR}/convert_protocol_to_json.py /tmp/inspector_protocol.pdl /tmp/inspector_protocol.json`); + return readFileSync("/tmp/inspector_protocol.json", 'utf8'); +} + +// "Main" -- get the V8 built-in inspector protocol definition, as well as the +// Node extensions, and then write this to inspector.d.ts. +Promise.all([ + httpsGet(V8_PROTOCOL_URL), + httpsGet(NODE_PROTOCOL_URL).then(convertPdlToJson).catch(() => '') +]).then(writeProtocolsToFile).catch(console.error);