chore: scaffold toolchain for the Electron shell

Set up the package, compiler and quality gates before any app code so
every later change lands under the same rules: Node 26 via .nvmrc and
engines, TypeScript 7 in strict mode with the extra strictness flags,
swc for transpiling, oxlint with type-aware, Unicorn and security rules
(eslint-plugin-security and no-unsanitized loaded as JS plugins),
oxfmt, Vitest with a 100% coverage gate, and husky hooks that run the
checks and commitlint.

Type escape hatches, lint suppressions and coverage exemptions are
banned unless registered in exceptions.json. scripts/audit-exceptions
enforces that, and its test runs it against this repository, so an
unregistered suppression fails both the pre-commit hook and CI.

npm 11 blocks dependency install scripts by default. None are needed:
Electron downloads its binary on first use, swc's native binding comes
from optionalDependencies, fsevents ships prebuilt, and
electron-winstaller only serves Squirrel installers, which aren't used.
They are recorded as denied in allowScripts.
This commit is contained in:
2026-09-28 15:52:48 -05:00
commit 648a6dd29c
26 changed files with 14369 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
node_modules/
dist/
release/
coverage/
test-results/
playwright-report/
.e2e-coverage/
*.log
.DS_Store
*.tsbuildinfo
+1
View File
@@ -0,0 +1 @@
npx --no -- commitlint --edit "$1"
+1
View File
@@ -0,0 +1 @@
npm run check
+3
View File
@@ -0,0 +1,3 @@
save-exact=true
engine-strict=true
fund=false
+1
View File
@@ -0,0 +1 @@
26
+8
View File
@@ -0,0 +1,8 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"printWidth": 100,
"singleQuote": true,
"proseWrap": "preserve",
"sortPackageJson": false,
"ignorePatterns": ["e2e/docs-fixture/**", "package-lock.json", "patches/**"]
}
+108
View File
@@ -0,0 +1,108 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["eslint", "typescript", "unicorn", "oxc", "import", "node", "promise", "vitest"],
"jsPlugins": ["eslint-plugin-security", "eslint-plugin-no-unsanitized"],
"options": {
"typeAware": true,
"denyWarnings": true,
"reportUnusedDisableDirectives": "error"
},
"env": { "es2024": true },
"categories": {
"correctness": "error",
"suspicious": "error",
"pedantic": "error",
"perf": "error",
"restriction": "error"
},
"ignorePatterns": ["dist/**", "release/**", "coverage/**", "e2e/docs-fixture/**"],
"rules": {
// Type escape hatches. Any exception needs an entry in exceptions.json.
"typescript/no-explicit-any": "error",
"typescript/no-non-null-assertion": "error",
"typescript/ban-ts-comment": [
"error",
{ "ts-expect-error": true, "ts-ignore": true, "ts-nocheck": true, "ts-check": false }
],
"typescript/consistent-type-assertions": ["error", { "assertionStyle": "never" }],
"typescript/no-unsafe-type-assertion": "error",
"typescript/strict-boolean-expressions": "error",
"typescript/switch-exhaustiveness-check": "error",
// Curated style rules.
"typescript/consistent-type-imports": "error",
"unicorn/filename-case": ["error", { "case": "kebabCase" }],
"eslint/curly": ["error", "all"],
"eslint/eqeqeq": "error",
"import/extensions": ["error", "always", { "ignorePackages": true }],
// Restriction rules that contradict how this codebase is written: modern syntax
// is the target, not something to avoid.
"oxc/no-async-await": "off",
"oxc/no-optional-chaining": "off",
"oxc/no-rest-spread-properties": "off",
"eslint/no-undefined": "off",
"eslint/no-plusplus": "off",
"import/no-relative-parent-imports": "off",
"typescript/explicit-function-return-type": "off",
"typescript/explicit-member-accessibility": "off",
// switch-exhaustiveness-check replaces this, and a default branch would hide
// a missing case from it.
"eslint/default-case": "off",
// tsc reports temporal-dead-zone use; hoisted function declarations are fine.
"eslint/no-use-before-define": "off",
// Flags every DOM node, Electron object and Node stream parameter; deep
// readonly isn't expressible for those types.
"typescript/prefer-readonly-parameter-types": "off",
// vitest.config.ts sets testTimeout globally.
"vitest/require-test-timeout": "off",
// Security (eslint-plugin-security, loaded as a JS plugin).
"security/detect-unsafe-regex": "error",
"security/detect-non-literal-regexp": "error",
"security/detect-non-literal-require": "error",
"security/detect-eval-with-expression": "error",
"security/detect-pseudoRandomBytes": "error",
"security/detect-possible-timing-attacks": "error",
"security/detect-no-csrf-before-method-override": "error",
"security/detect-buffer-noassert": "error",
"security/detect-child-process": "error",
"security/detect-disable-mustache-escape": "error",
"security/detect-new-buffer": "error",
"security/detect-bidi-characters": "error",
"security/detect-invisible-characters": "error",
// Approved off: fires on every bracket access. noUncheckedIndexedAccess and
// zod-validated data cover the risk this rule guesses at.
"security/detect-object-injection": "off",
// Approved off: fires on every fs call with a variable path. Paths reach fs
// only through open-target/contain, which the boundary tests cover.
"security/detect-non-literal-fs-filename": "off",
// DOM injection sinks (eslint-plugin-no-unsanitized).
"no-unsanitized/method": "error",
"no-unsanitized/property": "error"
},
"overrides": [
{
"files": ["*.config.ts", "e2e/playwright.config.ts"],
"rules": { "import/no-default-export": "off" }
},
{
"files": ["scripts/**/*.ts"],
// CLI entry points run directly under node and are never require()d.
"rules": {
"eslint/no-console": "off",
"unicorn/no-process-exit": "off",
"node/no-top-level-await": "off"
}
},
{
"files": ["**/*.test.ts", "e2e/**/*.ts"],
"rules": {
"eslint/max-lines-per-function": "off",
"eslint/max-lines": "off",
"node/no-process-env": "off"
}
}
]
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Jason Ross
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+22
View File
@@ -0,0 +1,22 @@
# mdts Desktop
A desktop app for browsing and previewing folders of Markdown, built on
[mdts](https://github.com/unhappychoice/mdts) and Electron. Work in progress.
## Development
Requires Node 26 (see `.nvmrc`) and npm 11.
```sh
npm ci
npm run check # format, lint, typecheck, exception audit
npm run test:unit # Vitest, 100% coverage gate
```
Type-safety escape hatches, lint suppressions and coverage exemptions are only
allowed with an entry in `exceptions.json`; `npm run audit:exceptions` enforces
this.
## License
MIT. mdts is MIT-licensed by its authors.
+7
View File
@@ -0,0 +1,7 @@
import type { UserConfig } from '@commitlint/types';
const config: UserConfig = {
extends: ['@commitlint/config-conventional'],
};
export default config;
+3
View File
@@ -0,0 +1,3 @@
{
"exceptions": []
}
+13585
View File
File diff suppressed because it is too large Load Diff
+87
View File
@@ -0,0 +1,87 @@
{
"name": "mdts-desktop",
"productName": "mdts Desktop",
"version": "0.0.0",
"private": true,
"description": "Desktop app for browsing and previewing Markdown folders, built on mdts and Electron",
"homepage": "https://github.com/JMR-dev/mdts-desktop",
"bugs": "https://github.com/JMR-dev/mdts-desktop/issues",
"license": "MIT",
"author": {
"name": "Jason Ross",
"email": "jason.ross841@gmail.com"
},
"repository": "github:JMR-dev/mdts-desktop",
"type": "module",
"main": "dist/main/index.js",
"scripts": {
"prepare": "husky",
"postinstall": "patch-package",
"clean": "node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
"build": "npm run clean && npm run build:esm && npm run build:preload",
"build:esm": "swc src/main src/host src/shared src/renderer --out-dir dist --strip-leading-paths --copy-files --ignore \"**/*.test.ts\" --config-file swc/esm.swcrc",
"build:preload": "swc src/preload/start.ts --out-file dist/preload/start.cjs --config-file swc/preload.swcrc",
"start": "npm run build && electron .",
"typecheck": "tsc --build",
"lint": "oxlint --type-aware --deny-warnings",
"format": "oxfmt",
"format:check": "oxfmt --check",
"audit:exceptions": "node scripts/audit-exceptions.ts",
"check": "npm run format:check && npm run lint && npm run typecheck && npm run audit:exceptions",
"test:unit": "vitest run --coverage",
"test:e2e": "npm run build && playwright test",
"dist": "npm run build && electron-builder --publish never"
},
"dependencies": {
"mdts": "0.21.0",
"zod": "4.6.5"
},
"devDependencies": {
"@commitlint/cli": "21.2.3",
"@commitlint/config-conventional": "21.2.3",
"@commitlint/types": "21.2.3",
"@cyclonedx/cyclonedx-npm": "6.0.1",
"@playwright/test": "1.63.0",
"@swc/cli": "0.8.1",
"@swc/core": "1.16.2",
"@types/node": "24.19.0",
"@types/ws": "8.18.1",
"@vitest/browser": "5.0.2",
"@vitest/browser-playwright": "5.0.2",
"@vitest/coverage-v8": "5.0.2",
"electron": "44.4.5",
"electron-builder": "26.17.0",
"eslint-plugin-no-unsanitized": "4.1.5",
"eslint-plugin-security": "4.1.0",
"husky": "9.1.7",
"monocart-coverage-reports": "2.13.0",
"oxfmt": "0.71.0",
"oxlint": "1.86.0",
"oxlint-tsgolint": "7.0.2003",
"patch-package": "8.0.1",
"playwright": "1.63.0",
"typescript": "7.0.2",
"vitest": "5.0.2",
"ws": "8.22.0"
},
"engines": {
"node": ">=26 <27"
},
"devEngines": {
"runtime": {
"name": "node",
"version": ">=26 <27",
"onFail": "error"
},
"packageManager": {
"name": "npm",
"version": ">=11",
"onFail": "error"
}
},
"allowScripts": {
"@swc/core": false,
"electron-winstaller": false,
"fsevents": false
}
}
+11
View File
@@ -0,0 +1,11 @@
import { afterEach, expect, it } from 'vitest';
afterEach(() => {
process.exitCode = undefined;
});
it('passes on this repository', async () => {
await import('./audit-exceptions.ts');
expect(process.exitCode).toBe(0);
});
+5
View File
@@ -0,0 +1,5 @@
import { runAudit } from './lib/exceptions.ts';
process.exitCode = await runAudit(process.cwd(), (line) => {
console.log(line);
});
+219
View File
@@ -0,0 +1,219 @@
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { audit, parseRegistry, runAudit, unitCoverageExemptPaths } from './exceptions.ts';
const approved = (id: string, kind: string, paths: string[]): Record<string, unknown> => ({
id,
kind,
paths,
reason: 'test reason',
approvedBy: 'test approver',
});
describe('parseRegistry', () => {
it('accepts a well-formed registry', () => {
const registry = parseRegistry({
exceptions: [approved('LINT-1', 'lint-suppression', ['src/a.ts'])],
});
expect(registry.exceptions).toHaveLength(1);
});
it('rejects entries with an unknown kind', () => {
expect(() =>
parseRegistry({ exceptions: [approved('X-1', 'anything-goes', ['src/a.ts'])] }),
).toThrow(/kind/u);
});
it('rejects duplicate ids', () => {
expect(() =>
parseRegistry({
exceptions: [
approved('LINT-1', 'lint-suppression', ['src/a.ts']),
approved('LINT-1', 'lint-suppression', ['src/b.ts']),
],
}),
).toThrow(/duplicate id LINT-1/u);
});
it('rejects ids that the APPROVED(...) marker cannot express', () => {
expect(() =>
parseRegistry({ exceptions: [approved('lint one', 'lint-suppression', ['src/a.ts'])] }),
).toThrow(/id/u);
});
});
describe('audit', () => {
const registry = parseRegistry({
exceptions: [
approved('LINT-1', 'lint-suppression', ['src/a.ts']),
approved('COV-1', 'coverage-ignore', ['src/**/*.ts']),
approved('DTS-1', 'dts-shim', ['src/types/mdts.d.ts']),
approved('UNIT-1', 'unit-coverage', ['src/main/electron/**']),
],
});
const baseline = [
{ path: 'src/a.ts', content: 'export const a = 1;\n' },
{ path: 'src/types/mdts.d.ts', content: 'export {};\n' },
{ path: 'src/main/electron/menu.ts', content: 'export {};\n' },
];
it('passes a tree with no suppressions and no stale entries', () => {
const files = [
...baseline,
{ path: 'src/a.ts', content: 'x(); // oxlint-disable-line no-x -- APPROVED(LINT-1)\n' },
{ path: 'src/b.ts', content: '/* v8 ignore next -- APPROVED(COV-1) */\n' },
];
expect(audit(files, registry)).toStrictEqual([]);
});
it('flags an oxlint suppression without an approval marker', () => {
const files = [
...baseline,
{ path: 'src/a.ts', content: 'const a = 1;\n// oxlint-disable-next-line no-x\n' },
];
expect(audit(files, registry)).toStrictEqual([
'src/a.ts:2: suppression has no APPROVED(<id>) marker',
]);
});
it('treats eslint directives, c8 and istanbul comments the same way', () => {
const files = [
...baseline,
{
path: 'src/c.ts',
content: '/* eslint-disable no-x */\n/* c8 ignore next */\n// istanbul ignore else\n',
},
];
expect(audit(files, registry)).toStrictEqual([
'src/c.ts:1: suppression has no APPROVED(<id>) marker',
'src/c.ts:2: suppression has no APPROVED(<id>) marker',
'src/c.ts:3: suppression has no APPROVED(<id>) marker',
]);
});
it('ignores directive text inside string literals', () => {
const content = [
"const a = '// oxlint-disable';",
"const b = 'x(); /* v8 ignore next */';",
'const c = "it\\"s // eslint-disable";',
'const d = `// c8 ignore next`;',
"// a comment that doesn't disable anything",
'',
].join('\n');
const files = [...baseline, { path: 'src/d.ts', content }];
expect(audit(files, registry)).toStrictEqual([]);
});
it('flags an approval id that is not in the registry', () => {
const files = [
...baseline,
{ path: 'src/a.ts', content: '// oxlint-disable -- APPROVED(NOPE)\n' },
];
expect(audit(files, registry)).toStrictEqual([
'src/a.ts:1: APPROVED(NOPE) is not in exceptions.json',
]);
});
it('flags an approval whose entry does not cover the file', () => {
const files = [
...baseline,
{ path: 'src/other.ts', content: '// oxlint-disable-line -- APPROVED(LINT-1)\n' },
];
expect(audit(files, registry)).toStrictEqual([
'src/other.ts:1: APPROVED(LINT-1) does not cover this file',
]);
});
it('flags an approval of the wrong kind', () => {
const files = [
...baseline,
{ path: 'src/a.ts', content: '// oxlint-disable-line -- APPROVED(COV-1)\n' },
];
expect(audit(files, registry)).toStrictEqual([
'src/a.ts:1: APPROVED(COV-1) is a coverage-ignore exception, not lint-suppression',
]);
});
it('flags a declaration file with no dts-shim entry', () => {
const files = [...baseline, { path: 'src/types/other.d.ts', content: 'export {};\n' }];
expect(audit(files, registry)).toStrictEqual([
'src/types/other.d.ts: declaration file is not approved in exceptions.json',
]);
});
it('flags registry entries that no longer match any file', () => {
expect(audit([], registry)).toStrictEqual([
'exceptions.json: LINT-1 matches no files',
'exceptions.json: COV-1 matches no files',
'exceptions.json: DTS-1 matches no files',
'exceptions.json: UNIT-1 matches no files',
]);
});
});
describe('unitCoverageExemptPaths', () => {
it('lists the paths of unit-coverage entries only', () => {
const registry = parseRegistry({
exceptions: [
approved('UNIT-1', 'unit-coverage', ['src/main/electron/**', 'src/main/index.ts']),
approved('LINT-1', 'lint-suppression', ['src/a.ts']),
],
});
expect(unitCoverageExemptPaths(registry)).toStrictEqual([
'src/main/electron/**',
'src/main/index.ts',
]);
});
});
describe('runAudit', () => {
let root = '';
const lines: string[] = [];
const log = (line: string): void => {
lines.push(line);
};
beforeEach(async () => {
root = await mkdtemp(path.join(tmpdir(), 'audit-'));
lines.length = 0;
await mkdir(path.join(root, 'src'), { recursive: true });
await mkdir(path.join(root, 'node_modules', 'pkg'), { recursive: true });
await writeFile(
path.join(root, 'exceptions.json'),
JSON.stringify({ exceptions: [approved('LINT-1', 'lint-suppression', ['src/a.ts'])] }),
);
await writeFile(path.join(root, 'node_modules', 'pkg', 'index.d.ts'), 'export {};\n');
});
afterEach(async () => {
await rm(root, { recursive: true, force: true });
});
it('returns 0 and reports success for a clean tree', async () => {
await writeFile(path.join(root, 'src', 'a.ts'), '// oxlint-disable-line -- APPROVED(LINT-1)\n');
await expect(runAudit(root, log)).resolves.toBe(0);
expect(lines).toStrictEqual(['audit-exceptions: 1 approved exception, no problems']);
});
it('returns 1 and prints every problem', async () => {
await writeFile(path.join(root, 'src', 'a.ts'), '// oxlint-disable-line no-x\n');
await expect(runAudit(root, log)).resolves.toBe(1);
expect(lines).toStrictEqual(['src/a.ts:1: suppression has no APPROVED(<id>) marker']);
});
});
+153
View File
@@ -0,0 +1,153 @@
import { globSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { z } from 'zod';
const kinds = ['lint-suppression', 'coverage-ignore', 'dts-shim', 'unit-coverage'] as const;
type Kind = (typeof kinds)[number];
const entrySchema = z.strictObject({
id: z.string().regex(/^[A-Z0-9-]+$/u, 'id must be upper-case letters, digits and dashes'),
kind: z.enum(kinds),
paths: z.array(z.string().min(1)).min(1),
reason: z.string().min(1),
approvedBy: z.string().min(1),
});
const registrySchema = z
.object({ exceptions: z.array(entrySchema) })
.superRefine((registry, context) => {
const seen = new Set<string>();
for (const entry of registry.exceptions) {
if (seen.has(entry.id)) {
context.addIssue({ code: 'custom', message: `duplicate id ${entry.id}` });
}
seen.add(entry.id);
}
});
export type Registry = z.infer<typeof registrySchema>;
type Entry = Registry['exceptions'][number];
export interface SourceFile {
readonly path: string;
readonly content: string;
}
export const parseRegistry = (value: unknown): Registry => registrySchema.parse(value);
export const unitCoverageExemptPaths = (registry: Registry): string[] =>
registry.exceptions
.filter((entry) => entry.kind === 'unit-coverage')
.flatMap((entry) => entry.paths);
// Directive text inside string literals (test fixtures, messages) isn't a
// directive, so literals are blanked out before matching.
const stringLiteral = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`/gu;
const lintDirective = /(?:\/\/|\/\*)\s*(?:oxlint|eslint)-disable/u;
const coverageDirective = /(?:\/\/|\/\*)\s*(?:v8|c8|istanbul) ignore/u;
const approvalMarker = /APPROVED\(([^)]*)\)/u;
const matches = (entry: Entry, file: string): boolean =>
entry.paths.some((pattern) => path.posix.matchesGlob(file, pattern));
const directiveKind = (line: string): Kind | undefined => {
const code = line.replaceAll(stringLiteral, "''");
if (lintDirective.test(code)) {
return 'lint-suppression';
}
if (coverageDirective.test(code)) {
return 'coverage-ignore';
}
return undefined;
};
const checkLine = (
file: string,
lineNumber: number,
line: string,
byId: ReadonlyMap<string, Entry>,
): string | undefined => {
const kind = directiveKind(line);
if (kind === undefined) {
return undefined;
}
const where = `${file}:${String(lineNumber)}`;
const id = approvalMarker.exec(line)?.[1];
if (id === undefined) {
return `${where}: suppression has no APPROVED(<id>) marker`;
}
const entry = byId.get(id);
if (entry === undefined) {
return `${where}: APPROVED(${id}) is not in exceptions.json`;
}
if (entry.kind !== kind) {
return `${where}: APPROVED(${id}) is a ${entry.kind} exception, not ${kind}`;
}
if (!matches(entry, file)) {
return `${where}: APPROVED(${id}) does not cover this file`;
}
return undefined;
};
export const audit = (files: readonly SourceFile[], registry: Registry): string[] => {
const byId = new Map(registry.exceptions.map((entry) => [entry.id, entry]));
const shims = registry.exceptions.filter((entry) => entry.kind === 'dts-shim');
const problems: string[] = [];
for (const file of files) {
for (const [index, line] of file.content.split('\n').entries()) {
const problem = checkLine(file.path, index + 1, line, byId);
if (problem !== undefined) {
problems.push(problem);
}
}
if (file.path.endsWith('.d.ts') && !shims.some((entry) => matches(entry, file.path))) {
problems.push(`${file.path}: declaration file is not approved in exceptions.json`);
}
}
for (const entry of registry.exceptions) {
if (!files.some((file) => matches(entry, file.path))) {
problems.push(`exceptions.json: ${entry.id} matches no files`);
}
}
return problems;
};
const sourceGlobs = ['**/*.{ts,mts,cts,js,mjs,cjs}'];
const ignoredGlobs = [
'**/node_modules/**',
'dist/**',
'release/**',
'coverage/**',
'e2e/docs-fixture/**',
];
export const runAudit = async (root: string, log: (line: string) => void): Promise<number> => {
const registry = parseRegistry(
JSON.parse(await readFile(path.join(root, 'exceptions.json'), 'utf8')),
);
const relativePaths = globSync(sourceGlobs, { cwd: root, exclude: ignoredGlobs });
const files = await Promise.all(
relativePaths.map(async (relativePath) => ({
path: relativePath.split(path.sep).join('/'),
content: await readFile(path.join(root, relativePath), 'utf8'),
})),
);
const problems = audit(files, registry);
for (const problem of problems) {
log(problem);
}
if (problems.length > 0) {
return 1;
}
const count = registry.exceptions.length;
log(
`audit-exceptions: ${String(count)} approved exception${count === 1 ? '' : 's'}, no problems`,
);
return 0;
};
+24
View File
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "es2024",
"lib": ["es2024"],
"module": "nodenext",
"moduleResolution": "nodenext",
"types": [],
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"erasableSyntaxOnly": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"noEmit": true
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"types": ["node"],
"lib": ["es2024", "dom"]
},
"include": ["e2e/**/*.ts"]
}
+11
View File
@@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.scripts.json"
},
{
"path": "./tsconfig.tooling.json"
}
]
}
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"types": ["node"]
},
"include": ["src/main/**/*.ts", "src/host/**/*.ts", "src/shared/**/*.ts", "src/types/**/*.d.ts"]
}
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"lib": ["es2024", "dom"]
},
"include": ["src/preload/**/*.ts", "src/shared/**/*.ts"]
}
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"lib": ["es2024", "dom", "dom.iterable"]
},
"include": ["src/renderer/**/*.ts", "src/shared/**/*.ts"]
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"types": ["node"],
"allowImportingTsExtensions": true
},
"include": ["scripts/**/*.ts"]
}
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"lib": ["es2024", "dom"],
"types": ["node"],
"allowImportingTsExtensions": true
},
"include": ["*.config.ts"]
}
+43
View File
@@ -0,0 +1,43 @@
import { readFileSync } from 'node:fs';
import { playwright } from '@vitest/browser-playwright';
import { defineConfig } from 'vitest/config';
import { parseRegistry, unitCoverageExemptPaths } from './scripts/lib/exceptions.ts';
const registry = parseRegistry(JSON.parse(readFileSync('exceptions.json', 'utf8')));
export default defineConfig({
test: {
testTimeout: 10_000,
projects: [
{
test: {
name: 'node',
environment: 'node',
include: ['src/**/*.test.ts', 'scripts/**/*.test.ts'],
exclude: ['src/renderer/**'],
},
},
{
test: {
name: 'renderer',
include: ['src/renderer/**/*.test.ts'],
browser: {
enabled: true,
headless: true,
provider: playwright(),
instances: [{ browser: 'chromium' }],
},
},
},
],
coverage: {
provider: 'v8',
include: ['src/**/*.ts', 'scripts/**/*.ts'],
exclude: ['**/*.test.ts', '**/*.d.ts', ...unitCoverageExemptPaths(registry)],
reporter: ['text', 'html', 'lcov'],
thresholds: { lines: 100, branches: 100, functions: 100, statements: 100 },
},
},
});