mediasnacks 0.32.0 → 0.33.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 +1 -0
- package/package.json +1 -1
- package/src/cli.js +7 -6
- package/src/dlaudio.js +4 -9
- package/src/info.js +40 -32
- package/src/seqcheck.js +1 -5
- package/src/tag.osascript +42 -0
- package/src/unemoji.js +4 -3
- package/src/utils/ffmpeg.js +17 -6
- package/src/utils/formatSeconds.js +14 -15
- package/src/utils/parseOptions.js +15 -22
- package/src/utils/parseTimecode.js +4 -4
- package/src/utils/printProgress.js +26 -6
- package/src/utils/test-utils.js +4 -13
package/README.md
CHANGED
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -18,7 +18,8 @@ const COMMANDS = {
|
|
|
18
18
|
countframes: ['./countframes.js', 'Counts frames in a video'],
|
|
19
19
|
ssim: ['./ssim.js', 'Computes SSIM between two images'],
|
|
20
20
|
gif: ['./gif.js', 'Video to GIF'],
|
|
21
|
-
info: ['./info.js', 'Prints video stream attributes
|
|
21
|
+
info: ['./info.js', 'Prints video stream attributes'],
|
|
22
|
+
tag: ['./tag.osascript', 'Adds color tag for Finder\n'],
|
|
22
23
|
|
|
23
24
|
detectdups: ['./detectdups.js', 'Detects duplicate frames in a video'],
|
|
24
25
|
dropdups: ['./dropdups.js', 'Removes duplicate frames in a video'],
|
|
@@ -55,7 +56,7 @@ SYNOPSIS
|
|
|
55
56
|
|
|
56
57
|
COMMANDS
|
|
57
58
|
${commandsSummary().map(([cmd, desc]) =>
|
|
58
|
-
` ${styleText('bold', cmd.padEnd(12
|
|
59
|
+
` ${styleText('bold', cmd.padEnd(12))}\t${desc}`).join('\n')}
|
|
59
60
|
`.trim()
|
|
60
61
|
|
|
61
62
|
|
|
@@ -77,11 +78,11 @@ async function main() {
|
|
|
77
78
|
if (!opt) throw HELP
|
|
78
79
|
if (!Object.hasOwn(COMMANDS, opt)) throw `'${opt}' is not a command. See mediasnacks --help\n`
|
|
79
80
|
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
await (await import(
|
|
81
|
+
const prog = COMMANDS[opt][0]
|
|
82
|
+
if (prog.endsWith('.js'))
|
|
83
|
+
await (await import(prog)).default()
|
|
83
84
|
else
|
|
84
|
-
spawn(join(import.meta.dirname,
|
|
85
|
+
spawn(join(import.meta.dirname, prog), args, { stdio: 'inherit' })
|
|
85
86
|
.on('exit', process.exit)
|
|
86
87
|
}
|
|
87
88
|
|
package/src/dlaudio.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { parseOptions } from './utils/parseOptions.js'
|
|
2
|
-
import {
|
|
3
|
-
import { unemoji } from './unemoji.js'
|
|
2
|
+
import { run } from './utils/subprocess.js'
|
|
4
3
|
|
|
5
4
|
|
|
6
5
|
const HELP = `
|
|
@@ -17,18 +16,14 @@ export default async function main() {
|
|
|
17
16
|
if (!positionals[0])
|
|
18
17
|
throw usage('Missing URL')
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
console.log(f)
|
|
19
|
+
await dlaudio(positionals[0])
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
export async function dlaudio(url) {
|
|
25
|
-
|
|
26
|
-
'--print', 'filename',
|
|
23
|
+
await run('yt-dlp', [
|
|
27
24
|
'--no-simulate',
|
|
28
25
|
'-o', '%(title)s.%(ext)s',
|
|
29
26
|
'-f', 'bestaudio[ext=m4a]/bestaudio',
|
|
30
27
|
url
|
|
31
|
-
])
|
|
32
|
-
|
|
33
|
-
return await unemoji(f) || f
|
|
28
|
+
])
|
|
34
29
|
}
|
package/src/info.js
CHANGED
|
@@ -5,13 +5,38 @@ import { formatSeconds, cleanDecimals } from './utils/formatSeconds.js'
|
|
|
5
5
|
|
|
6
6
|
const HELP = `
|
|
7
7
|
SYNOPSIS
|
|
8
|
-
mediasnacks info [-a | --all] <
|
|
8
|
+
mediasnacks info [-a | --all] <files>
|
|
9
9
|
|
|
10
10
|
DESCRIPTION
|
|
11
|
-
Prints
|
|
11
|
+
Prints video or image attributes using ffprobe. By default, it’s similar to
|
|
12
|
+
\`ls\` but prints the: width, height, fps, duration, codec, and filename.
|
|
12
13
|
|
|
13
14
|
OPTIONS
|
|
14
|
-
-a, --all Prints
|
|
15
|
+
-a, --all Prints everything as JSON
|
|
16
|
+
|
|
17
|
+
EXAMPLES
|
|
18
|
+
Short summary of each match:
|
|
19
|
+
mediasnacks info *.mp4
|
|
20
|
+
|
|
21
|
+
Sort by fps:
|
|
22
|
+
mediasnacks info *.* | sort -k3,3n
|
|
23
|
+
|
|
24
|
+
Move 60fps videos into 60fps/ subdir:
|
|
25
|
+
FPS=60
|
|
26
|
+
DIR=\${FPS}fps
|
|
27
|
+
mkdir -p \$DIR
|
|
28
|
+
mediasnacks info *.* |
|
|
29
|
+
awk "\\$3==\\"\${FPS}fps\\" { print \\$NF }" |
|
|
30
|
+
while read -r f; do
|
|
31
|
+
mv -- "$f" \$DIR/
|
|
32
|
+
done
|
|
33
|
+
|
|
34
|
+
Tag with "red" (in macOS Finder) 1080 videos:
|
|
35
|
+
COLOR=red
|
|
36
|
+
HEIGHT=1080
|
|
37
|
+
mediasnacks info *.mov |
|
|
38
|
+
awk "\\$2==\$HEIGHT { print \\$NF }" |
|
|
39
|
+
xargs -I{} mediasnacks tag $COLOR "{}"
|
|
15
40
|
`
|
|
16
41
|
|
|
17
42
|
export default async function main() {
|
|
@@ -19,45 +44,28 @@ export default async function main() {
|
|
|
19
44
|
all: { short: 'a', type: 'boolean' }
|
|
20
45
|
})
|
|
21
46
|
|
|
22
|
-
|
|
23
|
-
if (!video) throw usage('No video file specified')
|
|
47
|
+
if (!files[0]) throw usage('No video file specified')
|
|
24
48
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
49
|
+
for (const video of files)
|
|
50
|
+
if (values.all)
|
|
51
|
+
console.log(JSON.stringify(await videoAttrs(video), '', 2))
|
|
52
|
+
else
|
|
53
|
+
console.log(`${await infoSummary(video)}\t${video}`)
|
|
29
54
|
}
|
|
30
55
|
|
|
31
56
|
|
|
32
57
|
export async function infoSummary(video) {
|
|
33
58
|
const v = await videoAttrs(video)
|
|
34
59
|
return [
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
60
|
+
String(v.width).padStart(4),
|
|
61
|
+
String(v.height).padStart(4),
|
|
62
|
+
`${fps(v.r_frame_rate)}fps`.padStart(7),
|
|
63
|
+
formatSeconds(v.duration, 0).padStart(8),
|
|
64
|
+
v.codec_name
|
|
65
|
+
].join('\t')
|
|
40
66
|
}
|
|
41
67
|
|
|
42
68
|
function fps(rFrameRate) {
|
|
43
69
|
const [num, den] = rFrameRate.split('/').map(Number)
|
|
44
70
|
return cleanDecimals((num / den).toFixed(2))
|
|
45
71
|
}
|
|
46
|
-
|
|
47
|
-
function prettyCodecName(codec) {
|
|
48
|
-
// ffmpeg -codecs | grep '^...V'
|
|
49
|
-
return {
|
|
50
|
-
'dnxhd': 'DNxHD',
|
|
51
|
-
'dvvideo': 'DV (Digital Video)',
|
|
52
|
-
'h264': 'H.264',
|
|
53
|
-
'hevc': 'H.265',
|
|
54
|
-
'jpeg2000': 'JPEG 2000',
|
|
55
|
-
'mpeg4': 'MPEG-4 Part 2',
|
|
56
|
-
'prores': 'ProRes',
|
|
57
|
-
'qtrle': 'QuickTime RLE',
|
|
58
|
-
'rawvideo': 'Uncompressed',
|
|
59
|
-
}[codec] || codec
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
package/src/seqcheck.js
CHANGED
|
@@ -35,7 +35,7 @@ export function seqcheck(dir, leftDelim = LEFT_DELIM, rightDelim = RIGHT_DELIM)
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export function extractSeqNums(names, leftDelim, rightDelim) {
|
|
38
|
-
const pattern = new RegExp(
|
|
38
|
+
const pattern = new RegExp(RegExp.escape(leftDelim) + '(\\d+)' + RegExp.escape(rightDelim))
|
|
39
39
|
const seq = []
|
|
40
40
|
for (const name of names) {
|
|
41
41
|
const match = name.match(pattern)
|
|
@@ -54,7 +54,3 @@ export function findMissingNumbers(seq) {
|
|
|
54
54
|
missing.push(i)
|
|
55
55
|
return missing
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
function escapeRegex(str) {
|
|
59
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
60
|
-
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/osascript
|
|
2
|
+
|
|
3
|
+
property colorNames : {"none", "orange", "red", "yellow", "blue", "purple", "green", "gray"}
|
|
4
|
+
|
|
5
|
+
on usage()
|
|
6
|
+
log "USAGE: mediasnacks tag <color> <file>"
|
|
7
|
+
set AppleScript's text item delimiters to " "
|
|
8
|
+
set colorList to colorNames as text
|
|
9
|
+
set AppleScript's text item delimiters to ""
|
|
10
|
+
log "COLORS: " & colorList
|
|
11
|
+
end usage
|
|
12
|
+
|
|
13
|
+
on labelNumForColor(theItem)
|
|
14
|
+
repeat with i from 1 to count of colorNames
|
|
15
|
+
if item i of colorNames as text is theItem as text then
|
|
16
|
+
return i - 1
|
|
17
|
+
end if
|
|
18
|
+
end repeat
|
|
19
|
+
return -1
|
|
20
|
+
end labelNumForColor
|
|
21
|
+
|
|
22
|
+
on run argv
|
|
23
|
+
if (count of argv) < 2 then
|
|
24
|
+
usage()
|
|
25
|
+
return
|
|
26
|
+
end if
|
|
27
|
+
set tagColor to item 1 of argv
|
|
28
|
+
set filePath to item 2 of argv
|
|
29
|
+
|
|
30
|
+
set labelNum to labelNumForColor(tagColor)
|
|
31
|
+
if labelNum is -1 then
|
|
32
|
+
log "invalid color: " & tagColor
|
|
33
|
+
usage()
|
|
34
|
+
return
|
|
35
|
+
end if
|
|
36
|
+
|
|
37
|
+
set theFile to POSIX file filePath as alias
|
|
38
|
+
tell application "Finder"
|
|
39
|
+
set label index of theFile to labelNum
|
|
40
|
+
end tell
|
|
41
|
+
return
|
|
42
|
+
end run
|
package/src/unemoji.js
CHANGED
|
@@ -44,7 +44,7 @@ export default async function main() {
|
|
|
44
44
|
|
|
45
45
|
for (const file of files) {
|
|
46
46
|
const newpath = await unemoji(file)
|
|
47
|
-
if (newpath)
|
|
47
|
+
if (newpath !== file)
|
|
48
48
|
console.log(`Renaming: ${file} -> ${newpath}`)
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -57,10 +57,11 @@ export async function unemoji(file) {
|
|
|
57
57
|
.replace(/\s+/g, ' ')
|
|
58
58
|
.replace(/\s+\./g, '.')
|
|
59
59
|
.trim()
|
|
60
|
+
const newpath = join(dir, newbase)
|
|
61
|
+
|
|
60
62
|
if (base === newbase)
|
|
61
|
-
return
|
|
63
|
+
return newpath
|
|
62
64
|
|
|
63
|
-
const newpath = join(dir, newbase)
|
|
64
65
|
if (existsSync(newpath))
|
|
65
66
|
throw `Skipping (exists): ${file} -> ${newpath}`
|
|
66
67
|
|
package/src/utils/ffmpeg.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import os from 'node:os'
|
|
1
2
|
import { spawn } from 'node:child_process'
|
|
2
|
-
import { printProgress } from './printProgress.js'
|
|
3
|
+
import { printProgress, showCursor } from './printProgress.js'
|
|
3
4
|
import { videoAttrs } from './videoAttrs.js'
|
|
4
5
|
import { runSilently } from './subprocess.js'
|
|
5
6
|
|
|
@@ -32,16 +33,26 @@ export async function ffmpegWithProgress(input, args, onProgress = printProgress
|
|
|
32
33
|
...args
|
|
33
34
|
], { stdio: ['inherit', 'pipe', 'pipe'] })
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
const startTime = performance.now()
|
|
36
37
|
p.stdout.on('data', chunk => {
|
|
37
38
|
const text = chunk.toString()
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
else {
|
|
39
|
+
const msElapsed = performance.now() - startTime
|
|
40
|
+
if (text.includes('progress=continue')) {
|
|
41
41
|
const m = text.match(/out_time_us=(\d+)/)
|
|
42
|
-
|
|
42
|
+
const progress = Number(m[1]) / µsVideoDuration
|
|
43
|
+
const msETA = msElapsed * (1 - progress) / progress
|
|
44
|
+
onProgress(progress, msElapsed, msETA)
|
|
43
45
|
}
|
|
46
|
+
else
|
|
47
|
+
onProgress(1, msElapsed, 0)
|
|
48
|
+
})
|
|
49
|
+
process.on('SIGINT', () => {
|
|
50
|
+
p?.kill('SIGINT')
|
|
51
|
+
showCursor()
|
|
52
|
+
console.log('\nAborted')
|
|
53
|
+
process.exit(128 + os.constants.signals.SIGINT)
|
|
44
54
|
})
|
|
55
|
+
p.stderr.pipe(process.stderr)
|
|
45
56
|
|
|
46
57
|
await new Promise((resolve, reject) => {
|
|
47
58
|
p.on('error', reject)
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes trailing zeros and a trailing decimal point.
|
|
3
|
+
*
|
|
4
|
+
* Examples:
|
|
5
|
+
* cleanDecimals(3.1400) -> "3.14"
|
|
6
|
+
* cleanDecimals(5.0) -> "5"
|
|
7
|
+
*/
|
|
8
|
+
export const cleanDecimals = Number
|
|
9
|
+
|
|
1
10
|
/**
|
|
2
11
|
* Converts seconds to a string like "9h9m9s".
|
|
3
12
|
*
|
|
@@ -8,26 +17,16 @@
|
|
|
8
17
|
* formatSeconds(3661.25, 2) -> "1h1m1.25s"
|
|
9
18
|
*/
|
|
10
19
|
export function formatSeconds(seconds, maxDecimals = 2) {
|
|
20
|
+
if (!Number.isFinite(+seconds))
|
|
21
|
+
return ''
|
|
11
22
|
const intSeconds = seconds | 0
|
|
12
23
|
const partialSeconds = seconds % 60
|
|
13
24
|
const minutes = (intSeconds % 3600) / 60 | 0
|
|
14
25
|
const hours = intSeconds / 3600 | 0
|
|
15
26
|
|
|
16
27
|
let result = ''
|
|
17
|
-
if (hours) result +=
|
|
18
|
-
if (minutes) result +=
|
|
19
|
-
if (partialSeconds || !result) result +=
|
|
28
|
+
if (hours) result += hours + 'h'
|
|
29
|
+
if (minutes) result += minutes + 'm'
|
|
30
|
+
if (partialSeconds || !result) result += cleanDecimals(partialSeconds.toFixed(maxDecimals)) + 's'
|
|
20
31
|
return result
|
|
21
32
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Removes trailing zeros and a trailing decimal point.
|
|
26
|
-
*
|
|
27
|
-
* Examples:
|
|
28
|
-
* cleanDecimals(3.1400) -> "3.14"
|
|
29
|
-
* cleanDecimals(5.0) -> "5"
|
|
30
|
-
*/
|
|
31
|
-
export function cleanDecimals(number) {
|
|
32
|
-
return String(number).replace(/\.?0+$/, '') || '0'
|
|
33
|
-
}
|
|
@@ -10,48 +10,41 @@ const glob = promisify(_glob)
|
|
|
10
10
|
* @param {Partial<import('node:util').ParseArgsConfig>} [config]
|
|
11
11
|
*/
|
|
12
12
|
export async function parseOptions(helpText, options = {}, config = {}) {
|
|
13
|
+
helpText = helpText.trim()
|
|
13
14
|
options.help = { short: 'h', type: 'boolean' }
|
|
14
15
|
|
|
15
|
-
const { values, positionals
|
|
16
|
+
const { values, positionals } = parseArgs({
|
|
16
17
|
args: process.argv.slice(3),
|
|
17
18
|
allowPositionals: true,
|
|
18
19
|
options,
|
|
19
|
-
...config
|
|
20
|
-
tokens: true
|
|
20
|
+
...config
|
|
21
21
|
})
|
|
22
22
|
|
|
23
23
|
if (values.help) {
|
|
24
|
-
console.log(helpText
|
|
24
|
+
console.log(helpText)
|
|
25
25
|
process.exit(0)
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
return {
|
|
29
29
|
values,
|
|
30
30
|
positionals,
|
|
31
|
-
files: await resolveGlobs(positionals
|
|
31
|
+
files: await resolveGlobs(positionals),
|
|
32
32
|
usage: err => err
|
|
33
|
-
? styleText('redBright',
|
|
33
|
+
? styleText('redBright', err + '\n') + helpText
|
|
34
34
|
: helpText
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
async function resolveGlobs(arr
|
|
39
|
-
const terminatorIndex = tokens.find(t => t.kind === 'option-terminator')?.index ?? -1
|
|
38
|
+
async function resolveGlobs(arr) {
|
|
40
39
|
const set = new Set()
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (terminatorIndex !== -1)
|
|
52
|
-
for (const literal of arr.slice(terminatorIndex))
|
|
53
|
-
set.add(literal)
|
|
54
|
-
|
|
40
|
+
for (const arg of arr) {
|
|
41
|
+
const matches = await glob(arg)
|
|
42
|
+
if (matches.length)
|
|
43
|
+
for (const file of matches)
|
|
44
|
+
set.add(file)
|
|
45
|
+
else
|
|
46
|
+
set.add(arg)
|
|
47
|
+
}
|
|
55
48
|
return Array.from(set)
|
|
56
49
|
}
|
|
57
50
|
|
|
@@ -6,11 +6,11 @@ export function parseTimecode(time) {
|
|
|
6
6
|
if (parts.some(isNaN) || parts.length > 3)
|
|
7
7
|
throw new Error(`Invalid time: ${time}`)
|
|
8
8
|
|
|
9
|
-
// HH:MM:SS or HH:MM:SS.mmm
|
|
10
|
-
|
|
9
|
+
if (parts.length === 3) // HH:MM:SS or HH:MM:SS.mmm
|
|
10
|
+
return (3600 * parts[0]) + (60 * parts[1]) + parts[2]
|
|
11
11
|
|
|
12
|
-
// MM:SS or MM:SS.mmm
|
|
13
|
-
|
|
12
|
+
if (parts.length === 2) // MM:SS or MM:SS.mmm
|
|
13
|
+
return (60 * parts[0]) + parts[1]
|
|
14
14
|
|
|
15
15
|
return parts[0]
|
|
16
16
|
}
|
|
@@ -1,14 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { formatSeconds } from './formatSeconds.js'
|
|
2
|
+
|
|
3
|
+
const HIDE_CURSOR = '\x1b[?25l'
|
|
4
|
+
const SHOW_CURSOR = '\x1b[?25h'
|
|
5
|
+
const ERASE_TO_END = '\x1b[K'
|
|
6
|
+
|
|
7
|
+
export const showCursor = () => process.stdout.write(SHOW_CURSOR)
|
|
8
|
+
|
|
9
|
+
export function printProgress(progress, msElapsed, msETA) {
|
|
10
|
+
const elapsed = msElapsed
|
|
11
|
+
? ` • ${formatSeconds(msElapsed / 1000, 0)}`
|
|
12
|
+
: ''
|
|
13
|
+
const eta = msETA
|
|
14
|
+
? ` • ETA ${formatSeconds(msETA / 1000, 0)}`
|
|
15
|
+
: ''
|
|
16
|
+
const percent = progress === 1
|
|
17
|
+
? '100%'
|
|
18
|
+
: `${(progress * 100).toFixed(1)}%`
|
|
19
|
+
process.stdout.write(HIDE_CURSOR)
|
|
20
|
+
process.stdout.write(`\r${progressBar(progress)} ${percent}${eta}${elapsed}${ERASE_TO_END}`)
|
|
3
21
|
if (progress === 1)
|
|
4
|
-
process.stdout.write('\n')
|
|
22
|
+
process.stdout.write('\n' + SHOW_CURSOR)
|
|
5
23
|
}
|
|
6
24
|
|
|
7
|
-
function progressBar(progress, width =
|
|
25
|
+
function progressBar(progress, width = 44) {
|
|
26
|
+
width-- // for partial char
|
|
8
27
|
const nFull = (width * progress) | 0
|
|
9
28
|
const fPartial = (width * progress) - nFull
|
|
10
29
|
const nRemaining = width - nFull
|
|
30
|
+
|
|
11
31
|
const partials = ' ▏▎▍▌▋▊▉'
|
|
12
|
-
const partial = partials
|
|
13
|
-
return '█'.repeat(nFull) + partial + '
|
|
32
|
+
const partial = partials.at(partials.length * fPartial)
|
|
33
|
+
return '█'.repeat(nFull) + partial + '•'.repeat(nRemaining)
|
|
14
34
|
}
|
package/src/utils/test-utils.js
CHANGED
|
@@ -5,19 +5,10 @@ import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
|
5
5
|
|
|
6
6
|
const rel = f => join(import.meta.dirname, f)
|
|
7
7
|
|
|
8
|
-
export
|
|
9
|
-
return mkdtempSync(join(tmpdir(), prefix))
|
|
10
|
-
}
|
|
8
|
+
export const cli = (...args) => spawnSync(rel('../cli.js'), args)
|
|
11
9
|
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
}
|
|
10
|
+
export const dir = (...args) => mkdirSync(join(...args), { recursive: true })
|
|
11
|
+
export const touch = (...args) => writeFileSync(join(...args), '')
|
|
15
12
|
|
|
16
|
-
export
|
|
17
|
-
return mkdirSync(join(...args), { recursive: true })
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function touch(...args) {
|
|
21
|
-
return writeFileSync(join(...args), '')
|
|
22
|
-
}
|
|
13
|
+
export const mkTempDir = (prefix = 'test-') => mkdtempSync(join(tmpdir(), prefix))
|
|
23
14
|
|