pingthings 0.3.0 → 0.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/README.md +24 -0
- package/bin/pingthings.js +12 -0
- package/package.json +1 -1
- package/packs/0ad-civilizations/manifest.json +1 -0
- package/packs/7kaa-soldiers/manifest.json +1 -0
- package/packs/fighting-announcer/manifest.json +62 -0
- package/packs/fighting-announcer/sounds/begin.wav +0 -0
- package/packs/fighting-announcer/sounds/complete.wav +0 -0
- package/packs/fighting-announcer/sounds/congratulations.wav +0 -0
- package/packs/fighting-announcer/sounds/defeat.wav +0 -0
- package/packs/fighting-announcer/sounds/failure.wav +0 -0
- package/packs/fighting-announcer/sounds/fight.wav +0 -0
- package/packs/fighting-announcer/sounds/final-round.wav +0 -0
- package/packs/fighting-announcer/sounds/finish.wav +0 -0
- package/packs/fighting-announcer/sounds/game-over.wav +0 -0
- package/packs/fighting-announcer/sounds/get-set.wav +0 -0
- package/packs/fighting-announcer/sounds/go.wav +0 -0
- package/packs/fighting-announcer/sounds/great.wav +0 -0
- package/packs/fighting-announcer/sounds/high-score.wav +0 -0
- package/packs/fighting-announcer/sounds/new-record.wav +0 -0
- package/packs/fighting-announcer/sounds/no-contest.wav +0 -0
- package/packs/fighting-announcer/sounds/ready.wav +0 -0
- package/packs/fighting-announcer/sounds/sudden-death.wav +0 -0
- package/packs/fighting-announcer/sounds/victory.wav +0 -0
- package/packs/fighting-announcer/sounds/you-lose.wav +0 -0
- package/packs/fighting-announcer/sounds/you-win.wav +0 -0
- package/packs/freedoom-arsenal/manifest.json +1 -0
- package/packs/kenney-interface/manifest.json +58 -0
- package/packs/kenney-interface/sounds/back-1.wav +0 -0
- package/packs/kenney-interface/sounds/bong.wav +0 -0
- package/packs/kenney-interface/sounds/click-1.wav +0 -0
- package/packs/kenney-interface/sounds/close-1.wav +0 -0
- package/packs/kenney-interface/sounds/confirmation-1.wav +0 -0
- package/packs/kenney-interface/sounds/confirmation-2.wav +0 -0
- package/packs/kenney-interface/sounds/confirmation-3.wav +0 -0
- package/packs/kenney-interface/sounds/error-1.wav +0 -0
- package/packs/kenney-interface/sounds/error-2.wav +0 -0
- package/packs/kenney-interface/sounds/error-3.wav +0 -0
- package/packs/kenney-interface/sounds/glitch-1.wav +0 -0
- package/packs/kenney-interface/sounds/maximize-1.wav +0 -0
- package/packs/kenney-interface/sounds/maximize-2.wav +0 -0
- package/packs/kenney-interface/sounds/open-1.wav +0 -0
- package/packs/kenney-interface/sounds/pluck-1.wav +0 -0
- package/packs/kenney-interface/sounds/question-1.wav +0 -0
- package/packs/kenney-interface/sounds/question-2.wav +0 -0
- package/packs/kenney-interface/sounds/question-3.wav +0 -0
- package/packs/kenney-scifi/manifest.json +58 -0
- package/packs/kenney-scifi/sounds/computer-1.wav +0 -0
- package/packs/kenney-scifi/sounds/computer-2.wav +0 -0
- package/packs/kenney-scifi/sounds/computer-3.wav +0 -0
- package/packs/kenney-scifi/sounds/door-close.wav +0 -0
- package/packs/kenney-scifi/sounds/door-open.wav +0 -0
- package/packs/kenney-scifi/sounds/engine-1.wav +0 -0
- package/packs/kenney-scifi/sounds/explosion-1.wav +0 -0
- package/packs/kenney-scifi/sounds/explosion-2.wav +0 -0
- package/packs/kenney-scifi/sounds/forcefield-1.wav +0 -0
- package/packs/kenney-scifi/sounds/forcefield-2.wav +0 -0
- package/packs/kenney-scifi/sounds/impact-1.wav +0 -0
- package/packs/kenney-scifi/sounds/impact-2.wav +0 -0
- package/packs/kenney-scifi/sounds/laser-large.wav +0 -0
- package/packs/kenney-scifi/sounds/laser-retro.wav +0 -0
- package/packs/kenney-scifi/sounds/low-explosion.wav +0 -0
- package/packs/kenney-scifi/sounds/slime.wav +0 -0
- package/packs/kenney-scifi/sounds/thruster-1.wav +0 -0
- package/packs/kenney-scifi/sounds/thruster-2.wav +0 -0
- package/packs/openarena-announcer/manifest.json +1 -0
- package/packs/retro-8bit/manifest.json +58 -0
- package/packs/retro-8bit/sounds/button-1.wav +0 -0
- package/packs/retro-8bit/sounds/button-2.wav +0 -0
- package/packs/retro-8bit/sounds/button-3.wav +0 -0
- package/packs/retro-8bit/sounds/coin-1.wav +0 -0
- package/packs/retro-8bit/sounds/coin-2.wav +0 -0
- package/packs/retro-8bit/sounds/coin-cluster.wav +0 -0
- package/packs/retro-8bit/sounds/coin-double.wav +0 -0
- package/packs/retro-8bit/sounds/damage-1.wav +0 -0
- package/packs/retro-8bit/sounds/error-1.wav +0 -0
- package/packs/retro-8bit/sounds/error-2.wav +0 -0
- package/packs/retro-8bit/sounds/error-3.wav +0 -0
- package/packs/retro-8bit/sounds/fanfare-1.wav +0 -0
- package/packs/retro-8bit/sounds/fanfare-2.wav +0 -0
- package/packs/retro-8bit/sounds/fanfare-3.wav +0 -0
- package/packs/retro-8bit/sounds/negative-1.wav +0 -0
- package/packs/retro-8bit/sounds/negative-2.wav +0 -0
- package/packs/retro-8bit/sounds/powerup-1.wav +0 -0
- package/packs/retro-8bit/sounds/powerup-2.wav +0 -0
- package/packs/warzone2100-command/manifest.json +1 -0
- package/packs/wesnoth-combat/manifest.json +2 -1
- package/packs/xonotic-announcer/manifest.json +52 -0
- package/packs/xonotic-announcer/sounds/1fragleft.wav +0 -0
- package/packs/xonotic-announcer/sounds/airshot.wav +0 -0
- package/packs/xonotic-announcer/sounds/amazing.wav +0 -0
- package/packs/xonotic-announcer/sounds/awesome.wav +0 -0
- package/packs/xonotic-announcer/sounds/begin.wav +0 -0
- package/packs/xonotic-announcer/sounds/botlike.wav +0 -0
- package/packs/xonotic-announcer/sounds/electrobitch.wav +0 -0
- package/packs/xonotic-announcer/sounds/headshot.wav +0 -0
- package/packs/xonotic-announcer/sounds/impressive.wav +0 -0
- package/packs/xonotic-announcer/sounds/lastsecond.wav +0 -0
- package/packs/xonotic-announcer/sounds/leadgained.wav +0 -0
- package/packs/xonotic-announcer/sounds/narrowly.wav +0 -0
- package/packs/xonotic-announcer/sounds/prepareforbattle.wav +0 -0
- package/packs/xonotic-announcer/sounds/terminated.wav +0 -0
- package/packs/xonotic-announcer/sounds/yoda.wav +0 -0
- package/src/cli/browse.js +75 -0
- package/src/cli/completions.js +182 -0
- package/src/cli/config.js +32 -3
- package/src/cli/create.js +1 -1
- package/src/cli/doctor.js +134 -0
- package/src/cli/init.js +11 -8
- package/src/cli/play.js +22 -1
- package/src/cli/random-pack.js +44 -0
- package/src/cli/search.js +81 -0
- package/src/cli/select.js +36 -32
- package/src/cli/sounds.js +76 -0
- package/src/cli/theme.js +34 -1
- package/src/cli/uninstall.js +5 -0
- package/src/config.js +30 -0
- package/src/packs.js +1 -0
- package/src/player.js +17 -12
package/src/cli/select.js
CHANGED
|
@@ -45,37 +45,41 @@ export default async function select(args) {
|
|
|
45
45
|
output: process.stdout,
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
await new Promise((resolve) => {
|
|
49
|
+
const ask = () => {
|
|
50
|
+
rl.question('> ', (answer) => {
|
|
51
|
+
const trimmed = answer.trim().toLowerCase();
|
|
52
|
+
|
|
53
|
+
if (trimmed === 'q' || trimmed === 'quit' || trimmed === '') {
|
|
54
|
+
rl.close();
|
|
55
|
+
resolve();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const num = parseInt(trimmed, 10);
|
|
60
|
+
if (isNaN(num) || num < 1 || num > packs.length) {
|
|
61
|
+
console.log(`Enter 1-${packs.length} or q to quit.`);
|
|
62
|
+
ask();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const chosen = packs[num - 1];
|
|
67
|
+
config.activePack = chosen.name;
|
|
68
|
+
writeConfig(config);
|
|
69
|
+
|
|
70
|
+
// Preview a sound from the chosen pack
|
|
71
|
+
const sounds = getPackSounds(chosen.name);
|
|
72
|
+
if (sounds.length > 0) {
|
|
73
|
+
const sample = sounds[Math.floor(Math.random() * sounds.length)];
|
|
74
|
+
playSound(sample, config.volume);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log(`\nActive pack set to: ${chosen.name}`);
|
|
53
78
|
rl.close();
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
ask();
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const chosen = packs[num - 1];
|
|
65
|
-
config.activePack = chosen.name;
|
|
66
|
-
writeConfig(config);
|
|
67
|
-
|
|
68
|
-
// Preview a sound from the chosen pack
|
|
69
|
-
const sounds = getPackSounds(chosen.name);
|
|
70
|
-
if (sounds.length > 0) {
|
|
71
|
-
const sample = sounds[Math.floor(Math.random() * sounds.length)];
|
|
72
|
-
playSound(sample, config.volume);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
console.log(`\nActive pack set to: ${chosen.name}`);
|
|
76
|
-
rl.close();
|
|
77
|
-
});
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
ask();
|
|
79
|
+
resolve();
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
ask();
|
|
84
|
+
});
|
|
81
85
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { readConfig } from '../config.js';
|
|
2
|
+
import { getPackSounds, resolvePack, getPackEvents, getEventSounds } from '../packs.js';
|
|
3
|
+
import { basename } from 'node:path';
|
|
4
|
+
|
|
5
|
+
function showHelp() {
|
|
6
|
+
console.log(`
|
|
7
|
+
Usage: pingthings sounds [pack]
|
|
8
|
+
|
|
9
|
+
List all individual sounds in a pack.
|
|
10
|
+
|
|
11
|
+
Arguments:
|
|
12
|
+
pack Pack to list sounds from (defaults to active pack)
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
--events Show sounds grouped by event mapping
|
|
16
|
+
|
|
17
|
+
Examples:
|
|
18
|
+
pingthings sounds List sounds in active pack
|
|
19
|
+
pingthings sounds openarena-announcer List sounds in a specific pack
|
|
20
|
+
pingthings sounds --events Show event mappings
|
|
21
|
+
`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function sounds(args) {
|
|
25
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
26
|
+
showHelp();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const showEvents = args.includes('--events');
|
|
31
|
+
const packName = args.find(a => !a.startsWith('-')) || readConfig().activePack;
|
|
32
|
+
|
|
33
|
+
const pack = resolvePack(packName);
|
|
34
|
+
if (!pack) {
|
|
35
|
+
console.error(`Pack not found: ${packName}`);
|
|
36
|
+
console.error('Run "pingthings list" to see available packs.');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const allSounds = getPackSounds(packName);
|
|
41
|
+
|
|
42
|
+
if (showEvents) {
|
|
43
|
+
const events = getPackEvents(packName);
|
|
44
|
+
console.log(`\n${packName} — sounds by event:\n`);
|
|
45
|
+
|
|
46
|
+
for (const event of events) {
|
|
47
|
+
const eventSounds = getEventSounds(packName, event);
|
|
48
|
+
console.log(` ${event}:`);
|
|
49
|
+
for (const s of eventSounds) {
|
|
50
|
+
console.log(` ${basename(s)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Show unmapped sounds
|
|
55
|
+
const mappedPaths = new Set();
|
|
56
|
+
for (const event of events) {
|
|
57
|
+
for (const s of getEventSounds(packName, event)) {
|
|
58
|
+
mappedPaths.add(s);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const unmapped = allSounds.filter(s => !mappedPaths.has(s));
|
|
62
|
+
if (unmapped.length > 0) {
|
|
63
|
+
console.log(' (unmapped):');
|
|
64
|
+
for (const s of unmapped) {
|
|
65
|
+
console.log(` ${basename(s)}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
console.log(`\n${packName} — ${allSounds.length} sounds:\n`);
|
|
70
|
+
for (const s of allSounds) {
|
|
71
|
+
console.log(` ${basename(s)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log('');
|
|
76
|
+
}
|
package/src/cli/theme.js
CHANGED
|
@@ -57,11 +57,44 @@ const THEMES = {
|
|
|
57
57
|
blocked: '0ad-civilizations',
|
|
58
58
|
},
|
|
59
59
|
},
|
|
60
|
+
'professional': {
|
|
61
|
+
description: 'Clean and minimal — Kenney UI sounds for everything',
|
|
62
|
+
activePack: 'kenney-interface',
|
|
63
|
+
eventPacks: {
|
|
64
|
+
done: 'kenney-interface',
|
|
65
|
+
permission: 'kenney-interface',
|
|
66
|
+
complete: 'kenney-interface',
|
|
67
|
+
error: 'kenney-interface',
|
|
68
|
+
blocked: 'kenney-interface',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
'8bit': {
|
|
72
|
+
description: 'Pure retro — 8-bit chiptune for everything',
|
|
73
|
+
activePack: 'retro-8bit',
|
|
74
|
+
eventPacks: {
|
|
75
|
+
done: 'retro-8bit',
|
|
76
|
+
permission: 'retro-8bit',
|
|
77
|
+
complete: 'retro-8bit',
|
|
78
|
+
error: 'retro-8bit',
|
|
79
|
+
blocked: 'retro-8bit',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
'space': {
|
|
83
|
+
description: 'Space station — Warzone 2100 + Kenney sci-fi',
|
|
84
|
+
activePack: 'kenney-scifi',
|
|
85
|
+
eventPacks: {
|
|
86
|
+
done: 'kenney-scifi',
|
|
87
|
+
permission: 'warzone2100-command',
|
|
88
|
+
complete: 'warzone2100-command',
|
|
89
|
+
error: 'kenney-scifi',
|
|
90
|
+
blocked: 'kenney-scifi',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
60
93
|
'chaos': {
|
|
61
94
|
description: 'Random pack for every event — maximum variety',
|
|
62
95
|
activePack: '7kaa-soldiers',
|
|
63
96
|
eventPacks: {
|
|
64
|
-
done: '
|
|
97
|
+
done: 'retro-8bit',
|
|
65
98
|
permission: 'openarena-announcer',
|
|
66
99
|
complete: 'warzone2100-command',
|
|
67
100
|
error: 'freedoom-arsenal',
|
package/src/cli/uninstall.js
CHANGED
|
@@ -25,6 +25,11 @@ export default function uninstall(args) {
|
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
if (packName.includes('..') || packName.includes('/') || packName.includes('\\')) {
|
|
29
|
+
console.error('Invalid pack name.');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
const userPackDir = join(getConfigDir(), 'packs', packName);
|
|
29
34
|
|
|
30
35
|
if (!existsSync(userPackDir)) {
|
package/src/config.js
CHANGED
|
@@ -8,6 +8,8 @@ const DEFAULTS = {
|
|
|
8
8
|
specificSound: null,
|
|
9
9
|
volume: 100,
|
|
10
10
|
eventPacks: {},
|
|
11
|
+
cooldown: true,
|
|
12
|
+
quietHours: null,
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
export function getConfigDir() {
|
|
@@ -45,6 +47,34 @@ export function getDefaults() {
|
|
|
45
47
|
return { ...DEFAULTS };
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
export function getLastPlayed() {
|
|
51
|
+
const path = join(getConfigDir(), '.last-played');
|
|
52
|
+
try {
|
|
53
|
+
return readFileSync(path, 'utf8').trim();
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function setLastPlayed(soundPath) {
|
|
60
|
+
const path = join(getConfigDir(), '.last-played');
|
|
61
|
+
try {
|
|
62
|
+
writeFileSync(path, soundPath, 'utf8');
|
|
63
|
+
} catch {}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function isQuietHours(config) {
|
|
67
|
+
if (!config.quietHours) return false;
|
|
68
|
+
const [start, end] = config.quietHours.split('-').map(Number);
|
|
69
|
+
if (isNaN(start) || isNaN(end)) return false;
|
|
70
|
+
const hour = new Date().getHours();
|
|
71
|
+
if (start < end) {
|
|
72
|
+
return hour >= start && hour < end;
|
|
73
|
+
}
|
|
74
|
+
// Wraps midnight (e.g., 22-7)
|
|
75
|
+
return hour >= start || hour < end;
|
|
76
|
+
}
|
|
77
|
+
|
|
48
78
|
export const VALID_MODES = ['random', 'specific', 'informational'];
|
|
49
79
|
|
|
50
80
|
export const VALID_EVENTS = ['done', 'permission', 'complete', 'error', 'blocked'];
|
package/src/packs.js
CHANGED
|
@@ -57,6 +57,7 @@ function scanDirectory(dir, isBuiltIn) {
|
|
|
57
57
|
description: manifest?.description || '',
|
|
58
58
|
license: manifest?.license || 'Unknown',
|
|
59
59
|
credits: manifest?.credits || '',
|
|
60
|
+
category: manifest?.category || 'other',
|
|
60
61
|
location: packDir,
|
|
61
62
|
soundCount: sounds.length,
|
|
62
63
|
isBuiltIn,
|
package/src/player.js
CHANGED
|
@@ -2,17 +2,23 @@ import { spawn, execFileSync } from 'node:child_process';
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { platform } from 'node:os';
|
|
4
4
|
|
|
5
|
+
function commandExists(cmd) {
|
|
6
|
+
try {
|
|
7
|
+
execFileSync('which', [cmd], { stdio: 'pipe' });
|
|
8
|
+
return true;
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
5
14
|
function getPlayerCommand() {
|
|
6
15
|
switch (platform()) {
|
|
7
16
|
case 'darwin':
|
|
8
17
|
return 'afplay';
|
|
9
18
|
case 'linux': {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} catch {
|
|
14
|
-
return 'aplay';
|
|
15
|
-
}
|
|
19
|
+
if (commandExists('paplay')) return 'paplay';
|
|
20
|
+
if (commandExists('aplay')) return 'aplay';
|
|
21
|
+
return null;
|
|
16
22
|
}
|
|
17
23
|
case 'win32':
|
|
18
24
|
return 'powershell';
|
|
@@ -26,20 +32,16 @@ function buildArgs(cmd, filePath, volume) {
|
|
|
26
32
|
|
|
27
33
|
switch (cmd) {
|
|
28
34
|
case 'afplay': {
|
|
29
|
-
// afplay volume: 0.0 to 1.0
|
|
30
35
|
const afplayVol = (vol / 100).toFixed(2);
|
|
31
36
|
return ['-v', afplayVol, filePath];
|
|
32
37
|
}
|
|
33
38
|
case 'paplay': {
|
|
34
|
-
// paplay volume: 0 to 65536 (100% = 65536)
|
|
35
39
|
const paplayVol = Math.round((vol / 100) * 65536).toString();
|
|
36
40
|
return ['--volume', paplayVol, filePath];
|
|
37
41
|
}
|
|
38
42
|
case 'aplay':
|
|
39
|
-
// aplay doesn't support volume natively
|
|
40
43
|
return [filePath];
|
|
41
44
|
case 'powershell': {
|
|
42
|
-
// PowerShell SoundPlayer doesn't support volume, but it plays the file
|
|
43
45
|
const script = `(New-Object System.Media.SoundPlayer '${filePath.replace(/'/g, "''")}').PlaySync()`;
|
|
44
46
|
return ['-NoProfile', '-Command', script];
|
|
45
47
|
}
|
|
@@ -55,7 +57,8 @@ export function playSound(filePath, volume) {
|
|
|
55
57
|
|
|
56
58
|
const cmd = getPlayerCommand();
|
|
57
59
|
if (!cmd) {
|
|
58
|
-
|
|
60
|
+
// No audio player available — skip silently (e.g. CI environments)
|
|
61
|
+
return;
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
const args = buildArgs(cmd, filePath, volume);
|
|
@@ -65,6 +68,8 @@ export function playSound(filePath, volume) {
|
|
|
65
68
|
stdio: 'ignore',
|
|
66
69
|
});
|
|
67
70
|
|
|
71
|
+
// Handle spawn errors gracefully (command not found, permission denied)
|
|
72
|
+
child.on('error', () => {});
|
|
68
73
|
child.unref();
|
|
69
74
|
}
|
|
70
75
|
|
|
@@ -75,7 +80,7 @@ export function playSoundSync(filePath, volume) {
|
|
|
75
80
|
|
|
76
81
|
const cmd = getPlayerCommand();
|
|
77
82
|
if (!cmd) {
|
|
78
|
-
|
|
83
|
+
return;
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
const args = buildArgs(cmd, filePath, volume);
|