starlight-cli 1.0.3 → 1.0.4
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/dist/index.js +2556 -0
- package/dist/read.cs.js +123 -0
- package/dist/read.ps1 +128 -0
- package/dist/read.sh +137 -0
- package/index.js +3 -5
- package/package.json +10 -3
- package/src/evaluator.js +400 -0
- package/src/lexer.js +157 -0
- package/src/parser.js +441 -0
- package/src/starlight.js +117 -0
package/dist/read.cs.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/* jshint wsh:true */
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* readlineSync
|
|
5
|
+
* https://github.com/anseki/readline-sync
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2019 anseki
|
|
8
|
+
* Licensed under the MIT license.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
var
|
|
12
|
+
FSO_ForReading = 1, FSO_ForWriting = 2,
|
|
13
|
+
PS_MSG = 'Microsoft Windows PowerShell is required.' +
|
|
14
|
+
' https://technet.microsoft.com/en-us/library/hh847837.aspx',
|
|
15
|
+
|
|
16
|
+
input = '', fso, tty,
|
|
17
|
+
options = (function(conf) {
|
|
18
|
+
var options = {}, arg, args =// Array.prototype.slice.call(WScript.Arguments),
|
|
19
|
+
(function() {
|
|
20
|
+
var args = [], i, iLen;
|
|
21
|
+
for (i = 0, iLen = WScript.Arguments.length; i < iLen; i++)
|
|
22
|
+
{ args.push(WScript.Arguments(i)); }
|
|
23
|
+
return args;
|
|
24
|
+
})(),
|
|
25
|
+
confLc = {}, key;
|
|
26
|
+
|
|
27
|
+
function decodeArg(arg) {
|
|
28
|
+
return arg.replace(/#(\d+);/g, function(str, charCode) {
|
|
29
|
+
return String.fromCharCode(+charCode);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (key in conf) {
|
|
34
|
+
if (conf.hasOwnProperty(key))
|
|
35
|
+
{ confLc[key.toLowerCase()] = {key: key, type: conf[key]}; }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
while (typeof(arg = args.shift()) === 'string') {
|
|
39
|
+
if (!(arg = (arg.match(/^\-+(.+)$/) || [])[1])) { continue; }
|
|
40
|
+
arg = arg.toLowerCase();
|
|
41
|
+
if (confLc[arg]) {
|
|
42
|
+
options[confLc[arg].key] =
|
|
43
|
+
confLc[arg].type === 'boolean' ? true :
|
|
44
|
+
confLc[arg].type === 'string' ? args.shift() : null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (key in conf) {
|
|
48
|
+
if (conf.hasOwnProperty(key) && conf[key] === 'string') {
|
|
49
|
+
if (typeof options[key] !== 'string') { options[key] = ''; }
|
|
50
|
+
else { options[key] = decodeArg(options[key]); }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return options;
|
|
54
|
+
})({
|
|
55
|
+
display: 'string',
|
|
56
|
+
displayOnly: 'boolean',
|
|
57
|
+
keyIn: 'boolean',
|
|
58
|
+
hideEchoBack: 'boolean',
|
|
59
|
+
mask: 'string'
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!options.hideEchoBack && !options.keyIn) {
|
|
63
|
+
if (options.display) { writeTTY(options.display); }
|
|
64
|
+
if (!options.displayOnly) { input = readByFSO(); }
|
|
65
|
+
} else if (options.hideEchoBack && !options.keyIn && !options.mask) {
|
|
66
|
+
if (options.display) { writeTTY(options.display); }
|
|
67
|
+
if (!options.displayOnly) { input = readByPW(); }
|
|
68
|
+
} else {
|
|
69
|
+
WScript.StdErr.WriteLine(PS_MSG);
|
|
70
|
+
WScript.Quit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
WScript.StdOut.Write('\'' + input + '\'');
|
|
74
|
+
|
|
75
|
+
WScript.Quit();
|
|
76
|
+
|
|
77
|
+
function writeTTY(text) {
|
|
78
|
+
try {
|
|
79
|
+
tty = tty || getFso().OpenTextFile('CONOUT$', FSO_ForWriting, true);
|
|
80
|
+
tty.Write(text);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
WScript.StdErr.WriteLine('TTY Write Error: ' + e.number +
|
|
83
|
+
'\n' + e.description + '\n' + PS_MSG);
|
|
84
|
+
WScript.Quit(e.number || 1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function readByFSO() {
|
|
89
|
+
var text;
|
|
90
|
+
try {
|
|
91
|
+
text = getFso().OpenTextFile('CONIN$', FSO_ForReading).ReadLine();
|
|
92
|
+
} catch (e) {
|
|
93
|
+
WScript.StdErr.WriteLine('TTY Read Error: ' + e.number +
|
|
94
|
+
'\n' + e.description + '\n' + PS_MSG);
|
|
95
|
+
WScript.Quit(e.number || 1);
|
|
96
|
+
}
|
|
97
|
+
return text;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// TTY must be STDIN that is not redirected and not piped.
|
|
101
|
+
function readByPW() {
|
|
102
|
+
var text;
|
|
103
|
+
try {
|
|
104
|
+
text = WScript.CreateObject('ScriptPW.Password').GetPassword()
|
|
105
|
+
// Bug? Illegal data may be returned when user types before initializing.
|
|
106
|
+
.replace(/[\u4000-\u40FF]/g, function(chr) {
|
|
107
|
+
var charCode = chr.charCodeAt(0);
|
|
108
|
+
return charCode >= 0x4020 && charCode <= 0x407F ?
|
|
109
|
+
String.fromCharCode(charCode - 0x4000) : '';
|
|
110
|
+
});
|
|
111
|
+
} catch (e) {
|
|
112
|
+
WScript.StdErr.WriteLine('ScriptPW.Password Error: ' + e.number +
|
|
113
|
+
'\n' + e.description + '\n' + PS_MSG);
|
|
114
|
+
WScript.Quit(e.number || 1);
|
|
115
|
+
}
|
|
116
|
+
writeTTY('\n');
|
|
117
|
+
return text;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getFso() {
|
|
121
|
+
if (!fso) { fso = new ActiveXObject('Scripting.FileSystemObject'); }
|
|
122
|
+
return fso;
|
|
123
|
+
}
|
package/dist/read.ps1
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# readlineSync
|
|
2
|
+
# https://github.com/anseki/readline-sync
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2019 anseki
|
|
5
|
+
# Licensed under the MIT license.
|
|
6
|
+
|
|
7
|
+
Param(
|
|
8
|
+
[string] $display,
|
|
9
|
+
[switch] $displayOnly,
|
|
10
|
+
[switch] $keyIn,
|
|
11
|
+
[switch] $hideEchoBack,
|
|
12
|
+
[string] $mask,
|
|
13
|
+
[string] $limit,
|
|
14
|
+
[switch] $caseSensitive
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
$ErrorActionPreference = 'Stop' # for cmdlet
|
|
18
|
+
trap {
|
|
19
|
+
# `throw $_` and `Write-Error $_` return exit-code 0
|
|
20
|
+
$Host.UI.WriteErrorLine($_)
|
|
21
|
+
exit 1
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function decodeArg ($arg) {
|
|
25
|
+
[Regex]::Replace($arg, '#(\d+);', { [char][int] $args[0].Groups[1].Value })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
$options = @{}
|
|
29
|
+
foreach ($arg in @('display', 'displayOnly', 'keyIn', 'hideEchoBack', 'mask', 'limit', 'caseSensitive')) {
|
|
30
|
+
$options.Add($arg, (Get-Variable $arg -ValueOnly))
|
|
31
|
+
}
|
|
32
|
+
$argList = New-Object string[] $options.Keys.Count
|
|
33
|
+
$options.Keys.CopyTo($argList, 0)
|
|
34
|
+
foreach ($arg in $argList) {
|
|
35
|
+
if ($options[$arg] -is [string] -and $options[$arg])
|
|
36
|
+
{ $options[$arg] = decodeArg $options[$arg] }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
[string] $inputTTY = ''
|
|
40
|
+
[bool] $silent = -not $options.display -and
|
|
41
|
+
$options.keyIn -and $options.hideEchoBack -and -not $options.mask
|
|
42
|
+
[bool] $isCooked = -not $options.hideEchoBack -and -not $options.keyIn
|
|
43
|
+
|
|
44
|
+
# Instant method that opens TTY without CreateFile via P/Invoke in .NET Framework
|
|
45
|
+
# **NOTE** Don't include special characters of DOS in $command when $getRes is True.
|
|
46
|
+
# [string] $cmdPath = $Env:ComSpec
|
|
47
|
+
# [string] $psPath = 'powershell.exe'
|
|
48
|
+
function execWithTTY ($command, $getRes = $False, $throwError = $False) {
|
|
49
|
+
if ($getRes) {
|
|
50
|
+
$res = (cmd.exe /C "<CON powershell.exe -Command $command")
|
|
51
|
+
if ($LastExitCode -ne 0) {
|
|
52
|
+
if ($throwError) { throw $LastExitCode }
|
|
53
|
+
else { exit $LastExitCode }
|
|
54
|
+
}
|
|
55
|
+
return $res
|
|
56
|
+
} else {
|
|
57
|
+
$command | cmd.exe /C ">CON powershell.exe -Command -"
|
|
58
|
+
if ($LastExitCode -ne 0) {
|
|
59
|
+
if ($throwError) { throw $LastExitCode }
|
|
60
|
+
else { exit $LastExitCode }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function writeTTY ($text) {
|
|
66
|
+
execWithTTY ('Write-Host (''' +
|
|
67
|
+
(($text -replace '''', '''''') -replace '[\r\n]', '''+"`n"+''') + ''') -NoNewline')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if ($options.display) {
|
|
71
|
+
writeTTY $options.display
|
|
72
|
+
}
|
|
73
|
+
if ($options.displayOnly) { return "''" }
|
|
74
|
+
|
|
75
|
+
if (-not $options.keyIn -and $options.hideEchoBack -and $options.mask -eq '*') {
|
|
76
|
+
# It fails when it's not ready.
|
|
77
|
+
try {
|
|
78
|
+
$inputTTY = execWithTTY ('$text = Read-Host -AsSecureString;' +
|
|
79
|
+
'$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($text);' +
|
|
80
|
+
'[Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)') $True $True
|
|
81
|
+
return '''' + $inputTTY + ''''
|
|
82
|
+
} catch {} # ignore
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if ($options.keyIn) { $reqSize = 1 }
|
|
86
|
+
|
|
87
|
+
if ($options.keyIn -and $options.limit) {
|
|
88
|
+
$limitPtn = '[^' + $options.limit + ']'
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
while ($True) {
|
|
92
|
+
if (-not $isCooked) {
|
|
93
|
+
$chunk = [char][int] (execWithTTY '[int] [Console]::ReadKey($True).KeyChar' $True)
|
|
94
|
+
} else {
|
|
95
|
+
$chunk = execWithTTY 'Read-Host' $True
|
|
96
|
+
$chunk += "`n"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if ($chunk -and $chunk -match '^(.*?)[\r\n]') {
|
|
100
|
+
$chunk = $Matches[1]
|
|
101
|
+
$atEol = $True
|
|
102
|
+
} else { $atEol = $False }
|
|
103
|
+
|
|
104
|
+
# other ctrl-chars
|
|
105
|
+
if ($chunk) { $chunk = $chunk -replace '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '' }
|
|
106
|
+
if ($chunk -and $limitPtn) {
|
|
107
|
+
if ($options.caseSensitive) { $chunk = $chunk -creplace $limitPtn, '' }
|
|
108
|
+
else { $chunk = $chunk -ireplace $limitPtn, '' }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if ($chunk) {
|
|
112
|
+
if (-not $isCooked) {
|
|
113
|
+
if (-not $options.hideEchoBack) {
|
|
114
|
+
writeTTY $chunk
|
|
115
|
+
} elseif ($options.mask) {
|
|
116
|
+
writeTTY ($options.mask * $chunk.Length)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
$inputTTY += $chunk
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if ((-not $options.keyIn -and $atEol) -or
|
|
123
|
+
($options.keyIn -and $inputTTY.Length -ge $reqSize)) { break }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (-not $isCooked -and -not $silent) { execWithTTY 'Write-Host ''''' } # new line
|
|
127
|
+
|
|
128
|
+
return "'$inputTTY'"
|
package/dist/read.sh
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# readlineSync
|
|
2
|
+
# https://github.com/anseki/readline-sync
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2019 anseki
|
|
5
|
+
# Licensed under the MIT license.
|
|
6
|
+
|
|
7
|
+
# Use perl for compatibility of sed/awk of GNU / POSIX, BSD. (and tr)
|
|
8
|
+
# Hide "\n" from shell by "\fNL"
|
|
9
|
+
|
|
10
|
+
decode_arg() {
|
|
11
|
+
printf '%s' "$(printf '%s' "$1" | perl -pe 's/#(\d+);/sprintf("%c", $1)/ge; s/[\r\n]/\fNL/g')"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# getopt(s)
|
|
15
|
+
while [ $# -ge 1 ]; do
|
|
16
|
+
arg="$(printf '%s' "$1" | grep -E '^-+[^-]+$' | tr '[A-Z]' '[a-z]' | tr -d '-')"
|
|
17
|
+
case "$arg" in
|
|
18
|
+
'display') shift; options_display="$(decode_arg "$1")";;
|
|
19
|
+
'displayonly') options_displayOnly=true;;
|
|
20
|
+
'keyin') options_keyIn=true;;
|
|
21
|
+
'hideechoback') options_hideEchoBack=true;;
|
|
22
|
+
'mask') shift; options_mask="$(decode_arg "$1")";;
|
|
23
|
+
'limit') shift; options_limit="$(decode_arg "$1")";;
|
|
24
|
+
'casesensitive') options_caseSensitive=true;;
|
|
25
|
+
esac
|
|
26
|
+
shift
|
|
27
|
+
done
|
|
28
|
+
|
|
29
|
+
reset_tty() {
|
|
30
|
+
if [ -n "$save_tty" ]; then
|
|
31
|
+
stty --file=/dev/tty "$save_tty" 2>/dev/null || \
|
|
32
|
+
stty -F /dev/tty "$save_tty" 2>/dev/null || \
|
|
33
|
+
stty -f /dev/tty "$save_tty" || exit $?
|
|
34
|
+
fi
|
|
35
|
+
}
|
|
36
|
+
trap 'reset_tty' EXIT
|
|
37
|
+
save_tty="$(stty --file=/dev/tty -g 2>/dev/null || stty -F /dev/tty -g 2>/dev/null || stty -f /dev/tty -g || exit $?)"
|
|
38
|
+
|
|
39
|
+
[ -z "$options_display" ] && [ "$options_keyIn" = true ] && \
|
|
40
|
+
[ "$options_hideEchoBack" = true ] && [ -z "$options_mask" ] && silent=true
|
|
41
|
+
[ "$options_hideEchoBack" != true ] && [ "$options_keyIn" != true ] && is_cooked=true
|
|
42
|
+
|
|
43
|
+
write_tty() {
|
|
44
|
+
# if [ "$2" = true ]; then
|
|
45
|
+
# printf '%b' "$1" >/dev/tty
|
|
46
|
+
# else
|
|
47
|
+
# printf '%s' "$1" >/dev/tty
|
|
48
|
+
# fi
|
|
49
|
+
printf '%s' "$1" | perl -pe 's/\fNL/\r\n/g' >/dev/tty
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
replace_allchars() { (
|
|
53
|
+
text=''
|
|
54
|
+
for i in $(seq 1 ${#1})
|
|
55
|
+
do
|
|
56
|
+
text="$text$2"
|
|
57
|
+
done
|
|
58
|
+
printf '%s' "$text"
|
|
59
|
+
) }
|
|
60
|
+
|
|
61
|
+
if [ -n "$options_display" ]; then
|
|
62
|
+
write_tty "$options_display"
|
|
63
|
+
fi
|
|
64
|
+
if [ "$options_displayOnly" = true ]; then
|
|
65
|
+
printf "'%s'" ''
|
|
66
|
+
exit 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
if [ "$is_cooked" = true ]; then
|
|
70
|
+
stty --file=/dev/tty cooked 2>/dev/null || \
|
|
71
|
+
stty -F /dev/tty cooked 2>/dev/null || \
|
|
72
|
+
stty -f /dev/tty cooked || exit $?
|
|
73
|
+
else
|
|
74
|
+
stty --file=/dev/tty raw -echo 2>/dev/null || \
|
|
75
|
+
stty -F /dev/tty raw -echo 2>/dev/null || \
|
|
76
|
+
stty -f /dev/tty raw -echo || exit $?
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
[ "$options_keyIn" = true ] && req_size=1
|
|
80
|
+
|
|
81
|
+
if [ "$options_keyIn" = true ] && [ -n "$options_limit" ]; then
|
|
82
|
+
if [ "$options_caseSensitive" = true ]; then
|
|
83
|
+
limit_ptn="$options_limit"
|
|
84
|
+
else
|
|
85
|
+
# Safe list
|
|
86
|
+
# limit_ptn="$(printf '%s' "$options_limit" | sed 's/\([a-z]\)/\L\1\U\1/ig')"
|
|
87
|
+
limit_ptn="$(printf '%s' "$options_limit" | perl -pe 's/([a-z])/lc($1) . uc($1)/ige')"
|
|
88
|
+
fi
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
while :
|
|
92
|
+
do
|
|
93
|
+
if [ "$is_cooked" != true ]; then
|
|
94
|
+
# chunk="$(dd if=/dev/tty bs=1 count=1 2>/dev/null)"
|
|
95
|
+
chunk="$(dd if=/dev/tty bs=1 count=1 2>/dev/null | perl -pe 's/[\r\n]/\fNL/g')"
|
|
96
|
+
else
|
|
97
|
+
IFS= read -r chunk </dev/tty || exit $?
|
|
98
|
+
chunk="$(printf '%s\fNL' "$chunk")"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# if [ -n "$chunk" ] && [ "$(printf '%s' "$chunk" | tr '\r' '\n' | wc -l)" != "0" ]; then
|
|
102
|
+
# chunk="$(printf '%s' "$chunk" | tr '\r' '\n' | head -n 1)"
|
|
103
|
+
if [ -n "$chunk" ] && printf '%s' "$chunk" | perl -ne '/\fNL/ or exit 1'; then
|
|
104
|
+
chunk="$(printf '%s' "$chunk" | perl -pe 's/^(.*?)\fNL.*$/$1/')"
|
|
105
|
+
at_eol=true
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# other ctrl-chars
|
|
109
|
+
if [ -n "$chunk" ]; then
|
|
110
|
+
# chunk="$(printf '%s' "$chunk" | tr -d '\00-\10\13\14\16-\37\177')"
|
|
111
|
+
# for System V
|
|
112
|
+
chunk="$(printf '%s' "$chunk" | tr -d '\00\01\02\03\04\05\06\07\10\13\14\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177')"
|
|
113
|
+
fi
|
|
114
|
+
if [ -n "$chunk" ] && [ -n "$limit_ptn" ]; then
|
|
115
|
+
chunk="$(printf '%s' "$chunk" | tr -cd "$limit_ptn")"
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
if [ -n "$chunk" ]; then
|
|
119
|
+
if [ "$is_cooked" != true ]; then
|
|
120
|
+
if [ "$options_hideEchoBack" != true ]; then
|
|
121
|
+
write_tty "$chunk"
|
|
122
|
+
elif [ -n "$options_mask" ]; then
|
|
123
|
+
write_tty "$(replace_allchars "$chunk" "$options_mask")"
|
|
124
|
+
fi
|
|
125
|
+
fi
|
|
126
|
+
input="$input$chunk"
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
if ( [ "$options_keyIn" != true ] && [ "$at_eol" = true ] ) || \
|
|
130
|
+
( [ "$options_keyIn" = true ] && [ ${#input} -ge $req_size ] ); then break; fi
|
|
131
|
+
done
|
|
132
|
+
|
|
133
|
+
if [ "$is_cooked" != true ] && [ "$silent" != true ]; then write_tty "$(printf '%b' '\fNL')"; fi
|
|
134
|
+
|
|
135
|
+
printf "'%s'" "$input"
|
|
136
|
+
|
|
137
|
+
exit 0
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starlight-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Starlight Programming Language CLI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"starlight": "index.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"
|
|
9
|
+
"build": "ncc build src/starlight.js -o dist",
|
|
10
|
+
"prepublishOnly": "npm run build"
|
|
10
11
|
},
|
|
11
12
|
"author": "Macedon",
|
|
12
13
|
"license": "MIT",
|
|
13
14
|
"os": [
|
|
14
15
|
"win32"
|
|
15
|
-
]
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"readline-sync": "^1.4.10"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@vercel/ncc": "^0.38.4"
|
|
22
|
+
}
|
|
16
23
|
}
|