fix: restore strict noUncheckedIndexedAccess and skip redundant docker build in CI
This commit is contained in:
@@ -36,6 +36,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Run test suite
|
- name: Run test suite
|
||||||
run: bun test
|
run: bun test
|
||||||
|
env:
|
||||||
|
SKIP_DOCKER_TESTS: "true"
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
name: Build & Publish Docker Image
|
name: Build & Publish Docker Image
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export async function getQueryResults(
|
|||||||
if (err instanceof OpenProjectNotFoundError) {
|
if (err instanceof OpenProjectNotFoundError) {
|
||||||
const queryDetail = await opClient.get<HalResource>(`queries/${id}`);
|
const queryDetail = await opClient.get<HalResource>(`queries/${id}`);
|
||||||
const resultsLink = queryDetail._links?.results;
|
const resultsLink = queryDetail._links?.results;
|
||||||
const resultsHref = Array.isArray(resultsLink) ? resultsLink[0]?.href : resultsLink?.href;
|
const resultsHref = Array.isArray(resultsLink) ? resultsLink?.[0]?.href : resultsLink?.href;
|
||||||
if (typeof resultsHref === "string" && resultsHref.length > 0) {
|
if (typeof resultsHref === "string" && resultsHref.length > 0) {
|
||||||
const response = await opClient.get<HalCollection<HalResource>>(resultsHref, query);
|
const response = await opClient.get<HalCollection<HalResource>>(resultsHref, query);
|
||||||
return unpackCollection(response, normalizeWorkPackageSummary);
|
return unpackCollection(response, normalizeWorkPackageSummary);
|
||||||
|
|||||||
+39
-3
@@ -31,6 +31,11 @@ describe("Docker Container Packaging", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("Docker image builds and starts up cleanly in container", async () => {
|
test("Docker image builds and starts up cleanly in container", async () => {
|
||||||
|
if (process.env.SKIP_DOCKER_TESTS === "true" || process.env.CI === "true") {
|
||||||
|
console.log("Skipping live docker build test in CI / SKIP_DOCKER_TESTS environment");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if docker daemon is available in test environment
|
// Check if docker daemon is available in test environment
|
||||||
const proc = Bun.spawn(["which", "docker"], { stdout: "pipe", stderr: "pipe" });
|
const proc = Bun.spawn(["which", "docker"], { stdout: "pipe", stderr: "pipe" });
|
||||||
const exitCode = await proc.exited;
|
const exitCode = await proc.exited;
|
||||||
@@ -70,12 +75,43 @@ describe("Docker Container Packaging", () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Give it 1.5s to start and write banner to stderr
|
// Read stderr with a timeout loop up to 5000ms waiting for the startup banner
|
||||||
await Bun.sleep(1500);
|
let stderrText = "";
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
const reader = runProc.stderr.getReader();
|
||||||
|
|
||||||
|
const readLoop = (async () => {
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
stderrText += decoder.decode(value, { stream: true });
|
||||||
|
if (stderrText.includes("[openproject-mcp] Server started (readOnly=true)")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// stream closed or cancelled
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
while (
|
||||||
|
Date.now() - startTime < 5000 &&
|
||||||
|
!stderrText.includes("[openproject-mcp] Server started (readOnly=true)")
|
||||||
|
) {
|
||||||
|
await Bun.sleep(50);
|
||||||
|
}
|
||||||
|
|
||||||
runProc.kill();
|
runProc.kill();
|
||||||
await runProc.exited;
|
await runProc.exited;
|
||||||
|
try {
|
||||||
|
await reader.cancel();
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
await readLoop;
|
||||||
|
|
||||||
const stderrText = await new Response(runProc.stderr).text();
|
|
||||||
expect(stderrText).toContain("[openproject-mcp] Server started (readOnly=true)");
|
expect(stderrText).toContain("[openproject-mcp] Server started (readOnly=true)");
|
||||||
}, 60000);
|
}, 60000);
|
||||||
});
|
});
|
||||||
|
|||||||
+26
-26
@@ -115,9 +115,9 @@ describe("Projects Service", () => {
|
|||||||
expect(requestedUrl).toContain("offset=1");
|
expect(requestedUrl).toContain("offset=1");
|
||||||
expect(requestedUrl).toContain("sortBy=");
|
expect(requestedUrl).toContain("sortBy=");
|
||||||
expect(result.total).toBe(1);
|
expect(result.total).toBe(1);
|
||||||
expect(result.items[0].id).toBe(4);
|
expect(result.items[0]!.id).toBe(4);
|
||||||
expect(result.items[0].identifier).toBe("mcp-test-project");
|
expect(result.items[0]!.identifier).toBe("mcp-test-project");
|
||||||
expect(result.items[0].name).toBe("MCP Test Project");
|
expect(result.items[0]!.name).toBe("MCP Test Project");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("getProject retrieves project detail by id or identifier", async () => {
|
test("getProject retrieves project detail by id or identifier", async () => {
|
||||||
@@ -304,10 +304,10 @@ describe("Work Packages Service", () => {
|
|||||||
expect(requestedUrl).toContain("filters=");
|
expect(requestedUrl).toContain("filters=");
|
||||||
expect(requestedUrl).toContain("pageSize=25");
|
expect(requestedUrl).toContain("pageSize=25");
|
||||||
expect(result.total).toBe(1);
|
expect(result.total).toBe(1);
|
||||||
expect(result.items[0].id).toBe(38);
|
expect(result.items[0]!.id).toBe(38);
|
||||||
expect(result.items[0].subject).toBe("Implement MCP Server Core Protocol");
|
expect(result.items[0]!.subject).toBe("Implement MCP Server Core Protocol");
|
||||||
expect(result.items[0].status).toBe("In progress");
|
expect(result.items[0]!.status).toBe("In progress");
|
||||||
expect(result.items[0].type).toBe("Task");
|
expect(result.items[0]!.type).toBe("Task");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("getWorkPackage returns full details with parent and children", async () => {
|
test("getWorkPackage returns full details with parent and children", async () => {
|
||||||
@@ -329,7 +329,7 @@ describe("Work Packages Service", () => {
|
|||||||
expect(wp.parent?.id).toBe(30);
|
expect(wp.parent?.id).toBe(30);
|
||||||
expect(wp.parent?.subject).toBe("Epic Parent");
|
expect(wp.parent?.subject).toBe("Epic Parent");
|
||||||
expect(wp.children?.length).toBe(2);
|
expect(wp.children?.length).toBe(2);
|
||||||
expect(wp.children?.[0].id).toBe(39);
|
expect(wp.children?.[0]?.id).toBe(39);
|
||||||
expect(wp.lockVersion).toBe(3);
|
expect(wp.lockVersion).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -425,9 +425,9 @@ describe("Queries Service", () => {
|
|||||||
expect(requestedUrl).toContain("pageSize=20");
|
expect(requestedUrl).toContain("pageSize=20");
|
||||||
const decodedUrl = decodeURIComponent(requestedUrl);
|
const decodedUrl = decodeURIComponent(requestedUrl);
|
||||||
expect(decodedUrl).toContain('"project"');
|
expect(decodedUrl).toContain('"project"');
|
||||||
expect(result.items[0].id).toBe(30);
|
expect(result.items[0]!.id).toBe(30);
|
||||||
expect(result.items[0].name).toBe("MCP Active Tasks");
|
expect(result.items[0]!.name).toBe("MCP Active Tasks");
|
||||||
expect(result.items[0].projectId).toBe(4);
|
expect(result.items[0]!.projectId).toBe(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("getQuery returns normalized details with columns and filters", async () => {
|
test("getQuery returns normalized details with columns and filters", async () => {
|
||||||
@@ -448,9 +448,9 @@ describe("Queries Service", () => {
|
|||||||
expect(query.name).toBe("MCP Active Tasks");
|
expect(query.name).toBe("MCP Active Tasks");
|
||||||
expect(query.columns).toContain("ID");
|
expect(query.columns).toContain("ID");
|
||||||
expect(query.columns).toContain("Subject");
|
expect(query.columns).toContain("Subject");
|
||||||
expect(query.sortBy?.[0].attribute).toBe("id");
|
expect(query.sortBy?.[0]?.attribute).toBe("id");
|
||||||
expect(query.sortBy?.[0].direction).toBe("asc");
|
expect(query.sortBy?.[0]?.direction).toBe("asc");
|
||||||
expect(query.filters?.[0].field).toBe("Status");
|
expect(query.filters?.[0]?.field).toBe("Status");
|
||||||
expect(query.resultsHref).toBe("/api/v3/queries/30/results");
|
expect(query.resultsHref).toBe("/api/v3/queries/30/results");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -493,8 +493,8 @@ describe("Queries Service", () => {
|
|||||||
const result = await getQueryResults(30, { pageSize: 10 }, client);
|
const result = await getQueryResults(30, { pageSize: 10 }, client);
|
||||||
expect(requestedUrl).toContain("/api/v3/queries/30/results");
|
expect(requestedUrl).toContain("/api/v3/queries/30/results");
|
||||||
expect(requestedUrl).toContain("pageSize=10");
|
expect(requestedUrl).toContain("pageSize=10");
|
||||||
expect(result.items[0].id).toBe(38);
|
expect(result.items[0]!.id).toBe(38);
|
||||||
expect(result.items[0].subject).toBe("Implement MCP Server Core Protocol");
|
expect(result.items[0]!.subject).toBe("Implement MCP Server Core Protocol");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -523,9 +523,9 @@ describe("Metadata Service", () => {
|
|||||||
|
|
||||||
const statuses = await listStatuses(client);
|
const statuses = await listStatuses(client);
|
||||||
expect(statuses.length).toBe(2);
|
expect(statuses.length).toBe(2);
|
||||||
expect(statuses[0].name).toBe("New");
|
expect(statuses[0]!.name).toBe("New");
|
||||||
expect(statuses[0].isClosed).toBe(false);
|
expect(statuses[0]!.isClosed).toBe(false);
|
||||||
expect(statuses[1].isClosed).toBe(true);
|
expect(statuses[1]!.isClosed).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("listTypes scopes to project when projectId is provided", async () => {
|
test("listTypes scopes to project when projectId is provided", async () => {
|
||||||
@@ -551,7 +551,7 @@ describe("Metadata Service", () => {
|
|||||||
|
|
||||||
const types = await listTypes({ projectId: 4 }, client);
|
const types = await listTypes({ projectId: 4 }, client);
|
||||||
expect(requestedUrl).toContain("/api/v3/projects/4/types");
|
expect(requestedUrl).toContain("/api/v3/projects/4/types");
|
||||||
expect(types[0].name).toBe("Task");
|
expect(types[0]!.name).toBe("Task");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("listTypes fetches global types when projectId is omitted", async () => {
|
test("listTypes fetches global types when projectId is omitted", async () => {
|
||||||
@@ -578,7 +578,7 @@ describe("Metadata Service", () => {
|
|||||||
const types = await listTypes(undefined, client);
|
const types = await listTypes(undefined, client);
|
||||||
expect(requestedUrl).toContain("/api/v3/types");
|
expect(requestedUrl).toContain("/api/v3/types");
|
||||||
expect(requestedUrl).not.toContain("/projects/");
|
expect(requestedUrl).not.toContain("/projects/");
|
||||||
expect(types[0].name).toBe("Task");
|
expect(types[0]!.name).toBe("Task");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("listPriorities retrieves priority items", async () => {
|
test("listPriorities retrieves priority items", async () => {
|
||||||
@@ -601,7 +601,7 @@ describe("Metadata Service", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const priorities = await listPriorities(client);
|
const priorities = await listPriorities(client);
|
||||||
expect(priorities[0].name).toBe("High");
|
expect(priorities[0]!.name).toBe("High");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("listUsers returns paginated UserItem list", async () => {
|
test("listUsers returns paginated UserItem list", async () => {
|
||||||
@@ -631,8 +631,8 @@ describe("Metadata Service", () => {
|
|||||||
|
|
||||||
const users = await listUsers({ pageSize: 25, offset: 1 }, client);
|
const users = await listUsers({ pageSize: 25, offset: 1 }, client);
|
||||||
expect(requestedUrl).toContain("/api/v3/users");
|
expect(requestedUrl).toContain("/api/v3/users");
|
||||||
expect(users.items[0].login).toBe("admin");
|
expect(users.items[0]!.login).toBe("admin");
|
||||||
expect(users.items[0].admin).toBe(true);
|
expect(users.items[0]!.admin).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("src/services/index.ts exports all services properly", () => {
|
test("src/services/index.ts exports all services properly", () => {
|
||||||
@@ -673,8 +673,8 @@ describe("Live Container Integration (Domain Services)", () => {
|
|||||||
const wps = await domainServices.listWorkPackages({ projectId: "mcp-test-project" }, client);
|
const wps = await domainServices.listWorkPackages({ projectId: "mcp-test-project" }, client);
|
||||||
expect(wps.items.length).toBeGreaterThan(0);
|
expect(wps.items.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
const firstWp = await domainServices.getWorkPackage(wps.items[0].id, client);
|
const firstWp = await domainServices.getWorkPackage(wps.items[0]!.id, client);
|
||||||
expect(firstWp.id).toBe(wps.items[0].id);
|
expect(firstWp.id).toBe(wps.items[0]!.id);
|
||||||
expect(firstWp.subject).toBeDefined();
|
expect(firstWp.subject).toBeDefined();
|
||||||
|
|
||||||
const searchRes = await domainServices.searchWorkPackages("MCP", { projectId: "mcp-test-project" }, client);
|
const searchRes = await domainServices.searchWorkPackages("MCP", { projectId: "mcp-test-project" }, client);
|
||||||
|
|||||||
+13
-13
@@ -1059,7 +1059,7 @@ describe("Tool Execution Wrapper", () => {
|
|||||||
>;
|
>;
|
||||||
}
|
}
|
||||||
)._registeredTools["wrapped_tool"];
|
)._registeredTools["wrapped_tool"];
|
||||||
const result = await registered.handler({});
|
const result = await registered!.handler({});
|
||||||
expect(wrapperCalled).toBe(true);
|
expect(wrapperCalled).toBe(true);
|
||||||
expect(result.content[0]?.text).toBe("success");
|
expect(result.content[0]?.text).toBe("success");
|
||||||
});
|
});
|
||||||
@@ -1101,7 +1101,7 @@ describe("Tool Execution Wrapper", () => {
|
|||||||
}
|
}
|
||||||
)._registeredTools["param_wrapped_tool"];
|
)._registeredTools["param_wrapped_tool"];
|
||||||
|
|
||||||
const result = await registered.handler({ id: 123 });
|
const result = await registered!.handler({ id: 123 });
|
||||||
expect(capturedToolName).toBe("param_wrapped_tool");
|
expect(capturedToolName).toBe("param_wrapped_tool");
|
||||||
expect(capturedArgs).toEqual({ id: 123 });
|
expect(capturedArgs).toEqual({ id: 123 });
|
||||||
expect(result.content[0]?.text).toBe("id: 123");
|
expect(result.content[0]?.text).toBe("id: 123");
|
||||||
@@ -1146,7 +1146,7 @@ describe("Tool Execution Wrapper", () => {
|
|||||||
} as unknown as OpenProjectClient;
|
} as unknown as OpenProjectClient;
|
||||||
|
|
||||||
await runWithContext({ client: dummyClient, isReadOnly: false }, () =>
|
await runWithContext({ client: dummyClient, isReadOnly: false }, () =>
|
||||||
listProjects.handler({})
|
listProjects!.handler({})
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(interceptedTools).toContain("openproject_list_projects");
|
expect(interceptedTools).toContain("openproject_list_projects");
|
||||||
@@ -1163,7 +1163,7 @@ describe("Live Container Integration (All 10 MCP Tools)", () => {
|
|||||||
await runWithContext({ client: liveClient, isReadOnly: false }, async () => {
|
await runWithContext({ client: liveClient, isReadOnly: false }, async () => {
|
||||||
const listRes = await handleListProjects({ pageSize: 5 });
|
const listRes = await handleListProjects({ pageSize: 5 });
|
||||||
expect(listRes.isError).toBeUndefined();
|
expect(listRes.isError).toBeUndefined();
|
||||||
const listData = JSON.parse(listRes.content[0].text);
|
const listData = JSON.parse(listRes.content[0]!.text);
|
||||||
expect(listData.projects.length).toBeGreaterThan(0);
|
expect(listData.projects.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
const targetProject =
|
const targetProject =
|
||||||
@@ -1172,7 +1172,7 @@ describe("Live Container Integration (All 10 MCP Tools)", () => {
|
|||||||
) || listData.projects[0];
|
) || listData.projects[0];
|
||||||
const getRes = await handleGetProject({ projectId: targetProject.id });
|
const getRes = await handleGetProject({ projectId: targetProject.id });
|
||||||
expect(getRes.isError).toBeUndefined();
|
expect(getRes.isError).toBeUndefined();
|
||||||
const getData = JSON.parse(getRes.content[0].text);
|
const getData = JSON.parse(getRes.content[0]!.text);
|
||||||
expect(getData.id).toBe(targetProject.id);
|
expect(getData.id).toBe(targetProject.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1181,13 +1181,13 @@ describe("Live Container Integration (All 10 MCP Tools)", () => {
|
|||||||
await runWithContext({ client: liveClient, isReadOnly: false }, async () => {
|
await runWithContext({ client: liveClient, isReadOnly: false }, async () => {
|
||||||
const listRes = await handleListWorkPackages({ pageSize: 5 });
|
const listRes = await handleListWorkPackages({ pageSize: 5 });
|
||||||
expect(listRes.isError).toBeUndefined();
|
expect(listRes.isError).toBeUndefined();
|
||||||
const listData = JSON.parse(listRes.content[0].text);
|
const listData = JSON.parse(listRes.content[0]!.text);
|
||||||
expect(listData.workPackages.length).toBeGreaterThan(0);
|
expect(listData.workPackages.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
const wpId = listData.workPackages[0].id;
|
const wpId = listData.workPackages[0].id;
|
||||||
const getRes = await handleGetWorkPackage({ workPackageId: wpId });
|
const getRes = await handleGetWorkPackage({ workPackageId: wpId });
|
||||||
expect(getRes.isError).toBeUndefined();
|
expect(getRes.isError).toBeUndefined();
|
||||||
const getData = JSON.parse(getRes.content[0].text);
|
const getData = JSON.parse(getRes.content[0]!.text);
|
||||||
expect(getData.id).toBe(wpId);
|
expect(getData.id).toBe(wpId);
|
||||||
expect(getData.subject).toBeDefined();
|
expect(getData.subject).toBeDefined();
|
||||||
});
|
});
|
||||||
@@ -1197,13 +1197,13 @@ describe("Live Container Integration (All 10 MCP Tools)", () => {
|
|||||||
await runWithContext({ client: liveClient, isReadOnly: false }, async () => {
|
await runWithContext({ client: liveClient, isReadOnly: false }, async () => {
|
||||||
const listRes = await handleListQueries({});
|
const listRes = await handleListQueries({});
|
||||||
expect(listRes.isError).toBeUndefined();
|
expect(listRes.isError).toBeUndefined();
|
||||||
const listData = JSON.parse(listRes.content[0].text);
|
const listData = JSON.parse(listRes.content[0]!.text);
|
||||||
expect(listData.queries.length).toBeGreaterThan(0);
|
expect(listData.queries.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
const queryId = listData.queries[0].id;
|
const queryId = listData.queries[0].id;
|
||||||
const getRes = await handleGetQuery({ queryId });
|
const getRes = await handleGetQuery({ queryId });
|
||||||
expect(getRes.isError).toBeUndefined();
|
expect(getRes.isError).toBeUndefined();
|
||||||
const getData = JSON.parse(getRes.content[0].text);
|
const getData = JSON.parse(getRes.content[0]!.text);
|
||||||
expect(getData.query.id).toBe(queryId);
|
expect(getData.query.id).toBe(queryId);
|
||||||
expect(getData.results).toBeDefined();
|
expect(getData.results).toBeDefined();
|
||||||
});
|
});
|
||||||
@@ -1213,22 +1213,22 @@ describe("Live Container Integration (All 10 MCP Tools)", () => {
|
|||||||
await runWithContext({ client: liveClient, isReadOnly: false }, async () => {
|
await runWithContext({ client: liveClient, isReadOnly: false }, async () => {
|
||||||
const typesRes = await handleListTypes({});
|
const typesRes = await handleListTypes({});
|
||||||
expect(typesRes.isError).toBeUndefined();
|
expect(typesRes.isError).toBeUndefined();
|
||||||
const typesData = JSON.parse(typesRes.content[0].text);
|
const typesData = JSON.parse(typesRes.content[0]!.text);
|
||||||
expect(typesData.types.length).toBeGreaterThan(0);
|
expect(typesData.types.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
const statusesRes = await handleListStatuses({});
|
const statusesRes = await handleListStatuses({});
|
||||||
expect(statusesRes.isError).toBeUndefined();
|
expect(statusesRes.isError).toBeUndefined();
|
||||||
const statusesData = JSON.parse(statusesRes.content[0].text);
|
const statusesData = JSON.parse(statusesRes.content[0]!.text);
|
||||||
expect(statusesData.statuses.length).toBeGreaterThan(0);
|
expect(statusesData.statuses.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
const prioritiesRes = await handleListPriorities({});
|
const prioritiesRes = await handleListPriorities({});
|
||||||
expect(prioritiesRes.isError).toBeUndefined();
|
expect(prioritiesRes.isError).toBeUndefined();
|
||||||
const prioritiesData = JSON.parse(prioritiesRes.content[0].text);
|
const prioritiesData = JSON.parse(prioritiesRes.content[0]!.text);
|
||||||
expect(prioritiesData.priorities.length).toBeGreaterThan(0);
|
expect(prioritiesData.priorities.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
const usersRes = await handleListUsers({ pageSize: 5 });
|
const usersRes = await handleListUsers({ pageSize: 5 });
|
||||||
expect(usersRes.isError).toBeUndefined();
|
expect(usersRes.isError).toBeUndefined();
|
||||||
const usersData = JSON.parse(usersRes.content[0].text);
|
const usersData = JSON.parse(usersRes.content[0]!.text);
|
||||||
expect(usersData.users.length).toBeGreaterThan(0);
|
expect(usersData.users.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUncheckedIndexedAccess": false,
|
"noUncheckedIndexedAccess": true,
|
||||||
"noImplicitOverride": true,
|
"noImplicitOverride": true,
|
||||||
|
|
||||||
// Developer ergonomic options
|
// Developer ergonomic options
|
||||||
|
|||||||
Reference in New Issue
Block a user