dcs-git-utils 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/config.ts ADDED
@@ -0,0 +1,120 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import inquirer from 'inquirer';
4
+ import { getMizPath } from './files/miz-selector';
5
+
6
+ interface AppConfig {
7
+ lastMissionPath: string | null;
8
+ backupEnabled: boolean;
9
+ }
10
+
11
+ // 2. Define Defaults (Used if file is missing or partial)
12
+ const DEFAULT_CONFIG: AppConfig = {
13
+ lastMissionPath: null,
14
+ backupEnabled: true
15
+ };
16
+
17
+ export function saveConfig(data: AppConfig) {
18
+ // 1. Determine the directory of the Executable
19
+ // Check if we are running inside a 'pkg' executable
20
+ const isPkg = (process as any).pkg !== undefined;
21
+
22
+ const exeDir = isPkg
23
+ ? path.dirname(process.execPath) // PROD: The folder containing .exe
24
+ : process.cwd(); // DEV: The folder where you ran 'npm start'
25
+
26
+ // 2. Construct the full path
27
+ const configPath = path.join(exeDir, 'config.json');
28
+
29
+ try {
30
+ // 3. Write the file
31
+ fs.writeFileSync(configPath, JSON.stringify(data, null, 4), 'utf8');
32
+ console.log(`Config saved to: ${configPath}`);
33
+ } catch (err) {
34
+ console.error(`Failed to write config`, err);
35
+ }
36
+ }
37
+
38
+
39
+
40
+ export function readConfig(): AppConfig {
41
+ // A. Determine the directory (Compatible with 'pkg')
42
+ const isPkg = (process as any).pkg !== undefined;
43
+ const exeDir = isPkg
44
+ ? path.dirname(process.execPath) // PROD: Next to the .exe
45
+ : process.cwd(); // DEV: Current working directory
46
+
47
+ const configPath = path.join(exeDir, 'config.json');
48
+
49
+ // B. Check if file exists
50
+ if (!fs.existsSync(configPath)) {
51
+ console.log("No config found. Using defaults.");
52
+ return DEFAULT_CONFIG;
53
+ }
54
+
55
+ try {
56
+ // C. Read and Parse
57
+ const rawData = fs.readFileSync(configPath, 'utf8');
58
+ const userConfig = JSON.parse(rawData);
59
+
60
+ // D. Merge with Defaults
61
+ // (Ensures your app doesn't crash if the user deletes a key from the JSON)
62
+ return { ...DEFAULT_CONFIG, ...userConfig };
63
+
64
+ } catch (err) {
65
+ console.error(`Error reading config (corrupt JSON?)`, err);
66
+ console.log("Reverting to default configuration.");
67
+ return DEFAULT_CONFIG;
68
+ }
69
+ }
70
+
71
+ export const initializeConfig = async (options?: { file?: string }) => {
72
+ const config = readConfig();
73
+ let missionPath = options?.file ?? "";
74
+ let outDir = "";
75
+
76
+ if (missionPath) {
77
+ const mizDir = missionPath.replace(path.basename(missionPath), "");
78
+ outDir = path.join(mizDir, "out");
79
+ }
80
+ else if (config.lastMissionPath === null) {
81
+ const { mizPath, dir } = await getMizPath();
82
+ missionPath = mizPath;
83
+ outDir = path.join(dir, "out");
84
+ } else {
85
+ missionPath = config.lastMissionPath;
86
+ const { action } = await inquirer.prompt([
87
+ {
88
+ type: 'rawlist',
89
+ name: 'action',
90
+ message: 'Target file already exists. What would you like to do?',
91
+ choices: [
92
+ {
93
+ name: `Reuse the existing path: ${config.lastMissionPath}`,
94
+ value: 'reusePath'
95
+ },
96
+ {
97
+ name: "Select new .miz",
98
+ value: 'selectMiz'
99
+ }
100
+ ]
101
+ }
102
+ ]);
103
+
104
+ const mizDir = missionPath.replace(path.basename(missionPath), "");
105
+
106
+ outDir = path.join(mizDir, "out");
107
+
108
+ if (action === "selectMiz") {
109
+ const { mizPath, dir } = await getMizPath(mizDir);
110
+ missionPath = mizPath;
111
+ outDir = path.join(dir, "out");;
112
+ }
113
+
114
+
115
+ }
116
+
117
+ saveConfig({ ...config, lastMissionPath: missionPath });
118
+
119
+ return { missionPath, outDir };
120
+ }
@@ -0,0 +1,68 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as fengari from 'fengari';
4
+
5
+ const lua = fengari.lua;
6
+ const lauxlib = fengari.lauxlib;
7
+ const lualib = fengari.lualib;
8
+ const to_luastring = fengari.to_luastring;
9
+
10
+ const scriptPath = path.join(__dirname, "../lua/sorter_lib.lua");
11
+
12
+ const runSorter = (dataFilePath: string, varName: string) => {
13
+ // 1. Read the Data File
14
+ const dataContent = fs.readFileSync(dataFilePath, 'utf8');
15
+
16
+ // --- DETECT LINE ENDINGS ---
17
+ // Check if the file contains Carriage Return + Line Feed
18
+ const useCRLF = dataContent.includes("\r\n");
19
+ const EOL = useCRLF ? "\r\n" : "\n";
20
+ // ---------------------------
21
+
22
+ const L = lauxlib.luaL_newstate();
23
+ lualib.luaL_openlibs(L);
24
+
25
+ // 2. Load Data into VM
26
+ // Strip shebang just in case, convert to Lua String
27
+ const cleanContent = dataContent.replace(/^#!\/.*\n/, "");
28
+ let status = lauxlib.luaL_dostring(L, to_luastring(cleanContent));
29
+
30
+ if (status !== lua.LUA_OK) {
31
+ throw new Error(`Lua Load Error: ${lua.lua_tojsstring(L, -1)}`);
32
+ }
33
+
34
+ // 3. Load Sorter Logic
35
+ const scriptContent = fs.readFileSync(scriptPath, 'utf8');
36
+ status = lauxlib.luaL_loadstring(L, to_luastring(scriptContent));
37
+
38
+ if (status !== lua.LUA_OK) {
39
+ throw new Error(`Script Load Error: ${lua.lua_tojsstring(L, -1)}`);
40
+ }
41
+
42
+ // 4. Run Sorter
43
+ lua.lua_pushstring(L, varName);
44
+ status = lua.lua_pcall(L, 1, 2, 0);
45
+
46
+ if (status !== lua.LUA_OK) {
47
+ throw new Error(`Runtime Error: ${lua.lua_tojsstring(L, -1)}`);
48
+ }
49
+
50
+ const errorMsg = lua.lua_tojsstring(L, -1);
51
+ let resultString = lua.lua_tojsstring(L, -2); // This will contain \n
52
+
53
+ if (!resultString && errorMsg) {
54
+ throw new Error(`Logic Error: ${errorMsg}`);
55
+ }
56
+
57
+ resultString = resultString.replace(/\r?\n/g, EOL);
58
+
59
+ // 5. Write back
60
+ fs.writeFileSync(dataFilePath, resultString);
61
+ console.log(`Success: Sorted ${varName}`);
62
+ };
63
+
64
+ export const fengariSortFile = (filepath: string) => {
65
+ const targetVar = path.basename(filepath);
66
+ runSorter(filepath, targetVar);
67
+
68
+ }
@@ -0,0 +1 @@
1
+ declare module 'fengari'
@@ -0,0 +1,64 @@
1
+ import { spawn } from "node:child_process";
2
+ import os from "node:os"
3
+
4
+ export async function pickMissionFileNative(): Promise<string> {
5
+ const platform = os.platform();
6
+
7
+ return new Promise((resolve, reject) => {
8
+ let cmd = '';
9
+ let args: string[] = [];
10
+
11
+ if (platform === 'win32') {
12
+ // WINDOWS: Use PowerShell with .NET Forms
13
+ cmd = 'powershell';
14
+ const psScript = `
15
+ Add-Type -AssemblyName System.Windows.Forms
16
+ $f = New-Object System.Windows.Forms.OpenFileDialog
17
+ $f.Filter = "Mission Files (*.miz)|*.miz"
18
+ $f.Title = "Select a DCS Mission File"
19
+ $f.ShowHelp = $true
20
+ $result = $f.ShowDialog()
21
+ if ($result -eq [System.Windows.Forms.DialogResult]::OK) {
22
+ Write-Host $f.FileName
23
+ }
24
+ `;
25
+ // Encode command to avoid escaping issues
26
+ args = ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', psScript];
27
+ }
28
+ else if (platform === 'linux') {
29
+ // LINUX: Use Zenity (Common on Ubuntu/Desktop Linux)
30
+ cmd = 'zenity';
31
+ args = ['--file-selection', '--file-filter=*.miz', '--title=Select a Mission File'];
32
+ }
33
+ else if (platform === 'darwin') {
34
+ // MAC: Use AppleScript
35
+ cmd = 'osascript';
36
+ const appleScript = `
37
+ set theFile to choose file with prompt "Select a Mission File" of type {"miz"}
38
+ POSIX path of theFile
39
+ `;
40
+ args = ['-e', appleScript];
41
+ }
42
+
43
+ const child = spawn(cmd, args);
44
+ let output = '';
45
+ let errorOutput = '';
46
+
47
+ child.stdout.on('data', (data) => { output += data.toString(); });
48
+ child.stderr.on('data', (data) => { errorOutput += data.toString(); });
49
+
50
+ child.on('close', (code) => {
51
+ const resultPath = output.trim();
52
+ if (code === 0 && resultPath) {
53
+ resolve(resultPath);
54
+ } else {
55
+ // If user cancelled, usually code is 1 or path is empty
56
+ reject(new Error("Selection cancelled"));
57
+ }
58
+ });
59
+
60
+ child.on('error', (err) => {
61
+ reject(new Error(`Failed to launch dialog: ${err.message}`));
62
+ });
63
+ });
64
+ }
@@ -0,0 +1,10 @@
1
+ import path from "node:path"
2
+ import * as fs from 'fs';
3
+ import { pickMissionFileNative } from "./file-picker"
4
+
5
+ export const getMizPath = async (root: string = ".") => {
6
+ const mizPath = await pickMissionFileNative();
7
+
8
+ const dir = mizPath.replace(path.basename(mizPath), "");
9
+ return { mizPath, dir };
10
+ }
package/src/main.ts ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ import process from "node:process";
4
+ import { promises as fs, existsSync } from "node:fs";
5
+ import path from "node:path"
6
+ import { startWatchers } from "./watchers";
7
+ import { handleArchive, unpackMiz } from "./compression";
8
+ import { sortFiles } from "./sorting";
9
+ import { initializeConfig } from "./config";
10
+
11
+ const [_0, _1, file] = process.argv;
12
+
13
+ const backupMiz = async (filepath: string) => {
14
+
15
+ if (!filepath?.indexOf(".miz")) {
16
+ throw new Error('no miz file ext');
17
+ }
18
+
19
+ const fileWithoutExt = filepath.replace(path.extname(filepath), "");
20
+ await fs.copyFile(filepath, `${fileWithoutExt}_backup_${new Date().toISOString().replace(/:/g, '-')}.miz`);
21
+ }
22
+
23
+ const initialize = async () => {
24
+
25
+ const { missionPath, outDir, } = await initializeConfig({ file });
26
+
27
+ backupMiz(missionPath);
28
+
29
+ if (existsSync(outDir)) {
30
+ console.log('out exists, using this as source');
31
+ await handleArchive(outDir, missionPath);
32
+ } else {
33
+ console.log("Initial unpack of .miz");
34
+ const files = await unpackMiz(missionPath, outDir);
35
+ const filePaths = files.filter(f => f.type === "file").map(f => path.join(outDir, f.path));
36
+ await sortFiles(filePaths);
37
+ }
38
+
39
+ return { mizPath: missionPath, outDir };
40
+ }
41
+
42
+
43
+ (async () => {
44
+
45
+ const { mizPath, outDir } = await initialize();
46
+
47
+ startWatchers(mizPath, outDir);
48
+ })()
49
+
package/src/sorting.ts ADDED
@@ -0,0 +1,128 @@
1
+ import path from "node:path"
2
+ import luaparse from "luaparse"
3
+ import promises from "node:fs/promises"
4
+ import * as _ from "lodash"
5
+ import { fengariSortFile } from "./fengari-wrapper";
6
+
7
+ const jsonExtensions = ["json", "dtc"];
8
+
9
+ /**
10
+ * Recursively sorts the keys of an object or elements of an array.
11
+ * This function handles arrays, plain objects, and primitives.
12
+ *
13
+ * @param obj The data structure to sort.
14
+ * @returns A new structure with sorted keys/elements.
15
+ */
16
+ function lodashSortKeysDeep(obj: any): any {
17
+ if (_.isArray(obj)) {
18
+ // Handle Arrays: Recursively sort each element
19
+ return obj.map(lodashSortKeysDeep);
20
+ }
21
+
22
+ if (!_.isPlainObject(obj)) {
23
+ // Handle Primitives and Non-Plain Objects: Return as is
24
+ return obj;
25
+ }
26
+
27
+ // Handle Plain Objects: Get sorted keys and build the new object
28
+ return _.keys(obj)
29
+ .sort() // Sort the keys alphabetically
30
+ .reduce((sortedObj: _.Dictionary<any>, key: string) => {
31
+ // Recursively sort the value for the current key
32
+ sortedObj[key] = lodashSortKeysDeep(obj[key]);
33
+ return sortedObj;
34
+ }, {});
35
+ }
36
+
37
+
38
+ const checkCharCount = (old: string, sorted: string) => {
39
+ return old.replace(/\s/g, "").length === sorted.replace(/\s/g, "").length
40
+ }
41
+
42
+ export const jsonSort = async (filepath: string) => {
43
+
44
+ const fileString = await promises.readFile(filepath, { encoding: 'utf-8' });
45
+
46
+ const useCRLF = fileString.includes("\r\n");
47
+ const EOL = useCRLF ? "\r\n" : "\n";
48
+
49
+ const obj = JSON.parse(fileString);
50
+ const sorted = lodashSortKeysDeep(obj);
51
+ const stringified = JSON.stringify(sorted);
52
+ if (!checkCharCount(fileString, stringified)) {
53
+ throw new Error(`mismatching input file char count to sorted`)
54
+ }
55
+
56
+ const resultString = stringified.replace(/\r?\n/g, EOL);
57
+ await promises.writeFile(filepath, resultString, 'utf-8');
58
+ console.log(`Sorted JSON: ${filepath}`)
59
+ }
60
+
61
+ const isLuaSortable = async (filepath: string) => {
62
+
63
+ const filename = path.basename(filepath);
64
+ const extension = path.extname(filename);
65
+ const lua = await promises.readFile(filepath, { encoding: 'utf-8' });
66
+
67
+ if (extension && extension !== "lua") {
68
+ return false;
69
+ }
70
+
71
+ try {
72
+ const ast = luaparse.parse(lua);
73
+
74
+ if (ast.type !== 'Chunk' || !ast.body) {
75
+ return false;
76
+ }
77
+
78
+ for (const statement of ast.body) {
79
+ let isMatch = false;
80
+
81
+ // Check for LOCAL assignment: local Config = {...}
82
+ if (statement.type === 'LocalStatement') {
83
+ isMatch = statement.variables.some(v => v.name === filename);
84
+ if (isMatch) return true;
85
+ }
86
+
87
+ // Check for GLOBAL assignment: Config = {...}
88
+ else if (statement.type === 'AssignmentStatement') {
89
+ isMatch = statement.variables.some(v => v.type === 'Identifier' && v.name === filename);
90
+ if (isMatch) return true;
91
+ }
92
+ }
93
+ }
94
+ catch {
95
+ console.log(`luaparse failed: ${filename}`);
96
+ }
97
+ return false;
98
+
99
+ }
100
+
101
+ const sortFile = async (filepath: string) => {
102
+
103
+ const extension = path.extname(filepath);
104
+
105
+
106
+ try {
107
+ if (extension && jsonExtensions.includes(extension)) {
108
+ await jsonSort(filepath);
109
+ }
110
+ else if (await isLuaSortable(filepath)) {
111
+ await fengariSortFile(filepath);
112
+ }
113
+ else {
114
+ console.log(`skipping: ${filepath}`);
115
+ }
116
+ } catch (err) {
117
+ console.error(err);
118
+ }
119
+
120
+
121
+ }
122
+
123
+ export const sortFiles = async (filePaths: string[]) => {
124
+ for (const filepath of filePaths) {
125
+ await sortFile(filepath);
126
+ }
127
+
128
+ }
@@ -0,0 +1,58 @@
1
+ import * as _ from "lodash"
2
+ import * as chokidar from 'chokidar';
3
+ import { sortFiles } from "./sorting";
4
+ import { handleArchive, unpackMiz } from "./compression";
5
+ import path from "node:path";
6
+ import { debounce } from "lodash";
7
+
8
+ let locked = false;
9
+
10
+ const unlock = debounce(() => {
11
+ locked = false;
12
+ }, 3000);
13
+
14
+ export const startWatchers = (mizPath: string, outDir: string) => {
15
+
16
+
17
+ chokidar.watch(mizPath, { awaitWriteFinish: true, ignoreInitial: true }).on("change", async () => {
18
+
19
+ if (locked) {
20
+ return;
21
+ }
22
+
23
+ console.log("miz changed");
24
+
25
+ locked = true;
26
+ unlock.cancel();
27
+ const files = await unpackMiz(mizPath, outDir)
28
+ const filePaths = files.map(f => path.join(outDir, f.path));
29
+ await sortFiles(filePaths);
30
+ await handleArchive(outDir, mizPath);
31
+ unlock();
32
+ })
33
+
34
+ const outChange = async () => {
35
+
36
+ if (locked) {
37
+ unlock();
38
+ return;
39
+ }
40
+
41
+ console.log("out changed");
42
+
43
+ locked = true;
44
+ unlock.cancel();
45
+ await handleArchive(outDir, mizPath);
46
+ unlock();
47
+ }
48
+
49
+ chokidar.watch(outDir, { ignoreInitial: true })
50
+ .on("add", async () => {
51
+ await outChange();
52
+ }).on("addDir", async () => {
53
+ await outChange();
54
+ }).on("change", async () => {
55
+ await outChange();
56
+ })
57
+
58
+ }
@@ -0,0 +1,26 @@
1
+ import { describe, it, expect, vi, beforeEach, afterAll } from 'vitest';
2
+ import * as fs from 'fs/promises';
3
+ import * as path from 'path';
4
+ import { saveConfig } from '../src/config'; // Assume you exported this
5
+ import { jsonSort } from '../src/sorting';
6
+
7
+ describe('Sorting', () => {
8
+ const filepath = "./test.json";
9
+
10
+ beforeEach(() => {
11
+ vi.clearAllMocks(); // Reset counters before each test
12
+ });
13
+ afterAll(async () => {
14
+ await fs.rm(filepath);
15
+ })
16
+ it('should write sort JSON keys', async () => {
17
+ // 2. Run your function
18
+
19
+ const testData = JSON.stringify({ key: "test", asdf: "test2" });
20
+ await fs.writeFile(filepath, testData, 'utf-8');
21
+ await jsonSort(filepath);
22
+ const sorted = await fs.readFile(filepath, { encoding: 'utf-8' });
23
+ expect(sorted.indexOf("asdf") > sorted.indexOf("key"));
24
+
25
+ });
26
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,101 @@
1
+ {
2
+ "compilerOptions": {
3
+ /* Visit https://aka.ms/tsconfig to read more about this file */
4
+ /* Projects */
5
+ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
6
+ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
7
+ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
8
+ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
9
+ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
10
+ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
11
+ /* Language and Environment */
12
+ "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
13
+ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
14
+ // "jsx": "preserve", /* Specify what JSX code is generated. */
15
+ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
16
+ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
17
+ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
18
+ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
19
+ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
20
+ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
21
+ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
22
+ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
23
+ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
24
+ /* Modules */
25
+ "module": "commonjs", /* Specify what module code is generated. */
26
+ // "rootDir": "./", /* Specify the root folder within your source files. */
27
+ // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
28
+ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
29
+ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
30
+ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
31
+ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
32
+ // "types": [], /* Specify type package names to be included without being referenced in a source file. */
33
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
34
+ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
35
+ // "resolveJsonModule": true, /* Enable importing .json files. */
36
+ // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
37
+ /* JavaScript Support */
38
+ // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
39
+ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
40
+ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
41
+ /* Emit */
42
+ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
43
+ // "declarationMap": true, /* Create sourcemaps for d.ts files. */
44
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
45
+ "sourceMap": true, /* Create source map files for emitted JavaScript files. */
46
+ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
47
+ "outDir": "./dist", /* Specify an output folder for all emitted files. */
48
+ // "removeComments": true, /* Disable emitting comments. */
49
+ // "noEmit": true, /* Disable emitting files from a compilation. */
50
+ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
51
+ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
52
+ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
53
+ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
54
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
55
+ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
56
+ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
57
+ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
58
+ // "newLine": "crlf", /* Set the newline character for emitting files. */
59
+ // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
60
+ // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
61
+ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
62
+ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
63
+ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
64
+ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
65
+ /* Interop Constraints */
66
+ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
67
+ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
68
+ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
69
+ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
70
+ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
71
+ /* Type Checking */
72
+ "strict": true, /* Enable all strict type-checking options. */
73
+ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
74
+ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
75
+ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
76
+ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
77
+ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
78
+ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
79
+ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
80
+ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
81
+ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
82
+ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
83
+ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
84
+ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
85
+ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
86
+ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
87
+ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
88
+ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
89
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
90
+ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
91
+ /* Completeness */
92
+ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
93
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
94
+ },
95
+ "exclude": [
96
+ "**/*.test.ts", // <--- Don't compile tests
97
+ "**/*.spec.ts",
98
+ "tests", // <--- Don't compile tests folder
99
+ "vitest.config.ts"
100
+ ]
101
+ }
@@ -0,0 +1,10 @@
1
+ // vitest.config.ts
2
+ import { defineConfig } from 'vitest/config';
3
+
4
+ export default defineConfig({
5
+ test: {
6
+ // Basic settings
7
+ globals: true, // allows using describe/it without importing them
8
+ environment: 'node',
9
+ },
10
+ });