delete legacy node test runner (#14572)

This commit is contained in:
Meghan Denny
2024-10-14 17:31:34 -07:00
committed by GitHub
parent 355dc56db0
commit ae0106b651
11 changed files with 0 additions and 1082 deletions

View File

@@ -1,6 +0,0 @@
# Paths copied from Node.js repository
upstream/
# Paths for test runner
summary/
summary.md

View File

@@ -1 +0,0 @@
upstream/

View File

@@ -1,2 +0,0 @@
[test]
preload = ["./common/preload.js"]

View File

@@ -1,273 +0,0 @@
import { expect } from "bun:test";
function deepEqual(actual, expected, message) {
if (isIgnored(expected, message)) {
return;
}
try {
expect(actual).toEqual(expected);
} catch (cause) {
throwError(cause, message);
}
}
function deepStrictEqual(actual, expected, message) {
if (isIgnored(expected, message)) {
return;
}
try {
expect(actual).toStrictEqual(expected);
} catch (cause) {
throwError(cause, message);
}
}
function doesNotMatch(string, regexp, message) {
if (isIgnored(regexp, message)) {
return;
}
try {
expect(string).not.toMatch(regexp);
} catch (cause) {
throwError(cause, message);
}
}
function doesNotReject(asyncFn, error, message) {
if (isIgnored(error, message)) {
return;
}
try {
expect(asyncFn).rejects.toThrow(error);
} catch (cause) {
throwError(cause, message);
}
}
function doesNotThrow(fn, error, message) {
if (isIgnored(error, message)) {
return;
}
todo("doesNotThrow");
}
function equal(actual, expected, message) {
if (isIgnored(expected, message)) {
return;
}
try {
expect(actual).toBe(expected);
} catch (cause) {
throwError(cause, message);
}
}
function fail(actual, expected, message, operator, stackStartFn) {
if (isIgnored(expected, message)) {
return;
}
todo("fail");
}
function ifError(value) {
if (isIgnored(value)) {
return;
}
todo("ifError");
}
function match(string, regexp, message) {
if (isIgnored(regexp, message)) {
return;
}
try {
expect(string).toMatch(regexp);
} catch (cause) {
throwError(cause, message);
}
}
function notDeepEqual(actual, expected, message) {
if (isIgnored(expected, message)) {
return;
}
todo("notDeepEqual");
}
function notDeepStrictEqual(actual, expected, message) {
if (isIgnored(expected, message)) {
return;
}
todo("notDeepStrictEqual");
}
function notEqual(actual, expected, message) {
if (isIgnored(expected, message)) {
return;
}
try {
expect(actual).not.toBe(expected);
} catch (cause) {
throwError(cause, message);
}
}
function notStrictEqual(actual, expected, message) {
if (isIgnored(expected, message)) {
return;
}
try {
expect(actual).not.toStrictEqual(expected);
} catch (cause) {
throwError(cause, message);
}
}
function ok(value, message) {
if (isIgnored(message)) {
return;
}
equal(!!value, true, message);
}
function rejects(asyncFn, error, message) {
if (isIgnored(error, message)) {
return;
}
todo("rejects");
}
function strictEqual(actual, expected, message) {
if (isIgnored(expected, message)) {
return;
}
try {
expect(actual).toBe(expected);
} catch (cause) {
throwError(cause, message);
}
}
function throws(fn, error, message) {
try {
let result;
try {
result = fn();
} catch (cause) {
const matcher = toErrorMatcher(error);
expect(cause).toEqual(matcher);
return;
}
expect(result).toBe("Expected function to throw an error, instead it returned");
} catch (cause) {
throwError(cause, message);
}
}
function toErrorMatcher(expected) {
let message;
if (typeof expected === "string") {
message = expected;
} else if (expected instanceof RegExp) {
message = expected.source;
} else if (typeof expected === "object") {
message = expected.message;
}
for (const [expected, actual] of similarErrors) {
if (message && expected.test(message)) {
message = actual;
break;
}
}
if (!message) {
return expect.anything();
}
if (typeof expected === "object") {
return expect.objectContaining({
...expected,
message: expect.stringMatching(message),
});
}
return expect.stringMatching(message);
}
const similarErrors = [
[/Invalid typed array length/i, /length too large/i],
[/Unknown encoding/i, /Invalid encoding/i],
[
/The ".*" argument must be of type string or an instance of Buffer or ArrayBuffer/i,
/Invalid input, must be a string, Buffer, or ArrayBuffer/i,
],
[/The ".*" argument must be an instance of Buffer or Uint8Array./i, /Expected Buffer/i],
[/The ".*" argument must be an instance of Array./i, /Argument must be an array/i],
[/The value of ".*" is out of range./i, /Offset is out of bounds/i],
[/Attempt to access memory outside buffer bounds/i, /Out of bounds access/i],
];
const ignoredExpectations = [
// Reason: Bun has a nicer format for `Buffer.inspect()`.
/^<Buffer /,
];
function isIgnored(...expectations) {
for (const expected of expectations) {
let query;
if (typeof expected === "string") {
query = expected;
} else if (expected instanceof RegExp) {
query = expected.source;
} else {
continue;
}
for (const pattern of ignoredExpectations) {
if (pattern.test(query)) {
console.warn("Ignoring expectation:", expected);
return true;
}
}
}
return false;
}
function throwError(error, message) {
if (isIgnored(error, message)) {
return;
}
if (typeof message === "string") {
const gray = "\x1b[90m";
const reset = "\x1b[0m";
error.message += `\n${gray}note: ${message}${reset}`;
}
throw error;
}
function todo(name) {
throw new Error(`TODO: ${name}`);
}
export default ok;
export {
deepEqual,
deepStrictEqual,
doesNotMatch,
doesNotReject,
doesNotThrow,
equal,
fail,
ifError,
match,
notDeepEqual,
notDeepStrictEqual,
notEqual,
notStrictEqual,
ok,
rejects,
strictEqual,
throws,
};

View File

@@ -1,122 +0,0 @@
// https://github.com/nodejs/node/blob/c975384264dc553de62398be814d0c66fc1fc1fb/test/common/index.js
import { inspect } from "bun";
import { expect, afterAll } from "bun:test";
const hasIntl = true;
const hasCrypto = true;
const hasOpenSSL3 = false;
const hasOpenSSL31 = false;
const hasQuic = false;
const { platform, env } = process;
const isWindows = platform === "win32";
const isSunOS = platform === "sunos";
const isFreeBSD = platform === "freebsd";
const isOpenBSD = platform === "openbsd";
const isLinux = platform === "linux";
const isOSX = platform === "darwin";
const isAsan = false;
const isPi = false;
const isDumbTerminal = env.TERM === "dumb";
function mustCall(fn, n = 1) {
const callSite = getCallSite(mustCall);
let calls = 0;
const mustCallFn = function (...args) {
calls++;
return fn.apply(this, args);
};
afterAll(() => {
if (calls !== n) {
throw new Error(`function should be called exactly ${n} times:\n ${callSite}`);
}
});
return mustCallFn;
}
function mustNotCall() {
const callSite = getCallSite(mustNotCall);
return function mustNotCall(...args) {
const argsInfo = args.length > 0 ? `\ncalled with arguments: ${args.map(arg => inspect(arg)).join(", ")}` : "";
assert.fail(`${msg || "function should not have been called"} at ${callSite}` + argsInfo);
};
}
function printSkipMessage(message) {
console.warn(message);
}
function skip(message) {
printSkipMessage(message);
process.exit(0);
}
function expectsError(validator, exact) {
return mustCall((...args) => {
if (args.length !== 1) {
// Do not use `assert.strictEqual()` to prevent `inspect` from
// always being called.
assert.fail(`Expected one argument, got ${inspect(args)}`);
}
const error = args.pop();
// The error message should be non-enumerable
assert.strictEqual(Object.prototype.propertyIsEnumerable.call(error, "message"), false);
assert.throws(() => {
throw error;
}, validator);
return true;
}, exact);
}
function expectWarning(name, code, message) {
// Do nothing
}
function invalidArgTypeHelper(input) {
return ` Received: ${inspect(input)}`;
}
function getCallSite(fn) {
const originalStackFormatter = Error.prepareStackTrace;
Error.prepareStackTrace = (_, stack) => `${stack[0].getFileName()}:${stack[0].getLineNumber()}`;
const error = new Error();
Error.captureStackTrace(error, fn);
error.stack; // With the V8 Error API, the stack is not formatted until it is accessed
Error.prepareStackTrace = originalStackFormatter;
return error.stack;
}
export {
hasIntl,
hasCrypto,
hasOpenSSL3,
hasOpenSSL31,
hasQuic,
// ...
isWindows,
isSunOS,
isFreeBSD,
isOpenBSD,
isLinux,
isOSX,
isAsan,
isPi,
// ...
isDumbTerminal,
// ...
mustCall,
mustNotCall,
printSkipMessage,
skip,
expectsError,
expectWarning,
// ...
inspect,
invalidArgTypeHelper,
};

View File

@@ -1,10 +0,0 @@
const { mock } = require("bun:test");
const assert = require("./assert");
mock.module("assert", () => {
return assert;
});
mock.module("internal/test/binding", () => {
return {};
});

View File

@@ -1,32 +0,0 @@
import { spawnSync } from "node:child_process";
const isBun = !!process.isBun;
const os = process.platform === "win32" ? "windows" : process.platform;
const arch = process.arch === "arm64" ? "aarch64" : process.arch;
const version = isBun ? Bun.version : process.versions.node;
const revision = isBun ? Bun.revision : undefined;
const baseline = (() => {
if (!isBun || arch !== "x64") {
return undefined;
}
const { stdout } = spawnSync(process.execPath, ["--print", "Bun.unsafe.segfault()"], {
encoding: "utf8",
timeout: 5_000,
});
if (stdout.includes("baseline")) {
return true;
}
return undefined;
})();
const name = baseline ? `bun-${os}-${arch}-baseline` : `${isBun ? "bun" : "node"}-${os}-${arch}`;
console.log(
JSON.stringify({
name,
os,
arch,
version,
revision,
baseline,
}),
);

View File

@@ -1,6 +0,0 @@
{
"private": true,
"scripts": {
"test": "node runner.mjs --exec-path $(which bun-debug || which bun)"
}
}

View File

@@ -1,437 +0,0 @@
import { parseArgs } from "node:util";
import { spawnSync } from "node:child_process";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, writeFileSync, appendFileSync, realpathSync } from "node:fs";
import { tmpdir } from "node:os";
import { basename, join } from "node:path";
import readline from "node:readline/promises";
const testPath = new URL("./", import.meta.url);
const nodePath = new URL("upstream/", testPath);
const nodeTestPath = new URL("test/", nodePath);
const metadataScriptPath = new URL("metadata.mjs", testPath);
const testJsonPath = new URL("tests.json", testPath);
const summariesPath = new URL("summary/", testPath);
const summaryMdPath = new URL("summary.md", testPath);
const cwd = new URL("../../", testPath);
async function main() {
const { values, positionals } = parseArgs({
allowPositionals: true,
options: {
help: {
type: "boolean",
short: "h",
},
baseline: {
type: "boolean",
},
interactive: {
type: "boolean",
short: "i",
},
"exec-path": {
type: "string",
},
pull: {
type: "boolean",
},
summary: {
type: "boolean",
},
},
});
if (values.help) {
printHelp();
return;
}
if (values.summary) {
printSummary();
return;
}
if (values.pull) {
pullTests(true);
return;
}
pullTests();
const summary = await runTests(values, positionals);
const regressedTests = appendSummary(summary);
printSummary(summary, regressedTests);
process.exit(regressedTests?.length ? 1 : 0);
}
function printHelp() {
console.log(`Usage: ${process.argv0} ${basename(import.meta.filename)} [options]`);
console.log();
console.log("Options:");
console.log(" -h, --help Show this help message");
console.log(" -e, --exec-path Path to the bun executable to run");
console.log(" -i, --interactive Pause and wait for input after a failing test");
console.log(" -s, --summary Print a summary of the tests (does not run tests)");
}
function pullTests(force) {
if (!force && existsSync(nodeTestPath)) {
return;
}
console.log("Pulling tests...");
const { status, error, stderr } = spawnSync(
"git",
["submodule", "update", "--init", "--recursive", "--progress", "--depth=1", "--checkout", "upstream"],
{
cwd: testPath,
stdio: "inherit",
},
);
if (error || status !== 0) {
throw error || new Error(stderr);
}
for (const { filename, status } of getTests(nodeTestPath)) {
if (status === "TODO") {
continue;
}
const src = new URL(filename, nodeTestPath);
const dst = new URL(filename, testPath);
try {
writeFileSync(dst, readFileSync(src));
} catch (error) {
if (error.code === "ENOENT") {
mkdirSync(new URL(".", dst), { recursive: true });
writeFileSync(dst, readFileSync(src));
} else {
throw error;
}
}
}
}
async function runTests(options, filters) {
const { interactive } = options;
const bunPath = process.isBun ? process.execPath : "bun";
const execPath = options["exec-path"] || bunPath;
let reader;
if (interactive) {
reader = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
}
const results = [];
const tests = getTests(testPath);
for (const { label, filename, status: filter } of tests) {
if (filters?.length && !filters.some(filter => label?.includes(filter))) {
continue;
}
if (filter !== "OK") {
results.push({ label, filename, status: filter });
continue;
}
const { pathname: filePath } = new URL(filename, testPath);
const tmp = tmpdirSync();
const timestamp = Date.now();
const {
status: exitCode,
signal: signalCode,
error: spawnError,
} = spawnSync(execPath, ["test", filePath], {
cwd: testPath,
stdio: "inherit",
env: {
PATH: process.env.PATH,
HOME: tmp,
TMPDIR: tmp,
TZ: "Etc/UTC",
FORCE_COLOR: "1",
BUN_DEBUG_QUIET_LOGS: "1",
BUN_GARBAGE_COLLECTOR_LEVEL: "1",
BUN_RUNTIME_TRANSPILER_CACHE_PATH: "0",
GITHUB_ACTIONS: "false", // disable for now
},
timeout: 30_000,
});
const duration = Math.ceil(Date.now() - timestamp);
const status = exitCode === 0 ? "PASS" : "FAIL";
let error;
if (signalCode) {
error = signalCode;
} else if (spawnError) {
const { message } = spawnError;
if (message.includes("timed out") || message.includes("timeout")) {
error = "TIMEOUT";
} else {
error = message;
}
} else if (exitCode !== 0) {
error = `code ${exitCode}`;
}
results.push({ label, filename, status, error, timestamp, duration });
if (reader && status === "FAIL") {
const answer = await reader.question("Continue? [Y/n] ");
if (answer.toUpperCase() !== "Y") {
break;
}
}
}
reader?.close();
return {
v: 1,
metadata: getMetadata(execPath),
tests: results,
};
}
function getTests(filePath) {
const tests = [];
const testData = JSON.parse(readFileSync(testJsonPath, "utf8"));
for (const filename of readdirSync(filePath, { recursive: true })) {
if (!isJavaScript(filename) || !isTest(filename)) {
continue;
}
let match;
for (const { label, pattern, skip: skipList = [], todo: todoList = [] } of testData) {
if (!filename.startsWith(pattern)) {
continue;
}
if (skipList.some(({ file }) => filename.endsWith(file))) {
tests.push({ label, filename, status: "SKIP" });
} else if (todoList.some(({ file }) => filename.endsWith(file))) {
tests.push({ label, filename, status: "TODO" });
} else {
tests.push({ label, filename, status: "OK" });
}
match = true;
break;
}
if (!match) {
tests.push({ filename, status: "TODO" });
}
}
return tests;
}
function appendSummary(summary) {
const { metadata, tests, ...extra } = summary;
const { name } = metadata;
const summaryPath = new URL(`${name}.json`, summariesPath);
const summaryData = {
metadata,
tests: tests.map(({ label, filename, status, error }) => ({ label, filename, status, error })),
...extra,
};
const regressedTests = [];
if (existsSync(summaryPath)) {
const previousData = JSON.parse(readFileSync(summaryPath, "utf8"));
const { v } = previousData;
if (v === 1) {
const { tests: previousTests } = previousData;
for (const { label, filename, status, error } of tests) {
if (status !== "FAIL") {
continue;
}
const previousTest = previousTests.find(({ filename: file }) => file === filename);
if (previousTest) {
const { status: previousStatus } = previousTest;
if (previousStatus !== "FAIL") {
regressedTests.push({ label, filename, error });
}
}
}
}
}
if (regressedTests.length) {
return regressedTests;
}
const summaryText = JSON.stringify(summaryData, null, 2);
try {
writeFileSync(summaryPath, summaryText);
} catch (error) {
if (error.code === "ENOENT") {
mkdirSync(summariesPath, { recursive: true });
writeFileSync(summaryPath, summaryText);
} else {
throw error;
}
}
}
function printSummary(summaryData, regressedTests) {
let metadataInfo = {};
let testInfo = {};
let labelInfo = {};
let errorInfo = {};
const summaryList = [];
if (summaryData) {
summaryList.push(summaryData);
} else {
for (const filename of readdirSync(summariesPath)) {
if (!filename.endsWith(".json")) {
continue;
}
const summaryPath = new URL(filename, summariesPath);
const summaryData = JSON.parse(readFileSync(summaryPath, "utf8"));
summaryList.push(summaryData);
}
}
for (const summaryData of summaryList) {
const { v, metadata, tests } = summaryData;
if (v !== 1) {
continue;
}
const { name, version, revision } = metadata;
if (revision) {
metadataInfo[name] =
`${version}-[\`${revision.slice(0, 7)}\`](https://github.com/oven-sh/bun/commit/${revision})`;
} else {
metadataInfo[name] = `${version}`;
}
for (const test of tests) {
const { label, filename, status, error } = test;
if (label) {
labelInfo[label] ||= { pass: 0, fail: 0, skip: 0, todo: 0, total: 0 };
labelInfo[label][status.toLowerCase()] += 1;
labelInfo[label].total += 1;
}
testInfo[name] ||= { pass: 0, fail: 0, skip: 0, todo: 0, total: 0 };
testInfo[name][status.toLowerCase()] += 1;
testInfo[name].total += 1;
if (status === "FAIL") {
errorInfo[filename] ||= {};
errorInfo[filename][name] = error;
}
}
}
let summaryMd = `## Node.js tests
`;
if (!summaryData) {
summaryMd += `
| Platform | Conformance | Passed | Failed | Skipped | Total |
| - | - | - | - | - | - |
`;
for (const [name, { pass, fail, skip, total }] of Object.entries(testInfo)) {
testInfo[name].coverage = (((pass + fail + skip) / total) * 100).toFixed(2);
testInfo[name].conformance = ((pass / total) * 100).toFixed(2);
}
for (const [name, { conformance, pass, fail, skip, total }] of Object.entries(testInfo)) {
summaryMd += `| \`${name}\` ${metadataInfo[name]} | ${conformance} % | ${pass} | ${fail} | ${skip} | ${total} |\n`;
}
}
summaryMd += `
| API | Conformance | Passed | Failed | Skipped | Total |
| - | - | - | - | - | - |
`;
for (const [label, { pass, fail, skip, total }] of Object.entries(labelInfo)) {
labelInfo[label].coverage = (((pass + fail + skip) / total) * 100).toFixed(2);
labelInfo[label].conformance = ((pass / total) * 100).toFixed(2);
}
for (const [label, { conformance, pass, fail, skip, total }] of Object.entries(labelInfo)) {
summaryMd += `| \`${label}\` | ${conformance} % | ${pass} | ${fail} | ${skip} | ${total} |\n`;
}
if (!summaryData) {
writeFileSync(summaryMdPath, summaryMd);
}
const githubSummaryPath = process.env.GITHUB_STEP_SUMMARY;
if (githubSummaryPath) {
appendFileSync(githubSummaryPath, summaryMd);
}
console.log("=".repeat(process.stdout.columns));
console.log("Summary by platform:");
console.table(testInfo);
console.log("Summary by label:");
console.table(labelInfo);
if (regressedTests?.length) {
const isTty = process.stdout.isTTY;
if (isTty) {
process.stdout.write("\x1b[31m");
}
const { name } = summaryData.metadata;
console.log(`Regressions found in ${regressedTests.length} tests for ${name}:`);
console.table(regressedTests);
if (isTty) {
process.stdout.write("\x1b[0m");
}
}
}
function isJavaScript(filename) {
return /\.(m|c)?js$/.test(filename);
}
function isTest(filename) {
return /^test-/.test(basename(filename));
}
function getMetadata(execPath) {
const { pathname: filePath } = metadataScriptPath;
const { status: exitCode, stdout } = spawnSync(execPath, [filePath], {
cwd,
stdio: ["ignore", "pipe", "ignore"],
env: {
PATH: process.env.PATH,
BUN_DEBUG_QUIET_LOGS: "1",
},
timeout: 5_000,
});
if (exitCode === 0) {
try {
return JSON.parse(stdout);
} catch {
// Ignore
}
}
return {
os: process.platform,
arch: process.arch,
};
}
function tmpdirSync(pattern = "bun.test.") {
return mkdtempSync(join(realpathSync(tmpdir()), pattern));
}
main().catch(error => {
console.error(error);
process.exit(1);
});

View File

@@ -1,166 +0,0 @@
[
{
"label": "node:buffer",
"pattern": "parallel/test-buffer",
"skip": [
{
"file": "backing-arraybuffer.js",
"reason": "Internal binding checks if the buffer is on the heap"
}
],
"todo": [
{
"file": "constants.js",
"reason": "Hangs"
},
{
"file": "tostring-rangeerror.js",
"reason": "Hangs"
}
]
},
{
"label": "node:path",
"pattern": "parallel/test-path"
},
{
"label": "node:child_process",
"pattern": "parallel/test-child-process"
},
{
"label": "node:async_hooks",
"pattern": "parallel/test-async-hooks"
},
{
"label": "node:crypto",
"pattern": "parallel/test-crypto"
},
{
"label": "node:dgram",
"pattern": "parallel/test-dgram"
},
{
"label": "node:diagnostics_channel",
"pattern": "parallel/test-diagnostics-channel"
},
{
"label": "node:fs",
"pattern": "parallel/test-fs"
},
{
"label": "node:dns",
"pattern": "parallel/test-dns"
},
{
"label": "node:domain",
"pattern": "parallel/test-domain"
},
{
"label": "node:events",
"pattern": "parallel/test-event-emitter"
},
{
"label": "node:http",
"pattern": "parallel/test-http"
},
{
"label": "node:http2",
"pattern": "parallel/test-http2"
},
{
"label": "node:https",
"pattern": "parallel/test-https"
},
{
"label": "node:net",
"pattern": "parallel/test-net"
},
{
"label": "node:os",
"pattern": "parallel/test-os"
},
{
"label": "process",
"pattern": "parallel/test-process"
},
{
"label": "node:stream",
"pattern": "parallel/test-stream"
},
{
"label": "node:stream",
"pattern": "parallel/test-readable"
},
{
"label": "node:timers",
"pattern": "parallel/test-timers"
},
{
"label": "node:timers",
"pattern": "parallel/test-next-tick"
},
{
"label": "node:tls",
"pattern": "parallel/test-tls"
},
{
"label": "node:tty",
"pattern": "parallel/test-tty"
},
{
"label": "node:url",
"pattern": "parallel/test-url"
},
{
"label": "node:util",
"pattern": "parallel/test-util"
},
{
"label": "node:trace_events",
"pattern": "parallel/test-trace-events"
},
{
"label": "node:vm",
"pattern": "parallel/test-vm"
},
{
"label": "node:zlib",
"pattern": "parallel/test-zlib"
},
{
"label": "node:worker_threads",
"pattern": "parallel/test-worker"
},
{
"label": "node:readline",
"pattern": "parallel/test-readline"
},
{
"label": "web:crypto",
"pattern": "parallel/test-webcrypto"
},
{
"label": "web:streams",
"pattern": "parallel/test-webstream"
},
{
"label": "web:streams",
"pattern": "parallel/test-whatwg-webstreams"
},
{
"label": "web:encoding",
"pattern": "parallel/test-whatwg-encoding"
},
{
"label": "web:url",
"pattern": "parallel/test-whatwg-url"
},
{
"label": "web:websocket",
"pattern": "parallel/test-websocket"
},
{
"label": "web:performance",
"pattern": "parallel/test-performance"
}
]

View File

@@ -1,27 +0,0 @@
{
"include": [".", "../../packages/bun-types/index.d.ts"],
"compilerOptions": {
"lib": ["ESNext"],
"module": "ESNext",
"target": "ESNext",
"moduleResolution": "bundler",
"moduleDetection": "force",
"allowImportingTsExtensions": true,
"experimentalDecorators": true,
"noEmit": true,
"composite": true,
"strict": true,
"downlevelIteration": true,
"skipLibCheck": true,
"jsx": "preserve",
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"allowJs": true,
"resolveJsonModule": true,
"noImplicitThis": false,
"paths": {
"assert": ["./common/assert.js"]
}
},
"exclude": []
}