diff --git a/.gitattributes b/.gitattributes index b27d219..6313b56 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1 @@ -* text=auto -*.zig text=auto eol=lf +* text=auto eol=lf diff --git a/.gitignore b/.gitignore index eec8995..6b430f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ -/zig-out -/zig-cache -/saved_logs -/repos +zig-out +zig-cache +.zig-cache +saved_logs +repos node_modules -/.env +.env d_*.log diff --git a/build.zig b/build.zig index 04a7d5b..a67b561 100644 --- a/build.zig +++ b/build.zig @@ -1,19 +1,31 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const zig_lsp = b.dependency("zig-lsp", .{}).module("zig-lsp"); - const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const block_len = b.option( + u8, + "block-len", + "how many bytes to consider when predicting the next character. " ++ + "defaults to 8. " ++ + "note: this may affect performance.", + ) orelse 8; + + const options = b.addOptions(); + options.addOption(u8, "block_len", block_len); + + const lsp_module = b.dependency("lsp-codegen", .{}).module("lsp"); + const exe = b.addExecutable(.{ .name = "sus", - .root_source_file = .{ .path = "src/main.zig" }, + .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); - exe.root_module.addImport("zig-lsp", zig_lsp); b.installArtifact(exe); + exe.root_module.addImport("lsp", lsp_module); + exe.root_module.addOptions("build_options", options); const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); @@ -23,25 +35,4 @@ pub fn build(b: *std.Build) void { const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); - - const build_exe_tests = b.addTest(.{ - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = .Debug, - }); - const run_exe_tests = b.addRunArtifact(build_exe_tests); - - const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&run_exe_tests.step); - - const block_len = b.option( - u8, - "block-len", - "how many bytes to consider when predicting the next character. " ++ - "defaults to 8. " ++ - "note: this may affect performance.", - ) orelse 8; - const options = b.addOptions(); - options.addOption(u8, "block_len", block_len); - exe.root_module.addOptions("build_options", options); } diff --git a/build.zig.zon b/build.zig.zon index f5a6c72..4d84dac 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,9 +2,9 @@ .name = "sus", .version = "0.1.0", .dependencies = .{ - .@"zig-lsp" = .{ - .url = "https://github.com/ziglibs/zig-lsp/archive/1c18c0c64b076e79385e525214a7acc4fdf7d398.tar.gz", - .hash = "12208c1385f4c1adca29fd17cc93397a60e54d5987bb27b9f961cb1945a96fc4a7e1", + .@"lsp-codegen" = .{ + .url = "git+https://github.com/zigtools/zig-lsp-codegen.git#13d1d9e44e1c602953437438b163950298ff8d85", + .hash = "1220518fd5cefa481497bb3484ffab48a42e69c31088e37f52ea361f9ce884d2131c", }, }, .paths = .{ diff --git a/server/index.ejs b/server/index.ejs new file mode 100644 index 0000000..85fea17 --- /dev/null +++ b/server/index.ejs @@ -0,0 +1,113 @@ + + + + + + sus + + + + +
+

Zig Language Server Fuzzer

+ +

Note that results are not expected to be long-lasting. When documenting a result, copy the reproduction steps.

+ + +
+ + + + \ No newline at end of file diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..8fee831 --- /dev/null +++ b/server/index.js @@ -0,0 +1,201 @@ +const express = require("express"); +const sqlite3 = require("better-sqlite3")(":memory:"); +const child_process = require("child_process"); +const axios = require("axios").default; +const fs = require("fs"); +const path = require("path"); + +function getHostZigName() { + let os = process.platform; + if (os == "darwin") os = "macos"; + if (os == "win32") os = "windows"; + let arch = process.arch; + if (arch == "ia32") arch = "x86"; + if (arch == "x64") arch = "x86_64"; + if (arch == "arm64") arch = "aarch64"; + if (arch == "ppc") arch = "powerpc"; + if (arch == "ppc64") arch = "powerpc64le"; + return `${os}-${arch}`; +} + +function getZigDownloadUrl(builtWithZigVersion) { + return `https://ziglang.org/builds/zig-${getHostZigName()}-${builtWithZigVersion}.${process.platform === "win32" ? "zip" : "tar.xz"}`; +} + +async function checkAndUpdateZigAndZls() { + const index = (await axios.get("https://zigtools-releases.nyc3.digitaloceanspaces.com/zls/index.json", { + responseType: "json", + })).data; + const latest = index.versions[index.latest]; + + const zigExePath = path.join(__dirname, "zig_install", `zig${process.platform === "win32" ? ".exe" : ""}`); + + var doWeNeedToInstallNewZig = true; + if (fs.existsSync(zigExePath)) { + const versionResult = child_process.spawnSync(zigExePath, ["version"]); + doWeNeedToInstallNewZig = versionResult.stdout.toString("ascii").trim() !== latest.builtWithZigVersion; + } + + if (doWeNeedToInstallNewZig) { + console.log("Installing new Zig version", latest.builtWithZigVersion); + fs.rmSync(path.join(__dirname, "zig_install"), { + force: true, + recursive: true, + }); + fs.mkdirSync(path.join(__dirname, "zig_install")); + + const tarball = (await axios.get(getZigDownloadUrl(latest.builtWithZigVersion), { + responseType: "arraybuffer" + })).data; + + const untar_result = child_process.spawnSync("tar", ["-xJf", "-", "-C", path.join(__dirname, "zig_install"), "--strip-components=1"], { + input: tarball + }); + + if (untar_result.status !== 0) throw "Failed to untar"; + } + + if (!fs.existsSync(path.join(__dirname, "zls_repo"))) { + const clone_result = child_process.spawnSync("git", ["clone", "https://github.com/zigtools/zls", "zls_repo"]); + if (clone_result.status !== 0) throw "Failed to clone"; + } + + const checkout_result = child_process.spawnSync("git", ["checkout", latest.commit], { + cwd: path.join(__dirname, "zls_repo"), + stdio: ["ignore", "inherit", "inherit"] + }); + if (checkout_result.status !== 0) throw "Failed to checkout"; + + const build_result = child_process.spawnSync(zigExePath, ["build"], { + cwd: path.join(__dirname, "zls_repo"), + stdio: ["ignore", "inherit", "inherit"] + }); + if (build_result.status !== 0) throw "Failed to build"; + + commit_hash = latest.commit; +} + +// TODO: handle multiple queued messages + +var commit_hash; +/** + * @type {child_process.ChildProcess | undefined} + */ +var fuzzer_process; +var remaining_length = 0; +var received_data = []; + +async function updateAllAndFuzz() { + if (fuzzer_process) { + fuzzer_process.kill("SIGKILL"); + } + + await checkAndUpdateZigAndZls(); + + fuzzer_process = child_process.spawn("../zig-out/bin/sus", ["--rpc"], { + stdio: ["pipe", "pipe", "ignore"], + env: { + zig_path: path.join(__dirname, "zig_install", `zig${process.platform === "win32" ? ".exe" : ""}`), + zls_path: path.join(__dirname, "zls_repo", "zig-out", "bin", `zls${process.platform === "win32" ? ".exe" : ""}`), + cycles_per_gen: "250", + mode: "markov", + markov_training_dir: path.join(__dirname, "zig_install", "lib", "std"), + ...process.env + } + }); + + fuzzer_process.stdout.on("data", data => { + if (!Buffer.isBuffer(data)) throw "expected buffer"; + + if (remaining_length === 0) { + remaining_length = data.readUInt32LE(); + + const d = data.subarray(4); + received_data.push(d); + remaining_length -= d.byteLength; + } else { + remaining_length -= data.byteLength; + received_data.push(data); + } + + if (remaining_length === 0) { + handleData(Buffer.concat(received_data)); + received_data = []; + } + + if (remaining_length < 0) { + console.error(remaining_length); + throw "too much data"; + } + }); +} + +sqlite3.exec(`CREATE TABLE entries ( + entry_id INTEGER PRIMARY KEY NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + zig_version VARCHAR(32) NOT NULL, + zls_version VARCHAR(32) NOT NULL, + zls_commit VARCHAR(40) NOT NULL, + + principal TEXT NOT NULL, + message TEXT NOT NULL, + stderr TEXT NOT NULL +);`); + +/** + * @param {Buffer} data + */ +function handleData(data) { + var offset = 0; + const timestamp = new Date(Number(data.readBigInt64LE())); + offset += 8; + + const zig_version_len = data.readInt8(offset); + offset += 1; + const zig_version = data.subarray(offset, offset + zig_version_len).toString("ascii"); + offset += zig_version_len; + + const zls_version_len = data.readInt8(offset); + offset += 1; + const zls_version = data.subarray(offset, offset + zls_version_len).toString("ascii"); + offset += zls_version_len; + + const principal_len = data.readUInt32LE(offset); + offset += 4; + const principal = data.subarray(offset, offset + principal_len).toString("utf8"); + offset += principal_len; + + const message_len = data.readUInt16LE(offset); + offset += 2; + const message = data.subarray(offset, offset + message_len).toString("utf8"); + offset += message_len; + + const stderr_len = data.readUInt16LE(offset); + offset += 2; + const stderr = data.subarray(offset, offset + stderr_len).toString("utf8"); + offset += stderr_len; + + sqlite3.prepare(` +INSERT INTO entries (created_at, zig_version, zls_version, zls_commit, principal, message, stderr) +VALUES (?, ?, ?, ?, ?, ?, ?); +`).bind([+timestamp, zig_version, zls_version, commit_hash, principal, message, stderr]).run(); +} + +const app = express(); +app.set("views", __dirname); + +app.get("/", (req, res) => { + res.render("index.ejs", { + entries: sqlite3.prepare("SELECT * from entries ORDER BY created_at DESC").all(), + }); +}); + +app.listen(3000, () => { + console.log("Server listening @ http://localhost:3000"); +}); + +updateAllAndFuzz(); + +setInterval(() => { + updateAllAndFuzz(); +}, 60_000); diff --git a/server/package-lock.json b/server/package-lock.json new file mode 100644 index 0000000..e0abfdd --- /dev/null +++ b/server/package-lock.json @@ -0,0 +1,1330 @@ +{ + "name": "server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "server", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "axios": "^1.6.7", + "better-sqlite3": "^9.4.3", + "ejs": "^3.1.9", + "express": "^4.18.2" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "dependencies": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/better-sqlite3": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.4.3.tgz", + "integrity": "sha512-ud0bTmD9O3uWJGuXDltyj3R47Nz0OHX8iqPOT5PMspGqlu/qQFn+5S2eFBUCrySpavTjFXbi4EgrfVvPAHlImw==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/ejs": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", + "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/jake": { + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.56.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.56.0.tgz", + "integrity": "sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "dependencies": { + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", + "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..a5be8e6 --- /dev/null +++ b/server/package.json @@ -0,0 +1,17 @@ +{ + "name": "server", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Auguste Rame", + "license": "MIT", + "dependencies": { + "axios": "^1.6.7", + "better-sqlite3": "^9.4.3", + "ejs": "^3.1.9", + "express": "^4.18.2" + } +} diff --git a/src/Fuzzer.zig b/src/Fuzzer.zig index a37ddc5..a9ac9bc 100644 --- a/src/Fuzzer.zig +++ b/src/Fuzzer.zig @@ -1,19 +1,16 @@ const std = @import("std"); -const lsp = @import("zig-lsp"); const utils = @import("utils.zig"); -const lsp_types = lsp.types; -const ChildProcess = std.ChildProcess; +const lsp = @import("lsp"); const Mode = @import("mode.zig").Mode; const ModeName = @import("mode.zig").ModeName; +const Reducer = @import("Reducer.zig"); const Fuzzer = @This(); -pub const Connection = lsp.Connection(std.fs.File.Reader, std.fs.File.Writer, Fuzzer); - // note: if you add or change config options, update the usage in main.zig then // run `zig build run -- --help` and paste the contents into the README pub const Config = struct { - output_as_dir: bool, + rpc: bool, zls_path: []const u8, mode_name: ModeName, cycles_per_gen: u32, @@ -22,7 +19,7 @@ pub const Config = struct { zls_version: []const u8, pub const Defaults = struct { - pub const output_as_dir = false; + pub const rpc = false; pub const cycles_per_gen: u32 = 25; }; @@ -43,100 +40,76 @@ pub const Config = struct { } }; +pub const SentMessage = struct { + id: i64, + start: u32, + end: u32, +}; + allocator: std.mem.Allocator, -connection: Connection, -progress_node: *std.Progress.Node, +progress_node: std.Progress.Node, mode: *Mode, config: Config, -rand: std.rand.DefaultPrng, +rand: std.Random.DefaultPrng, cycle: usize = 0, -zls_process: ChildProcess, -stderr_thread_keep_running: std.atomic.Value(bool) = std.atomic.Value(bool).init(true), -stderr_thread: std.Thread, +zls_process: std.process.Child, +id: i64 = 0, + +transport: lsp.TransportOverStdio, + +sent_data: std.ArrayListUnmanaged(u8) = .{}, +sent_messages: std.ArrayListUnmanaged(SentMessage) = .{}, +sent_ids: std.AutoArrayHashMapUnmanaged(i64, void) = .{}, -stdin_output: std.ArrayListUnmanaged(u8) = .{}, -stdout_output: std.ArrayListUnmanaged(u8) = .{}, -stderr_output: std.ArrayListUnmanaged(u8) = .{}, principal_file_source: []const u8 = "", principal_file_uri: []const u8, pub fn create( allocator: std.mem.Allocator, - progress: *std.Progress, + progress: std.Progress.Node, mode: *Mode, config: Config, + principal_file_uri: []const u8, ) !*Fuzzer { - var fuzzer = try allocator.create(Fuzzer); + const fuzzer = try allocator.create(Fuzzer); errdefer allocator.destroy(fuzzer); - var seed: u64 = 0; - try std.os.getrandom(std.mem.asBytes(&seed)); - - const cwd_path = try std.process.getCwdAlloc(allocator); - defer allocator.free(cwd_path); - - const principal_file_path = try std.fs.path.join(allocator, &.{ cwd_path, "tmp", "principal.zig" }); - defer allocator.free(principal_file_path); - - const principal_file_uri = try std.fmt.allocPrint(allocator, "{+/}", .{std.Uri{ - .scheme = "file", - .user = null, - .password = null, - .host = null, - .port = null, - .path = principal_file_path, - .query = null, - .fragment = null, - }}); - errdefer allocator.free(principal_file_uri); - - var env_map = try allocator.create(std.process.EnvMap); - env_map.* = std.process.getEnvMap(allocator) catch std.process.EnvMap.init(allocator); - try env_map.put("NO_COLOR", ""); + const seed = std.crypto.random.int(u64); - defer { - env_map.deinit(); - allocator.destroy(env_map); - } + var env_map = try std.process.getEnvMap(allocator); + defer env_map.deinit(); + + try env_map.put("NO_COLOR", ""); - var zls_process = std.ChildProcess.init(&.{ config.zls_path, "--enable-debug-log" }, allocator); - zls_process.env_map = env_map; + var zls_process = std.process.Child.init(&.{ config.zls_path, "--enable-debug-log" }, allocator); + zls_process.env_map = &env_map; zls_process.stdin_behavior = .Pipe; - zls_process.stderr_behavior = .Pipe; + zls_process.stderr_behavior = .Ignore; zls_process.stdout_behavior = .Pipe; try zls_process.spawn(); errdefer _ = zls_process.kill() catch @panic("failed to kill zls process"); + var sent_ids = std.AutoArrayHashMapUnmanaged(i64, void){}; + try sent_ids.ensureTotalCapacity(allocator, config.cycles_per_gen); + fuzzer.* = .{ .allocator = allocator, - .connection = undefined, // set below .progress_node = progress.start("fuzzer", 0), .mode = mode, .config = config, - .rand = std.rand.DefaultPrng.init(seed), + .rand = std.Random.DefaultPrng.init(seed), .zls_process = zls_process, - .stderr_thread = undefined, // set below + .transport = lsp.TransportOverStdio.init(zls_process.stdout.?, zls_process.stdin.?), + .sent_ids = sent_ids, .principal_file_uri = principal_file_uri, }; - fuzzer.connection = Connection.init( - allocator, - zls_process.stdout.?.reader(), - zls_process.stdin.?.writer(), - fuzzer, - ); - - fuzzer.stderr_thread = try std.Thread.spawn(.{}, readStderr, .{fuzzer}); - return fuzzer; } pub fn wait(fuzzer: *Fuzzer) void { - fuzzer.stderr_thread_keep_running.store(false, .Release); - fuzzer.stderr_thread.join(); - _ = fuzzer.zls_process.wait() catch |err| { std.log.err("failed to await zls process: {}", .{err}); }; @@ -145,54 +118,36 @@ pub fn wait(fuzzer: *Fuzzer) void { pub fn destroy(fuzzer: *Fuzzer) void { const allocator = fuzzer.allocator; - fuzzer.stdin_output.deinit(allocator); - fuzzer.stdout_output.deinit(allocator); - fuzzer.stderr_output.deinit(allocator); + fuzzer.sent_data.deinit(allocator); + fuzzer.sent_messages.deinit(allocator); + fuzzer.sent_ids.deinit(allocator); allocator.free(fuzzer.principal_file_source); - allocator.free(fuzzer.principal_file_uri); - - fuzzer.connection.write_buffer.deinit(fuzzer.connection.allocator); - fuzzer.connection.callback_map.deinit(fuzzer.connection.allocator); fuzzer.* = undefined; allocator.destroy(fuzzer); } -fn readStderr(fuzzer: *Fuzzer) void { - var buffer: [std.mem.page_size]u8 = undefined; - while (fuzzer.stderr_thread_keep_running.load(.Acquire)) { - const stderr = fuzzer.zls_process.stderr.?; - const amt = stderr.reader().read(&buffer) catch break; - fuzzer.stderr_output.appendSlice(fuzzer.allocator, buffer[0..amt]) catch break; - } -} - -pub fn random(fuzzer: *Fuzzer) std.rand.Random { +pub fn random(fuzzer: *Fuzzer) std.Random { return fuzzer.rand.random(); } pub fn initCycle(fuzzer: *Fuzzer) !void { - fuzzer.progress_node.activate(); - - var arena = std.heap.ArenaAllocator.init(fuzzer.allocator); - defer arena.deinit(); - - _ = try fuzzer.connection.requestSync(arena.allocator(), "initialize", lsp_types.InitializeParams{ + try fuzzer.sendRequest("initialize", lsp.types.InitializeParams{ .capabilities = .{}, }); - try fuzzer.connection.notify("initialized", .{}); + try fuzzer.sendNotification("initialized", .{}); var settings = std.json.ObjectMap.init(fuzzer.allocator); defer settings.deinit(); try settings.putNoClobber("skip_std_references", .{ .bool = true }); // references collection into std is very slow try settings.putNoClobber("zig_exe_path", .{ .string = fuzzer.config.zig_env.value.zig_exe }); - try fuzzer.connection.notify("workspace/didChangeConfiguration", lsp_types.DidChangeConfigurationParams{ + try fuzzer.sendNotification("workspace/didChangeConfiguration", lsp.types.DidChangeConfigurationParams{ .settings = .{ .object = settings }, }); - try fuzzer.connection.notify("textDocument/didOpen", lsp_types.DidOpenTextDocumentParams{ .textDocument = .{ + try fuzzer.sendNotification("textDocument/didOpen", lsp.types.DidOpenTextDocumentParams{ .textDocument = .{ .uri = fuzzer.principal_file_uri, .languageId = "zig", .version = @intCast(fuzzer.cycle), @@ -203,30 +158,31 @@ pub fn initCycle(fuzzer: *Fuzzer) !void { pub fn closeCycle(fuzzer: *Fuzzer) !void { fuzzer.progress_node.end(); - var arena = std.heap.ArenaAllocator.init(fuzzer.allocator); - defer arena.deinit(); - - _ = try fuzzer.connection.notify("textDocument/didClose", .{ + _ = try fuzzer.sendNotification("textDocument/didClose", .{ .textDocument = .{ .uri = fuzzer.principal_file_uri }, }); - _ = try fuzzer.connection.requestSync(arena.allocator(), "shutdown", {}); - try fuzzer.connection.notify("exit", {}); + _ = try fuzzer.sendRequest("shutdown", {}); + try fuzzer.sendNotification("exit", {}); +} + +pub fn reduce(fuzzer: *Fuzzer) !void { + var reducer = Reducer.fromFuzzer(fuzzer); + defer reducer.deinit(); + + try reducer.reduce(); } pub fn fuzz(fuzzer: *Fuzzer) !void { - fuzzer.progress_node.setCompletedItems(fuzzer.cycle); fuzzer.cycle += 1; if (fuzzer.cycle % fuzzer.config.cycles_per_gen == 0) { - var arena_allocator = std.heap.ArenaAllocator.init(fuzzer.allocator); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - while (fuzzer.connection.callback_map.count() != 0) { - _ = arena_allocator.reset(.retain_capacity); - try fuzzer.connection.acceptUntilResponse(arena); - } + // detch from cycle count to prevent pipe fillage on windows + try utils.waitForResponseToRequests( + fuzzer.allocator, + &fuzzer.transport, + &fuzzer.sent_ids, + ); while (true) { fuzzer.allocator.free(fuzzer.principal_file_source); @@ -234,80 +190,20 @@ pub fn fuzz(fuzzer: *Fuzzer) !void { if (std.unicode.utf8ValidateSlice(fuzzer.principal_file_source)) break; } - try fuzzer.connection.notify("textDocument/didChange", lsp_types.DidChangeTextDocumentParams{ + fuzzer.sent_data.clearRetainingCapacity(); + fuzzer.sent_messages.clearRetainingCapacity(); + std.debug.assert(fuzzer.sent_ids.count() == 0); + + try fuzzer.sendNotification("textDocument/didChange", lsp.types.DidChangeTextDocumentParams{ .textDocument = .{ .uri = fuzzer.principal_file_uri, .version = @intCast(fuzzer.cycle) }, - .contentChanges = &[1]lsp_types.TextDocumentContentChangeEvent{ + .contentChanges = &[1]lsp.types.TextDocumentContentChangeEvent{ .{ .literal_1 = .{ .text = fuzzer.principal_file_source } }, }, }); } - try fuzzer.fuzzFeatureRandom(fuzzer.principal_file_uri, fuzzer.principal_file_source); -} - -pub fn logPrincipal(fuzzer: *Fuzzer) !void { - var bytes: [32]u8 = undefined; - fuzzer.random().bytes(&bytes); - - try std.fs.cwd().makePath("saved_logs"); - - const log_entry_path = try std.fmt.allocPrint(fuzzer.allocator, "saved_logs/{d}", .{std.fmt.fmtSliceHexLower(&bytes)}); - defer fuzzer.allocator.free(log_entry_path); - - if (fuzzer.config.output_as_dir) { - try std.fs.cwd().makeDir(log_entry_path); - - var entry_dir = try std.fs.cwd().openDir(log_entry_path, .{}); - defer entry_dir.close(); - const principal_file = try entry_dir.createFile("principal.zig", .{}); - defer principal_file.close(); - - try principal_file.writeAll(fuzzer.principal_file_source); - - for ( - [_]std.ArrayListUnmanaged(u8){ fuzzer.stdin_output, fuzzer.stdout_output, fuzzer.stderr_output }, - [_][]const u8{ "stdin.log", "stdout.log", "stderr.log" }, - ) |output, path| { - const output_file = try entry_dir.createFile(path, .{}); - defer output_file.close(); - - try output_file.writeAll(output.items); - } - } else { - const entry_file = try std.fs.cwd().createFile(log_entry_path, .{}); - defer entry_file.close(); - - var iovecs: [13]std.os.iovec_const = undefined; - - for ([_][]const u8{ - std.mem.asBytes(&std.time.milliTimestamp()), - - std.mem.asBytes(&@as(u8, @intCast(fuzzer.config.zig_env.value.version.len))), - fuzzer.config.zig_env.value.version, - - std.mem.asBytes(&@as(u8, @intCast(fuzzer.config.zls_version.len))), - fuzzer.config.zls_version, - - std.mem.asBytes(&@as(u32, @intCast(fuzzer.principal_file_source.len))), - fuzzer.principal_file_source, - - std.mem.asBytes(&@as(u32, @intCast(fuzzer.stdin_output.items.len))), - fuzzer.stdin_output.items, - - std.mem.asBytes(&@as(u32, @intCast(fuzzer.stdout_output.items.len))), - fuzzer.stdout_output.items, - - std.mem.asBytes(&@as(u32, @intCast(fuzzer.stderr_output.items.len))), - fuzzer.stderr_output.items, - }, 0..) |val, i| { - iovecs[i] = .{ - .iov_base = val.ptr, - .iov_len = val.len, - }; - } - - try entry_file.writevAll(&iovecs); - } + try fuzzer.fuzzFeatureRandom(fuzzer.principal_file_uri, fuzzer.principal_file_source); + fuzzer.progress_node.completeOne(); } pub const WhatToFuzz = enum { @@ -329,29 +225,6 @@ pub const WhatToFuzz = enum { rename, }; -fn requestCallback(comptime method: []const u8) lsp.RequestCallback(Connection, method) { - const Context = struct { - pub fn res(_: *Connection, _: lsp.Result(method)) !void {} - - pub fn err(_: *Connection, resperr: lsp_types.ResponseError) !void { - return switch (resperr.code) { - @intFromEnum(lsp_types.ErrorCodes.ParseError) => error.ParseError, - @intFromEnum(lsp_types.ErrorCodes.InvalidRequest) => error.InvalidRequest, - @intFromEnum(lsp_types.ErrorCodes.MethodNotFound) => error.MethodNotFound, - @intFromEnum(lsp_types.ErrorCodes.InvalidParams) => error.InvalidParams, - @intFromEnum(lsp_types.ErrorCodes.InternalError) => error.InternalError, - @intFromEnum(lsp_types.ErrorCodes.ServerNotInitialized) => error.ServerNotInitialized, - @intFromEnum(lsp_types.ErrorCodes.UnknownErrorCode) => error.UnknownErrorCode, - else => error.InternalError, - }; - } - }; - return .{ - .onResponse = &Context.res, - .onError = &Context.err, - }; -} - pub fn fuzzFeatureRandom( fuzzer: *Fuzzer, file_uri: []const u8, @@ -361,89 +234,78 @@ pub fn fuzzFeatureRandom( const wtf = rand.enumValue(WhatToFuzz); switch (wtf) { - .completion => try fuzzer.connection.request( + .completion => try fuzzer.sendRequest( "textDocument/completion", .{ .textDocument = .{ .uri = file_uri }, .position = utils.randomPosition(rand, file_data), }, - requestCallback("textDocument/completion"), ), - .declaration => try fuzzer.connection.request( + .declaration => try fuzzer.sendRequest( "textDocument/declaration", .{ .textDocument = .{ .uri = file_uri }, .position = utils.randomPosition(rand, file_data), }, - requestCallback("textDocument/declaration"), ), - .definition => try fuzzer.connection.request( + .definition => try fuzzer.sendRequest( "textDocument/definition", .{ .textDocument = .{ .uri = file_uri }, .position = utils.randomPosition(rand, file_data), }, - requestCallback("textDocument/definition"), ), - .type_definition => try fuzzer.connection.request( + .type_definition => try fuzzer.sendRequest( "textDocument/typeDefinition", .{ .textDocument = .{ .uri = file_uri }, .position = utils.randomPosition(rand, file_data), }, - requestCallback("textDocument/typeDefinition"), ), - .implementation => try fuzzer.connection.request( + .implementation => try fuzzer.sendRequest( "textDocument/implementation", .{ .textDocument = .{ .uri = file_uri }, .position = utils.randomPosition(rand, file_data), }, - requestCallback("textDocument/implementation"), ), - .references => try fuzzer.connection.request( + .references => try fuzzer.sendRequest( "textDocument/references", .{ .context = .{ .includeDeclaration = rand.boolean() }, .textDocument = .{ .uri = file_uri }, .position = utils.randomPosition(rand, file_data), }, - requestCallback("textDocument/references"), ), - .signature_help => try fuzzer.connection.request( + .signature_help => try fuzzer.sendRequest( "textDocument/signatureHelp", .{ .textDocument = .{ .uri = file_uri }, .position = utils.randomPosition(rand, file_data), }, - requestCallback("textDocument/signatureHelp"), ), - .hover => try fuzzer.connection.request( + .hover => try fuzzer.sendRequest( "textDocument/hover", .{ .textDocument = .{ .uri = file_uri }, .position = utils.randomPosition(rand, file_data), }, - requestCallback("textDocument/hover"), ), - .semantic => try fuzzer.connection.request( + .semantic => try fuzzer.sendRequest( "textDocument/semanticTokens/full", .{ .textDocument = .{ .uri = file_uri } }, - requestCallback("textDocument/semanticTokens/full"), ), - .document_symbol => try fuzzer.connection.request( + .document_symbol => try fuzzer.sendRequest( "textDocument/documentSymbol", .{ .textDocument = .{ .uri = file_uri } }, - requestCallback("textDocument/documentSymbol"), ), .folding_range => { - _ = try fuzzer.connection.request( + _ = try fuzzer.sendRequest( "textDocument/foldingRange", .{ .textDocument = .{ .uri = file_uri } }, - requestCallback("textDocument/foldingRange"), ); }, - .formatting => try fuzzer.connection.request( + .formatting => try fuzzer.sendRequest( "textDocument/formatting", .{ .textDocument = .{ .uri = file_uri }, @@ -452,90 +314,63 @@ pub fn fuzzFeatureRandom( .insertSpaces = true, }, }, - requestCallback("textDocument/formatting"), ), - .document_highlight => try fuzzer.connection.request( + .document_highlight => try fuzzer.sendRequest( "textDocument/documentHighlight", .{ .textDocument = .{ .uri = file_uri }, .position = utils.randomPosition(rand, file_data), }, - requestCallback("textDocument/documentHighlight"), ), - .inlay_hint => try fuzzer.connection.request( + .inlay_hint => try fuzzer.sendRequest( "textDocument/inlayHint", .{ .textDocument = .{ .uri = file_uri }, .range = utils.randomRange(rand, file_data), }, - requestCallback("textDocument/inlayHint"), ), - // TODO: Nest positions properly to avoid crash - // .selection_range => { - // var positions: [16]lsp_types.Position = undefined; - // for (positions) |*pos| { - // pos.* = utils.randomPosition(rand, file_data); - // } - // try fuzzer.connection.request( - // "textDocument/selectionRange", - // .{ - // .textDocument = .{ .uri = file_uri }, - // .positions = &positions, - // }, - // requestCallback("textDocument/selectionRange"), - // ); - // }, - .rename => try fuzzer.connection.request( + .rename => try fuzzer.sendRequest( "textDocument/rename", .{ .textDocument = .{ .uri = file_uri }, .position = utils.randomPosition(rand, file_data), .newName = "helloWorld", }, - requestCallback("textDocument/rename"), ), } } -// Handlers +fn sendRequest(fuzzer: *Fuzzer, comptime method: []const u8, params: lsp.ParamsType(method)) !void { + const start = fuzzer.sent_data.items.len; -pub fn @"window/logMessage"(_: *Connection, params: lsp.Params("window/logMessage")) !void { - switch (params.type) { - .Error => std.log.err("logMessage: {s}", .{params.message}), - .Warning => std.log.warn("logMessage: {s}", .{params.message}), - .Info => std.log.info("logMessage: {s}", .{params.message}), - .Log => std.log.debug("logMessage: {s}", .{params.message}), - } -} + const request_id = fuzzer.id; -pub fn @"window/showMessage"(_: *Connection, params: lsp.Params("window/showMessage")) !void { - switch (params.type) { - .Error => std.log.err("showMessage: {s}", .{params.message}), - .Warning => std.log.warn("showMessage: {s}", .{params.message}), - .Info => std.log.info("showMessage: {s}", .{params.message}), - .Log => std.log.debug("showMessage: {s}", .{params.message}), - } -} + try utils.stringifyRequest( + fuzzer.sent_data.writer(fuzzer.allocator), + &fuzzer.id, + method, + params, + ); -pub fn @"textDocument/publishDiagnostics"(_: *Connection, _: lsp.Params("textDocument/publishDiagnostics")) !void {} -pub fn @"workspace/semanticTokens/refresh"(_: *Connection, _: lsp.types.RequestId, _: lsp.Params("workspace/semanticTokens/refresh")) !void {} + try fuzzer.transport.writeJsonMessage(fuzzer.sent_data.items[start..]); -pub fn dataRecv( - conn: *Connection, - data: []const u8, -) !void { - const fuzzer: *Fuzzer = conn.context; - try fuzzer.stdout_output.ensureUnusedCapacity(fuzzer.allocator, data.len + 1); - fuzzer.stdout_output.appendSliceAssumeCapacity(data); - fuzzer.stdout_output.appendAssumeCapacity('\n'); + try fuzzer.sent_messages.append(fuzzer.allocator, .{ + .id = request_id, + .start = @intCast(start), + .end = @intCast(fuzzer.sent_data.items.len), + }); + + fuzzer.sent_ids.putAssumeCapacityNoClobber(request_id, {}); } -pub fn dataSend( - conn: *Connection, - data: []const u8, -) !void { - const fuzzer: *Fuzzer = conn.context; - try fuzzer.stdin_output.ensureUnusedCapacity(fuzzer.allocator, data.len + 1); - fuzzer.stdin_output.appendSliceAssumeCapacity(data); - fuzzer.stdin_output.appendAssumeCapacity('\n'); +fn sendNotification(fuzzer: *Fuzzer, comptime method: []const u8, params: lsp.ParamsType(method)) !void { + const start = fuzzer.sent_data.items.len; + + try utils.stringifyNotification( + fuzzer.sent_data.writer(fuzzer.allocator), + method, + params, + ); + + try fuzzer.transport.writeJsonMessage(fuzzer.sent_data.items[start..]); } diff --git a/src/Reducer.zig b/src/Reducer.zig new file mode 100644 index 0000000..90a4251 --- /dev/null +++ b/src/Reducer.zig @@ -0,0 +1,265 @@ +const std = @import("std"); + +const utils = @import("utils.zig"); +const Fuzzer = @import("Fuzzer.zig"); +const lsp = @import("lsp"); + +const Reducer = @This(); + +allocator: std.mem.Allocator, +sent_data: []const u8, +sent_messages: []const Fuzzer.SentMessage, +principal_file_source: []const u8, +principal_file_uri: []const u8, +config: Fuzzer.Config, +random: std.Random, + +zls_process: std.process.Child, +transport: lsp.TransportOverStdio, +id: i64 = 0, +write_buffer: std.ArrayListUnmanaged(u8) = .{}, + +pub fn fromFuzzer(fuzzer: *Fuzzer) Reducer { + return .{ + .allocator = fuzzer.allocator, + .sent_data = fuzzer.sent_data.items, + .sent_messages = fuzzer.sent_messages.items, + .config = fuzzer.config, + .principal_file_source = fuzzer.principal_file_source, + .principal_file_uri = fuzzer.principal_file_uri, + .random = fuzzer.random(), + + .zls_process = undefined, + .transport = undefined, + }; +} + +pub fn deinit(reducer: *Reducer) void { + reducer.write_buffer.deinit(reducer.allocator); +} + +const Message = struct { id: i64, data: []const u8 }; +fn message(reducer: *Reducer, index: u32) Message { + const msg = reducer.sent_messages[index]; + return .{ + .id = msg.id, + .data = reducer.sent_data[msg.start..msg.end], + }; +} + +/// Creates new process and does LSP init; does not kill old one +fn createNewProcessAndInitialize(reducer: *Reducer) !void { + reducer.id = 0; + + var env_map = try std.process.getEnvMap(reducer.allocator); + defer env_map.deinit(); + + try env_map.put("NO_COLOR", ""); + + var zls_process = std.process.Child.init(&.{ reducer.config.zls_path, "--enable-debug-log" }, reducer.allocator); + zls_process.env_map = &env_map; + zls_process.stdin_behavior = .Pipe; + zls_process.stderr_behavior = .Pipe; + zls_process.stdout_behavior = .Pipe; + + try zls_process.spawn(); + errdefer _ = zls_process.kill() catch @panic("failed to kill zls process"); + + reducer.zls_process = zls_process; + reducer.transport = lsp.TransportOverStdio.init(reducer.zls_process.stdout.?, reducer.zls_process.stdin.?); + + try reducer.sendRequest("initialize", lsp.types.InitializeParams{ + .capabilities = .{}, + }); + try reducer.sendNotification("initialized", .{}); + + var settings = std.json.ObjectMap.init(reducer.allocator); + defer settings.deinit(); + try settings.putNoClobber("skip_std_references", .{ .bool = true }); // references collection into std is very slow + try settings.putNoClobber("zig_exe_path", .{ .string = reducer.config.zig_env.value.zig_exe }); + + try reducer.sendNotification("workspace/didChangeConfiguration", lsp.types.DidChangeConfigurationParams{ + .settings = .{ .object = settings }, + }); + + try reducer.sendNotification("textDocument/didOpen", lsp.types.DidOpenTextDocumentParams{ .textDocument = .{ + .uri = reducer.principal_file_uri, + .languageId = "zig", + .version = 0, + .text = reducer.principal_file_source, + } }); +} + +fn shutdownProcessCleanly(reducer: *Reducer) !void { + _ = try reducer.sendNotification("textDocument/didClose", .{ + .textDocument = .{ .uri = reducer.principal_file_uri }, + }); + + _ = try reducer.sendRequest("shutdown", {}); + try reducer.sendNotification("exit", {}); +} + +pub fn reduce(reducer: *Reducer) !void { + try reducer.createNewProcessAndInitialize(); + + var stderr = std.ArrayListUnmanaged(u8){}; + defer stderr.deinit(reducer.allocator); + + var keep_running_stderr = std.atomic.Value(bool).init(true); + const stderr_thread = try std.Thread.spawn(.{}, readStderr, .{ + reducer.allocator, + reducer.zls_process.stderr.?, + &stderr, + &keep_running_stderr, + }); + + const msg: Message = for (0..reducer.sent_messages.len) |msg_idx| { + const msg = reducer.message(@intCast(msg_idx)); + reducer.repeatMessage(msg) catch { + keep_running_stderr.store(false, .release); + stderr_thread.join(); + _ = try reducer.zls_process.wait(); + break msg; + }; + } else { + std.log.err("Could not reproduce!", .{}); + try reducer.shutdownProcessCleanly(); + keep_running_stderr.store(false, .release); + stderr_thread.join(); + _ = try reducer.zls_process.wait(); + return; + }; + + const processed_stderr = if (std.mem.indexOf(u8, stderr.items, "panic:")) |panic_start| + stderr.items[panic_start..] + else + stderr.items; + + if (reducer.config.rpc) { + var iovecs: [12]std.posix.iovec_const = undefined; + + for ([_][]const u8{ + std.mem.asBytes(&@as(u32, @intCast( + 8 + + 1 + reducer.config.zig_env.value.version.len + + 1 + reducer.config.zls_version.len + + 4 + reducer.principal_file_source.len + + 2 + msg.data.len + + 2 + processed_stderr.len, + ))), + + std.mem.asBytes(&std.time.milliTimestamp()), + + std.mem.asBytes(&@as(u8, @intCast(reducer.config.zig_env.value.version.len))), + reducer.config.zig_env.value.version, + + std.mem.asBytes(&@as(u8, @intCast(reducer.config.zls_version.len))), + reducer.config.zls_version, + + std.mem.asBytes(&@as(u32, @intCast(reducer.principal_file_source.len))), + reducer.principal_file_source, + + std.mem.asBytes(&@as(u16, @intCast(msg.data.len))), + msg.data, + + std.mem.asBytes(&@as(u16, @intCast(processed_stderr.len))), + processed_stderr, + }, 0..) |val, i| { + iovecs[i] = .{ .base = val.ptr, .len = val.len }; + } + + try std.io.getStdOut().writevAll(&iovecs); + } else { + var bytes: [32]u8 = undefined; + reducer.random.bytes(&bytes); + + try std.fs.cwd().makePath("saved_logs"); + + const log_entry_path = try std.fmt.allocPrint(reducer.allocator, "saved_logs/{d}", .{std.fmt.fmtSliceHexLower(&bytes)}); + defer reducer.allocator.free(log_entry_path); + + const entry_file = try std.fs.cwd().createFile(log_entry_path, .{}); + defer entry_file.close(); + + var timestamp_buf: [32]u8 = undefined; + var iovecs: [16]std.posix.iovec_const = undefined; + + for ([_][]const u8{ + "timestamp: ", + try std.fmt.bufPrint(×tamp_buf, "{d}", .{std.time.milliTimestamp()}), + "\n", + "zig version: ", + reducer.config.zig_env.value.version, + "\nzls version: ", + reducer.config.zls_version, + "\n\n", + "principal:\n", + reducer.principal_file_source, + "\n\n", + "message:\n", + msg.data, + "\n\n", + "stderr:\n", + processed_stderr, + }, &iovecs) |val, *iovec| { + iovec.* = .{ .base = val.ptr, .len = val.len }; + } + + try entry_file.writevAll(&iovecs); + } +} + +fn repeatMessage(reducer: *Reducer, msg: Message) !void { + try reducer.transport.writeJsonMessage(msg.data); + + try utils.waitForResponseToRequest( + reducer.allocator, + &reducer.transport, + msg.id, + ); +} + +fn sendRequest(reducer: *Reducer, comptime method: []const u8, params: lsp.ParamsType(method)) !void { + const request_id = reducer.id; + reducer.write_buffer.clearRetainingCapacity(); + + try utils.stringifyRequest( + reducer.write_buffer.writer(reducer.allocator), + &reducer.id, + method, + params, + ); + + try reducer.transport.writeJsonMessage(reducer.write_buffer.items); + + try utils.waitForResponseToRequest( + reducer.allocator, + &reducer.transport, + request_id, + ); +} + +fn sendNotification(reducer: *Reducer, comptime method: []const u8, params: lsp.ParamsType(method)) !void { + reducer.write_buffer.clearRetainingCapacity(); + + try utils.stringifyNotification( + reducer.write_buffer.writer(reducer.allocator), + method, + params, + ); + + try reducer.transport.writeJsonMessage(reducer.write_buffer.items); +} + +fn readStderr( + allocator: std.mem.Allocator, + stderr: std.fs.File, + out: *std.ArrayListUnmanaged(u8), + keep_running: *std.atomic.Value(bool), +) void { + var buffer: [4096]u8 = undefined; + while (keep_running.load(.acquire)) { + const amt = stderr.read(&buffer) catch break; + out.appendSlice(allocator, buffer[0..amt]) catch break; + } +} diff --git a/src/main.zig b/src/main.zig index c864c21..19a88a3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -46,18 +46,18 @@ fn initConfig(allocator: std.mem.Allocator, env_map: std.process.EnvMap, arg_it: }; defer if (maybe_zig_path) |path| allocator.free(path); - var output_as_dir = - if (env_map.get("output_as_dir")) |str| + var rpc = + if (env_map.get("rpc")) |str| if (std.mem.eql(u8, str, "false")) false else if (std.mem.eql(u8, str, "true")) true else blk: { - std.log.warn("expected boolean (true|false) in env option 'output_as_dir' but got '{s}'", .{str}); - break :blk Fuzzer.Config.Defaults.output_as_dir; + std.log.warn("expected boolean (true|false) in env option 'rpc' but got '{s}'", .{str}); + break :blk Fuzzer.Config.Defaults.rpc; } else - Fuzzer.Config.Defaults.output_as_dir; + Fuzzer.Config.Defaults.rpc; var mode_name: ?ModeName = blk: { if (env_map.get("mode")) |mode_name| { @@ -91,8 +91,8 @@ fn initConfig(allocator: std.mem.Allocator, env_map: std.process.EnvMap, arg_it: if (std.mem.eql(u8, arg, "--help")) { try std.io.getStdOut().writeAll(usage); std.process.exit(0); - } else if (std.mem.eql(u8, arg, "--output-as-dir")) { - output_as_dir = true; + } else if (std.mem.eql(u8, arg, "--rpc")) { + rpc = true; } else if (std.mem.eql(u8, arg, "--zls-path")) { if (maybe_zls_path) |path| { allocator.free(path); @@ -179,7 +179,7 @@ fn initConfig(allocator: std.mem.Allocator, env_map: std.process.EnvMap, arg_it: errdefer zig_env.deinit(); return .{ - .output_as_dir = output_as_dir, + .rpc = rpc, .zls_path = zls_path, .mode_name = mode, .cycles_per_gen = cycles_per_gen, @@ -189,7 +189,7 @@ fn initConfig(allocator: std.mem.Allocator, env_map: std.process.EnvMap, arg_it: }; } -// note: if you change this text, run `zig build run -- --help` and paste the contents into the README +// if you change this text, run `zig build run -- --help` and paste the contents into the README const usage = std.fmt.comptimePrint( \\sus - ZLS fuzzing tooling @@ -202,7 +202,7 @@ const usage = \\General Options: \\ --help Print this help and exit \\ --mode [mode] Specify fuzzing mode - one of {s} - \\ --output-as-dir Output fuzzing results as directories (default: {s}) + \\ --rpc Use RPC mode (default: {}) \\ --zls-path [path] Specify path to ZLS executable (default: Search in PATH) \\ --zig-path [path] Specify path to Zig executable (default: Search in PATH) \\ --cycles-per-gen How many times to fuzz a random feature before regenerating a new file. (default: {d}) @@ -211,8 +211,8 @@ const usage = \\For a listing of build options, use 'zig build --help'. \\ , .{ - if (Fuzzer.Config.Defaults.output_as_dir) "true" else "false", std.meta.fieldNames(ModeName).*, + Fuzzer.Config.Defaults.rpc, Fuzzer.Config.Defaults.cycles_per_gen, }); @@ -268,11 +268,10 @@ pub fn main() !void { var config = try initConfig(gpa, env_map, &arg_it); defer config.deinit(gpa); - var progress = std.Progress{ - .terminal = null, - }; + var progress = std.Progress.start(.{}); + defer progress.end(); - progress.log( + std.debug.print( \\zig-version: {s} \\zls-version: {s} \\zig-path: {s} @@ -289,11 +288,29 @@ pub fn main() !void { config.cycles_per_gen, }); - var mode = try Mode.init(config.mode_name, gpa, &progress, &arg_it, env_map); + var mode = try Mode.init(config.mode_name, gpa, progress, &arg_it, env_map); defer mode.deinit(gpa); + const cwd_path = try std.process.getCwdAlloc(gpa); + defer gpa.free(cwd_path); + + const principal_file_path = try std.fs.path.join(gpa, &.{ cwd_path, "tmp", "principal.zig" }); + defer gpa.free(principal_file_path); + + const principal_file_uri = try std.fmt.allocPrint(gpa, "{}", .{std.Uri{ + .scheme = "file", + .path = .{ .raw = principal_file_path }, + }}); + defer gpa.free(principal_file_uri); + while (true) { - var fuzzer = try Fuzzer.create(gpa, &progress, &mode, config); + var fuzzer = try Fuzzer.create( + gpa, + progress, + &mode, + config, + principal_file_uri, + ); errdefer { fuzzer.wait(); fuzzer.destroy(); @@ -302,10 +319,8 @@ pub fn main() !void { try fuzzer.initCycle(); while (true) { - progress.maybeRefresh(); - if (fuzzer.cycle >= 100_000) { - progress.log("Fuzzer running too long with no result... restarting\n", .{}); + std.debug.print("Fuzzer running too long with no result... restarting\n", .{}); try fuzzer.closeCycle(); fuzzer.wait(); @@ -314,13 +329,14 @@ pub fn main() !void { } fuzzer.fuzz() catch { - progress.log("Restarting fuzzer...\n", .{}); + std.debug.print("Reducing...\n", .{}); fuzzer.wait(); - fuzzer.logPrincipal() catch { - progress.log("failed to log principal\n", .{}); - }; + try fuzzer.reduce(); fuzzer.destroy(); + + std.debug.print("Restarting fuzzer...\n", .{}); + break; }; } diff --git a/src/mode.zig b/src/mode.zig index 9457c7b..d35ac3a 100644 --- a/src/mode.zig +++ b/src/mode.zig @@ -8,7 +8,7 @@ pub const Mode = union(enum) { pub fn init( mode_name: ModeName, allocator: std.mem.Allocator, - progress: *std.Progress, + progress: std.Progress.Node, arg_it: *std.process.ArgIterator, envmap: std.process.EnvMap, ) !Mode { diff --git a/src/modes/BestBehavior.zig b/src/modes/BestBehavior.zig index e952e5d..384fa39 100644 --- a/src/modes/BestBehavior.zig +++ b/src/modes/BestBehavior.zig @@ -1,9 +1,8 @@ const std = @import("std"); -const utils = @import("../utils.zig"); const BestBehavior = @This(); -random: std.rand.DefaultPrng, +random: std.Random.DefaultPrng, tests: std.ArrayListUnmanaged([]const u8), const usage = @@ -24,18 +23,17 @@ fn fatal(comptime format: []const u8, args: anytype) noreturn { pub fn init( allocator: std.mem.Allocator, - progress: *std.Progress, + progress: std.Progress.Node, arg_it: *std.process.ArgIterator, envmap: std.process.EnvMap, ) !*BestBehavior { var bb = try allocator.create(BestBehavior); errdefer allocator.destroy(bb); - var seed: u64 = 0; - try std.os.getrandom(std.mem.asBytes(&seed)); + const seed = std.crypto.random.int(u64); bb.* = .{ - .random = std.rand.DefaultPrng.init(seed), + .random = std.Random.DefaultPrng.init(seed), .tests = .{}, }; errdefer bb.deinit(allocator); @@ -58,7 +56,7 @@ pub fn init( fatalWithUsage("missing mode argument '--source-dir'", .{}); } - progress.log( + std.debug.print( \\ \\source-dir: {s} \\ @@ -92,7 +90,6 @@ pub fn init( var file_buf = std.ArrayListUnmanaged(u8){}; defer file_buf.deinit(allocator); - progress_node.activate(); while (try walker.next()) |entry| { if (entry.kind != .file) continue; if (!std.mem.eql(u8, std.fs.path.extension(entry.basename), ".zig")) continue; diff --git a/src/modes/MarkovMode.zig b/src/modes/MarkovMode.zig index 6ac19bb..850c162 100644 --- a/src/modes/MarkovMode.zig +++ b/src/modes/MarkovMode.zig @@ -7,7 +7,7 @@ const Markov = @This(); const MarkovModel = markov.Model(build_options.block_len, false); model: MarkovModel, -random: std.rand.DefaultPrng, +random: std.Random.DefaultPrng, maxlen: u32 = Defaults.maxlen, const Defaults = struct { @@ -36,22 +36,19 @@ fn fatal(comptime format: []const u8, args: anytype) noreturn { pub fn init( allocator: std.mem.Allocator, - progress: *std.Progress, + progress: std.Progress.Node, arg_it: *std.process.ArgIterator, envmap: std.process.EnvMap, ) !*Markov { - var seed: u64 = 0; - try std.os.getrandom(std.mem.asBytes(&seed)); + const seed = std.crypto.random.int(u64); var mm = try allocator.create(Markov); mm.* = .{ - .model = undefined, // set below - .random = std.rand.DefaultPrng.init(seed), + .model = .{ .allocator = allocator }, + .random = std.Random.DefaultPrng.init(seed), }; errdefer mm.deinit(allocator); - mm.model = MarkovModel.init(allocator, mm.random.random()); - var training_dir: ?[]const u8 = envmap.get("markov_training_dir"); if (envmap.get("markov_maxlen")) |str| { @@ -80,7 +77,7 @@ pub fn init( fatalWithUsage("missing mode argument '--training-dir'", .{}); } - progress.log( + std.debug.print( \\ \\training-dir: {s} \\maxlen: {d} @@ -112,7 +109,6 @@ pub fn init( var file_buf = std.ArrayListUnmanaged(u8){}; defer file_buf.deinit(allocator); - progress_node.activate(); while (try walker.next()) |entry| { if (entry.kind != .file) continue; if (!std.mem.eql(u8, std.fs.path.extension(entry.basename), ".zig")) continue; @@ -146,6 +142,6 @@ pub fn deinit(mm: *Markov, allocator: std.mem.Allocator) void { pub fn gen(mm: *Markov, allocator: std.mem.Allocator) ![]const u8 { var buffer = try std.ArrayListUnmanaged(u8).initCapacity(allocator, mm.maxlen); errdefer buffer.deinit(allocator); - try mm.model.gen(buffer.writer(allocator), .{ .maxlen = mm.maxlen }); + try mm.model.gen(mm.random.random(), buffer.writer(allocator), .{ .maxlen = mm.maxlen }); return try buffer.toOwnedSlice(allocator); } diff --git a/src/modes/markov.zig b/src/modes/markov.zig index 3c726f1..3e3681f 100644 --- a/src/modes/markov.zig +++ b/src/modes/markov.zig @@ -151,15 +151,11 @@ pub fn Model(comptime byte_len: comptime_int, comptime debug: bool) type { return struct { table: Table = .{}, allocator: mem.Allocator, - rand: std.rand.Random, pub const Iter = Iterator(byte_len); pub const Table = std.AutoArrayHashMapUnmanaged(Iter.Block, Follows); const Self = @This(); - pub fn init(allocator: mem.Allocator, rand: std.rand.Random) Self { - return .{ .allocator = allocator, .rand = rand }; - } pub fn deinit(self: *Self, allocator: mem.Allocator) void { var iter = self.table.iterator(); while (iter.next()) |*m| m.value_ptr.deinit(allocator); @@ -195,6 +191,7 @@ pub fn Model(comptime byte_len: comptime_int, comptime debug: bool) type { /// previous input to feed(). pub fn gen( self: *Self, + random: std.Random, writer: anytype, options: GenOptions, ) !void { @@ -204,7 +201,7 @@ pub fn Model(comptime byte_len: comptime_int, comptime debug: bool) type { else start_block) else blk: { - const id = self.rand.intRangeLessThan(usize, 0, self.table.count()); + const id = random.intRangeLessThan(usize, 0, self.table.count()); break :blk self.table.keys()[id]; }; _ = try writer.write(&start_block); @@ -227,7 +224,7 @@ pub fn Model(comptime byte_len: comptime_int, comptime debug: bool) type { }; // pick a random item - var r = self.rand.intRangeAtMost(usize, 0, follows.count()); + var r = random.intRangeAtMost(usize, 0, follows.count()); const first_follow = follows.constItems()[0]; var c = first_follow.char; if (debug) std.debug.print("follows {any} r {}\n", .{ follows.constItems(), r }); @@ -250,7 +247,7 @@ pub fn Model(comptime byte_len: comptime_int, comptime debug: bool) type { // var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); // const allr = arena.allocator(); // var argit = try std.process.ArgIterator.initWithAllocator(allr); -// var prng = std.rand.DefaultPrng.init(@bitCast(u64, std.time.milliTimestamp())); +// var prng = std.Random.DefaultPrng.init(@bitCast(u64, std.time.milliTimestamp())); // const M = Model(build_options.block_len, false); // var model = M.init(allr, prng.random()); // _ = argit.next(); diff --git a/src/utils.zig b/src/utils.zig index b9b0577..e7c16fc 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -1,6 +1,5 @@ const std = @import("std"); -const lsp = @import("zig-lsp"); -const lsp_types = lsp.types; +const lsp = @import("lsp"); /// Use after `isArrayList` and/or `isHashMap` pub fn isManaged(comptime T: type) bool { @@ -60,7 +59,7 @@ pub fn isHashMap(comptime T: type) bool { pub fn randomize( comptime T: type, allocator: std.mem.Allocator, - random: std.rand.Random, + random: std.Random, ) anyerror!T { if (T == std.json.Value) { const Valids = enum { @@ -138,12 +137,12 @@ pub fn randomize( }; } -pub fn randomPosition(random: std.rand.Random, data: []const u8) lsp_types.Position { +pub fn randomPosition(random: std.Random, data: []const u8) lsp.types.Position { // TODO: Consider offsets const line_count = std.mem.count(u8, data, "\n"); const line = if (line_count == 0) 0 else random.intRangeLessThan(usize, 0, line_count); - var lines = std.mem.split(u8, data, "\n"); + var lines = std.mem.splitScalar(u8, data, '\n'); var character: usize = 0; @@ -161,7 +160,7 @@ pub fn randomPosition(random: std.rand.Random, data: []const u8) lsp_types.Posit }; } -pub fn randomRange(random: std.rand.Random, data: []const u8) lsp_types.Range { +pub fn randomRange(random: std.Random, data: []const u8) lsp.types.Range { const a = randomPosition(random, data); const b = randomPosition(random, data); @@ -169,3 +168,88 @@ pub fn randomRange(random: std.rand.Random, data: []const u8) lsp_types.Range { return if (is_a_first) .{ .start = a, .end = b } else .{ .start = b, .end = a }; } + +pub fn stringifyRequest( + writer: anytype, + id: *i64, + comptime method: []const u8, + params: lsp.ParamsType(method), +) !void { + try std.json.stringify(.{ + .jsonrpc = "2.0", + .id = id.*, + .method = method, + .params = switch (@TypeOf(params)) { + void => .{}, + ?void => null, + else => params, + }, + }, .{}, writer); + id.* +%= 1; +} + +pub fn stringifyNotification( + writer: anytype, + comptime method: []const u8, + params: lsp.ParamsType(method), +) !void { + try std.json.stringify(.{ + .jsonrpc = "2.0", + .method = method, + .params = params, + }, .{}, writer); +} + +pub fn waitForResponseToRequest( + allocator: std.mem.Allocator, + transport: *lsp.TransportOverStdio, + id: i64, +) !void { + while (true) { + const json_message = try transport.readJsonMessage(allocator); + defer allocator.free(json_message); + + const result = try std.json.parseFromSlice( + struct { id: ?lsp.JsonRPCMessage.ID }, + allocator, + json_message, + .{ + .ignore_unknown_fields = true, + }, + ); + + defer result.deinit(); + + if (result.value.id) |received_id| { + if (received_id == .number and received_id.number == id) { + return; + } + } + } +} + +pub fn waitForResponseToRequests( + allocator: std.mem.Allocator, + transport: *lsp.TransportOverStdio, + ids: *std.AutoArrayHashMapUnmanaged(i64, void), +) !void { + while (ids.count() != 0) { + const json_message = try transport.readJsonMessage(allocator); + defer allocator.free(json_message); + + const result = try std.json.parseFromSlice( + struct { id: ?lsp.JsonRPCMessage.ID }, + allocator, + json_message, + .{ + .ignore_unknown_fields = true, + }, + ); + defer result.deinit(); + + if (result.value.id) |received_id| { + if (received_id != .number) continue; + std.debug.assert(ids.swapRemove(received_id.number)); + } + } +}