dcs-git-utils 1.0.0 → 1.0.9

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 CHANGED
@@ -1,120 +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 };
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
120
  }
@@ -1,68 +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
-
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
68
  }
@@ -1,64 +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
- });
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
64
  }
@@ -1,10 +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 };
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
10
  }
package/src/main.ts CHANGED
@@ -1,49 +1,53 @@
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
-
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, backup] = process.argv;
12
+
13
+ const shouldBackup = backup === "--backup";
14
+
15
+ const backupMiz = async (filepath: string) => {
16
+
17
+ if (!filepath?.indexOf(".miz")) {
18
+ throw new Error('no miz file ext');
19
+ }
20
+
21
+ const fileWithoutExt = filepath.replace(path.extname(filepath), "");
22
+ await fs.copyFile(filepath, `${fileWithoutExt}_backup_${new Date().toISOString().replace(/:/g, '-')}.miz`);
23
+ }
24
+
25
+ const initialize = async () => {
26
+
27
+ const { missionPath, outDir, } = await initializeConfig({ file });
28
+
29
+ if (shouldBackup) {
30
+ backupMiz(missionPath);
31
+ }
32
+
33
+ if (existsSync(outDir)) {
34
+ console.log('out exists, using this as source');
35
+ await handleArchive(outDir, missionPath);
36
+ } else {
37
+ console.log("Initial unpack of .miz");
38
+ const files = await unpackMiz(missionPath, outDir);
39
+ const filePaths = files.filter(f => f.type === "file").map(f => path.join(outDir, f.path));
40
+ await sortFiles(filePaths);
41
+ }
42
+
43
+ return { mizPath: missionPath, outDir };
44
+ }
45
+
46
+
47
+ (async () => {
48
+
49
+ const { mizPath, outDir } = await initialize();
50
+
51
+ startWatchers(mizPath, outDir);
52
+ })()
53
+