my_wins 1.3.4 → 1.5.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/lib/index.js +223 -18
- package/package.json +36 -36
- package/readme.md +11 -2
- package/republish.bat +1 -1
- package/test/run-test.js +69 -15
package/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { spawn, execFile, exec } = require("child_process");
|
|
2
2
|
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
3
4
|
const JSON5 = require("json5");
|
|
4
5
|
const inquirer = require("inquirer");
|
|
5
6
|
const keysender = require("@sumbat/keysender");
|
|
@@ -42,6 +43,188 @@ function awaitDelay(delay) {
|
|
|
42
43
|
});
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
function execFileAsync(file, args, options) {
|
|
47
|
+
return new Promise((resolve, reject)=>{
|
|
48
|
+
execFile(file, args, options || {}, (error, stdout, stderr)=>{
|
|
49
|
+
if (error) {
|
|
50
|
+
error.stdout = stdout;
|
|
51
|
+
error.stderr = stderr;
|
|
52
|
+
reject(error);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
resolve({ stdout, stderr });
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function toPowerShellSingleQuotedString(value) {
|
|
61
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getConEmuInfo(settings) {
|
|
65
|
+
if (!settings.conemu) return null;
|
|
66
|
+
|
|
67
|
+
const guiPath = path.resolve(settings.conemu);
|
|
68
|
+
const baseDir = path.dirname(guiPath);
|
|
69
|
+
const helperName = /64\.exe$/i.test(guiPath) ? "ConEmuC64.exe" : "ConEmuC.exe";
|
|
70
|
+
const helperCandidates = [
|
|
71
|
+
path.join(baseDir, helperName),
|
|
72
|
+
path.join(baseDir, "ConEmu", helperName),
|
|
73
|
+
];
|
|
74
|
+
const macroPath = helperCandidates.find((candidate)=>fs.existsSync(candidate));
|
|
75
|
+
|
|
76
|
+
if (!fs.existsSync(guiPath)) {
|
|
77
|
+
throw new Error(`ConEmu executable not found: ${guiPath}`);
|
|
78
|
+
}
|
|
79
|
+
if (!macroPath) {
|
|
80
|
+
throw new Error(`ConEmu GuiMacro executable not found near: ${guiPath}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
guiPath,
|
|
85
|
+
macroPath,
|
|
86
|
+
guiPid: null,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function shouldUseConEmuForWin(settings, win, conemuInfo) {
|
|
91
|
+
if (!conemuInfo) return false;
|
|
92
|
+
if (!win || win.app) return false;
|
|
93
|
+
if (typeof win.conemu === "boolean") return win.conemu;
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function execConEmuMacro(conemu, macro) {
|
|
98
|
+
if (!conemu || !conemu.guiPid) throw new Error("ConEmu GUI PID is not initialized");
|
|
99
|
+
const command = [
|
|
100
|
+
"&",
|
|
101
|
+
toPowerShellSingleQuotedString(conemu.macroPath),
|
|
102
|
+
toPowerShellSingleQuotedString(`-GuiMacro:${conemu.guiPid}`),
|
|
103
|
+
toPowerShellSingleQuotedString(macro),
|
|
104
|
+
].join(" ");
|
|
105
|
+
const result = await execFileAsync("powershell", ["-NoProfile", "-Command", command]);
|
|
106
|
+
return (result.stdout || "").trim();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function execConEmuCommand(conemu, command, commandArgs) {
|
|
110
|
+
const serializedArgs = (commandArgs || []).map((arg)=>{
|
|
111
|
+
if (typeof arg === "number") return String(arg);
|
|
112
|
+
return `"${String(arg).replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
113
|
+
});
|
|
114
|
+
return execConEmuMacro(conemu, `${command}(${serializedArgs.join(",")})`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function findConEmuGuiPidByActiveTitle(title) {
|
|
118
|
+
if (!title) return null;
|
|
119
|
+
const titleMask = `*${title}*`;
|
|
120
|
+
const command = [
|
|
121
|
+
"(",
|
|
122
|
+
"Get-Process ConEmu64,ConEmu -ErrorAction SilentlyContinue",
|
|
123
|
+
`| Where-Object { $_.MainWindowHandle -ne 0 -and $_.MainWindowTitle -like ${toPowerShellSingleQuotedString(titleMask)} }`,
|
|
124
|
+
"| Sort-Object Id -Descending",
|
|
125
|
+
"| Select-Object -First 1 -ExpandProperty Id",
|
|
126
|
+
")",
|
|
127
|
+
].join(" ");
|
|
128
|
+
const result = await execFileAsync("powershell", ["-NoProfile", "-Command", command]);
|
|
129
|
+
const pid = parseInt((result.stdout || "").trim(), 10);
|
|
130
|
+
return Number.isFinite(pid) ? pid : null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function listConEmuGuiPids() {
|
|
134
|
+
const command = [
|
|
135
|
+
"(",
|
|
136
|
+
"Get-Process ConEmu64,ConEmu -ErrorAction SilentlyContinue",
|
|
137
|
+
"| Where-Object { $_.MainWindowHandle -ne 0 -and $_.MainWindowTitle -notlike 'About ConEmu*' }",
|
|
138
|
+
"| Sort-Object Id",
|
|
139
|
+
"| Select-Object -ExpandProperty Id",
|
|
140
|
+
")",
|
|
141
|
+
].join(" ");
|
|
142
|
+
const result = await execFileAsync("powershell", ["-NoProfile", "-Command", command]);
|
|
143
|
+
return (result.stdout || "")
|
|
144
|
+
.split(/\r?\n/)
|
|
145
|
+
.map((line)=>parseInt(line.trim(), 10))
|
|
146
|
+
.filter((pid)=>Number.isFinite(pid));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function listConEmuTabsByPid(guiPid, macroPath) {
|
|
150
|
+
const command = [
|
|
151
|
+
"&",
|
|
152
|
+
toPowerShellSingleQuotedString(macroPath),
|
|
153
|
+
toPowerShellSingleQuotedString(`-GuiMacro:${guiPid}`),
|
|
154
|
+
toPowerShellSingleQuotedString("Tab(12)"),
|
|
155
|
+
].join(" ");
|
|
156
|
+
const result = await execFileAsync("powershell", ["-NoProfile", "-Command", command]);
|
|
157
|
+
return String(result.stdout || "")
|
|
158
|
+
.split(/\r?\n/)
|
|
159
|
+
.map((line)=>line.trim())
|
|
160
|
+
.filter(Boolean);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function waitForConEmuGuiPid(title, timeoutMs, beforePids) {
|
|
164
|
+
const started = Date.now();
|
|
165
|
+
const knownBefore = new Set(beforePids || []);
|
|
166
|
+
while (Date.now() - started < timeoutMs) {
|
|
167
|
+
const pid = await findConEmuGuiPidByActiveTitle(title);
|
|
168
|
+
if (pid) return pid;
|
|
169
|
+
|
|
170
|
+
const currentPids = await listConEmuGuiPids();
|
|
171
|
+
const newPid = currentPids.find((currentPid)=>!knownBefore.has(currentPid));
|
|
172
|
+
if (newPid) return newPid;
|
|
173
|
+
if (currentPids.length === 1) return currentPids[0];
|
|
174
|
+
if (currentPids.length > 1) return currentPids[currentPids.length - 1];
|
|
175
|
+
await awaitDelay(200);
|
|
176
|
+
}
|
|
177
|
+
throw new Error(`Couldn't find ConEmu GUI window for title '${title}'`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function launchConEmuTab(conemu, win, isFirstTab) {
|
|
181
|
+
const titleCommand = `title ${win.tabTitle}`;
|
|
182
|
+
if (isFirstTab) {
|
|
183
|
+
const beforePids = await listConEmuGuiPids();
|
|
184
|
+
const beforePid = beforePids.length ? beforePids[beforePids.length - 1] : null;
|
|
185
|
+
const beforeTabs = beforePid ? await listConEmuTabsByPid(beforePid, conemu.macroPath) : [];
|
|
186
|
+
const childArgs = ["-run", "cmd.exe", "/k", titleCommand];
|
|
187
|
+
const child = spawn(conemu.guiPath, childArgs, {
|
|
188
|
+
detached: true,
|
|
189
|
+
stdio: "ignore",
|
|
190
|
+
});
|
|
191
|
+
child.unref();
|
|
192
|
+
conemu.guiPid = await waitForConEmuGuiPid(null, 10000, beforePids);
|
|
193
|
+
const afterTabs = await listConEmuTabsByPid(conemu.guiPid, conemu.macroPath);
|
|
194
|
+
return afterTabs.length > beforeTabs.length ? afterTabs.length : afterTabs.length;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const beforeTabs = await listConEmuTabsByPid(conemu.guiPid, conemu.macroPath);
|
|
198
|
+
await execConEmuCommand(conemu, "Shell", ["new_console", "cmd.exe", `/k ${titleCommand}`]);
|
|
199
|
+
await awaitDelay(300);
|
|
200
|
+
const afterTabs = await listConEmuTabsByPid(conemu.guiPid, conemu.macroPath);
|
|
201
|
+
return afterTabs.length > beforeTabs.length ? afterTabs.length : afterTabs.length;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function activateConEmuTab(conemu, tabIndex) {
|
|
205
|
+
await execConEmuCommand(conemu, "Tab", [7, tabIndex]);
|
|
206
|
+
await awaitDelay(150);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function setConEmuBackgroundColor(conemu, tabIndex, backgroundcolor) {
|
|
210
|
+
if (!backgroundcolor) return;
|
|
211
|
+
await activateConEmuTab(conemu, tabIndex);
|
|
212
|
+
await execConEmuCommand(conemu, "SetOption", ["bgImage", backgroundcolor]);
|
|
213
|
+
await awaitDelay(150);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function printInConEmuTab(conemu, tabIndex, cmd) {
|
|
217
|
+
await activateConEmuTab(conemu, tabIndex);
|
|
218
|
+
await execConEmuCommand(conemu, "Print", [cmd]);
|
|
219
|
+
await awaitDelay(200);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function pressEnterInConEmuTab(conemu, tabIndex) {
|
|
223
|
+
await activateConEmuTab(conemu, tabIndex);
|
|
224
|
+
await execConEmuCommand(conemu, "Keys", ["Enter"]);
|
|
225
|
+
await awaitDelay(200);
|
|
226
|
+
}
|
|
227
|
+
|
|
45
228
|
function parseArgs(argv) {
|
|
46
229
|
const raw = argv.slice(2);
|
|
47
230
|
let menu = false;
|
|
@@ -150,7 +333,8 @@ function buildRunSettings(baseSettings, filterNames) {
|
|
|
150
333
|
for(let k in settings.wins) {
|
|
151
334
|
const w = settings.wins[k];
|
|
152
335
|
w.name = k;
|
|
153
|
-
w.
|
|
336
|
+
w.tabTitle = w.title || w.name;
|
|
337
|
+
w.matchTitle = `my_wins_${w.name.replace(/[^\w.-]+/g, "_")}${randSuffix()}`;
|
|
154
338
|
}
|
|
155
339
|
|
|
156
340
|
return settings;
|
|
@@ -159,17 +343,23 @@ function buildRunSettings(baseSettings, filterNames) {
|
|
|
159
343
|
async function runOnce(baseSettings, filterNames) {
|
|
160
344
|
const settings = buildRunSettings(baseSettings, filterNames);
|
|
161
345
|
if (!Object.keys(settings.wins).length) return;
|
|
346
|
+
const conemu = getConEmuInfo(settings);
|
|
162
347
|
|
|
163
348
|
let wins = keysender.getAllWindows();
|
|
349
|
+
let createdConsoleCount = 0;
|
|
164
350
|
|
|
165
351
|
for(let k in settings.wins) {
|
|
166
352
|
const w = settings.wins[k];
|
|
167
|
-
const {name,
|
|
353
|
+
const {name, app, no_run, cmd} = w;
|
|
354
|
+
const useConEmu = shouldUseConEmuForWin(settings, w, conemu);
|
|
168
355
|
if(app && !no_run) {
|
|
169
356
|
exec(cmd);
|
|
170
357
|
delete settings.wins[k];
|
|
358
|
+
} else if (useConEmu) {
|
|
359
|
+
w.conemuTabIndex = await launchConEmuTab(conemu, w, createdConsoleCount === 0);
|
|
360
|
+
createdConsoleCount++;
|
|
171
361
|
} else
|
|
172
|
-
exec(`start cmd.exe @cmd /k "title ${
|
|
362
|
+
exec(`start cmd.exe @cmd /k "title ${w.matchTitle}"`);
|
|
173
363
|
}
|
|
174
364
|
|
|
175
365
|
await awaitDelay(settings.startTimeout || 1500);
|
|
@@ -181,14 +371,34 @@ async function runOnce(baseSettings, filterNames) {
|
|
|
181
371
|
let winIndex = 0;
|
|
182
372
|
for(let k in settings.wins) {
|
|
183
373
|
const w = settings.wins[k];
|
|
184
|
-
const {name,
|
|
374
|
+
const {name, app, cmd, no_run, delay} = w;
|
|
375
|
+
const useConEmu = shouldUseConEmuForWin(settings, w, conemu);
|
|
185
376
|
|
|
186
|
-
let win_info_candidates = wins.filter(c => c.title.includes(
|
|
377
|
+
let win_info_candidates = wins.filter(c => c.title.includes(w.matchTitle));
|
|
187
378
|
const win_info = win_info_candidates[0];
|
|
188
379
|
|
|
189
|
-
if (
|
|
380
|
+
if (useConEmu) {
|
|
381
|
+
w.winIndex = winIndex;
|
|
382
|
+
w.view = winsArray[winIndex];
|
|
383
|
+
await setConEmuBackgroundColor(conemu, w.conemuTabIndex, w.backgroundcolor);
|
|
384
|
+
|
|
385
|
+
w.activate = async ()=>{
|
|
386
|
+
await activateConEmuTab(conemu, w.conemuTabIndex);
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
w.print = async (nextCmd) => {
|
|
390
|
+
await printInConEmuTab(conemu, w.conemuTabIndex, nextCmd);
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
w.pressEnter = async () => {
|
|
394
|
+
await pressEnterInConEmuTab(conemu, w.conemuTabIndex);
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
await w.print(cmd);
|
|
398
|
+
}
|
|
399
|
+
else if (!win_info) console.warn(`CODE00000000 Couldn't find window ${w.matchTitle}`);
|
|
190
400
|
else if (win_info_candidates.length > 1)
|
|
191
|
-
console.warn(`CODE00000000 There are ${win_info_candidates.length} windows with title '${
|
|
401
|
+
console.warn(`CODE00000000 There are ${win_info_candidates.length} windows with title '${w.matchTitle}'. Only one window expected!`);
|
|
192
402
|
else {
|
|
193
403
|
const win = new keysender.Hardware(win_info.handle);
|
|
194
404
|
w.win = win;
|
|
@@ -214,14 +424,6 @@ async function runOnce(baseSettings, filterNames) {
|
|
|
214
424
|
}
|
|
215
425
|
|
|
216
426
|
await w.print(cmd);
|
|
217
|
-
// await awaitDelay(200);
|
|
218
|
-
// if(!no_run) {
|
|
219
|
-
// if(delay) {
|
|
220
|
-
// await awaitDelay(delay);
|
|
221
|
-
// }
|
|
222
|
-
// w.pressEnter();
|
|
223
|
-
// }
|
|
224
|
-
// await awaitDelay(300);
|
|
225
427
|
}
|
|
226
428
|
winIndex++;
|
|
227
429
|
}
|
|
@@ -242,9 +444,9 @@ async function runOnce(baseSettings, filterNames) {
|
|
|
242
444
|
console.log(`my_wins finished successfully!`);
|
|
243
445
|
}
|
|
244
446
|
|
|
245
|
-
async function promptMenu(winNames) {
|
|
447
|
+
async function promptMenu(winNames, exitChecked) {
|
|
246
448
|
const choices = winNames.map((name)=>({ name, value: name }));
|
|
247
|
-
choices.push({ name: "exit", value: "__exit__", checked:
|
|
449
|
+
choices.push({ name: "exit", value: "__exit__", checked: !!exitChecked });
|
|
248
450
|
const prompt = inquirer.prompt || (inquirer.default && inquirer.default.prompt);
|
|
249
451
|
if (!prompt) {
|
|
250
452
|
throw new Error("Inquirer prompt API is unavailable. Please use a compatible inquirer version.");
|
|
@@ -262,6 +464,7 @@ async function promptMenu(winNames) {
|
|
|
262
464
|
}
|
|
263
465
|
|
|
264
466
|
module.exports.defaultSettings = defaultSettings;
|
|
467
|
+
module.exports.shouldUseConEmuForWin = shouldUseConEmuForWin;
|
|
265
468
|
module.exports.run = async ()=>{
|
|
266
469
|
const args = parseArgs(process.argv);
|
|
267
470
|
if (args.version) {
|
|
@@ -282,8 +485,10 @@ module.exports.run = async ()=>{
|
|
|
282
485
|
console.log("No wins configured.");
|
|
283
486
|
process.exit(0);
|
|
284
487
|
}
|
|
488
|
+
let isFirstRun = true;
|
|
285
489
|
while (true) {
|
|
286
|
-
const selections = await promptMenu(winNames);
|
|
490
|
+
const selections = await promptMenu(winNames, isFirstRun);
|
|
491
|
+
isFirstRun = false;
|
|
287
492
|
const exitSelected = selections.includes("__exit__");
|
|
288
493
|
const selectedNames = selections.filter((item)=>item !== "__exit__");
|
|
289
494
|
if (selectedNames.length) {
|
package/package.json
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
2
|
+
"name": "my_wins",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "Automates starting and laying out your console windows for tsc, babel, tests, verdaccio, etc...",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"automation",
|
|
7
|
+
"cmd",
|
|
8
|
+
"keysender",
|
|
9
|
+
"windows",
|
|
10
|
+
"autostart",
|
|
11
|
+
"autorun"
|
|
12
|
+
],
|
|
13
|
+
"main": "./index.js",
|
|
14
|
+
"bin": {
|
|
15
|
+
"my_wins": "bin/index.js"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"start": "node index.js",
|
|
19
|
+
"startm": "node index.js -m",
|
|
20
|
+
"test": "node test/run-test.js"
|
|
21
|
+
},
|
|
22
|
+
"author": "Yuri Yaryshev",
|
|
23
|
+
"license": "Unlicense",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@sumbat/keysender": "^2.3.0",
|
|
26
|
+
"inquirer": "^13.2.1",
|
|
27
|
+
"json5": "^2.2.3"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"prettier": "^3.8.1"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/yuyaryshev/my_wins",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/yuyaryshev/my_wins.git"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/readme.md
CHANGED
|
@@ -4,6 +4,8 @@ Automates starting your console windows for tsc, babel, tests, verdaccio, etc...
|
|
|
4
4
|
|
|
5
5
|
my_wins opens **cmd** promts for you, position them nicely and types-in your commands into them.
|
|
6
6
|
|
|
7
|
+
If you set `conemu`, my_wins will open tabs in that ConEmu instance instead of separate system `cmd` windows.
|
|
8
|
+
|
|
7
9
|
## Why?
|
|
8
10
|
|
|
9
11
|
Why this is better than just running cmd/bat files?
|
|
@@ -35,9 +37,11 @@ Create "my_wins.json" in your project with contents:
|
|
|
35
37
|
|
|
36
38
|
```json
|
|
37
39
|
{
|
|
40
|
+
"conemu": "D:\\ProgsReady\\ConEmu\\ConEmu64.exe",
|
|
38
41
|
"wins": {
|
|
39
42
|
"cmd1": "echo cm1",
|
|
40
|
-
"cmd2": "echo cmd2"
|
|
43
|
+
"cmd2": { "cmd": "echo cmd2", "conemu": false },
|
|
44
|
+
"cmd3": { "cmd": "echo cmd3", "conemu": true }
|
|
41
45
|
}
|
|
42
46
|
}
|
|
43
47
|
```
|
|
@@ -104,13 +108,15 @@ It's recommended to gitignore "my_wins_personal.json".
|
|
|
104
108
|
```json
|
|
105
109
|
{
|
|
106
110
|
"startTimeout": 700,
|
|
111
|
+
"conemu": "D:\\ProgsReady\\ConEmu\\ConEmu64.exe",
|
|
107
112
|
"x": 0,
|
|
108
113
|
"y": 0,
|
|
109
114
|
"height": 120,
|
|
110
115
|
"width": 500,
|
|
111
116
|
"wins": {
|
|
112
117
|
"foo":"my command line 1",
|
|
113
|
-
"baz":{"no_run":true, "cmd":"my command line 2"},
|
|
118
|
+
"baz":{"no_run":true, "cmd":"my command line 2", "conemu":false},
|
|
119
|
+
"qux":{"cmd":"my command line 3", "conemu":true},
|
|
114
120
|
"webstorm": {"app":true, "cmd":"\"C:\\Program Files\\JetBrains\\WebStorm 2019.2\\bin\\webstorm64.exe\""} // notice escaped quotes !
|
|
115
121
|
// "commented": "Comments and trailing commas are supported!",
|
|
116
122
|
},
|
|
@@ -118,12 +124,15 @@ It's recommended to gitignore "my_wins_personal.json".
|
|
|
118
124
|
```
|
|
119
125
|
**x,y,height,width** *number* - is first cmd window's position and size
|
|
120
126
|
|
|
127
|
+
**conemu** *string* - optional full path to `ConEmu.exe` or `ConEmu64.exe`. When present, console commands use ConEmu by default and are sent through ConEmu GuiMacro instead of the window key sender.
|
|
128
|
+
|
|
121
129
|
**y** gets incremented by **height** for each next window
|
|
122
130
|
|
|
123
131
|
**wins** *object*- your windows
|
|
124
132
|
|
|
125
133
|
- if you enter a string it resolves to `{"cmd":"string"}`
|
|
126
134
|
- **cmd** *string* - your command
|
|
135
|
+
- **conemu** *boolean* - optional per-win override. `true` forces this win into ConEmu, `false` forces it into a normal system `cmd` window.
|
|
127
136
|
- **no_run** *boolean* - types in the command, but won't hit "Enter".
|
|
128
137
|
- **app** *boolean* - runs yours command as Windows application, not as console. (Incompartible with "no_run").
|
|
129
138
|
|
package/republish.bat
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
npm publish && npm uninstall -g my_wins && npm install -g my_wins && my_wins
|
|
1
|
+
version-select && npm publish && npm uninstall -g my_wins && npm install -g my_wins && my_wins
|
package/test/run-test.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
const { spawn } = require("child_process");
|
|
1
|
+
const { spawn, execFileSync } = require("child_process");
|
|
2
2
|
const fs = require("fs");
|
|
3
3
|
const path = require("path");
|
|
4
|
+
const { shouldUseConEmuForWin } = require("../lib");
|
|
4
5
|
|
|
5
6
|
const repoRoot = path.resolve(__dirname, "..");
|
|
6
7
|
const tempDir = path.join(repoRoot, "temp");
|
|
7
|
-
const
|
|
8
|
+
const conemuTestFile = path.join(tempDir, "my_wins_conemu_test.txt");
|
|
9
|
+
const conemuPath = "D:\\ProgsReady\\ConEmu\\ConEmu64.exe";
|
|
10
|
+
const conemuTabTitle = "My Wins Visible Title";
|
|
11
|
+
const conemuBackgroundColor = "#123456";
|
|
8
12
|
|
|
9
13
|
function sleep(ms) {
|
|
10
14
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -20,29 +24,79 @@ async function waitForFile(filePath, timeoutMs) {
|
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
async function run() {
|
|
27
|
+
const fakeConEmu = { guiPath: conemuPath };
|
|
28
|
+
if (shouldUseConEmuForWin({ conemu: conemuPath }, { cmd: "echo default" }, fakeConEmu) !== true) {
|
|
29
|
+
throw new Error("Expected wins to use ConEmu by default when top-level conemu path is set");
|
|
30
|
+
}
|
|
31
|
+
if (shouldUseConEmuForWin({ conemu: conemuPath }, { cmd: "echo plain", conemu: false }, fakeConEmu) !== false) {
|
|
32
|
+
throw new Error("Expected win-level conemu:false to force a normal cmd window");
|
|
33
|
+
}
|
|
34
|
+
if (shouldUseConEmuForWin({ conemu: conemuPath }, { cmd: "echo tab", conemu: true }, fakeConEmu) !== true) {
|
|
35
|
+
throw new Error("Expected win-level conemu:true to force ConEmu");
|
|
36
|
+
}
|
|
37
|
+
if (shouldUseConEmuForWin({}, { cmd: "echo plain", conemu: true }, null) !== false) {
|
|
38
|
+
throw new Error("Expected win-level conemu:true to be ignored when no top-level ConEmu path is configured");
|
|
39
|
+
}
|
|
23
40
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
24
|
-
if (fs.existsSync(
|
|
25
|
-
if (fs.existsSync(
|
|
26
|
-
throw new Error(`Expected test file to be deleted: ${
|
|
41
|
+
if (fs.existsSync(conemuTestFile)) fs.unlinkSync(conemuTestFile);
|
|
42
|
+
if (fs.existsSync(conemuTestFile)) {
|
|
43
|
+
throw new Error(`Expected test file to be deleted: ${conemuTestFile}`);
|
|
27
44
|
}
|
|
28
45
|
|
|
29
|
-
|
|
30
|
-
|
|
46
|
+
if (!fs.existsSync(conemuPath)) {
|
|
47
|
+
console.log(`Skipping ConEmu integration test because ConEmu was not found at ${conemuPath}`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const conemuProjectDir = path.join(tempDir, "conemu-project");
|
|
52
|
+
fs.mkdirSync(conemuProjectDir, { recursive: true });
|
|
53
|
+
fs.writeFileSync(
|
|
54
|
+
path.join(conemuProjectDir, "my_wins.json"),
|
|
55
|
+
JSON.stringify(
|
|
56
|
+
{
|
|
57
|
+
conemu: conemuPath,
|
|
58
|
+
startTimeout: 2200,
|
|
59
|
+
wins: {
|
|
60
|
+
test_conemu: {
|
|
61
|
+
title: conemuTabTitle,
|
|
62
|
+
backgroundcolor: conemuBackgroundColor,
|
|
63
|
+
cmd: `echo my_wins conemu test> ${conemuTestFile} && ping -n 6 127.0.0.1 > nul && exit`,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
null,
|
|
68
|
+
2,
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const conemuChild = spawn("node", [path.join(repoRoot, "index.js"), "--run", "test_conemu"], {
|
|
73
|
+
cwd: conemuProjectDir,
|
|
31
74
|
stdio: "inherit",
|
|
32
75
|
});
|
|
33
76
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
77
|
+
const conemuExitCode = await new Promise((resolve, reject) => {
|
|
78
|
+
conemuChild.on("error", reject);
|
|
79
|
+
conemuChild.on("close", resolve);
|
|
37
80
|
});
|
|
38
81
|
|
|
39
|
-
if (
|
|
40
|
-
throw new Error(`my_wins exited with code ${
|
|
82
|
+
if (conemuExitCode !== 0) {
|
|
83
|
+
throw new Error(`my_wins ConEmu run exited with code ${conemuExitCode}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const conemuCreated = await waitForFile(conemuTestFile, 10000);
|
|
87
|
+
if (!conemuCreated) {
|
|
88
|
+
throw new Error(`Expected ConEmu test file to exist: ${conemuTestFile}`);
|
|
41
89
|
}
|
|
42
90
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
91
|
+
const macroPath = path.join(path.dirname(conemuPath), "ConEmu", "ConEmuC64.exe");
|
|
92
|
+
const guiPid = execFileSync("powershell", [
|
|
93
|
+
"-NoProfile",
|
|
94
|
+
"-Command",
|
|
95
|
+
"(Get-Process ConEmu64,ConEmu -ErrorAction SilentlyContinue | Where-Object { $_.MainWindowHandle -ne 0 -and $_.MainWindowTitle -notlike 'About ConEmu*' } | Sort-Object Id -Descending | Select-Object -First 1 -ExpandProperty Id)",
|
|
96
|
+
], { encoding: "utf8" }).trim();
|
|
97
|
+
const tabs = execFileSync(macroPath, [`-GuiMacro:${guiPid}`, "Tab(12)"], { encoding: "utf8" });
|
|
98
|
+
if (!tabs.includes(conemuTabTitle)) {
|
|
99
|
+
throw new Error(`Expected ConEmu tabs list to include visible title '${conemuTabTitle}', got: ${tabs}`);
|
|
46
100
|
}
|
|
47
101
|
}
|
|
48
102
|
|