首次提交

This commit is contained in:
Teng 2025-06-27 08:56:14 +08:00
commit f9a4c5ad14
2354 changed files with 984865 additions and 0 deletions

18
App.vue Normal file
View File

@ -0,0 +1,18 @@
<script>
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style lang="scss">
/*每个页面公共css */
@import "./uni_modules/vk-uview-ui/index.scss";
</style>

50
api/loginApi.js Normal file
View File

@ -0,0 +1,50 @@
// src/composables/useWeChatAuth.js
import { ref } from 'vue';
import request from '@/request/index.js';
//滑块获取验证码
export function getHkCode(data){
return request({
url: `/sys/getHkCode`,
method: 'post',
data,
})
}
//发送验证码
export function smsCode(data){
return request({
url: `/sys/smsCode`,
method: 'post',
data,
})
}
//验证码是否正确
export function checkPhoneCode(data){
return request({
url: `/sys/checkPhoneCode`,
method: 'post',
data,
})
}
//不知道干啥的
export function getList(){
return request({
url: `/NuBizAdvisoryInfo/nuBizAdvisoryInfo/list`,
method: 'get',
})
}
//微信code换openid
export function getOpenid(code){
return request({
url: `/weixin/wechat/callback?code=${code}`,
method: 'get',
})
}
//微信openid换个人信息
export function getMessage(openId){
return request({
url: `/h5Api/nuBizAdvisoryInfo/queryWeixinInfo?openId=${openId}`,
method: 'get',
})
}

47
api/main.js Normal file
View File

@ -0,0 +1,47 @@
// src/composables/useWeChatAuth.js
import { ref } from 'vue';
import request from '@/request/index.js';
//解决跨域
export function proxy(res){
return request({
url: `/api/proxy/get?apiUrl=${res.apiUrl}&params=${res.other}`,
method: 'get',
})
}
export const jsonp = function (url, data) {
return new Promise((resolve, reject) => {
// 1.初始化url
let dataString = url.indexOf('?') === -1 ? '?' : '&'
let callbackName = `jsonpCB_${Date.now()}`;
url += `${dataString}`
if (data) {
// 2.有请求参数依次添加到url
for (let k in data) {
url += `&${k}=${data[k]}`
}
}
let scriptNode = document.createElement('script');
scriptNode.src = url;
// 3. callback
window[callbackName] = (result) => {
result ? resolve(result) : reject('没有返回数据');
delete window[callbackName];
document.body.removeChild(scriptNode);
}
// 4. 异常情况
scriptNode.addEventListener('error', () => {
reject('接口返回数据失败');
delete window[callbackName];
document.body.removeChild(scriptNode);
}, false)
// 5. 开始请求
document.body.appendChild(scriptNode)
})
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

3
books/证书信息.txt Normal file
View File

@ -0,0 +1,3 @@
您的证书密码为Xo3GhjdI
__uni__010b3c0

View File

@ -0,0 +1,37 @@
<template>
<view>
<!-- https://ext.dcloud.net.cn/plugin?id=10333 -->
<qf-image-cropper :src="src" :width="width" :height="height" :radius="0" @crop="handleCrop" />
</view>
</template>
<script setup>
import {
ref
} from 'vue'
import {
onLoad
} from '@dcloudio/uni-app';
import QfImageCropper from '@/uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.vue'
//
const src = ref('') //
const type = ref(0);
const width = ref(900);
const height = ref(600);
//
const handleCrop = (e) => {
// console.log("")
uni.setStorageSync(`imgkey${type.value}`, e.tempFilePath);
uni.navigateBack()
}
onLoad((options) => {
src.value = options.url
//type
type.value = options.type
if(options.size){
width.value = 1500;
height.value = 1000;
}
});
</script>

View File

@ -0,0 +1,107 @@
<template>
<!-- 底部的栏为啥这样写是因为要做左右拉动 -->
<view class="botton-view">
<view v-for="(item,index) in itemArray" :key="index" class="array-father">
<view :class="itemTarget===index ? `bottom-button-target` : `bottom-button`" @click="jumpto(index)">
<image class="botton-img"
:src="`https://www.focusnu.com/media/directive/index/itemsbutton/${index}${itemTarget===index?1:0}.png`" />
<view class="bottom-text">
{{item}}
</view>
</view>
</view>
</view>
</template>
<script setup>
import {
ref
} from 'vue'
const props = defineProps({
itemTarget: {
type: Number,
required: true //
// default: 0 //
}
})
const itemArray = ["NU", "动态", "我的"];
const jumpto = (index) => {
if(index!=props.itemTarget){
switch (index) {
case 0:
uni.redirectTo({
url: `/pages/index/index`
});
break;
case 1:
break;
case 2:
uni.redirectTo({
url: `/pages/index/mine`
});
break;
}
}
}
</script>
<style lang="scss" scoped>
.botton-view {
position: fixed;
bottom: 0;
left: 0;
height: 120rpx;
width: 100%;
background-color: #fff;
display: flex;
justify-content: space-between;
font-weight: 500;
z-index: 999;
.bottom-button {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.bottom-button-target {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: rgb(42, 133, 235);
flex-direction: column;
}
.blue-heng {
height: 6rpx;
width: 150rpx;
background-color: rgb(42, 133, 235);
position: absolute;
bottom: 55rpx;
left: 50%;
/* 左边缘到父容器左边的距离占父宽度 50% */
transform: translateX(-50%);
}
}
.array-father {
width: 33%;
position: relative;
}
.botton-img {
width: 38rpx;
height: 38rpx;
margin-bottom: 5rpx;
}
</style>

View File

@ -0,0 +1,269 @@
<template>
<view class="captcha-container" id="container">
<view class="font-title">请通过滑块验证</view>
<view class="captcha-image" style="position: relative; width: 100%; height: 400rpx; overflow: hidden;">
<image :src="bgImage" class="bg-image" mode="widthFix" @load="init" />
<view class="overlay" :style="{ width: containerWidth + 'rpx', height: containerHeight + 'rpx' }">
<view
class="hole"
:style="{
top: originY + 'rpx',
left: originX + 'rpx',
width: pieceSize + 'rpx',
height: pieceSize + 'rpx',
clipPath: clipPath,
transform: 'translate(-50%, -50%)',
backgroundColor: 'rgba(0,0,0,0.6)'
}"
></view>
<view
class="piece"
:style="{
top: originY + 'rpx',
left: offsetX + 'rpx',
width: pieceSize + 'rpx',
height: pieceSize + 'rpx',
backgroundImage: `url(${bgImage})`,
backgroundSize: containerWidth + 'rpx ' + containerHeight + 'rpx',
backgroundPosition: `-${originX+30}rpx -${originY+43}rpx`,
clipPath: clipPath,
transform: 'translate(-50%, -50%)'
}"
></view>
</view>
</view>
<view class="slider-bar">
<view class="slider-bar-font">向右滑动滑块填充拼图</view>
<view
class="slider-button"
ref="btn"
@touchstart="onStart"
@touchmove="onMove"
@touchend="onEnd"
:style="{ left: offsetX + 'rpx', maxWidth: (containerWidth - pieceSize) + 'rpx' }"
>
<image
src="https://www.focusnu.com/media/directive/login/right.png"
style="width: 50rpx; height: 50rpx;"
mode="widthFix"
/>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, nextTick, getCurrentInstance } from 'vue'
const emit = defineEmits(['success'])
const pieceSizePx = 50
const pieceSize = pieceSizePx * 2
const tolerance = 20
const containerWidth = ref(400)
const containerHeight = ref(400)
const originX = ref(0)
const originY = ref(0)
const offsetX = ref(0)
const dragging = ref(false)
const startX = ref(0)
const bgImage = ref('')
const instance = getCurrentInstance()
function getPuzzlePiecePath(size) {
const s = size
return `
M${10 * 2} 0
h${s / 3 - 10 * 2}
a${10 * 2} ${10 * 2} 0 0 1 0 ${20 * 2}
h${s / 3}
a${10 * 2} ${10 * 2} 0 0 0 0 -${20 * 2}
h${s / 3 - 10 * 2}
v${s / 3 - 10 * 2}
a${10 * 2} ${10 * 2} 0 0 1 -${20 * 2} 0
v${s / 3}
a${10 * 2} ${10 * 2} 0 0 0 ${20 * 2} 0
v${s / 3 - 10 * 2}
h-${s / 3 - 10 * 2}
a${10 * 2} ${10 * 2} 0 0 1 0 -${20 * 2}
h-${s / 3}
a${10 * 2} ${10 * 2} 0 0 0 0 ${20 * 2}
h-${s / 3 - 10 * 2}
z
`
}
const clipPath = `path('${getPuzzlePiecePath(pieceSize)}')`
function init() {
nextTick(() => {
if (!instance) {
console.error('无法获取组件实例')
return
}
uni.createSelectorQuery()
.in(instance.proxy)
.select('.bg-image')
.boundingClientRect(data => {
if (!data) {
console.error('无法获取图片尺寸')
return
}
containerWidth.value = data.width * 2
containerHeight.value = data.height * 2
originX.value = Math.random() * (containerWidth.value - pieceSize * 2) + pieceSize
if (originX.value < 100) originX.value = 100
if (originX.value > 400) originX.value = 400
originY.value = containerHeight.value / 2
offsetX.value = 0
})
.exec()
})
}
function onStart(e) {
dragging.value = true
startX.value = e.touches[0].clientX * 2
}
function onMove(e) {
if (!dragging.value) return
const clientX = e.touches[0].clientX * 2
let dx = clientX - startX.value
dx = Math.max(0, Math.min(dx, containerWidth.value - pieceSize))
offsetX.value = dx
}
function onEnd() {
dragging.value = false
if (Math.abs(offsetX.value - originX.value) < tolerance) {
uni.showToast({
title: '验证成功',
icon: 'none',
duration: 2000
})
// console.log("????", originX.value)
emit('success')
} else {
offsetX.value = 0
uni.showToast({
title: '验证失败',
icon: 'none',
duration: 2000
})
}
}
onMounted(() => {
const images = [
'https://www.focusnu.com/media/directive/login/0.png',
'https://www.focusnu.com/media/directive/login/1.png',
'https://www.focusnu.com/media/directive/login/2.png',
// 'https://www.focusnu.com/media/directive/login/3.png'
]
bgImage.value = images[Math.floor(Math.random() * images.length)]
console.log('加载图片:', bgImage.value)
})
</script>
<style scoped lang="scss">
.captcha-container {
user-select: none;
background-color: #fff;
width: 600rpx;
height: 700rpx;
margin: 0 auto;
border-radius: 30rpx;
overflow: hidden;
padding: 0 30rpx;
}
.captcha-image {
position: relative;
overflow: hidden;
}
.bg-image {
position: relative;
z-index: 1;
display: block;
width: 100%;
height: 100%;
}
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 100;
pointer-events: none;
}
.hole,
.piece {
position: absolute;
z-index: 101;
box-shadow: 0 0 6rpx rgba(0, 0, 0, 0.4);
}
.hole {
background-color: rgba(0, 0, 0, 0.3);
}
.piece {
background-repeat: no-repeat;
background-size: cover;
cursor: grab;
}
.slider-bar {
position: relative;
height: 100rpx;
margin-top: 0rpx;
background: rgb(245, 246, 252);
width: 540rpx;
display: flex;
justify-content: center;
align-items: center;
.slider-bar-font {
color: rgb(169, 169, 171);
font-size: 28rpx;
}
}
.slider-button {
position: absolute;
top: 0rpx;
left: 0;
width: 100rpx;
height: 100rpx;
background: #fff;
box-shadow: 0 0 20rpx rgba(0, 0, 0, 0.2);
user-select: none;
border-radius: 10rpx;
border: 3rpx solid rgb(139, 218, 202);
display: flex;
justify-content: center;
align-items: center;
}
.font-title {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
height: 100rpx;
font-size: 35rpx;
font-weight: 700;
}
</style>

26
index.html Normal file
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<!-- <script
src="https://map.qq.com/api/js?v=2.exp&key=LOLBZ-Z2PKW-JJ6RO-3Y7Z7-BSKWT-DLFNC"
crossorigin="anonymous">
</script> -->
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script src="https://unpkg.com/html5-qrcode"></script>
<script type="module" src="/main.js"></script>
</body>
</html>

28
main.js Normal file
View File

@ -0,0 +1,28 @@
import App from './App'
import uView from './uni_modules/vk-uview-ui';
// #ifndef VUE3
import Vue from 'vue'
import './uni.promisify.adaptor'
import uView from './uni_modules/vk-uview-ui';
Vue.use(uView);
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import {
createSSRApp
} from 'vue'
export function createApp() {
const app = createSSRApp(App)
// 使用 uView UI
app.use(uView)
return {
app
}
}
// #endif

82
manifest.json Normal file
View File

@ -0,0 +1,82 @@
{
"name" : "weixin-officialaccount",
"appid" : "__UNI__010B3C0",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* */
"modules" : {},
/* */
"distribute" : {
/* android */
"android" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios */
"ios" : {},
/* SDK */
"sdkConfigs" : {}
}
},
/* */
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "wx53bc8a44e780d26a",
"setting" : {
"urlCheck" : false,
"minified" : true,
"es6" : false,
"postcss" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3",
"h5" : {
"title" : "登录中",
"publicPath" : "/thd/", // /thd/
"router" : {
"base" : "/wechat/thd/" // /thd/
}
}
}

16
node_modules/.bin/detect-libc generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../detect-libc/bin/detect-libc.js" "$@"
else
exec node "$basedir/../detect-libc/bin/detect-libc.js" "$@"
fi

17
node_modules/.bin/detect-libc.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\detect-libc\bin\detect-libc.js" %*

28
node_modules/.bin/detect-libc.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../detect-libc/bin/detect-libc.js" $args
} else {
& "$basedir/node$exe" "$basedir/../detect-libc/bin/detect-libc.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../detect-libc/bin/detect-libc.js" $args
} else {
& "node$exe" "$basedir/../detect-libc/bin/detect-libc.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/qrcode generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../qrcode/bin/qrcode" "$@"
else
exec node "$basedir/../qrcode/bin/qrcode" "$@"
fi

17
node_modules/.bin/qrcode.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\qrcode\bin\qrcode" %*

28
node_modules/.bin/qrcode.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../qrcode/bin/qrcode" $args
} else {
& "$basedir/node$exe" "$basedir/../qrcode/bin/qrcode" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../qrcode/bin/qrcode" $args
} else {
& "node$exe" "$basedir/../qrcode/bin/qrcode" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/sass generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../sass/sass.js" "$@"
else
exec node "$basedir/../sass/sass.js" "$@"
fi

17
node_modules/.bin/sass.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\sass\sass.js" %*

28
node_modules/.bin/sass.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../sass/sass.js" $args
} else {
& "$basedir/node$exe" "$basedir/../sass/sass.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../sass/sass.js" $args
} else {
& "node$exe" "$basedir/../sass/sass.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

621
node_modules/.package-lock.json generated vendored Normal file
View File

@ -0,0 +1,621 @@
{
"name": "officialAccount",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/@parcel/watcher": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.1.tgz",
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
"detect-libc": "^1.0.3",
"is-glob": "^4.0.3",
"micromatch": "^4.0.5",
"node-addon-api": "^7.0.0"
},
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"@parcel/watcher-android-arm64": "2.5.1",
"@parcel/watcher-darwin-arm64": "2.5.1",
"@parcel/watcher-darwin-x64": "2.5.1",
"@parcel/watcher-freebsd-x64": "2.5.1",
"@parcel/watcher-linux-arm-glibc": "2.5.1",
"@parcel/watcher-linux-arm-musl": "2.5.1",
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
"@parcel/watcher-linux-arm64-musl": "2.5.1",
"@parcel/watcher-linux-x64-glibc": "2.5.1",
"@parcel/watcher-linux-x64-musl": "2.5.1",
"@parcel/watcher-win32-arm64": "2.5.1",
"@parcel/watcher-win32-ia32": "2.5.1",
"@parcel/watcher-win32-x64": "2.5.1"
}
},
"node_modules/@parcel/watcher-win32-x64": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"bin": {
"detect-libc": "bin/detect-libc.js"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/dijkstrajs": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
"license": "MIT"
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"license": "MIT",
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/immutable": {
"version": "5.1.2",
"resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.2.tgz",
"integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==",
"dev": true,
"license": "MIT"
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"license": "MIT",
"dependencies": {
"p-locate": "^4.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/neo-async": {
"version": "2.6.2",
"resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true,
"license": "MIT"
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"license": "MIT",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"license": "MIT",
"dependencies": {
"p-limit": "^2.2.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pngjs": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
"license": "MIT",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/qrcode": {
"version": "1.5.4",
"resolved": "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.4.tgz",
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
"license": "MIT",
"dependencies": {
"dijkstrajs": "^1.0.1",
"pngjs": "^5.0.0",
"yargs": "^15.3.1"
},
"bin": {
"qrcode": "bin/qrcode"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"license": "ISC"
},
"node_modules/sass": {
"version": "1.89.0",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.89.0.tgz",
"integrity": "sha512-ld+kQU8YTdGNjOLfRWBzewJpU5cwEv/h5yyqlSeJcj6Yh8U4TDA9UA5FPicqDz/xgRPWRSYIQNiFks21TbA9KQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=14.0.0"
},
"optionalDependencies": {
"@parcel/watcher": "^2.4.1"
}
},
"node_modules/sass-loader": {
"version": "16.0.5",
"resolved": "https://registry.npmmirror.com/sass-loader/-/sass-loader-16.0.5.tgz",
"integrity": "sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"neo-async": "^2.6.2"
},
"engines": {
"node": ">= 18.12.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"@rspack/core": "0.x || 1.x",
"node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
"sass": "^1.3.0",
"sass-embedded": "*",
"webpack": "^5.0.0"
},
"peerDependenciesMeta": {
"@rspack/core": {
"optional": true
},
"node-sass": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"webpack": {
"optional": true
}
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"license": "ISC"
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/three": {
"version": "0.176.0",
"resolved": "https://registry.npmmirror.com/three/-/three-0.176.0.tgz",
"integrity": "sha512-PWRKYWQo23ojf9oZSlRGH8K09q7nRSWx6LY/HF/UUrMdYgN9i1e2OwJYHoQjwc6HF/4lvvYLC5YC1X8UJL2ZpA==",
"license": "MIT"
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
"license": "ISC"
},
"node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"license": "ISC"
},
"node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"license": "MIT",
"dependencies": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"license": "ISC",
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"engines": {
"node": ">=6"
}
}
}
}

21
node_modules/@parcel/watcher-win32-x64/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017-present Devon Govett
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1
node_modules/@parcel/watcher-win32-x64/README.md generated vendored Normal file
View File

@ -0,0 +1 @@
This is the win32-x64 build of @parcel/watcher. See https://github.com/parcel-bundler/watcher for details.

30
node_modules/@parcel/watcher-win32-x64/package.json generated vendored Normal file
View File

@ -0,0 +1,30 @@
{
"name": "@parcel/watcher-win32-x64",
"version": "2.5.1",
"main": "watcher.node",
"repository": {
"type": "git",
"url": "https://github.com/parcel-bundler/watcher.git"
},
"description": "A native C++ Node module for querying and subscribing to filesystem events. Used by Parcel 2.",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"files": [
"watcher.node"
],
"engines": {
"node": ">= 10.0.0"
},
"os": [
"win32"
],
"cpu": [
"x64"
]
}

BIN
node_modules/@parcel/watcher-win32-x64/watcher.node generated vendored Normal file

Binary file not shown.

21
node_modules/@parcel/watcher/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017-present Devon Govett
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

135
node_modules/@parcel/watcher/README.md generated vendored Normal file
View File

@ -0,0 +1,135 @@
# @parcel/watcher
A native C++ Node module for querying and subscribing to filesystem events. Used by [Parcel 2](https://github.com/parcel-bundler/parcel).
## Features
- **Watch** - subscribe to realtime recursive directory change notifications when files or directories are created, updated, or deleted.
- **Query** - performantly query for historical change events in a directory, even when your program is not running.
- **Native** - implemented in C++ for performance and low-level integration with the operating system.
- **Cross platform** - includes backends for macOS, Linux, Windows, FreeBSD, and Watchman.
- **Performant** - events are throttled in C++ so the JavaScript thread is not overwhelmed during large filesystem changes (e.g. `git checkout` or `npm install`).
- **Scalable** - tens of thousands of files can be watched or queried at once with good performance.
## Example
```javascript
const watcher = require('@parcel/watcher');
const path = require('path');
// Subscribe to events
let subscription = await watcher.subscribe(process.cwd(), (err, events) => {
console.log(events);
});
// later on...
await subscription.unsubscribe();
// Get events since some saved snapshot in the past
let snapshotPath = path.join(process.cwd(), 'snapshot.txt');
let events = await watcher.getEventsSince(process.cwd(), snapshotPath);
// Save a snapshot for later
await watcher.writeSnapshot(process.cwd(), snapshotPath);
```
## Watching
`@parcel/watcher` supports subscribing to realtime notifications of changes in a directory. It works recursively, so changes in sub-directories will also be emitted.
Events are throttled and coalesced for performance during large changes like `git checkout` or `npm install`, and a single notification will be emitted with all of the events at the end.
Only one notification will be emitted per file. For example, if a file was both created and updated since the last event, you'll get only a `create` event. If a file is both created and deleted, you will not be notifed of that file. Renames cause two events: a `delete` for the old name, and a `create` for the new name.
```javascript
let subscription = await watcher.subscribe(process.cwd(), (err, events) => {
console.log(events);
});
```
Events have two properties:
- `type` - the event type: `create`, `update`, or `delete`.
- `path` - the absolute path to the file or directory.
To unsubscribe from change notifications, call the `unsubscribe` method on the returned subscription object.
```javascript
await subscription.unsubscribe();
```
`@parcel/watcher` has the following watcher backends, listed in priority order:
- [FSEvents](https://developer.apple.com/documentation/coreservices/file_system_events) on macOS
- [Watchman](https://facebook.github.io/watchman/) if installed
- [inotify](http://man7.org/linux/man-pages/man7/inotify.7.html) on Linux
- [ReadDirectoryChangesW](https://msdn.microsoft.com/en-us/library/windows/desktop/aa365465%28v%3Dvs.85%29.aspx) on Windows
- [kqueue](https://man.freebsd.org/cgi/man.cgi?kqueue) on FreeBSD, or as an alternative to FSEvents on macOS
You can specify the exact backend you wish to use by passing the `backend` option. If that backend is not available on the current platform, the default backend will be used instead. See below for the list of backend names that can be passed to the options.
## Querying
`@parcel/watcher` also supports querying for historical changes made in a directory, even when your program is not running. This makes it easy to invalidate a cache and re-build only the files that have changed, for example. It can be **significantly** faster than traversing the entire filesystem to determine what files changed, depending on the platform.
In order to query for historical changes, you first need a previous snapshot to compare to. This can be saved to a file with the `writeSnapshot` function, e.g. just before your program exits.
```javascript
await watcher.writeSnapshot(dirPath, snapshotPath);
```
When your program starts up, you can query for changes that have occurred since that snapshot using the `getEventsSince` function.
```javascript
let events = await watcher.getEventsSince(dirPath, snapshotPath);
```
The events returned are exactly the same as the events that would be passed to the `subscribe` callback (see above).
`@parcel/watcher` has the following watcher backends, listed in priority order:
- [FSEvents](https://developer.apple.com/documentation/coreservices/file_system_events) on macOS
- [Watchman](https://facebook.github.io/watchman/) if installed
- [fts](http://man7.org/linux/man-pages/man3/fts.3.html) (brute force) on Linux and FreeBSD
- [FindFirstFile](https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-findfirstfilea) (brute force) on Windows
The FSEvents (macOS) and Watchman backends are significantly more performant than the brute force backends used by default on Linux and Windows, for example returning results in miliseconds instead of seconds for large directory trees. This is because a background daemon monitoring filesystem changes on those platforms allows us to query cached data rather than traversing the filesystem manually (brute force).
macOS has good performance with FSEvents by default. For the best performance on other platforms, install [Watchman](https://facebook.github.io/watchman/) and it will be used by `@parcel/watcher` automatically.
You can specify the exact backend you wish to use by passing the `backend` option. If that backend is not available on the current platform, the default backend will be used instead. See below for the list of backend names that can be passed to the options.
## Options
All of the APIs in `@parcel/watcher` support the following options, which are passed as an object as the last function argument.
- `ignore` - an array of paths or glob patterns to ignore. uses [`is-glob`](https://github.com/micromatch/is-glob) to distinguish paths from globs. glob patterns are parsed with [`micromatch`](https://github.com/micromatch/micromatch) (see [features](https://github.com/micromatch/micromatch#matching-features)).
- paths can be relative or absolute and can either be files or directories. No events will be emitted about these files or directories or their children.
- glob patterns match on relative paths from the root that is watched. No events will be emitted for matching paths.
- `backend` - the name of an explicitly chosen backend to use. Allowed options are `"fs-events"`, `"watchman"`, `"inotify"`, `"kqueue"`, `"windows"`, or `"brute-force"` (only for querying). If the specified backend is not available on the current platform, the default backend will be used instead.
## WASM
The `@parcel/watcher-wasm` package can be used in place of `@parcel/watcher` on unsupported platforms. It relies on the Node `fs` module, so in non-Node environments such as browsers, an `fs` polyfill will be needed.
**Note**: the WASM implementation is significantly less efficient than the native implementations because it must crawl the file system to watch each directory individually. Use the native `@parcel/watcher` package wherever possible.
```js
import {subscribe} from '@parcel/watcher-wasm';
// Use the module as documented above.
subscribe(/* ... */);
```
## Who is using this?
- [Parcel 2](https://parceljs.org/)
- [VSCode](https://code.visualstudio.com/updates/v1_62#_file-watching-changes)
- [Tailwind CSS Intellisense](https://github.com/tailwindlabs/tailwindcss-intellisense)
- [Gatsby Cloud](https://twitter.com/chatsidhartha/status/1435647412828196867)
- [Nx](https://nx.dev)
- [Nuxt](https://nuxt.com)
## License
MIT

93
node_modules/@parcel/watcher/binding.gyp generated vendored Normal file
View File

@ -0,0 +1,93 @@
{
"targets": [
{
"target_name": "watcher",
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ],
"sources": [ "src/binding.cc", "src/Watcher.cc", "src/Backend.cc", "src/DirTree.cc", "src/Glob.cc", "src/Debounce.cc" ],
"include_dirs" : ["<!(node -p \"require('node-addon-api').include_dir\")"],
'cflags!': [ '-fno-exceptions', '-std=c++17' ],
'cflags_cc!': [ '-fno-exceptions', '-std=c++17' ],
"conditions": [
['OS=="mac"', {
"sources": [
"src/watchman/BSER.cc",
"src/watchman/WatchmanBackend.cc",
"src/shared/BruteForceBackend.cc",
"src/unix/fts.cc",
"src/macos/FSEventsBackend.cc",
"src/kqueue/KqueueBackend.cc"
],
"link_settings": {
"libraries": ["CoreServices.framework"]
},
"defines": [
"WATCHMAN",
"BRUTE_FORCE",
"FS_EVENTS",
"KQUEUE"
],
"xcode_settings": {
"GCC_ENABLE_CPP_EXCEPTIONS": "YES"
}
}],
['OS=="mac" and target_arch=="arm64"', {
"xcode_settings": {
"ARCHS": ["arm64"]
}
}],
['OS=="linux" or OS=="android"', {
"sources": [
"src/watchman/BSER.cc",
"src/watchman/WatchmanBackend.cc",
"src/shared/BruteForceBackend.cc",
"src/linux/InotifyBackend.cc",
"src/unix/legacy.cc"
],
"defines": [
"WATCHMAN",
"INOTIFY",
"BRUTE_FORCE"
]
}],
['OS=="win"', {
"sources": [
"src/watchman/BSER.cc",
"src/watchman/WatchmanBackend.cc",
"src/shared/BruteForceBackend.cc",
"src/windows/WindowsBackend.cc",
"src/windows/win_utils.cc"
],
"defines": [
"WATCHMAN",
"WINDOWS",
"BRUTE_FORCE"
],
"msvs_settings": {
"VCCLCompilerTool": {
"ExceptionHandling": 1, # /EHsc
"AdditionalOptions": ['-std:c++17']
}
}
}],
['OS=="freebsd"', {
"sources": [
"src/watchman/BSER.cc",
"src/watchman/WatchmanBackend.cc",
"src/shared/BruteForceBackend.cc",
"src/unix/fts.cc",
"src/kqueue/KqueueBackend.cc"
],
"defines": [
"WATCHMAN",
"BRUTE_FORCE",
"KQUEUE"
]
}]
]
}
],
"variables": {
"openssl_fips": "",
"node_use_dtrace": "false"
}
}

49
node_modules/@parcel/watcher/index.d.ts generated vendored Normal file
View File

@ -0,0 +1,49 @@
declare type FilePath = string;
declare type GlobPattern = string;
declare namespace ParcelWatcher {
export type BackendType =
| 'fs-events'
| 'watchman'
| 'inotify'
| 'windows'
| 'brute-force';
export type EventType = 'create' | 'update' | 'delete';
export interface Options {
ignore?: (FilePath|GlobPattern)[];
backend?: BackendType;
}
export type SubscribeCallback = (
err: Error | null,
events: Event[]
) => unknown;
export interface AsyncSubscription {
unsubscribe(): Promise<void>;
}
export interface Event {
path: FilePath;
type: EventType;
}
export function getEventsSince(
dir: FilePath,
snapshot: FilePath,
opts?: Options
): Promise<Event[]>;
export function subscribe(
dir: FilePath,
fn: SubscribeCallback,
opts?: Options
): Promise<AsyncSubscription>;
export function unsubscribe(
dir: FilePath,
fn: SubscribeCallback,
opts?: Options
): Promise<void>;
export function writeSnapshot(
dir: FilePath,
snapshot: FilePath,
opts?: Options
): Promise<FilePath>;
}
export = ParcelWatcher;

41
node_modules/@parcel/watcher/index.js generated vendored Normal file
View File

@ -0,0 +1,41 @@
const {createWrapper} = require('./wrapper');
let name = `@parcel/watcher-${process.platform}-${process.arch}`;
if (process.platform === 'linux') {
const { MUSL, family } = require('detect-libc');
if (family === MUSL) {
name += '-musl';
} else {
name += '-glibc';
}
}
let binding;
try {
binding = require(name);
} catch (err) {
handleError(err);
try {
binding = require('./build/Release/watcher.node');
} catch (err) {
handleError(err);
try {
binding = require('./build/Debug/watcher.node');
} catch (err) {
handleError(err);
throw new Error(`No prebuild or local build of @parcel/watcher found. Tried ${name}. Please ensure it is installed (don't use --no-optional when installing with npm). Otherwise it is possible we don't support your platform yet. If this is the case, please report an issue to https://github.com/parcel-bundler/watcher.`);
}
}
}
function handleError(err) {
if (err?.code !== 'MODULE_NOT_FOUND') {
throw err;
}
}
const wrapper = createWrapper(binding);
exports.writeSnapshot = wrapper.writeSnapshot;
exports.getEventsSince = wrapper.getEventsSince;
exports.subscribe = wrapper.subscribe;
exports.unsubscribe = wrapper.unsubscribe;

48
node_modules/@parcel/watcher/index.js.flow generated vendored Normal file
View File

@ -0,0 +1,48 @@
// @flow
declare type FilePath = string;
declare type GlobPattern = string;
export type BackendType =
| 'fs-events'
| 'watchman'
| 'inotify'
| 'windows'
| 'brute-force';
export type EventType = 'create' | 'update' | 'delete';
export interface Options {
ignore?: Array<FilePath | GlobPattern>,
backend?: BackendType
}
export type SubscribeCallback = (
err: ?Error,
events: Array<Event>
) => mixed;
export interface AsyncSubscription {
unsubscribe(): Promise<void>
}
export interface Event {
path: FilePath,
type: EventType
}
declare module.exports: {
getEventsSince(
dir: FilePath,
snapshot: FilePath,
opts?: Options
): Promise<Array<Event>>,
subscribe(
dir: FilePath,
fn: SubscribeCallback,
opts?: Options
): Promise<AsyncSubscription>,
unsubscribe(
dir: FilePath,
fn: SubscribeCallback,
opts?: Options
): Promise<void>,
writeSnapshot(
dir: FilePath,
snapshot: FilePath,
opts?: Options
): Promise<FilePath>
}

88
node_modules/@parcel/watcher/package.json generated vendored Normal file
View File

@ -0,0 +1,88 @@
{
"name": "@parcel/watcher",
"version": "2.5.1",
"main": "index.js",
"types": "index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/parcel-bundler/watcher.git"
},
"description": "A native C++ Node module for querying and subscribing to filesystem events. Used by Parcel 2.",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"files": [
"index.js",
"index.js.flow",
"index.d.ts",
"wrapper.js",
"package.json",
"README.md",
"LICENSE",
"src",
"scripts/build-from-source.js",
"binding.gyp"
],
"scripts": {
"prebuild": "prebuildify --napi --strip --tag-libc",
"format": "prettier --write \"./**/*.{js,json,md}\"",
"build": "node-gyp rebuild",
"install": "node scripts/build-from-source.js",
"test": "mocha"
},
"engines": {
"node": ">= 10.0.0"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,json,md}": [
"prettier --write",
"git add"
]
},
"dependencies": {
"detect-libc": "^1.0.3",
"is-glob": "^4.0.3",
"micromatch": "^4.0.5",
"node-addon-api": "^7.0.0"
},
"devDependencies": {
"esbuild": "^0.19.8",
"fs-extra": "^10.0.0",
"husky": "^7.0.2",
"lint-staged": "^11.1.2",
"mocha": "^9.1.1",
"napi-wasm": "^1.1.0",
"prebuildify": "^6.0.1",
"prettier": "^2.3.2"
},
"binary": {
"napi_versions": [
3
]
},
"optionalDependencies": {
"@parcel/watcher-darwin-x64": "2.5.1",
"@parcel/watcher-darwin-arm64": "2.5.1",
"@parcel/watcher-win32-x64": "2.5.1",
"@parcel/watcher-win32-arm64": "2.5.1",
"@parcel/watcher-win32-ia32": "2.5.1",
"@parcel/watcher-linux-x64-glibc": "2.5.1",
"@parcel/watcher-linux-x64-musl": "2.5.1",
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
"@parcel/watcher-linux-arm64-musl": "2.5.1",
"@parcel/watcher-linux-arm-glibc": "2.5.1",
"@parcel/watcher-linux-arm-musl": "2.5.1",
"@parcel/watcher-android-arm64": "2.5.1",
"@parcel/watcher-freebsd-x64": "2.5.1"
}
}

View File

@ -0,0 +1,13 @@
#!/usr/bin/env node
const {spawn} = require('child_process');
if (process.env.npm_config_build_from_source === 'true') {
build();
}
function build() {
spawn('node-gyp', ['rebuild'], { stdio: 'inherit', shell: true }).on('exit', function (code) {
process.exit(code);
});
}

182
node_modules/@parcel/watcher/src/Backend.cc generated vendored Normal file
View File

@ -0,0 +1,182 @@
#ifdef FS_EVENTS
#include "macos/FSEventsBackend.hh"
#endif
#ifdef WATCHMAN
#include "watchman/WatchmanBackend.hh"
#endif
#ifdef WINDOWS
#include "windows/WindowsBackend.hh"
#endif
#ifdef INOTIFY
#include "linux/InotifyBackend.hh"
#endif
#ifdef KQUEUE
#include "kqueue/KqueueBackend.hh"
#endif
#ifdef __wasm32__
#include "wasm/WasmBackend.hh"
#endif
#include "shared/BruteForceBackend.hh"
#include "Backend.hh"
#include <unordered_map>
static std::unordered_map<std::string, std::shared_ptr<Backend>> sharedBackends;
std::shared_ptr<Backend> getBackend(std::string backend) {
// Use FSEvents on macOS by default.
// Use watchman by default if available on other platforms.
// Fall back to brute force.
#ifdef FS_EVENTS
if (backend == "fs-events" || backend == "default") {
return std::make_shared<FSEventsBackend>();
}
#endif
#ifdef WATCHMAN
if ((backend == "watchman" || backend == "default") && WatchmanBackend::checkAvailable()) {
return std::make_shared<WatchmanBackend>();
}
#endif
#ifdef WINDOWS
if (backend == "windows" || backend == "default") {
return std::make_shared<WindowsBackend>();
}
#endif
#ifdef INOTIFY
if (backend == "inotify" || backend == "default") {
return std::make_shared<InotifyBackend>();
}
#endif
#ifdef KQUEUE
if (backend == "kqueue" || backend == "default") {
return std::make_shared<KqueueBackend>();
}
#endif
#ifdef __wasm32__
if (backend == "wasm" || backend == "default") {
return std::make_shared<WasmBackend>();
}
#endif
if (backend == "brute-force" || backend == "default") {
return std::make_shared<BruteForceBackend>();
}
return nullptr;
}
std::shared_ptr<Backend> Backend::getShared(std::string backend) {
auto found = sharedBackends.find(backend);
if (found != sharedBackends.end()) {
return found->second;
}
auto result = getBackend(backend);
if (!result) {
return getShared("default");
}
result->run();
sharedBackends.emplace(backend, result);
return result;
}
void removeShared(Backend *backend) {
for (auto it = sharedBackends.begin(); it != sharedBackends.end(); it++) {
if (it->second.get() == backend) {
sharedBackends.erase(it);
break;
}
}
// Free up memory.
if (sharedBackends.size() == 0) {
sharedBackends.rehash(0);
}
}
void Backend::run() {
#ifndef __wasm32__
mThread = std::thread([this] () {
try {
start();
} catch (std::exception &err) {
handleError(err);
}
});
if (mThread.joinable()) {
mStartedSignal.wait();
}
#else
try {
start();
} catch (std::exception &err) {
handleError(err);
}
#endif
}
void Backend::notifyStarted() {
mStartedSignal.notify();
}
void Backend::start() {
notifyStarted();
}
Backend::~Backend() {
#ifndef __wasm32__
// Wait for thread to stop
if (mThread.joinable()) {
// If the backend is being destroyed from the thread itself, detach, otherwise join.
if (mThread.get_id() == std::this_thread::get_id()) {
mThread.detach();
} else {
mThread.join();
}
}
#endif
}
void Backend::watch(WatcherRef watcher) {
std::unique_lock<std::mutex> lock(mMutex);
auto res = mSubscriptions.find(watcher);
if (res == mSubscriptions.end()) {
try {
this->subscribe(watcher);
mSubscriptions.insert(watcher);
} catch (std::exception &err) {
unref();
throw;
}
}
}
void Backend::unwatch(WatcherRef watcher) {
std::unique_lock<std::mutex> lock(mMutex);
size_t deleted = mSubscriptions.erase(watcher);
if (deleted > 0) {
this->unsubscribe(watcher);
unref();
}
}
void Backend::unref() {
if (mSubscriptions.size() == 0) {
removeShared(this);
}
}
void Backend::handleWatcherError(WatcherError &err) {
unwatch(err.mWatcher);
err.mWatcher->notifyError(err);
}
void Backend::handleError(std::exception &err) {
std::unique_lock<std::mutex> lock(mMutex);
for (auto it = mSubscriptions.begin(); it != mSubscriptions.end(); it++) {
(*it)->notifyError(err);
}
removeShared(this);
}

37
node_modules/@parcel/watcher/src/Backend.hh generated vendored Normal file
View File

@ -0,0 +1,37 @@
#ifndef BACKEND_H
#define BACKEND_H
#include "Event.hh"
#include "Watcher.hh"
#include "Signal.hh"
#include <thread>
class Backend {
public:
virtual ~Backend();
void run();
void notifyStarted();
virtual void start();
virtual void writeSnapshot(WatcherRef watcher, std::string *snapshotPath) = 0;
virtual void getEventsSince(WatcherRef watcher, std::string *snapshotPath) = 0;
virtual void subscribe(WatcherRef watcher) = 0;
virtual void unsubscribe(WatcherRef watcher) = 0;
static std::shared_ptr<Backend> getShared(std::string backend);
void watch(WatcherRef watcher);
void unwatch(WatcherRef watcher);
void unref();
void handleWatcherError(WatcherError &err);
std::mutex mMutex;
std::thread mThread;
private:
std::unordered_set<WatcherRef> mSubscriptions;
Signal mStartedSignal;
void handleError(std::exception &err);
};
#endif

113
node_modules/@parcel/watcher/src/Debounce.cc generated vendored Normal file
View File

@ -0,0 +1,113 @@
#include "Debounce.hh"
#ifdef __wasm32__
extern "C" void on_timeout(void *ctx) {
Debounce *debounce = (Debounce *)ctx;
debounce->notify();
}
#endif
std::shared_ptr<Debounce> Debounce::getShared() {
static std::weak_ptr<Debounce> sharedInstance;
std::shared_ptr<Debounce> shared = sharedInstance.lock();
if (!shared) {
shared = std::make_shared<Debounce>();
sharedInstance = shared;
}
return shared;
}
Debounce::Debounce() {
mRunning = true;
#ifndef __wasm32__
mThread = std::thread([this] () {
loop();
});
#endif
}
Debounce::~Debounce() {
mRunning = false;
#ifndef __wasm32__
mWaitSignal.notify();
mThread.join();
#endif
}
void Debounce::add(void *key, std::function<void()> cb) {
std::unique_lock<std::mutex> lock(mMutex);
mCallbacks.emplace(key, cb);
}
void Debounce::remove(void *key) {
std::unique_lock<std::mutex> lock(mMutex);
mCallbacks.erase(key);
}
void Debounce::trigger() {
std::unique_lock<std::mutex> lock(mMutex);
#ifdef __wasm32__
notifyIfReady();
#else
mWaitSignal.notify();
#endif
}
#ifndef __wasm32__
void Debounce::loop() {
while (mRunning) {
mWaitSignal.wait();
if (!mRunning) {
break;
}
notifyIfReady();
}
}
#endif
void Debounce::notifyIfReady() {
if (!mRunning) {
return;
}
// If we haven't seen an event in more than the maximum wait time, notify callbacks immediately
// to ensure that we don't wait forever. Otherwise, wait for the minimum wait time and batch
// subsequent fast changes. This also means the first file change in a batch is notified immediately,
// separately from the rest of the batch. This seems like an acceptable tradeoff if the common case
// is that only a single file was updated at a time.
auto time = std::chrono::steady_clock::now();
if ((time - mLastTime) > std::chrono::milliseconds(MAX_WAIT_TIME)) {
mLastTime = time;
notify();
} else {
wait();
}
}
void Debounce::wait() {
#ifdef __wasm32__
clear_timeout(mTimeout);
mTimeout = set_timeout(MIN_WAIT_TIME, this);
#else
auto status = mWaitSignal.waitFor(std::chrono::milliseconds(MIN_WAIT_TIME));
if (mRunning && (status == std::cv_status::timeout)) {
notify();
}
#endif
}
void Debounce::notify() {
std::unique_lock<std::mutex> lock(mMutex);
mLastTime = std::chrono::steady_clock::now();
for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
auto cb = it->second;
cb();
}
#ifndef __wasm32__
mWaitSignal.reset();
#endif
}

49
node_modules/@parcel/watcher/src/Debounce.hh generated vendored Normal file
View File

@ -0,0 +1,49 @@
#ifndef DEBOUNCE_H
#define DEBOUNCE_H
#include <thread>
#include <unordered_map>
#include <functional>
#include "Signal.hh"
#define MIN_WAIT_TIME 50
#define MAX_WAIT_TIME 500
#ifdef __wasm32__
extern "C" {
int set_timeout(int ms, void *ctx);
void clear_timeout(int timeout);
void on_timeout(void *ctx);
};
#endif
class Debounce {
public:
static std::shared_ptr<Debounce> getShared();
Debounce();
~Debounce();
void add(void *key, std::function<void()> cb);
void remove(void *key);
void trigger();
void notify();
private:
bool mRunning;
std::mutex mMutex;
#ifdef __wasm32__
int mTimeout;
#else
Signal mWaitSignal;
std::thread mThread;
#endif
std::unordered_map<void *, std::function<void()>> mCallbacks;
std::chrono::time_point<std::chrono::steady_clock> mLastTime;
void loop();
void notifyIfReady();
void wait();
};
#endif

152
node_modules/@parcel/watcher/src/DirTree.cc generated vendored Normal file
View File

@ -0,0 +1,152 @@
#include "DirTree.hh"
#include <inttypes.h>
static std::mutex mDirCacheMutex;
static std::unordered_map<std::string, std::weak_ptr<DirTree>> dirTreeCache;
struct DirTreeDeleter {
void operator()(DirTree *tree) {
std::lock_guard<std::mutex> lock(mDirCacheMutex);
dirTreeCache.erase(tree->root);
delete tree;
// Free up memory.
if (dirTreeCache.size() == 0) {
dirTreeCache.rehash(0);
}
}
};
std::shared_ptr<DirTree> DirTree::getCached(std::string root) {
std::lock_guard<std::mutex> lock(mDirCacheMutex);
auto found = dirTreeCache.find(root);
std::shared_ptr<DirTree> tree;
// Use cached tree, or create an empty one.
if (found != dirTreeCache.end()) {
tree = found->second.lock();
} else {
tree = std::shared_ptr<DirTree>(new DirTree(root), DirTreeDeleter());
dirTreeCache.emplace(root, tree);
}
return tree;
}
DirTree::DirTree(std::string root, FILE *f) : root(root), isComplete(true) {
size_t size;
if (fscanf(f, "%zu", &size)) {
for (size_t i = 0; i < size; i++) {
DirEntry entry(f);
entries.emplace(entry.path, entry);
}
}
}
// Internal find method that has no lock
DirEntry *DirTree::_find(std::string path) {
auto found = entries.find(path);
if (found == entries.end()) {
return NULL;
}
return &found->second;
}
DirEntry *DirTree::add(std::string path, uint64_t mtime, bool isDir) {
std::lock_guard<std::mutex> lock(mMutex);
DirEntry entry(path, mtime, isDir);
auto it = entries.emplace(entry.path, entry);
return &it.first->second;
}
DirEntry *DirTree::find(std::string path) {
std::lock_guard<std::mutex> lock(mMutex);
return _find(path);
}
DirEntry *DirTree::update(std::string path, uint64_t mtime) {
std::lock_guard<std::mutex> lock(mMutex);
DirEntry *found = _find(path);
if (found) {
found->mtime = mtime;
}
return found;
}
void DirTree::remove(std::string path) {
std::lock_guard<std::mutex> lock(mMutex);
DirEntry *found = _find(path);
// Remove all sub-entries if this is a directory
if (found && found->isDir) {
std::string pathStart = path + DIR_SEP;
for (auto it = entries.begin(); it != entries.end();) {
if (it->first.rfind(pathStart, 0) == 0) {
it = entries.erase(it);
} else {
it++;
}
}
}
entries.erase(path);
}
void DirTree::write(FILE *f) {
std::lock_guard<std::mutex> lock(mMutex);
fprintf(f, "%zu\n", entries.size());
for (auto it = entries.begin(); it != entries.end(); it++) {
it->second.write(f);
}
}
void DirTree::getChanges(DirTree *snapshot, EventList &events) {
std::lock_guard<std::mutex> lock(mMutex);
std::lock_guard<std::mutex> snapshotLock(snapshot->mMutex);
for (auto it = entries.begin(); it != entries.end(); it++) {
auto found = snapshot->entries.find(it->first);
if (found == snapshot->entries.end()) {
events.create(it->second.path);
} else if (found->second.mtime != it->second.mtime && !found->second.isDir && !it->second.isDir) {
events.update(it->second.path);
}
}
for (auto it = snapshot->entries.begin(); it != snapshot->entries.end(); it++) {
size_t count = entries.count(it->first);
if (count == 0) {
events.remove(it->second.path);
}
}
}
DirEntry::DirEntry(std::string p, uint64_t t, bool d) {
path = p;
mtime = t;
isDir = d;
state = NULL;
}
DirEntry::DirEntry(FILE *f) {
size_t size;
if (fscanf(f, "%zu", &size)) {
path.resize(size);
if (fread(&path[0], sizeof(char), size, f)) {
int d = 0;
fscanf(f, "%" PRIu64 " %d\n", &mtime, &d);
isDir = d == 1;
}
}
}
void DirEntry::write(FILE *f) const {
fprintf(f, "%zu%s%" PRIu64 " %d\n", path.size(), path.c_str(), mtime, isDir);
}

50
node_modules/@parcel/watcher/src/DirTree.hh generated vendored Normal file
View File

@ -0,0 +1,50 @@
#ifndef DIR_TREE_H
#define DIR_TREE_H
#include <string>
#include <unordered_map>
#include <memory>
#include "Event.hh"
#ifdef _WIN32
#define DIR_SEP "\\"
#else
#define DIR_SEP "/"
#endif
struct DirEntry {
std::string path;
uint64_t mtime;
bool isDir;
mutable void *state;
DirEntry(std::string p, uint64_t t, bool d);
DirEntry(FILE *f);
void write(FILE *f) const;
bool operator==(const DirEntry &other) const {
return path == other.path;
}
};
class DirTree {
public:
static std::shared_ptr<DirTree> getCached(std::string root);
DirTree(std::string root) : root(root), isComplete(false) {}
DirTree(std::string root, FILE *f);
DirEntry *add(std::string path, uint64_t mtime, bool isDir);
DirEntry *find(std::string path);
DirEntry *update(std::string path, uint64_t mtime);
void remove(std::string path);
void write(FILE *f);
void getChanges(DirTree *snapshot, EventList &events);
std::mutex mMutex;
std::string root;
bool isComplete;
std::unordered_map<std::string, DirEntry> entries;
private:
DirEntry *_find(std::string path);
};
#endif

109
node_modules/@parcel/watcher/src/Event.hh generated vendored Normal file
View File

@ -0,0 +1,109 @@
#ifndef EVENT_H
#define EVENT_H
#include <string>
#include <node_api.h>
#include "wasm/include.h"
#include <napi.h>
#include <mutex>
#include <map>
#include <optional>
using namespace Napi;
struct Event {
std::string path;
bool isCreated;
bool isDeleted;
Event(std::string path) : path(path), isCreated(false), isDeleted(false) {}
Value toJS(const Env& env) {
EscapableHandleScope scope(env);
Object res = Object::New(env);
std::string type = isCreated ? "create" : isDeleted ? "delete" : "update";
res.Set(String::New(env, "path"), String::New(env, path.c_str()));
res.Set(String::New(env, "type"), String::New(env, type.c_str()));
return scope.Escape(res);
}
};
class EventList {
public:
void create(std::string path) {
std::lock_guard<std::mutex> l(mMutex);
Event *event = internalUpdate(path);
if (event->isDeleted) {
// Assume update event when rapidly removed and created
// https://github.com/parcel-bundler/watcher/issues/72
event->isDeleted = false;
} else {
event->isCreated = true;
}
}
Event *update(std::string path) {
std::lock_guard<std::mutex> l(mMutex);
return internalUpdate(path);
}
void remove(std::string path) {
std::lock_guard<std::mutex> l(mMutex);
Event *event = internalUpdate(path);
event->isDeleted = true;
}
size_t size() {
std::lock_guard<std::mutex> l(mMutex);
return mEvents.size();
}
std::vector<Event> getEvents() {
std::lock_guard<std::mutex> l(mMutex);
std::vector<Event> eventsCloneVector;
for(auto it = mEvents.begin(); it != mEvents.end(); ++it) {
if (!(it->second.isCreated && it->second.isDeleted)) {
eventsCloneVector.push_back(it->second);
}
}
return eventsCloneVector;
}
void clear() {
std::lock_guard<std::mutex> l(mMutex);
mEvents.clear();
mError.reset();
}
void error(std::string err) {
std::lock_guard<std::mutex> l(mMutex);
if (!mError.has_value()) {
mError.emplace(err);
}
}
bool hasError() {
std::lock_guard<std::mutex> l(mMutex);
return mError.has_value();
}
std::string getError() {
std::lock_guard<std::mutex> l(mMutex);
return mError.value_or("");
}
private:
mutable std::mutex mMutex;
std::map<std::string, Event> mEvents;
std::optional<std::string> mError;
Event *internalUpdate(std::string path) {
auto found = mEvents.find(path);
if (found == mEvents.end()) {
auto it = mEvents.emplace(path, Event(path));
return &it.first->second;
}
return &found->second;
}
};
#endif

22
node_modules/@parcel/watcher/src/Glob.cc generated vendored Normal file
View File

@ -0,0 +1,22 @@
#include "Glob.hh"
#ifdef __wasm32__
extern "C" bool wasm_regex_match(const char *s, const char *regex);
#endif
Glob::Glob(std::string raw) {
mRaw = raw;
mHash = std::hash<std::string>()(raw);
#ifndef __wasm32__
mRegex = std::regex(raw);
#endif
}
bool Glob::isIgnored(std::string relative_path) const {
// Use native JS regex engine for wasm to reduce binary size.
#ifdef __wasm32__
return wasm_regex_match(relative_path.c_str(), mRaw.c_str());
#else
return std::regex_match(relative_path, mRegex);
#endif
}

34
node_modules/@parcel/watcher/src/Glob.hh generated vendored Normal file
View File

@ -0,0 +1,34 @@
#ifndef GLOB_H
#define GLOB_H
#include <unordered_set>
#include <regex>
struct Glob {
std::size_t mHash;
std::string mRaw;
#ifndef __wasm32__
std::regex mRegex;
#endif
Glob(std::string raw);
bool operator==(const Glob &other) const {
return mHash == other.mHash;
}
bool isIgnored(std::string relative_path) const;
};
namespace std
{
template <>
struct hash<Glob>
{
size_t operator()(const Glob& g) const {
return g.mHash;
}
};
}
#endif

101
node_modules/@parcel/watcher/src/PromiseRunner.hh generated vendored Normal file
View File

@ -0,0 +1,101 @@
#ifndef PROMISE_RUNNER_H
#define PROMISE_RUNNER_H
#include <node_api.h>
#include "wasm/include.h"
#include <napi.h>
using namespace Napi;
class PromiseRunner {
public:
const Env env;
Promise::Deferred deferred;
PromiseRunner(Env env) : env(env), deferred(Promise::Deferred::New(env)) {
napi_status status = napi_create_async_work(env, nullptr, env.Undefined(),
onExecute, onWorkComplete, this, &work);
if (status != napi_ok) {
work = nullptr;
const napi_extended_error_info *error_info = 0;
napi_get_last_error_info(env, &error_info);
if (error_info->error_message) {
Error::New(env, error_info->error_message).ThrowAsJavaScriptException();
} else {
Error::New(env).ThrowAsJavaScriptException();
}
}
}
virtual ~PromiseRunner() {}
Value queue() {
if (work) {
napi_status status = napi_queue_async_work(env, work);
if (status != napi_ok) {
onError(Error::New(env));
}
}
return deferred.Promise();
}
private:
napi_async_work work;
std::string error;
static void onExecute(napi_env env, void *this_pointer) {
PromiseRunner* self = (PromiseRunner*) this_pointer;
try {
self->execute();
} catch (std::exception &err) {
self->error = err.what();
}
}
static void onWorkComplete(napi_env env, napi_status status, void *this_pointer) {
PromiseRunner* self = (PromiseRunner*) this_pointer;
if (status != napi_cancelled) {
HandleScope scope(self->env);
if (status == napi_ok) {
status = napi_delete_async_work(self->env, self->work);
if (status == napi_ok) {
if (self->error.size() == 0) {
self->onOK();
} else {
self->onError(Error::New(self->env, self->error));
}
delete self;
return;
}
}
}
// fallthrough for error handling
const napi_extended_error_info *error_info = 0;
napi_get_last_error_info(env, &error_info);
if (error_info->error_message){
self->onError(Error::New(env, error_info->error_message));
} else {
self->onError(Error::New(env));
}
delete self;
}
virtual void execute() {}
virtual Value getResult() {
return env.Null();
}
void onOK() {
HandleScope scope(env);
Value result = getResult();
deferred.Resolve(result);
}
void onError(const Error &e) {
deferred.Reject(e.Value());
}
};
#endif

46
node_modules/@parcel/watcher/src/Signal.hh generated vendored Normal file
View File

@ -0,0 +1,46 @@
#ifndef SIGNAL_H
#define SIGNAL_H
#include <mutex>
#include <condition_variable>
class Signal {
public:
Signal() : mFlag(false), mWaiting(false) {}
void wait() {
std::unique_lock<std::mutex> lock(mMutex);
while (!mFlag) {
mWaiting = true;
mCond.wait(lock);
}
}
std::cv_status waitFor(std::chrono::milliseconds ms) {
std::unique_lock<std::mutex> lock(mMutex);
return mCond.wait_for(lock, ms);
}
void notify() {
std::unique_lock<std::mutex> lock(mMutex);
mFlag = true;
mCond.notify_all();
}
void reset() {
std::unique_lock<std::mutex> lock(mMutex);
mFlag = false;
mWaiting = false;
}
bool isWaiting() {
return mWaiting;
}
private:
bool mFlag;
bool mWaiting;
std::mutex mMutex;
std::condition_variable mCond;
};
#endif

237
node_modules/@parcel/watcher/src/Watcher.cc generated vendored Normal file
View File

@ -0,0 +1,237 @@
#include "Watcher.hh"
#include <unordered_set>
using namespace Napi;
struct WatcherHash {
std::size_t operator() (WatcherRef const &k) const {
return std::hash<std::string>()(k->mDir);
}
};
struct WatcherCompare {
size_t operator() (WatcherRef const &a, WatcherRef const &b) const {
return *a == *b;
}
};
static std::unordered_set<WatcherRef , WatcherHash, WatcherCompare> sharedWatchers;
WatcherRef Watcher::getShared(std::string dir, std::unordered_set<std::string> ignorePaths, std::unordered_set<Glob> ignoreGlobs) {
WatcherRef watcher = std::make_shared<Watcher>(dir, ignorePaths, ignoreGlobs);
auto found = sharedWatchers.find(watcher);
if (found != sharedWatchers.end()) {
return *found;
}
sharedWatchers.insert(watcher);
return watcher;
}
void removeShared(Watcher *watcher) {
for (auto it = sharedWatchers.begin(); it != sharedWatchers.end(); it++) {
if (it->get() == watcher) {
sharedWatchers.erase(it);
break;
}
}
// Free up memory.
if (sharedWatchers.size() == 0) {
sharedWatchers.rehash(0);
}
}
Watcher::Watcher(std::string dir, std::unordered_set<std::string> ignorePaths, std::unordered_set<Glob> ignoreGlobs)
: mDir(dir),
mIgnorePaths(ignorePaths),
mIgnoreGlobs(ignoreGlobs) {
mDebounce = Debounce::getShared();
mDebounce->add(this, [this] () {
triggerCallbacks();
});
}
Watcher::~Watcher() {
mDebounce->remove(this);
}
void Watcher::wait() {
std::unique_lock<std::mutex> lk(mMutex);
mCond.wait(lk);
}
void Watcher::notify() {
std::unique_lock<std::mutex> lk(mMutex);
mCond.notify_all();
if (mCallbacks.size() > 0 && mEvents.size() > 0) {
// We must release our lock before calling into the debouncer
// to avoid a deadlock: the debouncer thread itself will require
// our lock from its thread when calling into `triggerCallbacks`
// while holding its own debouncer lock.
lk.unlock();
mDebounce->trigger();
}
}
struct CallbackData {
std::string error;
std::vector<Event> events;
CallbackData(std::string error, std::vector<Event> events) : error(error), events(events) {}
};
Value callbackEventsToJS(const Env &env, std::vector<Event> &events) {
EscapableHandleScope scope(env);
Array arr = Array::New(env, events.size());
size_t currentEventIndex = 0;
for (auto eventIterator = events.begin(); eventIterator != events.end(); eventIterator++) {
arr.Set(currentEventIndex++, eventIterator->toJS(env));
}
return scope.Escape(arr);
}
void callJSFunction(Napi::Env env, Function jsCallback, CallbackData *data) {
HandleScope scope(env);
auto err = data->error.size() > 0 ? Error::New(env, data->error).Value() : env.Null();
auto events = callbackEventsToJS(env, data->events);
jsCallback.Call({err, events});
delete data;
// Throw errors from the callback as fatal exceptions
// If we don't handle these node segfaults...
if (env.IsExceptionPending()) {
Napi::Error err = env.GetAndClearPendingException();
napi_fatal_exception(env, err.Value());
}
}
void Watcher::notifyError(std::exception &err) {
std::unique_lock<std::mutex> lk(mMutex);
for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
CallbackData *data = new CallbackData(err.what(), {});
it->tsfn.BlockingCall(data, callJSFunction);
}
clearCallbacks();
}
// This function is called from the debounce thread.
void Watcher::triggerCallbacks() {
std::unique_lock<std::mutex> lk(mMutex);
if (mCallbacks.size() > 0 && (mEvents.size() > 0 || mEvents.hasError())) {
auto error = mEvents.getError();
auto events = mEvents.getEvents();
mEvents.clear();
for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
it->tsfn.BlockingCall(new CallbackData(error, events), callJSFunction);
}
}
}
// This should be called from the JavaScript thread.
bool Watcher::watch(Function callback) {
std::unique_lock<std::mutex> lk(mMutex);
auto it = findCallback(callback);
if (it != mCallbacks.end()) {
return false;
}
auto tsfn = ThreadSafeFunction::New(
callback.Env(),
callback,
"Watcher callback",
0, // Unlimited queue
1 // Initial thread count
);
mCallbacks.push_back(Callback {
tsfn,
Napi::Persistent(callback),
std::this_thread::get_id()
});
return true;
}
// This should be called from the JavaScript thread.
std::vector<Callback>::iterator Watcher::findCallback(Function callback) {
for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
// Only consider callbacks created by the same thread, or V8 will panic.
if (it->threadId == std::this_thread::get_id() && it->ref.Value() == callback) {
return it;
}
}
return mCallbacks.end();
}
// This should be called from the JavaScript thread.
bool Watcher::unwatch(Function callback) {
std::unique_lock<std::mutex> lk(mMutex);
bool removed = false;
auto it = findCallback(callback);
if (it != mCallbacks.end()) {
it->tsfn.Release();
it->ref.Unref();
mCallbacks.erase(it);
removed = true;
}
if (removed && mCallbacks.size() == 0) {
unref();
return true;
}
return false;
}
void Watcher::unref() {
if (mCallbacks.size() == 0) {
removeShared(this);
}
}
void Watcher::destroy() {
std::unique_lock<std::mutex> lk(mMutex);
clearCallbacks();
}
// Private because it doesn't lock.
void Watcher::clearCallbacks() {
for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
it->tsfn.Release();
it->ref.Unref();
}
mCallbacks.clear();
unref();
}
bool Watcher::isIgnored(std::string path) {
for (auto it = mIgnorePaths.begin(); it != mIgnorePaths.end(); it++) {
auto dir = *it + DIR_SEP;
if (*it == path || path.compare(0, dir.size(), dir) == 0) {
return true;
}
}
auto basePath = mDir + DIR_SEP;
if (path.rfind(basePath, 0) != 0) {
return false;
}
auto relativePath = path.substr(basePath.size());
for (auto it = mIgnoreGlobs.begin(); it != mIgnoreGlobs.end(); it++) {
if (it->isIgnored(relativePath)) {
return true;
}
}
return false;
}

73
node_modules/@parcel/watcher/src/Watcher.hh generated vendored Normal file
View File

@ -0,0 +1,73 @@
#ifndef WATCHER_H
#define WATCHER_H
#include <condition_variable>
#include <unordered_set>
#include <set>
#include <node_api.h>
#include "Glob.hh"
#include "Event.hh"
#include "Debounce.hh"
#include "DirTree.hh"
#include "Signal.hh"
using namespace Napi;
struct Watcher;
using WatcherRef = std::shared_ptr<Watcher>;
struct Callback {
Napi::ThreadSafeFunction tsfn;
Napi::FunctionReference ref;
std::thread::id threadId;
};
class WatcherState {
public:
virtual ~WatcherState() = default;
};
struct Watcher {
std::string mDir;
std::unordered_set<std::string> mIgnorePaths;
std::unordered_set<Glob> mIgnoreGlobs;
EventList mEvents;
std::shared_ptr<WatcherState> state;
Watcher(std::string dir, std::unordered_set<std::string> ignorePaths, std::unordered_set<Glob> ignoreGlobs);
~Watcher();
bool operator==(const Watcher &other) const {
return mDir == other.mDir && mIgnorePaths == other.mIgnorePaths && mIgnoreGlobs == other.mIgnoreGlobs;
}
void wait();
void notify();
void notifyError(std::exception &err);
bool watch(Function callback);
bool unwatch(Function callback);
void unref();
bool isIgnored(std::string path);
void destroy();
static WatcherRef getShared(std::string dir, std::unordered_set<std::string> ignorePaths, std::unordered_set<Glob> ignoreGlobs);
private:
std::mutex mMutex;
std::condition_variable mCond;
std::vector<Callback> mCallbacks;
std::shared_ptr<Debounce> mDebounce;
std::vector<Callback>::iterator findCallback(Function callback);
void clearCallbacks();
void triggerCallbacks();
};
class WatcherError : public std::runtime_error {
public:
WatcherRef mWatcher;
WatcherError(std::string msg, WatcherRef watcher) : std::runtime_error(msg), mWatcher(watcher) {}
WatcherError(const char *msg, WatcherRef watcher) : std::runtime_error(msg), mWatcher(watcher) {}
};
#endif

268
node_modules/@parcel/watcher/src/binding.cc generated vendored Normal file
View File

@ -0,0 +1,268 @@
#include <unordered_set>
#include <node_api.h>
#include "wasm/include.h"
#include <napi.h>
#include "Glob.hh"
#include "Event.hh"
#include "Backend.hh"
#include "Watcher.hh"
#include "PromiseRunner.hh"
using namespace Napi;
std::unordered_set<std::string> getIgnorePaths(Env env, Value opts) {
std::unordered_set<std::string> result;
if (opts.IsObject()) {
Value v = opts.As<Object>().Get(String::New(env, "ignorePaths"));
if (v.IsArray()) {
Array items = v.As<Array>();
for (size_t i = 0; i < items.Length(); i++) {
Value item = items.Get(Number::New(env, i));
if (item.IsString()) {
result.insert(std::string(item.As<String>().Utf8Value().c_str()));
}
}
}
}
return result;
}
std::unordered_set<Glob> getIgnoreGlobs(Env env, Value opts) {
std::unordered_set<Glob> result;
if (opts.IsObject()) {
Value v = opts.As<Object>().Get(String::New(env, "ignoreGlobs"));
if (v.IsArray()) {
Array items = v.As<Array>();
for (size_t i = 0; i < items.Length(); i++) {
Value item = items.Get(Number::New(env, i));
if (item.IsString()) {
auto key = item.As<String>().Utf8Value();
try {
result.emplace(key);
} catch (const std::regex_error& e) {
Error::New(env, e.what()).ThrowAsJavaScriptException();
}
}
}
}
}
return result;
}
std::shared_ptr<Backend> getBackend(Env env, Value opts) {
Value b = opts.As<Object>().Get(String::New(env, "backend"));
std::string backendName;
if (b.IsString()) {
backendName = std::string(b.As<String>().Utf8Value().c_str());
}
return Backend::getShared(backendName);
}
class WriteSnapshotRunner : public PromiseRunner {
public:
WriteSnapshotRunner(Env env, Value dir, Value snap, Value opts)
: PromiseRunner(env),
snapshotPath(std::string(snap.As<String>().Utf8Value().c_str())) {
watcher = Watcher::getShared(
std::string(dir.As<String>().Utf8Value().c_str()),
getIgnorePaths(env, opts),
getIgnoreGlobs(env, opts)
);
backend = getBackend(env, opts);
}
~WriteSnapshotRunner() {
watcher->unref();
backend->unref();
}
private:
std::shared_ptr<Backend> backend;
WatcherRef watcher;
std::string snapshotPath;
void execute() override {
backend->writeSnapshot(watcher, &snapshotPath);
}
};
class GetEventsSinceRunner : public PromiseRunner {
public:
GetEventsSinceRunner(Env env, Value dir, Value snap, Value opts)
: PromiseRunner(env),
snapshotPath(std::string(snap.As<String>().Utf8Value().c_str())) {
watcher = std::make_shared<Watcher>(
std::string(dir.As<String>().Utf8Value().c_str()),
getIgnorePaths(env, opts),
getIgnoreGlobs(env, opts)
);
backend = getBackend(env, opts);
}
~GetEventsSinceRunner() {
watcher->unref();
backend->unref();
}
private:
std::shared_ptr<Backend> backend;
WatcherRef watcher;
std::string snapshotPath;
void execute() override {
backend->getEventsSince(watcher, &snapshotPath);
if (watcher->mEvents.hasError()) {
throw std::runtime_error(watcher->mEvents.getError());
}
}
Value getResult() override {
std::vector<Event> events = watcher->mEvents.getEvents();
Array eventsArray = Array::New(env, events.size());
size_t i = 0;
for (auto it = events.begin(); it != events.end(); it++) {
eventsArray.Set(i++, it->toJS(env));
}
return eventsArray;
}
};
template<class Runner>
Value queueSnapshotWork(const CallbackInfo& info) {
Env env = info.Env();
if (info.Length() < 1 || !info[0].IsString()) {
TypeError::New(env, "Expected a string").ThrowAsJavaScriptException();
return env.Null();
}
if (info.Length() < 2 || !info[1].IsString()) {
TypeError::New(env, "Expected a string").ThrowAsJavaScriptException();
return env.Null();
}
if (info.Length() >= 3 && !info[2].IsObject()) {
TypeError::New(env, "Expected an object").ThrowAsJavaScriptException();
return env.Null();
}
Runner *runner = new Runner(info.Env(), info[0], info[1], info[2]);
return runner->queue();
}
Value writeSnapshot(const CallbackInfo& info) {
return queueSnapshotWork<WriteSnapshotRunner>(info);
}
Value getEventsSince(const CallbackInfo& info) {
return queueSnapshotWork<GetEventsSinceRunner>(info);
}
class SubscribeRunner : public PromiseRunner {
public:
SubscribeRunner(Env env, Value dir, Value fn, Value opts) : PromiseRunner(env) {
watcher = Watcher::getShared(
std::string(dir.As<String>().Utf8Value().c_str()),
getIgnorePaths(env, opts),
getIgnoreGlobs(env, opts)
);
backend = getBackend(env, opts);
watcher->watch(fn.As<Function>());
}
private:
WatcherRef watcher;
std::shared_ptr<Backend> backend;
FunctionReference callback;
void execute() override {
try {
backend->watch(watcher);
} catch (std::exception &err) {
watcher->destroy();
throw;
}
}
};
class UnsubscribeRunner : public PromiseRunner {
public:
UnsubscribeRunner(Env env, Value dir, Value fn, Value opts) : PromiseRunner(env) {
watcher = Watcher::getShared(
std::string(dir.As<String>().Utf8Value().c_str()),
getIgnorePaths(env, opts),
getIgnoreGlobs(env, opts)
);
backend = getBackend(env, opts);
shouldUnwatch = watcher->unwatch(fn.As<Function>());
}
private:
WatcherRef watcher;
std::shared_ptr<Backend> backend;
bool shouldUnwatch;
void execute() override {
if (shouldUnwatch) {
backend->unwatch(watcher);
}
}
};
template<class Runner>
Value queueSubscriptionWork(const CallbackInfo& info) {
Env env = info.Env();
if (info.Length() < 1 || !info[0].IsString()) {
TypeError::New(env, "Expected a string").ThrowAsJavaScriptException();
return env.Null();
}
if (info.Length() < 2 || !info[1].IsFunction()) {
TypeError::New(env, "Expected a function").ThrowAsJavaScriptException();
return env.Null();
}
if (info.Length() >= 3 && !info[2].IsObject()) {
TypeError::New(env, "Expected an object").ThrowAsJavaScriptException();
return env.Null();
}
Runner *runner = new Runner(info.Env(), info[0], info[1], info[2]);
return runner->queue();
}
Value subscribe(const CallbackInfo& info) {
return queueSubscriptionWork<SubscribeRunner>(info);
}
Value unsubscribe(const CallbackInfo& info) {
return queueSubscriptionWork<UnsubscribeRunner>(info);
}
Object Init(Env env, Object exports) {
exports.Set(
String::New(env, "writeSnapshot"),
Function::New(env, writeSnapshot)
);
exports.Set(
String::New(env, "getEventsSince"),
Function::New(env, getEventsSince)
);
exports.Set(
String::New(env, "subscribe"),
Function::New(env, subscribe)
);
exports.Set(
String::New(env, "unsubscribe"),
Function::New(env, unsubscribe)
);
return exports;
}
NODE_API_MODULE(watcher, Init)

View File

@ -0,0 +1,306 @@
#include <memory>
#include <poll.h>
#include <unistd.h>
#include <libgen.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "KqueueBackend.hh"
#if __APPLE__
#define st_mtim st_mtimespec
#endif
#if !defined(O_EVTONLY)
#define O_EVTONLY O_RDONLY
#endif
#define CONVERT_TIME(ts) ((uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec)
void KqueueBackend::start() {
if ((mKqueue = kqueue()) < 0) {
throw std::runtime_error(std::string("Unable to open kqueue: ") + strerror(errno));
}
// Create a pipe that we will write to when we want to end the thread.
int err = pipe(mPipe);
if (err == -1) {
throw std::runtime_error(std::string("Unable to open pipe: ") + strerror(errno));
}
// Subscribe kqueue to this pipe.
struct kevent ev;
EV_SET(
&ev,
mPipe[0],
EVFILT_READ,
EV_ADD | EV_CLEAR,
0,
0,
0
);
if (kevent(mKqueue, &ev, 1, NULL, 0, 0)) {
close(mPipe[0]);
close(mPipe[1]);
throw std::runtime_error(std::string("Unable to watch pipe: ") + strerror(errno));
}
notifyStarted();
struct kevent events[128];
while (true) {
int event_count = kevent(mKqueue, NULL, 0, events, 128, 0);
if (event_count < 0 || events[0].flags == EV_ERROR) {
throw std::runtime_error(std::string("kevent error: ") + strerror(errno));
}
// Track all of the watchers that are touched so we can notify them at the end of the events.
std::unordered_set<WatcherRef> watchers;
for (int i = 0; i < event_count; i++) {
int flags = events[i].fflags;
int fd = events[i].ident;
if (fd == mPipe[0]) {
// pipe was written to. break out of the loop.
goto done;
}
auto it = mFdToEntry.find(fd);
if (it == mFdToEntry.end()) {
// If fd wasn't in our map, we may have already stopped watching it. Ignore the event.
continue;
}
DirEntry *entry = it->second;
if (flags & NOTE_WRITE && entry && entry->isDir) {
// If a write occurred on a directory, we have to diff the contents of that
// directory to determine what file was added/deleted.
compareDir(fd, entry->path, watchers);
} else {
std::vector<KqueueSubscription *> subs = findSubscriptions(entry->path);
for (auto it = subs.begin(); it != subs.end(); it++) {
KqueueSubscription *sub = *it;
watchers.insert(sub->watcher);
if (flags & (NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE)) {
sub->watcher->mEvents.remove(sub->path);
sub->tree->remove(sub->path);
mFdToEntry.erase((int)(size_t)entry->state);
mSubscriptions.erase(sub->path);
} else if (flags & (NOTE_WRITE | NOTE_ATTRIB | NOTE_EXTEND)) {
struct stat st;
lstat(sub->path.c_str(), &st);
if (entry->mtime != CONVERT_TIME(st.st_mtim)) {
entry->mtime = CONVERT_TIME(st.st_mtim);
sub->watcher->mEvents.update(sub->path);
}
}
}
}
}
for (auto it = watchers.begin(); it != watchers.end(); it++) {
(*it)->notify();
}
}
done:
close(mPipe[0]);
close(mPipe[1]);
mEndedSignal.notify();
}
KqueueBackend::~KqueueBackend() {
write(mPipe[1], "X", 1);
mEndedSignal.wait();
}
void KqueueBackend::subscribe(WatcherRef watcher) {
// Build a full directory tree recursively, and watch each directory.
std::shared_ptr<DirTree> tree = getTree(watcher);
for (auto it = tree->entries.begin(); it != tree->entries.end(); it++) {
bool success = watchDir(watcher, it->second.path, tree);
if (!success) {
throw WatcherError(std::string("error watching " + watcher->mDir + ": " + strerror(errno)), watcher);
}
}
}
bool KqueueBackend::watchDir(WatcherRef watcher, std::string path, std::shared_ptr<DirTree> tree) {
if (watcher->isIgnored(path)) {
return false;
}
DirEntry *entry = tree->find(path);
if (!entry) {
return false;
}
KqueueSubscription sub = {
.watcher = watcher,
.path = path,
.tree = tree
};
if (!entry->state) {
int fd = open(path.c_str(), O_EVTONLY);
if (fd <= 0) {
return false;
}
struct kevent event;
EV_SET(
&event,
fd,
EVFILT_VNODE,
EV_ADD | EV_CLEAR | EV_ENABLE,
NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE,
0,
0
);
if (kevent(mKqueue, &event, 1, NULL, 0, 0)) {
close(fd);
return false;
}
entry->state = (void *)(size_t)fd;
mFdToEntry.emplace(fd, entry);
}
sub.fd = (int)(size_t)entry->state;
mSubscriptions.emplace(path, sub);
return true;
}
std::vector<KqueueSubscription *> KqueueBackend::findSubscriptions(std::string &path) {
// Find the subscriptions affected by this path.
// Copy pointers to them into a vector so that modifying mSubscriptions doesn't invalidate the iterator.
auto range = mSubscriptions.equal_range(path);
std::vector<KqueueSubscription *> subs;
for (auto it = range.first; it != range.second; it++) {
subs.push_back(&it->second);
}
return subs;
}
bool KqueueBackend::compareDir(int fd, std::string &path, std::unordered_set<WatcherRef> &watchers) {
// macOS doesn't support fdclosedir, so we have to duplicate the file descriptor
// to ensure the closedir doesn't also stop watching.
#if __APPLE__
fd = dup(fd);
#endif
DIR *dir = fdopendir(fd);
if (dir == NULL) {
return false;
}
// fdopendir doesn't rewind to the beginning.
rewinddir(dir);
std::vector<KqueueSubscription *> subs = findSubscriptions(path);
std::string dirStart = path + DIR_SEP;
std::unordered_set<std::shared_ptr<DirTree>> trees;
for (auto it = subs.begin(); it != subs.end(); it++) {
trees.emplace((*it)->tree);
}
std::unordered_set<std::string> entries;
struct dirent *entry;
while ((entry = readdir(dir))) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
std::string fullpath = dirStart + entry->d_name;
entries.emplace(fullpath);
for (auto it = trees.begin(); it != trees.end(); it++) {
std::shared_ptr<DirTree> tree = *it;
if (!tree->find(fullpath)) {
struct stat st;
fstatat(fd, entry->d_name, &st, AT_SYMLINK_NOFOLLOW);
tree->add(fullpath, CONVERT_TIME(st.st_mtim), S_ISDIR(st.st_mode));
// Notify all watchers with the same tree.
for (auto i = subs.begin(); i != subs.end(); i++) {
KqueueSubscription *sub = *i;
if (sub->tree == tree) {
if (sub->watcher->isIgnored(fullpath)) {
continue;
}
sub->watcher->mEvents.create(fullpath);
watchers.emplace(sub->watcher);
bool success = watchDir(sub->watcher, fullpath, sub->tree);
if (!success) {
sub->tree->remove(fullpath);
return false;
}
}
}
}
}
}
for (auto it = trees.begin(); it != trees.end(); it++) {
std::shared_ptr<DirTree> tree = *it;
for (auto entry = tree->entries.begin(); entry != tree->entries.end();) {
if (
entry->first.rfind(dirStart, 0) == 0 &&
entry->first.find(DIR_SEP, dirStart.length()) == std::string::npos &&
entries.count(entry->first) == 0
) {
// Notify all watchers with the same tree.
for (auto i = subs.begin(); i != subs.end(); i++) {
if ((*i)->tree == tree) {
KqueueSubscription *sub = *i;
if (!sub->watcher->isIgnored(entry->first)) {
sub->watcher->mEvents.remove(entry->first);
watchers.emplace(sub->watcher);
}
}
}
mFdToEntry.erase((int)(size_t)entry->second.state);
mSubscriptions.erase(entry->first);
entry = tree->entries.erase(entry);
} else {
entry++;
}
}
}
#if __APPLE__
closedir(dir);
#else
fdclosedir(dir);
#endif
return true;
}
void KqueueBackend::unsubscribe(WatcherRef watcher) {
// Find any subscriptions pointing to this watcher, and remove them.
for (auto it = mSubscriptions.begin(); it != mSubscriptions.end();) {
if (it->second.watcher.get() == watcher.get()) {
if (mSubscriptions.count(it->first) == 1) {
// Closing the file descriptor automatically unwatches it in the kqueue.
close(it->second.fd);
mFdToEntry.erase(it->second.fd);
}
it = mSubscriptions.erase(it);
} else {
it++;
}
}
}

View File

@ -0,0 +1,35 @@
#ifndef KQUEUE_H
#define KQUEUE_H
#include <unordered_map>
#include <sys/event.h>
#include "../shared/BruteForceBackend.hh"
#include "../DirTree.hh"
#include "../Signal.hh"
struct KqueueSubscription {
WatcherRef watcher;
std::string path;
std::shared_ptr<DirTree> tree;
int fd;
};
class KqueueBackend : public BruteForceBackend {
public:
void start() override;
~KqueueBackend();
void subscribe(WatcherRef watcher) override;
void unsubscribe(WatcherRef watcher) override;
private:
int mKqueue;
int mPipe[2];
std::unordered_multimap<std::string, KqueueSubscription> mSubscriptions;
std::unordered_map<int, DirEntry *> mFdToEntry;
Signal mEndedSignal;
bool watchDir(WatcherRef watcher, std::string path, std::shared_ptr<DirTree> tree);
bool compareDir(int fd, std::string &dir, std::unordered_set<WatcherRef> &watchers);
std::vector<KqueueSubscription *> findSubscriptions(std::string &path);
};
#endif

View File

@ -0,0 +1,232 @@
#include <memory>
#include <poll.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "InotifyBackend.hh"
#define INOTIFY_MASK \
IN_ATTRIB | IN_CREATE | IN_DELETE | \
IN_DELETE_SELF | IN_MODIFY | IN_MOVE_SELF | IN_MOVED_FROM | \
IN_MOVED_TO | IN_DONT_FOLLOW | IN_ONLYDIR | IN_EXCL_UNLINK
#define BUFFER_SIZE 8192
#define CONVERT_TIME(ts) ((uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec)
void InotifyBackend::start() {
// Create a pipe that we will write to when we want to end the thread.
int err = pipe2(mPipe, O_CLOEXEC | O_NONBLOCK);
if (err == -1) {
throw std::runtime_error(std::string("Unable to open pipe: ") + strerror(errno));
}
// Init inotify file descriptor.
mInotify = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
if (mInotify == -1) {
throw std::runtime_error(std::string("Unable to initialize inotify: ") + strerror(errno));
}
pollfd pollfds[2];
pollfds[0].fd = mPipe[0];
pollfds[0].events = POLLIN;
pollfds[0].revents = 0;
pollfds[1].fd = mInotify;
pollfds[1].events = POLLIN;
pollfds[1].revents = 0;
notifyStarted();
// Loop until we get an event from the pipe.
while (true) {
int result = poll(pollfds, 2, 500);
if (result < 0) {
throw std::runtime_error(std::string("Unable to poll: ") + strerror(errno));
}
if (pollfds[0].revents) {
break;
}
if (pollfds[1].revents) {
handleEvents();
}
}
close(mPipe[0]);
close(mPipe[1]);
close(mInotify);
mEndedSignal.notify();
}
InotifyBackend::~InotifyBackend() {
write(mPipe[1], "X", 1);
mEndedSignal.wait();
}
// This function is called by Backend::watch which takes a lock on mMutex
void InotifyBackend::subscribe(WatcherRef watcher) {
// Build a full directory tree recursively, and watch each directory.
std::shared_ptr<DirTree> tree = getTree(watcher);
for (auto it = tree->entries.begin(); it != tree->entries.end(); it++) {
if (it->second.isDir) {
bool success = watchDir(watcher, it->second.path, tree);
if (!success) {
throw WatcherError(std::string("inotify_add_watch on '") + it->second.path + std::string("' failed: ") + strerror(errno), watcher);
}
}
}
}
bool InotifyBackend::watchDir(WatcherRef watcher, std::string path, std::shared_ptr<DirTree> tree) {
int wd = inotify_add_watch(mInotify, path.c_str(), INOTIFY_MASK);
if (wd == -1) {
return false;
}
std::shared_ptr<InotifySubscription> sub = std::make_shared<InotifySubscription>();
sub->tree = tree;
sub->path = path;
sub->watcher = watcher;
mSubscriptions.emplace(wd, sub);
return true;
}
void InotifyBackend::handleEvents() {
char buf[BUFFER_SIZE] __attribute__ ((aligned(__alignof__(struct inotify_event))));;
struct inotify_event *event;
// Track all of the watchers that are touched so we can notify them at the end of the events.
std::unordered_set<WatcherRef> watchers;
while (true) {
int n = read(mInotify, &buf, BUFFER_SIZE);
if (n < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
break;
}
throw std::runtime_error(std::string("Error reading from inotify: ") + strerror(errno));
}
if (n == 0) {
break;
}
for (char *ptr = buf; ptr < buf + n; ptr += sizeof(*event) + event->len) {
event = (struct inotify_event *)ptr;
if ((event->mask & IN_Q_OVERFLOW) == IN_Q_OVERFLOW) {
// overflow
continue;
}
handleEvent(event, watchers);
}
}
for (auto it = watchers.begin(); it != watchers.end(); it++) {
(*it)->notify();
}
}
void InotifyBackend::handleEvent(struct inotify_event *event, std::unordered_set<WatcherRef> &watchers) {
std::unique_lock<std::mutex> lock(mMutex);
// Find the subscriptions for this watch descriptor
auto range = mSubscriptions.equal_range(event->wd);
std::unordered_set<std::shared_ptr<InotifySubscription>> set;
for (auto it = range.first; it != range.second; it++) {
set.insert(it->second);
}
for (auto it = set.begin(); it != set.end(); it++) {
if (handleSubscription(event, *it)) {
watchers.insert((*it)->watcher);
}
}
}
bool InotifyBackend::handleSubscription(struct inotify_event *event, std::shared_ptr<InotifySubscription> sub) {
// Build full path and check if its in our ignore list.
std::shared_ptr<Watcher> watcher = sub->watcher;
std::string path = std::string(sub->path);
bool isDir = event->mask & IN_ISDIR;
if (event->len > 0) {
path += "/" + std::string(event->name);
}
if (watcher->isIgnored(path)) {
return false;
}
// If this is a create, check if it's a directory and start watching if it is.
// In any case, keep the directory tree up to date.
if (event->mask & (IN_CREATE | IN_MOVED_TO)) {
watcher->mEvents.create(path);
struct stat st;
// Use lstat to avoid resolving symbolic links that we cannot watch anyway
// https://github.com/parcel-bundler/watcher/issues/76
lstat(path.c_str(), &st);
DirEntry *entry = sub->tree->add(path, CONVERT_TIME(st.st_mtim), S_ISDIR(st.st_mode));
if (entry->isDir) {
bool success = watchDir(watcher, path, sub->tree);
if (!success) {
sub->tree->remove(path);
return false;
}
}
} else if (event->mask & (IN_MODIFY | IN_ATTRIB)) {
watcher->mEvents.update(path);
struct stat st;
stat(path.c_str(), &st);
sub->tree->update(path, CONVERT_TIME(st.st_mtim));
} else if (event->mask & (IN_DELETE | IN_DELETE_SELF | IN_MOVED_FROM | IN_MOVE_SELF)) {
bool isSelfEvent = (event->mask & (IN_DELETE_SELF | IN_MOVE_SELF));
// Ignore delete/move self events unless this is the recursive watch root
if (isSelfEvent && path != watcher->mDir) {
return false;
}
// If the entry being deleted/moved is a directory, remove it from the list of subscriptions
// XXX: self events don't have the IN_ISDIR mask
if (isSelfEvent || isDir) {
for (auto it = mSubscriptions.begin(); it != mSubscriptions.end();) {
if (it->second->path == path) {
it = mSubscriptions.erase(it);
} else {
++it;
}
}
}
watcher->mEvents.remove(path);
sub->tree->remove(path);
}
return true;
}
// This function is called by Backend::unwatch which takes a lock on mMutex
void InotifyBackend::unsubscribe(WatcherRef watcher) {
// Find any subscriptions pointing to this watcher, and remove them.
for (auto it = mSubscriptions.begin(); it != mSubscriptions.end();) {
if (it->second->watcher.get() == watcher.get()) {
if (mSubscriptions.count(it->first) == 1) {
int err = inotify_rm_watch(mInotify, it->first);
if (err == -1) {
throw WatcherError(std::string("Unable to remove watcher: ") + strerror(errno), watcher);
}
}
it = mSubscriptions.erase(it);
} else {
it++;
}
}
}

View File

@ -0,0 +1,34 @@
#ifndef INOTIFY_H
#define INOTIFY_H
#include <unordered_map>
#include <sys/inotify.h>
#include "../shared/BruteForceBackend.hh"
#include "../DirTree.hh"
#include "../Signal.hh"
struct InotifySubscription {
std::shared_ptr<DirTree> tree;
std::string path;
WatcherRef watcher;
};
class InotifyBackend : public BruteForceBackend {
public:
void start() override;
~InotifyBackend();
void subscribe(WatcherRef watcher) override;
void unsubscribe(WatcherRef watcher) override;
private:
int mPipe[2];
int mInotify;
std::unordered_multimap<int, std::shared_ptr<InotifySubscription>> mSubscriptions;
Signal mEndedSignal;
bool watchDir(WatcherRef watcher, std::string path, std::shared_ptr<DirTree> tree);
void handleEvents();
void handleEvent(struct inotify_event *event, std::unordered_set<WatcherRef> &watchers);
bool handleSubscription(struct inotify_event *event, std::shared_ptr<InotifySubscription> sub);
};
#endif

View File

@ -0,0 +1,338 @@
#include <CoreServices/CoreServices.h>
#include <sys/stat.h>
#include <string>
#include <fstream>
#include <unordered_set>
#include "../Event.hh"
#include "../Backend.hh"
#include "./FSEventsBackend.hh"
#include "../Watcher.hh"
#define CONVERT_TIME(ts) ((uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec)
#define IGNORED_FLAGS (kFSEventStreamEventFlagItemIsHardlink | kFSEventStreamEventFlagItemIsLastHardlink | kFSEventStreamEventFlagItemIsSymlink | kFSEventStreamEventFlagItemIsDir | kFSEventStreamEventFlagItemIsFile)
void stopStream(FSEventStreamRef stream, CFRunLoopRef runLoop) {
FSEventStreamStop(stream);
FSEventStreamUnscheduleFromRunLoop(stream, runLoop, kCFRunLoopDefaultMode);
FSEventStreamInvalidate(stream);
FSEventStreamRelease(stream);
}
// macOS has a case insensitive file system by default. In order to detect
// file renames that only affect case, we need to get the canonical path
// and compare it with the input path to determine if a file was created or deleted.
bool pathExists(char *path) {
int fd = open(path, O_RDONLY | O_SYMLINK);
if (fd == -1) {
return false;
}
char buf[PATH_MAX];
if (fcntl(fd, F_GETPATH, buf) == -1) {
close(fd);
return false;
}
bool res = strncmp(path, buf, PATH_MAX) == 0;
close(fd);
return res;
}
class State: public WatcherState {
public:
FSEventStreamRef stream;
std::shared_ptr<DirTree> tree;
uint64_t since;
};
void FSEventsCallback(
ConstFSEventStreamRef streamRef,
void *clientCallBackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[]
) {
char **paths = (char **)eventPaths;
std::shared_ptr<Watcher>& watcher = *static_cast<std::shared_ptr<Watcher> *>(clientCallBackInfo);
EventList& list = watcher->mEvents;
if (watcher->state == nullptr) {
return;
}
auto stateGuard = watcher->state;
auto* state = static_cast<State*>(stateGuard.get());
uint64_t since = state->since;
bool deletedRoot = false;
for (size_t i = 0; i < numEvents; ++i) {
bool isCreated = (eventFlags[i] & kFSEventStreamEventFlagItemCreated) == kFSEventStreamEventFlagItemCreated;
bool isRemoved = (eventFlags[i] & kFSEventStreamEventFlagItemRemoved) == kFSEventStreamEventFlagItemRemoved;
bool isModified = (eventFlags[i] & kFSEventStreamEventFlagItemModified) == kFSEventStreamEventFlagItemModified ||
(eventFlags[i] & kFSEventStreamEventFlagItemInodeMetaMod) == kFSEventStreamEventFlagItemInodeMetaMod ||
(eventFlags[i] & kFSEventStreamEventFlagItemFinderInfoMod) == kFSEventStreamEventFlagItemFinderInfoMod ||
(eventFlags[i] & kFSEventStreamEventFlagItemChangeOwner) == kFSEventStreamEventFlagItemChangeOwner ||
(eventFlags[i] & kFSEventStreamEventFlagItemXattrMod) == kFSEventStreamEventFlagItemXattrMod;
bool isRenamed = (eventFlags[i] & kFSEventStreamEventFlagItemRenamed) == kFSEventStreamEventFlagItemRenamed;
bool isDone = (eventFlags[i] & kFSEventStreamEventFlagHistoryDone) == kFSEventStreamEventFlagHistoryDone;
bool isDir = (eventFlags[i] & kFSEventStreamEventFlagItemIsDir) == kFSEventStreamEventFlagItemIsDir;
if (eventFlags[i] & kFSEventStreamEventFlagMustScanSubDirs) {
if (eventFlags[i] & kFSEventStreamEventFlagUserDropped) {
list.error("Events were dropped by the FSEvents client. File system must be re-scanned.");
} else if (eventFlags[i] & kFSEventStreamEventFlagKernelDropped) {
list.error("Events were dropped by the kernel. File system must be re-scanned.");
} else {
list.error("Too many events. File system must be re-scanned.");
}
}
if (isDone) {
watcher->notify();
break;
}
auto ignoredFlags = IGNORED_FLAGS;
if (__builtin_available(macOS 10.13, *)) {
ignoredFlags |= kFSEventStreamEventFlagItemCloned;
}
// If we don't care about any of the flags that are set, ignore this event.
if ((eventFlags[i] & ~ignoredFlags) == 0) {
continue;
}
// FSEvents exclusion paths only apply to files, not directories.
if (watcher->isIgnored(paths[i])) {
continue;
}
// Handle unambiguous events first
if (isCreated && !(isRemoved || isModified || isRenamed)) {
state->tree->add(paths[i], 0, isDir);
list.create(paths[i]);
} else if (isRemoved && !(isCreated || isModified || isRenamed)) {
state->tree->remove(paths[i]);
list.remove(paths[i]);
if (paths[i] == watcher->mDir) {
deletedRoot = true;
}
} else if (isModified && !(isCreated || isRemoved || isRenamed)) {
struct stat file;
if (stat(paths[i], &file)) {
continue;
}
// Ignore if mtime is the same as the last event.
// This prevents duplicate events from being emitted.
// If tv_nsec is zero, the file system probably only has second-level
// granularity so allow the even through in that case.
uint64_t mtime = CONVERT_TIME(file.st_mtimespec);
DirEntry *entry = state->tree->find(paths[i]);
if (entry && mtime == entry->mtime && file.st_mtimespec.tv_nsec != 0) {
continue;
}
if (entry) {
// Update mtime.
entry->mtime = mtime;
} else {
// Add to tree if this path has not been discovered yet.
state->tree->add(paths[i], mtime, S_ISDIR(file.st_mode));
}
list.update(paths[i]);
} else {
// If multiple flags were set, then we need to call `stat` to determine if the file really exists.
// This helps disambiguate creates, updates, and deletes.
struct stat file;
if (stat(paths[i], &file) || !pathExists(paths[i])) {
// File does not exist, so we have to assume it was removed. This is not exact since the
// flags set by fsevents get coalesced together (e.g. created & deleted), so there is no way to
// know whether the create and delete both happened since our snapshot (in which case
// we'd rather ignore this event completely). This will result in some extra delete events
// being emitted for files we don't know about, but that is the best we can do.
state->tree->remove(paths[i]);
list.remove(paths[i]);
if (paths[i] == watcher->mDir) {
deletedRoot = true;
}
continue;
}
// If the file was modified, and existed before, then this is an update, otherwise a create.
uint64_t ctime = CONVERT_TIME(file.st_birthtimespec);
uint64_t mtime = CONVERT_TIME(file.st_mtimespec);
DirEntry *entry = !since ? state->tree->find(paths[i]) : NULL;
if (entry && entry->mtime == mtime && file.st_mtimespec.tv_nsec != 0) {
continue;
}
// Some mounted file systems report a creation time of 0/unix epoch which we special case.
if (isModified && (entry || (ctime <= since && ctime != 0))) {
state->tree->update(paths[i], mtime);
list.update(paths[i]);
} else {
state->tree->add(paths[i], mtime, S_ISDIR(file.st_mode));
list.create(paths[i]);
}
}
}
if (!since) {
watcher->notify();
}
// Stop watching if the root directory was deleted.
if (deletedRoot) {
stopStream((FSEventStreamRef)streamRef, CFRunLoopGetCurrent());
watcher->state = nullptr;
}
}
void checkWatcher(WatcherRef watcher) {
struct stat file;
if (stat(watcher->mDir.c_str(), &file)) {
throw WatcherError(strerror(errno), watcher);
}
if (!S_ISDIR(file.st_mode)) {
throw WatcherError(strerror(ENOTDIR), watcher);
}
}
void FSEventsBackend::startStream(WatcherRef watcher, FSEventStreamEventId id) {
checkWatcher(watcher);
CFAbsoluteTime latency = 0.001;
CFStringRef fileWatchPath = CFStringCreateWithCString(
NULL,
watcher->mDir.c_str(),
kCFStringEncodingUTF8
);
CFArrayRef pathsToWatch = CFArrayCreate(
NULL,
(const void **)&fileWatchPath,
1,
NULL
);
// Make a watcher reference we can pass into the callback. This ensures bumped ref-count.
std::shared_ptr<Watcher>* callbackWatcher = new std::shared_ptr<Watcher> (watcher);
FSEventStreamContext callbackInfo {0, static_cast<void*> (callbackWatcher), nullptr, nullptr, nullptr};
FSEventStreamRef stream = FSEventStreamCreate(
NULL,
&FSEventsCallback,
&callbackInfo,
pathsToWatch,
id,
latency,
kFSEventStreamCreateFlagFileEvents
);
CFMutableArrayRef exclusions = CFArrayCreateMutable(NULL, watcher->mIgnorePaths.size(), NULL);
for (auto it = watcher->mIgnorePaths.begin(); it != watcher->mIgnorePaths.end(); it++) {
CFStringRef path = CFStringCreateWithCString(
NULL,
it->c_str(),
kCFStringEncodingUTF8
);
CFArrayAppendValue(exclusions, (const void *)path);
}
FSEventStreamSetExclusionPaths(stream, exclusions);
FSEventStreamScheduleWithRunLoop(stream, mRunLoop, kCFRunLoopDefaultMode);
bool started = FSEventStreamStart(stream);
CFRelease(pathsToWatch);
CFRelease(fileWatchPath);
if (!started) {
FSEventStreamRelease(stream);
throw WatcherError("Error starting FSEvents stream", watcher);
}
auto stateGuard = watcher->state;
State* s = static_cast<State*>(stateGuard.get());
s->tree = std::make_shared<DirTree>(watcher->mDir);
s->stream = stream;
}
void FSEventsBackend::start() {
mRunLoop = CFRunLoopGetCurrent();
CFRetain(mRunLoop);
// Unlock once run loop has started.
CFRunLoopPerformBlock(mRunLoop, kCFRunLoopDefaultMode, ^ {
notifyStarted();
});
CFRunLoopWakeUp(mRunLoop);
CFRunLoopRun();
}
FSEventsBackend::~FSEventsBackend() {
std::unique_lock<std::mutex> lock(mMutex);
CFRunLoopStop(mRunLoop);
CFRelease(mRunLoop);
}
void FSEventsBackend::writeSnapshot(WatcherRef watcher, std::string *snapshotPath) {
std::unique_lock<std::mutex> lock(mMutex);
checkWatcher(watcher);
FSEventStreamEventId id = FSEventsGetCurrentEventId();
std::ofstream ofs(*snapshotPath);
ofs << id;
ofs << "\n";
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
ofs << CONVERT_TIME(now);
}
void FSEventsBackend::getEventsSince(WatcherRef watcher, std::string *snapshotPath) {
std::unique_lock<std::mutex> lock(mMutex);
std::ifstream ifs(*snapshotPath);
if (ifs.fail()) {
return;
}
FSEventStreamEventId id;
uint64_t since;
ifs >> id;
ifs >> since;
auto s = std::make_shared<State>();
s->since = since;
watcher->state = s;
startStream(watcher, id);
watcher->wait();
stopStream(s->stream, mRunLoop);
watcher->state = nullptr;
}
// This function is called by Backend::watch which takes a lock on mMutex
void FSEventsBackend::subscribe(WatcherRef watcher) {
auto s = std::make_shared<State>();
s->since = 0;
watcher->state = s;
startStream(watcher, kFSEventStreamEventIdSinceNow);
}
// This function is called by Backend::unwatch which takes a lock on mMutex
void FSEventsBackend::unsubscribe(WatcherRef watcher) {
auto stateGuard = watcher->state;
State* s = static_cast<State*>(stateGuard.get());
if (s != nullptr) {
stopStream(s->stream, mRunLoop);
watcher->state = nullptr;
}
}

View File

@ -0,0 +1,20 @@
#ifndef FS_EVENTS_H
#define FS_EVENTS_H
#include <CoreServices/CoreServices.h>
#include "../Backend.hh"
class FSEventsBackend : public Backend {
public:
void start() override;
~FSEventsBackend();
void writeSnapshot(WatcherRef watcher, std::string *snapshotPath) override;
void getEventsSince(WatcherRef watcher, std::string *snapshotPath) override;
void subscribe(WatcherRef watcher) override;
void unsubscribe(WatcherRef watcher) override;
private:
void startStream(WatcherRef watcher, FSEventStreamEventId id);
CFRunLoopRef mRunLoop;
};
#endif

View File

@ -0,0 +1,41 @@
#include <string>
#include "../DirTree.hh"
#include "../Event.hh"
#include "./BruteForceBackend.hh"
std::shared_ptr<DirTree> BruteForceBackend::getTree(WatcherRef watcher, bool shouldRead) {
auto tree = DirTree::getCached(watcher->mDir);
// If the tree is not complete, read it if needed.
if (!tree->isComplete && shouldRead) {
readTree(watcher, tree);
tree->isComplete = true;
}
return tree;
}
void BruteForceBackend::writeSnapshot(WatcherRef watcher, std::string *snapshotPath) {
std::unique_lock<std::mutex> lock(mMutex);
auto tree = getTree(watcher);
FILE *f = fopen(snapshotPath->c_str(), "w");
if (!f) {
throw std::runtime_error(std::string("Unable to open snapshot file: ") + strerror(errno));
}
tree->write(f);
fclose(f);
}
void BruteForceBackend::getEventsSince(WatcherRef watcher, std::string *snapshotPath) {
std::unique_lock<std::mutex> lock(mMutex);
FILE *f = fopen(snapshotPath->c_str(), "r");
if (!f) {
throw std::runtime_error(std::string("Unable to open snapshot file: ") + strerror(errno));
}
DirTree snapshot{watcher->mDir, f};
auto now = getTree(watcher);
now->getChanges(&snapshot, watcher->mEvents);
fclose(f);
}

View File

@ -0,0 +1,25 @@
#ifndef BRUTE_FORCE_H
#define BRUTE_FORCE_H
#include "../Backend.hh"
#include "../DirTree.hh"
#include "../Watcher.hh"
class BruteForceBackend : public Backend {
public:
void writeSnapshot(WatcherRef watcher, std::string *snapshotPath) override;
void getEventsSince(WatcherRef watcher, std::string *snapshotPath) override;
void subscribe(WatcherRef watcher) override {
throw "Brute force backend doesn't support subscriptions.";
}
void unsubscribe(WatcherRef watcher) override {
throw "Brute force backend doesn't support subscriptions.";
}
std::shared_ptr<DirTree> getTree(WatcherRef watcher, bool shouldRead = true);
private:
void readTree(WatcherRef watcher, std::shared_ptr<DirTree> tree);
};
#endif

50
node_modules/@parcel/watcher/src/unix/fts.cc generated vendored Normal file
View File

@ -0,0 +1,50 @@
#include <string>
// weird error on linux
#ifdef __THROW
#undef __THROW
#endif
#define __THROW
#include <fts.h>
#include <sys/stat.h>
#include "../DirTree.hh"
#include "../shared/BruteForceBackend.hh"
#define CONVERT_TIME(ts) ((uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec)
#if __APPLE__
#define st_mtim st_mtimespec
#endif
void BruteForceBackend::readTree(WatcherRef watcher, std::shared_ptr<DirTree> tree) {
char *paths[2] {(char *)watcher->mDir.c_str(), NULL};
FTS *fts = fts_open(paths, FTS_NOCHDIR | FTS_PHYSICAL, NULL);
if (!fts) {
throw WatcherError(strerror(errno), watcher);
}
FTSENT *node;
bool isRoot = true;
while ((node = fts_read(fts)) != NULL) {
if (node->fts_errno) {
fts_close(fts);
throw WatcherError(strerror(node->fts_errno), watcher);
}
if (isRoot && !(node->fts_info & FTS_D)) {
fts_close(fts);
throw WatcherError(strerror(ENOTDIR), watcher);
}
if (watcher->isIgnored(std::string(node->fts_path))) {
fts_set(fts, node, FTS_SKIP);
continue;
}
tree->add(node->fts_path, CONVERT_TIME(node->fts_statp->st_mtim), (node->fts_info & FTS_D) == FTS_D);
isRoot = false;
}
fts_close(fts);
}

77
node_modules/@parcel/watcher/src/unix/legacy.cc generated vendored Normal file
View File

@ -0,0 +1,77 @@
#include <string>
// weird error on linux
#ifdef __THROW
#undef __THROW
#endif
#define __THROW
#ifdef _LIBC
# include <include/sys/stat.h>
#else
# include <sys/stat.h>
#endif
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include "../DirTree.hh"
#include "../shared/BruteForceBackend.hh"
#define CONVERT_TIME(ts) ((uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec)
#if __APPLE__
#define st_mtim st_mtimespec
#endif
#define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2])))
void iterateDir(WatcherRef watcher, const std::shared_ptr <DirTree> tree, const char *relative, int parent_fd, const std::string &dirname) {
int open_flags = (O_RDONLY | O_CLOEXEC | O_DIRECTORY | O_NOCTTY | O_NONBLOCK | O_NOFOLLOW);
int new_fd = openat(parent_fd, relative, open_flags);
if (new_fd == -1) {
if (errno == EACCES) {
return; // ignore insufficient permissions
}
throw WatcherError(strerror(errno), watcher);
}
struct stat rootAttributes;
fstatat(new_fd, ".", &rootAttributes, AT_SYMLINK_NOFOLLOW);
tree->add(dirname, CONVERT_TIME(rootAttributes.st_mtim), true);
if (DIR *dir = fdopendir(new_fd)) {
while (struct dirent *ent = (errno = 0, readdir(dir))) {
if (ISDOT(ent->d_name)) continue;
std::string fullPath = dirname + "/" + ent->d_name;
if (!watcher->isIgnored(fullPath)) {
struct stat attrib;
fstatat(new_fd, ent->d_name, &attrib, AT_SYMLINK_NOFOLLOW);
bool isDir = ent->d_type == DT_DIR;
if (isDir) {
iterateDir(watcher, tree, ent->d_name, new_fd, fullPath);
} else {
tree->add(fullPath, CONVERT_TIME(attrib.st_mtim), isDir);
}
}
}
closedir(dir);
} else {
close(new_fd);
}
if (errno) {
throw WatcherError(strerror(errno), watcher);
}
}
void BruteForceBackend::readTree(WatcherRef watcher, std::shared_ptr <DirTree> tree) {
int fd = open(watcher->mDir.c_str(), O_RDONLY);
if (fd) {
iterateDir(watcher, tree, ".", fd, watcher->mDir);
close(fd);
}
}

132
node_modules/@parcel/watcher/src/wasm/WasmBackend.cc generated vendored Normal file
View File

@ -0,0 +1,132 @@
#include <sys/stat.h>
#include "WasmBackend.hh"
#define CONVERT_TIME(ts) ((uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec)
void WasmBackend::start() {
notifyStarted();
}
void WasmBackend::subscribe(WatcherRef watcher) {
// Build a full directory tree recursively, and watch each directory.
std::shared_ptr<DirTree> tree = getTree(watcher);
for (auto it = tree->entries.begin(); it != tree->entries.end(); it++) {
if (it->second.isDir) {
watchDir(watcher, it->second.path, tree);
}
}
}
void WasmBackend::watchDir(WatcherRef watcher, std::string path, std::shared_ptr<DirTree> tree) {
int wd = wasm_backend_add_watch(path.c_str(), (void *)this);
std::shared_ptr<WasmSubscription> sub = std::make_shared<WasmSubscription>();
sub->tree = tree;
sub->path = path;
sub->watcher = watcher;
mSubscriptions.emplace(wd, sub);
}
extern "C" void wasm_backend_event_handler(void *backend, int wd, int type, char *filename) {
WasmBackend *b = (WasmBackend *)(backend);
b->handleEvent(wd, type, filename);
}
void WasmBackend::handleEvent(int wd, int type, char *filename) {
// Find the subscriptions for this watch descriptor
auto range = mSubscriptions.equal_range(wd);
std::unordered_set<std::shared_ptr<WasmSubscription>> set;
for (auto it = range.first; it != range.second; it++) {
set.insert(it->second);
}
for (auto it = set.begin(); it != set.end(); it++) {
if (handleSubscription(type, filename, *it)) {
(*it)->watcher->notify();
}
}
}
bool WasmBackend::handleSubscription(int type, char *filename, std::shared_ptr<WasmSubscription> sub) {
// Build full path and check if its in our ignore list.
WatcherRef watcher = sub->watcher;
std::string path = std::string(sub->path);
if (filename[0] != '\0') {
path += "/" + std::string(filename);
}
if (watcher->isIgnored(path)) {
return false;
}
if (type == 1) {
struct stat st;
stat(path.c_str(), &st);
sub->tree->update(path, CONVERT_TIME(st.st_mtim));
watcher->mEvents.update(path);
} else if (type == 2) {
// Determine if this is a create or delete depending on if the file exists or not.
struct stat st;
if (lstat(path.c_str(), &st)) {
// If the entry being deleted/moved is a directory, remove it from the list of subscriptions
DirEntry *entry = sub->tree->find(path);
if (!entry) {
return false;
}
if (entry->isDir) {
std::string pathStart = path + DIR_SEP;
for (auto it = mSubscriptions.begin(); it != mSubscriptions.end();) {
if (it->second->path == path || it->second->path.rfind(pathStart, 0) == 0) {
wasm_backend_remove_watch(it->first);
it = mSubscriptions.erase(it);
} else {
++it;
}
}
// Remove all sub-entries
for (auto it = sub->tree->entries.begin(); it != sub->tree->entries.end();) {
if (it->first.rfind(pathStart, 0) == 0) {
watcher->mEvents.remove(it->first);
it = sub->tree->entries.erase(it);
} else {
it++;
}
}
}
watcher->mEvents.remove(path);
sub->tree->remove(path);
} else if (sub->tree->find(path)) {
sub->tree->update(path, CONVERT_TIME(st.st_mtim));
watcher->mEvents.update(path);
} else {
watcher->mEvents.create(path);
// If this is a create, check if it's a directory and start watching if it is.
DirEntry *entry = sub->tree->add(path, CONVERT_TIME(st.st_mtim), S_ISDIR(st.st_mode));
if (entry->isDir) {
watchDir(watcher, path, sub->tree);
}
}
}
return true;
}
void WasmBackend::unsubscribe(WatcherRef watcher) {
// Find any subscriptions pointing to this watcher, and remove them.
for (auto it = mSubscriptions.begin(); it != mSubscriptions.end();) {
if (it->second->watcher.get() == watcher.get()) {
if (mSubscriptions.count(it->first) == 1) {
wasm_backend_remove_watch(it->first);
}
it = mSubscriptions.erase(it);
} else {
it++;
}
}
}

34
node_modules/@parcel/watcher/src/wasm/WasmBackend.hh generated vendored Normal file
View File

@ -0,0 +1,34 @@
#ifndef WASM_H
#define WASM_H
#include <unordered_map>
#include "../shared/BruteForceBackend.hh"
#include "../DirTree.hh"
extern "C" {
int wasm_backend_add_watch(const char *filename, void *backend);
void wasm_backend_remove_watch(int wd);
void wasm_backend_event_handler(void *backend, int wd, int type, char *filename);
};
struct WasmSubscription {
std::shared_ptr<DirTree> tree;
std::string path;
WatcherRef watcher;
};
class WasmBackend : public BruteForceBackend {
public:
void start() override;
void subscribe(WatcherRef watcher) override;
void unsubscribe(WatcherRef watcher) override;
void handleEvent(int wd, int type, char *filename);
private:
int mWasm;
std::unordered_multimap<int, std::shared_ptr<WasmSubscription>> mSubscriptions;
void watchDir(WatcherRef watcher, std::string path, std::shared_ptr<DirTree> tree);
bool handleSubscription(int type, char *filename, std::shared_ptr<WasmSubscription> sub);
};
#endif

74
node_modules/@parcel/watcher/src/wasm/include.h generated vendored Normal file
View File

@ -0,0 +1,74 @@
/*
Copyright Node.js contributors. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
*/
// Node does not include the headers for these functions when compiling for WASM, so add them here.
#ifdef __wasm32__
extern "C" {
NAPI_EXTERN napi_status NAPI_CDECL
napi_create_threadsafe_function(napi_env env,
napi_value func,
napi_value async_resource,
napi_value async_resource_name,
size_t max_queue_size,
size_t initial_thread_count,
void* thread_finalize_data,
napi_finalize thread_finalize_cb,
void* context,
napi_threadsafe_function_call_js call_js_cb,
napi_threadsafe_function* result);
NAPI_EXTERN napi_status NAPI_CDECL napi_get_threadsafe_function_context(
napi_threadsafe_function func, void** result);
NAPI_EXTERN napi_status NAPI_CDECL
napi_call_threadsafe_function(napi_threadsafe_function func,
void* data,
napi_threadsafe_function_call_mode is_blocking);
NAPI_EXTERN napi_status NAPI_CDECL
napi_acquire_threadsafe_function(napi_threadsafe_function func);
NAPI_EXTERN napi_status NAPI_CDECL napi_release_threadsafe_function(
napi_threadsafe_function func, napi_threadsafe_function_release_mode mode);
NAPI_EXTERN napi_status NAPI_CDECL
napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func);
NAPI_EXTERN napi_status NAPI_CDECL
napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func);
NAPI_EXTERN napi_status NAPI_CDECL
napi_create_async_work(napi_env env,
napi_value async_resource,
napi_value async_resource_name,
napi_async_execute_callback execute,
napi_async_complete_callback complete,
void* data,
napi_async_work* result);
NAPI_EXTERN napi_status NAPI_CDECL napi_delete_async_work(napi_env env,
napi_async_work work);
NAPI_EXTERN napi_status NAPI_CDECL napi_queue_async_work(napi_env env,
napi_async_work work);
NAPI_EXTERN napi_status NAPI_CDECL napi_cancel_async_work(napi_env env,
napi_async_work work);
}
#endif

302
node_modules/@parcel/watcher/src/watchman/BSER.cc generated vendored Normal file
View File

@ -0,0 +1,302 @@
#include <stdint.h>
#include "./BSER.hh"
BSERType decodeType(std::istream &iss) {
int8_t type;
iss.read(reinterpret_cast<char*>(&type), sizeof(type));
return (BSERType) type;
}
void expectType(std::istream &iss, BSERType expected) {
BSERType got = decodeType(iss);
if (got != expected) {
throw std::runtime_error("Unexpected BSER type");
}
}
void encodeType(std::ostream &oss, BSERType type) {
int8_t t = (int8_t)type;
oss.write(reinterpret_cast<char*>(&t), sizeof(t));
}
template<typename T>
class Value : public BSERValue {
public:
T value;
Value(T val) {
value = val;
}
Value() {}
};
class BSERInteger : public Value<int64_t> {
public:
BSERInteger(int64_t value) : Value(value) {}
BSERInteger(std::istream &iss) {
int8_t int8;
int16_t int16;
int32_t int32;
int64_t int64;
BSERType type = decodeType(iss);
switch (type) {
case BSER_INT8:
iss.read(reinterpret_cast<char*>(&int8), sizeof(int8));
value = int8;
break;
case BSER_INT16:
iss.read(reinterpret_cast<char*>(&int16), sizeof(int16));
value = int16;
break;
case BSER_INT32:
iss.read(reinterpret_cast<char*>(&int32), sizeof(int32));
value = int32;
break;
case BSER_INT64:
iss.read(reinterpret_cast<char*>(&int64), sizeof(int64));
value = int64;
break;
default:
throw std::runtime_error("Invalid BSER int type");
}
}
int64_t intValue() override {
return value;
}
void encode(std::ostream &oss) override {
if (value <= INT8_MAX) {
encodeType(oss, BSER_INT8);
int8_t v = (int8_t)value;
oss.write(reinterpret_cast<char*>(&v), sizeof(v));
} else if (value <= INT16_MAX) {
encodeType(oss, BSER_INT16);
int16_t v = (int16_t)value;
oss.write(reinterpret_cast<char*>(&v), sizeof(v));
} else if (value <= INT32_MAX) {
encodeType(oss, BSER_INT32);
int32_t v = (int32_t)value;
oss.write(reinterpret_cast<char*>(&v), sizeof(v));
} else {
encodeType(oss, BSER_INT64);
oss.write(reinterpret_cast<char*>(&value), sizeof(value));
}
}
};
class BSERArray : public Value<BSER::Array> {
public:
BSERArray() : Value() {}
BSERArray(BSER::Array value) : Value(value) {}
BSERArray(std::istream &iss) {
expectType(iss, BSER_ARRAY);
int64_t len = BSERInteger(iss).intValue();
for (int64_t i = 0; i < len; i++) {
value.push_back(BSER(iss));
}
}
BSER::Array arrayValue() override {
return value;
}
void encode(std::ostream &oss) override {
encodeType(oss, BSER_ARRAY);
BSERInteger(value.size()).encode(oss);
for (auto it = value.begin(); it != value.end(); it++) {
it->encode(oss);
}
}
};
class BSERString : public Value<std::string> {
public:
BSERString(std::string value) : Value(value) {}
BSERString(std::istream &iss) {
expectType(iss, BSER_STRING);
int64_t len = BSERInteger(iss).intValue();
value.resize(len);
iss.read(&value[0], len);
}
std::string stringValue() override {
return value;
}
void encode(std::ostream &oss) override {
encodeType(oss, BSER_STRING);
BSERInteger(value.size()).encode(oss);
oss << value;
}
};
class BSERObject : public Value<BSER::Object> {
public:
BSERObject() : Value() {}
BSERObject(BSER::Object value) : Value(value) {}
BSERObject(std::istream &iss) {
expectType(iss, BSER_OBJECT);
int64_t len = BSERInteger(iss).intValue();
for (int64_t i = 0; i < len; i++) {
auto key = BSERString(iss).stringValue();
auto val = BSER(iss);
value.emplace(key, val);
}
}
BSER::Object objectValue() override {
return value;
}
void encode(std::ostream &oss) override {
encodeType(oss, BSER_OBJECT);
BSERInteger(value.size()).encode(oss);
for (auto it = value.begin(); it != value.end(); it++) {
BSERString(it->first).encode(oss);
it->second.encode(oss);
}
}
};
class BSERDouble : public Value<double> {
public:
BSERDouble(double value) : Value(value) {}
BSERDouble(std::istream &iss) {
expectType(iss, BSER_REAL);
iss.read(reinterpret_cast<char*>(&value), sizeof(value));
}
double doubleValue() override {
return value;
}
void encode(std::ostream &oss) override {
encodeType(oss, BSER_REAL);
oss.write(reinterpret_cast<char*>(&value), sizeof(value));
}
};
class BSERBoolean : public Value<bool> {
public:
BSERBoolean(bool value) : Value(value) {}
bool boolValue() override { return value; }
void encode(std::ostream &oss) override {
int8_t t = value == true ? BSER_BOOL_TRUE : BSER_BOOL_FALSE;
oss.write(reinterpret_cast<char*>(&t), sizeof(t));
}
};
class BSERNull : public Value<bool> {
public:
BSERNull() : Value(false) {}
void encode(std::ostream &oss) override {
encodeType(oss, BSER_NULL);
}
};
std::shared_ptr<BSERArray> decodeTemplate(std::istream &iss) {
expectType(iss, BSER_TEMPLATE);
auto keys = BSERArray(iss).arrayValue();
auto len = BSERInteger(iss).intValue();
std::shared_ptr<BSERArray> arr = std::make_shared<BSERArray>();
for (int64_t i = 0; i < len; i++) {
BSER::Object obj;
for (auto it = keys.begin(); it != keys.end(); it++) {
if (iss.peek() == 0x0c) {
iss.ignore(1);
continue;
}
auto val = BSER(iss);
obj.emplace(it->stringValue(), val);
}
arr->value.push_back(obj);
}
return arr;
}
BSER::BSER(std::istream &iss) {
BSERType type = decodeType(iss);
iss.unget();
switch (type) {
case BSER_ARRAY:
m_ptr = std::make_shared<BSERArray>(iss);
break;
case BSER_OBJECT:
m_ptr = std::make_shared<BSERObject>(iss);
break;
case BSER_STRING:
m_ptr = std::make_shared<BSERString>(iss);
break;
case BSER_INT8:
case BSER_INT16:
case BSER_INT32:
case BSER_INT64:
m_ptr = std::make_shared<BSERInteger>(iss);
break;
case BSER_REAL:
m_ptr = std::make_shared<BSERDouble>(iss);
break;
case BSER_BOOL_TRUE:
iss.ignore(1);
m_ptr = std::make_shared<BSERBoolean>(true);
break;
case BSER_BOOL_FALSE:
iss.ignore(1);
m_ptr = std::make_shared<BSERBoolean>(false);
break;
case BSER_NULL:
iss.ignore(1);
m_ptr = std::make_shared<BSERNull>();
break;
case BSER_TEMPLATE:
m_ptr = decodeTemplate(iss);
break;
default:
throw std::runtime_error("unknown BSER type");
}
}
BSER::BSER() : m_ptr(std::make_shared<BSERNull>()) {}
BSER::BSER(BSER::Array value) : m_ptr(std::make_shared<BSERArray>(value)) {}
BSER::BSER(BSER::Object value) : m_ptr(std::make_shared<BSERObject>(value)) {}
BSER::BSER(const char *value) : m_ptr(std::make_shared<BSERString>(value)) {}
BSER::BSER(std::string value) : m_ptr(std::make_shared<BSERString>(value)) {}
BSER::BSER(int64_t value) : m_ptr(std::make_shared<BSERInteger>(value)) {}
BSER::BSER(double value) : m_ptr(std::make_shared<BSERDouble>(value)) {}
BSER::BSER(bool value) : m_ptr(std::make_shared<BSERBoolean>(value)) {}
BSER::Array BSER::arrayValue() { return m_ptr->arrayValue(); }
BSER::Object BSER::objectValue() { return m_ptr->objectValue(); }
std::string BSER::stringValue() { return m_ptr->stringValue(); }
int64_t BSER::intValue() { return m_ptr->intValue(); }
double BSER::doubleValue() { return m_ptr->doubleValue(); }
bool BSER::boolValue() { return m_ptr->boolValue(); }
void BSER::encode(std::ostream &oss) {
m_ptr->encode(oss);
}
int64_t BSER::decodeLength(std::istream &iss) {
char pdu[2];
if (!iss.read(pdu, 2) || pdu[0] != 0 || pdu[1] != 1) {
throw std::runtime_error("Invalid BSER");
}
return BSERInteger(iss).intValue();
}
std::string BSER::encode() {
std::ostringstream oss(std::ios_base::binary);
encode(oss);
std::ostringstream res(std::ios_base::binary);
res.write("\x00\x01", 2);
BSERInteger(oss.str().size()).encode(res);
res << oss.str();
return res.str();
}

69
node_modules/@parcel/watcher/src/watchman/BSER.hh generated vendored Normal file
View File

@ -0,0 +1,69 @@
#ifndef BSER_H
#define BSER_H
#include <string>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <memory>
enum BSERType {
BSER_ARRAY = 0x00,
BSER_OBJECT = 0x01,
BSER_STRING = 0x02,
BSER_INT8 = 0x03,
BSER_INT16 = 0x04,
BSER_INT32 = 0x05,
BSER_INT64 = 0x06,
BSER_REAL = 0x07,
BSER_BOOL_TRUE = 0x08,
BSER_BOOL_FALSE = 0x09,
BSER_NULL = 0x0a,
BSER_TEMPLATE = 0x0b
};
class BSERValue;
class BSER {
public:
typedef std::vector<BSER> Array;
typedef std::unordered_map<std::string, BSER> Object;
BSER();
BSER(BSER::Array value);
BSER(BSER::Object value);
BSER(std::string value);
BSER(const char *value);
BSER(int64_t value);
BSER(double value);
BSER(bool value);
BSER(std::istream &iss);
BSER::Array arrayValue();
BSER::Object objectValue();
std::string stringValue();
int64_t intValue();
double doubleValue();
bool boolValue();
void encode(std::ostream &oss);
static int64_t decodeLength(std::istream &iss);
std::string encode();
private:
std::shared_ptr<BSERValue> m_ptr;
};
class BSERValue {
protected:
friend class BSER;
virtual BSER::Array arrayValue() { return BSER::Array(); }
virtual BSER::Object objectValue() { return BSER::Object(); }
virtual std::string stringValue() { return std::string(); }
virtual int64_t intValue() { return 0; }
virtual double doubleValue() { return 0; }
virtual bool boolValue() { return false; }
virtual void encode(std::ostream &oss) {}
virtual ~BSERValue() {}
};
#endif

175
node_modules/@parcel/watcher/src/watchman/IPC.hh generated vendored Normal file
View File

@ -0,0 +1,175 @@
#ifndef IPC_H
#define IPC_H
#include <string>
#include <stdlib.h>
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#else
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#endif
class IPC {
public:
IPC(std::string path) {
mStopped = false;
#ifdef _WIN32
while (true) {
mPipe = CreateFile(
path.data(), // pipe name
GENERIC_READ | GENERIC_WRITE, // read and write access
0, // no sharing
NULL, // default security attributes
OPEN_EXISTING, // opens existing pipe
FILE_FLAG_OVERLAPPED, // attributes
NULL // no template file
);
if (mPipe != INVALID_HANDLE_VALUE) {
break;
}
if (GetLastError() != ERROR_PIPE_BUSY) {
throw std::runtime_error("Could not open pipe");
}
// Wait for pipe to become available if it is busy
if (!WaitNamedPipe(path.data(), 30000)) {
throw std::runtime_error("Error waiting for pipe");
}
}
mReader = CreateEvent(NULL, true, false, NULL);
mWriter = CreateEvent(NULL, true, false, NULL);
#else
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path) - 1);
mSock = socket(AF_UNIX, SOCK_STREAM, 0);
if (connect(mSock, (struct sockaddr *) &addr, sizeof(struct sockaddr_un))) {
throw std::runtime_error("Error connecting to socket");
}
#endif
}
~IPC() {
mStopped = true;
#ifdef _WIN32
CancelIo(mPipe);
CloseHandle(mPipe);
CloseHandle(mReader);
CloseHandle(mWriter);
#else
shutdown(mSock, SHUT_RDWR);
#endif
}
void write(std::string buf) {
#ifdef _WIN32
OVERLAPPED overlapped;
overlapped.hEvent = mWriter;
bool success = WriteFile(
mPipe, // pipe handle
buf.data(), // message
buf.size(), // message length
NULL, // bytes written
&overlapped // overlapped
);
if (mStopped) {
return;
}
if (!success) {
if (GetLastError() != ERROR_IO_PENDING) {
throw std::runtime_error("Write error");
}
}
DWORD written;
success = GetOverlappedResult(mPipe, &overlapped, &written, true);
if (!success) {
throw std::runtime_error("GetOverlappedResult failed");
}
if (written != buf.size()) {
throw std::runtime_error("Wrong number of bytes written");
}
#else
int r = 0;
for (unsigned int i = 0; i != buf.size(); i += r) {
r = ::write(mSock, &buf[i], buf.size() - i);
if (r == -1) {
if (errno == EAGAIN) {
r = 0;
} else if (mStopped) {
return;
} else {
throw std::runtime_error("Write error");
}
}
}
#endif
}
int read(char *buf, size_t len) {
#ifdef _WIN32
OVERLAPPED overlapped;
overlapped.hEvent = mReader;
bool success = ReadFile(
mPipe, // pipe handle
buf, // buffer to receive reply
len, // size of buffer
NULL, // number of bytes read
&overlapped // overlapped
);
if (!success && !mStopped) {
if (GetLastError() != ERROR_IO_PENDING) {
throw std::runtime_error("Read error");
}
}
DWORD read = 0;
success = GetOverlappedResult(mPipe, &overlapped, &read, true);
if (!success && !mStopped) {
throw std::runtime_error("GetOverlappedResult failed");
}
return read;
#else
int r = ::read(mSock, buf, len);
if (r == 0 && !mStopped) {
throw std::runtime_error("Socket ended unexpectedly");
}
if (r < 0) {
if (mStopped) {
return 0;
}
throw std::runtime_error(strerror(errno));
}
return r;
#endif
}
private:
bool mStopped;
#ifdef _WIN32
HANDLE mPipe;
HANDLE mReader;
HANDLE mWriter;
#else
int mSock;
#endif
};
#endif

View File

@ -0,0 +1,338 @@
#include <string>
#include <fstream>
#include <stdlib.h>
#include <algorithm>
#include "../DirTree.hh"
#include "../Event.hh"
#include "./BSER.hh"
#include "./WatchmanBackend.hh"
#ifdef _WIN32
#include "../windows/win_utils.hh"
#define S_ISDIR(mode) ((mode & _S_IFDIR) == _S_IFDIR)
#define popen _popen
#define pclose _pclose
#else
#include <sys/stat.h>
#define normalizePath(dir) dir
#endif
template<typename T>
BSER readBSER(T &&do_read) {
std::stringstream oss;
char buffer[256];
int r;
int64_t len = -1;
do {
// Start by reading a minimal amount of data in order to decode the length.
// After that, attempt to read the remaining length, up to the buffer size.
r = do_read(buffer, len == -1 ? 20 : (len < 256 ? len : 256));
oss << std::string(buffer, r);
if (len == -1) {
uint64_t l = BSER::decodeLength(oss);
len = l + oss.tellg();
}
len -= r;
} while (len > 0);
return BSER(oss);
}
std::string getSockPath() {
auto var = getenv("WATCHMAN_SOCK");
if (var && *var) {
return std::string(var);
}
FILE *fp = popen("watchman --output-encoding=bser get-sockname", "r");
if (fp == NULL || errno == ECHILD) {
throw std::runtime_error("Failed to execute watchman");
}
BSER b = readBSER([fp] (char *buf, size_t len) {
return fread(buf, sizeof(char), len, fp);
});
pclose(fp);
auto objValue = b.objectValue();
auto foundSockname = objValue.find("sockname");
if (foundSockname == objValue.end()) {
throw std::runtime_error("sockname not found");
}
return foundSockname->second.stringValue();
}
std::unique_ptr<IPC> watchmanConnect() {
std::string path = getSockPath();
return std::unique_ptr<IPC>(new IPC(path));
}
BSER watchmanRead(IPC *ipc) {
return readBSER([ipc] (char *buf, size_t len) {
return ipc->read(buf, len);
});
}
BSER::Object WatchmanBackend::watchmanRequest(BSER b) {
std::string cmd = b.encode();
mIPC->write(cmd);
mRequestSignal.notify();
mResponseSignal.wait();
mResponseSignal.reset();
if (!mError.empty()) {
std::runtime_error err = std::runtime_error(mError);
mError = std::string();
throw err;
}
return mResponse;
}
void WatchmanBackend::watchmanWatch(std::string dir) {
std::vector<BSER> cmd;
cmd.push_back("watch");
cmd.push_back(normalizePath(dir));
watchmanRequest(cmd);
}
bool WatchmanBackend::checkAvailable() {
try {
watchmanConnect();
return true;
} catch (std::exception &err) {
return false;
}
}
void handleFiles(WatcherRef watcher, BSER::Object obj) {
auto found = obj.find("files");
if (found == obj.end()) {
throw WatcherError("Error reading changes from watchman", watcher);
}
auto files = found->second.arrayValue();
for (auto it = files.begin(); it != files.end(); it++) {
auto file = it->objectValue();
auto name = file.find("name")->second.stringValue();
#ifdef _WIN32
std::replace(name.begin(), name.end(), '/', '\\');
#endif
auto mode = file.find("mode")->second.intValue();
auto isNew = file.find("new")->second.boolValue();
auto exists = file.find("exists")->second.boolValue();
auto path = watcher->mDir + DIR_SEP + name;
if (watcher->isIgnored(path)) {
continue;
}
if (isNew && exists) {
watcher->mEvents.create(path);
} else if (exists && !S_ISDIR(mode)) {
watcher->mEvents.update(path);
} else if (!isNew && !exists) {
watcher->mEvents.remove(path);
}
}
}
void WatchmanBackend::handleSubscription(BSER::Object obj) {
std::unique_lock<std::mutex> lock(mMutex);
auto subscription = obj.find("subscription")->second.stringValue();
auto it = mSubscriptions.find(subscription);
if (it == mSubscriptions.end()) {
return;
}
auto watcher = it->second;
try {
handleFiles(watcher, obj);
watcher->notify();
} catch (WatcherError &err) {
handleWatcherError(err);
}
}
void WatchmanBackend::start() {
mIPC = watchmanConnect();
notifyStarted();
while (true) {
// If there are no subscriptions we are reading, wait for a request.
if (mSubscriptions.size() == 0) {
mRequestSignal.wait();
mRequestSignal.reset();
}
// Break out of loop if we are stopped.
if (mStopped) {
break;
}
// Attempt to read from the socket.
// If there is an error and we are stopped, break.
BSER b;
try {
b = watchmanRead(&*mIPC);
} catch (std::exception &err) {
if (mStopped) {
break;
} else if (mResponseSignal.isWaiting()) {
mError = err.what();
mResponseSignal.notify();
} else {
// Throwing causes the backend to be destroyed, but we never reach the code below to notify the signal
mEndedSignal.notify();
throw;
}
}
auto obj = b.objectValue();
auto error = obj.find("error");
if (error != obj.end()) {
mError = error->second.stringValue();
mResponseSignal.notify();
continue;
}
// If this message is for a subscription, handle it, otherwise notify the request.
auto subscription = obj.find("subscription");
if (subscription != obj.end()) {
handleSubscription(obj);
} else {
mResponse = obj;
mResponseSignal.notify();
}
}
mEndedSignal.notify();
}
WatchmanBackend::~WatchmanBackend() {
// Mark the watcher as stopped, close the socket, and trigger the lock.
// This will cause the read loop to be broken and the thread to exit.
mStopped = true;
mIPC.reset();
mRequestSignal.notify();
// If not ended yet, wait.
mEndedSignal.wait();
}
std::string WatchmanBackend::clock(WatcherRef watcher) {
BSER::Array cmd;
cmd.push_back("clock");
cmd.push_back(normalizePath(watcher->mDir));
BSER::Object obj = watchmanRequest(cmd);
auto found = obj.find("clock");
if (found == obj.end()) {
throw WatcherError("Error reading clock from watchman", watcher);
}
return found->second.stringValue();
}
void WatchmanBackend::writeSnapshot(WatcherRef watcher, std::string *snapshotPath) {
std::unique_lock<std::mutex> lock(mMutex);
watchmanWatch(watcher->mDir);
std::ofstream ofs(*snapshotPath);
ofs << clock(watcher);
}
void WatchmanBackend::getEventsSince(WatcherRef watcher, std::string *snapshotPath) {
std::unique_lock<std::mutex> lock(mMutex);
std::ifstream ifs(*snapshotPath);
if (ifs.fail()) {
return;
}
watchmanWatch(watcher->mDir);
std::string clock;
ifs >> clock;
BSER::Array cmd;
cmd.push_back("since");
cmd.push_back(normalizePath(watcher->mDir));
cmd.push_back(clock);
BSER::Object obj = watchmanRequest(cmd);
handleFiles(watcher, obj);
}
std::string getId(WatcherRef watcher) {
std::ostringstream id;
id << "parcel-";
id << static_cast<void*>(watcher.get());
return id.str();
}
// This function is called by Backend::watch which takes a lock on mMutex
void WatchmanBackend::subscribe(WatcherRef watcher) {
watchmanWatch(watcher->mDir);
std::string id = getId(watcher);
BSER::Array cmd;
cmd.push_back("subscribe");
cmd.push_back(normalizePath(watcher->mDir));
cmd.push_back(id);
BSER::Array fields;
fields.push_back("name");
fields.push_back("mode");
fields.push_back("exists");
fields.push_back("new");
BSER::Object opts;
opts.emplace("fields", fields);
opts.emplace("since", clock(watcher));
if (watcher->mIgnorePaths.size() > 0) {
BSER::Array ignore;
BSER::Array anyOf;
anyOf.push_back("anyof");
for (auto it = watcher->mIgnorePaths.begin(); it != watcher->mIgnorePaths.end(); it++) {
std::string pathStart = watcher->mDir + DIR_SEP;
if (it->rfind(pathStart, 0) == 0) {
auto relative = it->substr(pathStart.size());
BSER::Array dirname;
dirname.push_back("dirname");
dirname.push_back(relative);
anyOf.push_back(dirname);
}
}
ignore.push_back("not");
ignore.push_back(anyOf);
opts.emplace("expression", ignore);
}
cmd.push_back(opts);
watchmanRequest(cmd);
mSubscriptions.emplace(id, watcher);
mRequestSignal.notify();
}
// This function is called by Backend::unwatch which takes a lock on mMutex
void WatchmanBackend::unsubscribe(WatcherRef watcher) {
std::string id = getId(watcher);
auto erased = mSubscriptions.erase(id);
if (erased) {
BSER::Array cmd;
cmd.push_back("unsubscribe");
cmd.push_back(normalizePath(watcher->mDir));
cmd.push_back(id);
watchmanRequest(cmd);
}
}

View File

@ -0,0 +1,35 @@
#ifndef WATCHMAN_H
#define WATCHMAN_H
#include "../Backend.hh"
#include "./BSER.hh"
#include "../Signal.hh"
#include "./IPC.hh"
class WatchmanBackend : public Backend {
public:
static bool checkAvailable();
void start() override;
WatchmanBackend() : mStopped(false) {};
~WatchmanBackend();
void writeSnapshot(WatcherRef watcher, std::string *snapshotPath) override;
void getEventsSince(WatcherRef watcher, std::string *snapshotPath) override;
void subscribe(WatcherRef watcher) override;
void unsubscribe(WatcherRef watcher) override;
private:
std::unique_ptr<IPC> mIPC;
Signal mRequestSignal;
Signal mResponseSignal;
BSER::Object mResponse;
std::string mError;
std::unordered_map<std::string, WatcherRef> mSubscriptions;
bool mStopped;
Signal mEndedSignal;
std::string clock(WatcherRef watcher);
void watchmanWatch(std::string dir);
BSER::Object watchmanRequest(BSER cmd);
void handleSubscription(BSER::Object obj);
};
#endif

View File

@ -0,0 +1,282 @@
#include <string>
#include <stack>
#include "../DirTree.hh"
#include "../shared/BruteForceBackend.hh"
#include "./WindowsBackend.hh"
#include "./win_utils.hh"
#define DEFAULT_BUF_SIZE 1024 * 1024
#define NETWORK_BUF_SIZE 64 * 1024
#define CONVERT_TIME(ft) ULARGE_INTEGER{ft.dwLowDateTime, ft.dwHighDateTime}.QuadPart
void BruteForceBackend::readTree(WatcherRef watcher, std::shared_ptr<DirTree> tree) {
std::stack<std::string> directories;
directories.push(watcher->mDir);
while (!directories.empty()) {
HANDLE hFind = INVALID_HANDLE_VALUE;
std::string path = directories.top();
std::string spec = path + "\\*";
directories.pop();
WIN32_FIND_DATA ffd;
hFind = FindFirstFile(spec.c_str(), &ffd);
if (hFind == INVALID_HANDLE_VALUE) {
if (path == watcher->mDir) {
FindClose(hFind);
throw WatcherError("Error opening directory", watcher);
}
tree->remove(path);
continue;
}
do {
if (strcmp(ffd.cFileName, ".") != 0 && strcmp(ffd.cFileName, "..") != 0) {
std::string fullPath = path + "\\" + ffd.cFileName;
if (watcher->isIgnored(fullPath)) {
continue;
}
tree->add(fullPath, CONVERT_TIME(ffd.ftLastWriteTime), ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
directories.push(fullPath);
}
}
} while (FindNextFile(hFind, &ffd) != 0);
FindClose(hFind);
}
}
void WindowsBackend::start() {
mRunning = true;
notifyStarted();
while (mRunning) {
SleepEx(INFINITE, true);
}
}
WindowsBackend::~WindowsBackend() {
// Mark as stopped, and queue a noop function in the thread to break the loop
mRunning = false;
QueueUserAPC([](__in ULONG_PTR) {}, mThread.native_handle(), (ULONG_PTR)this);
}
class Subscription: public WatcherState {
public:
Subscription(WindowsBackend *backend, WatcherRef watcher, std::shared_ptr<DirTree> tree) {
mRunning = true;
mBackend = backend;
mWatcher = watcher;
mTree = tree;
ZeroMemory(&mOverlapped, sizeof(OVERLAPPED));
mOverlapped.hEvent = this;
mReadBuffer.resize(DEFAULT_BUF_SIZE);
mWriteBuffer.resize(DEFAULT_BUF_SIZE);
mDirectoryHandle = CreateFileW(
utf8ToUtf16(watcher->mDir).data(),
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL
);
if (mDirectoryHandle == INVALID_HANDLE_VALUE) {
throw WatcherError("Invalid handle", mWatcher);
}
// Ensure that the path is a directory
BY_HANDLE_FILE_INFORMATION info;
bool success = GetFileInformationByHandle(
mDirectoryHandle,
&info
);
if (!success) {
throw WatcherError("Could not get file information", mWatcher);
}
if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
throw WatcherError("Not a directory", mWatcher);
}
}
virtual ~Subscription() {
stop();
}
void run() {
try {
poll();
} catch (WatcherError &err) {
mBackend->handleWatcherError(err);
}
}
void stop() {
if (mRunning) {
mRunning = false;
CancelIo(mDirectoryHandle);
CloseHandle(mDirectoryHandle);
}
}
void poll() {
if (!mRunning) {
return;
}
// Asynchronously wait for changes.
int success = ReadDirectoryChangesW(
mDirectoryHandle,
mWriteBuffer.data(),
static_cast<DWORD>(mWriteBuffer.size()),
TRUE, // recursive
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES
| FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE,
NULL,
&mOverlapped,
[](DWORD errorCode, DWORD numBytes, LPOVERLAPPED overlapped) {
auto subscription = reinterpret_cast<Subscription *>(overlapped->hEvent);
try {
subscription->processEvents(errorCode);
} catch (WatcherError &err) {
subscription->mBackend->handleWatcherError(err);
}
}
);
if (!success) {
throw WatcherError("Failed to read changes", mWatcher);
}
}
void processEvents(DWORD errorCode) {
if (!mRunning) {
return;
}
switch (errorCode) {
case ERROR_OPERATION_ABORTED:
return;
case ERROR_INVALID_PARAMETER:
// resize buffers to network size (64kb), and try again
mReadBuffer.resize(NETWORK_BUF_SIZE);
mWriteBuffer.resize(NETWORK_BUF_SIZE);
poll();
return;
case ERROR_NOTIFY_ENUM_DIR:
throw WatcherError("Buffer overflow. Some events may have been lost.", mWatcher);
case ERROR_ACCESS_DENIED: {
// This can happen if the watched directory is deleted. Check if that is the case,
// and if so emit a delete event. Otherwise, fall through to default error case.
DWORD attrs = GetFileAttributesW(utf8ToUtf16(mWatcher->mDir).data());
bool isDir = attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY);
if (!isDir) {
mWatcher->mEvents.remove(mWatcher->mDir);
mTree->remove(mWatcher->mDir);
mWatcher->notify();
stop();
return;
}
}
default:
if (errorCode != ERROR_SUCCESS) {
throw WatcherError("Unknown error", mWatcher);
}
}
// Swap read and write buffers, and poll again
std::swap(mWriteBuffer, mReadBuffer);
poll();
// Read change events
BYTE *base = mReadBuffer.data();
while (true) {
PFILE_NOTIFY_INFORMATION info = (PFILE_NOTIFY_INFORMATION)base;
processEvent(info);
if (info->NextEntryOffset == 0) {
break;
}
base += info->NextEntryOffset;
}
mWatcher->notify();
}
void processEvent(PFILE_NOTIFY_INFORMATION info) {
std::string path = mWatcher->mDir + "\\" + utf16ToUtf8(info->FileName, info->FileNameLength / sizeof(WCHAR));
if (mWatcher->isIgnored(path)) {
return;
}
switch (info->Action) {
case FILE_ACTION_ADDED:
case FILE_ACTION_RENAMED_NEW_NAME: {
WIN32_FILE_ATTRIBUTE_DATA data;
if (GetFileAttributesExW(utf8ToUtf16(path).data(), GetFileExInfoStandard, &data)) {
mWatcher->mEvents.create(path);
mTree->add(path, CONVERT_TIME(data.ftLastWriteTime), data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
}
break;
}
case FILE_ACTION_MODIFIED: {
WIN32_FILE_ATTRIBUTE_DATA data;
if (GetFileAttributesExW(utf8ToUtf16(path).data(), GetFileExInfoStandard, &data)) {
mTree->update(path, CONVERT_TIME(data.ftLastWriteTime));
if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
mWatcher->mEvents.update(path);
}
}
break;
}
case FILE_ACTION_REMOVED:
case FILE_ACTION_RENAMED_OLD_NAME:
mWatcher->mEvents.remove(path);
mTree->remove(path);
break;
}
}
private:
WindowsBackend *mBackend;
std::shared_ptr<Watcher> mWatcher;
std::shared_ptr<DirTree> mTree;
bool mRunning;
HANDLE mDirectoryHandle;
std::vector<BYTE> mReadBuffer;
std::vector<BYTE> mWriteBuffer;
OVERLAPPED mOverlapped;
};
// This function is called by Backend::watch which takes a lock on mMutex
void WindowsBackend::subscribe(WatcherRef watcher) {
// Create a subscription for this watcher
auto sub = std::make_shared<Subscription>(this, watcher, getTree(watcher, false));
watcher->state = sub;
// Queue polling for this subscription in the correct thread.
bool success = QueueUserAPC([](__in ULONG_PTR ptr) {
Subscription *sub = (Subscription *)ptr;
sub->run();
}, mThread.native_handle(), (ULONG_PTR)sub.get());
if (!success) {
throw std::runtime_error("Unable to queue APC");
}
}
// This function is called by Backend::unwatch which takes a lock on mMutex
void WindowsBackend::unsubscribe(WatcherRef watcher) {
watcher->state = nullptr;
}

View File

@ -0,0 +1,18 @@
#ifndef WINDOWS_H
#define WINDOWS_H
#include <winsock2.h>
#include <windows.h>
#include "../shared/BruteForceBackend.hh"
class WindowsBackend : public BruteForceBackend {
public:
void start() override;
~WindowsBackend();
void subscribe(WatcherRef watcher) override;
void unsubscribe(WatcherRef watcher) override;
private:
bool mRunning;
};
#endif

44
node_modules/@parcel/watcher/src/windows/win_utils.cc generated vendored Normal file
View File

@ -0,0 +1,44 @@
#include "./win_utils.hh"
std::wstring utf8ToUtf16(std::string input) {
unsigned int len = MultiByteToWideChar(CP_UTF8, 0, input.c_str(), -1, NULL, 0);
WCHAR *output = new WCHAR[len];
MultiByteToWideChar(CP_UTF8, 0, input.c_str(), -1, output, len);
std::wstring res(output);
delete output;
return res;
}
std::string utf16ToUtf8(const WCHAR *input, size_t length) {
unsigned int len = WideCharToMultiByte(CP_UTF8, 0, input, length, NULL, 0, NULL, NULL);
char *output = new char[len + 1];
WideCharToMultiByte(CP_UTF8, 0, input, length, output, len, NULL, NULL);
output[len] = '\0';
std::string res(output);
delete output;
return res;
}
std::string normalizePath(std::string path) {
// Prevent truncation to MAX_PATH characters by adding the \\?\ prefix
std::wstring p = utf8ToUtf16("\\\\?\\" + path);
// Get the required length for the output
unsigned int len = GetLongPathNameW(p.data(), NULL, 0);
if (!len) {
return path;
}
// Allocate output array and get long path
WCHAR *output = new WCHAR[len];
len = GetLongPathNameW(p.data(), output, len);
if (!len) {
delete output;
return path;
}
// Convert back to utf8
std::string res = utf16ToUtf8(output + 4, len - 4);
delete output;
return res;
}

11
node_modules/@parcel/watcher/src/windows/win_utils.hh generated vendored Normal file
View File

@ -0,0 +1,11 @@
#ifndef WIN_UTILS_H
#define WIN_UTILS_H
#include <string>
#include <windows.h>
std::wstring utf8ToUtf16(std::string input);
std::string utf16ToUtf8(const WCHAR *input, size_t length);
std::string normalizePath(std::string path);
#endif

77
node_modules/@parcel/watcher/wrapper.js generated vendored Normal file
View File

@ -0,0 +1,77 @@
const path = require('path');
const micromatch = require('micromatch');
const isGlob = require('is-glob');
function normalizeOptions(dir, opts = {}) {
const { ignore, ...rest } = opts;
if (Array.isArray(ignore)) {
opts = { ...rest };
for (const value of ignore) {
if (isGlob(value)) {
if (!opts.ignoreGlobs) {
opts.ignoreGlobs = [];
}
const regex = micromatch.makeRe(value, {
// We set `dot: true` to workaround an issue with the
// regular expression on Linux where the resulting
// negative lookahead `(?!(\\/|^)` was never matching
// in some cases. See also https://bit.ly/3UZlQDm
dot: true,
// C++ does not support lookbehind regex patterns, they
// were only added later to JavaScript engines
// (https://bit.ly/3V7S6UL)
lookbehinds: false
});
opts.ignoreGlobs.push(regex.source);
} else {
if (!opts.ignorePaths) {
opts.ignorePaths = [];
}
opts.ignorePaths.push(path.resolve(dir, value));
}
}
}
return opts;
}
exports.createWrapper = (binding) => {
return {
writeSnapshot(dir, snapshot, opts) {
return binding.writeSnapshot(
path.resolve(dir),
path.resolve(snapshot),
normalizeOptions(dir, opts),
);
},
getEventsSince(dir, snapshot, opts) {
return binding.getEventsSince(
path.resolve(dir),
path.resolve(snapshot),
normalizeOptions(dir, opts),
);
},
async subscribe(dir, fn, opts) {
dir = path.resolve(dir);
opts = normalizeOptions(dir, opts);
await binding.subscribe(dir, fn, opts);
return {
unsubscribe() {
return binding.unsubscribe(dir, fn, opts);
},
};
},
unsubscribe(dir, fn, opts) {
return binding.unsubscribe(
path.resolve(dir),
fn,
normalizeOptions(dir, opts),
);
}
};
};

37
node_modules/ansi-regex/index.d.ts generated vendored Normal file
View File

@ -0,0 +1,37 @@
declare namespace ansiRegex {
interface Options {
/**
Match only the first ANSI escape.
@default false
*/
onlyFirst: boolean;
}
}
/**
Regular expression for matching ANSI escape codes.
@example
```
import ansiRegex = require('ansi-regex');
ansiRegex().test('\u001B[4mcake\u001B[0m');
//=> true
ansiRegex().test('cake');
//=> false
'\u001B[4mcake\u001B[0m'.match(ansiRegex());
//=> ['\u001B[4m', '\u001B[0m']
'\u001B[4mcake\u001B[0m'.match(ansiRegex({onlyFirst: true}));
//=> ['\u001B[4m']
'\u001B]8;;https://github.com\u0007click\u001B]8;;\u0007'.match(ansiRegex());
//=> ['\u001B]8;;https://github.com\u0007', '\u001B]8;;\u0007']
```
*/
declare function ansiRegex(options?: ansiRegex.Options): RegExp;
export = ansiRegex;

10
node_modules/ansi-regex/index.js generated vendored Normal file
View File

@ -0,0 +1,10 @@
'use strict';
module.exports = ({onlyFirst = false} = {}) => {
const pattern = [
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'
].join('|');
return new RegExp(pattern, onlyFirst ? undefined : 'g');
};

9
node_modules/ansi-regex/license generated vendored Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

55
node_modules/ansi-regex/package.json generated vendored Normal file
View File

@ -0,0 +1,55 @@
{
"name": "ansi-regex",
"version": "5.0.1",
"description": "Regular expression for matching ANSI escape codes",
"license": "MIT",
"repository": "chalk/ansi-regex",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"engines": {
"node": ">=8"
},
"scripts": {
"test": "xo && ava && tsd",
"view-supported": "node fixtures/view-codes.js"
},
"files": [
"index.js",
"index.d.ts"
],
"keywords": [
"ansi",
"styles",
"color",
"colour",
"colors",
"terminal",
"console",
"cli",
"string",
"tty",
"escape",
"formatting",
"rgb",
"256",
"shell",
"xterm",
"command-line",
"text",
"regex",
"regexp",
"re",
"match",
"test",
"find",
"pattern"
],
"devDependencies": {
"ava": "^2.4.0",
"tsd": "^0.9.0",
"xo": "^0.25.3"
}
}

78
node_modules/ansi-regex/readme.md generated vendored Normal file
View File

@ -0,0 +1,78 @@
# ansi-regex
> Regular expression for matching [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code)
## Install
```
$ npm install ansi-regex
```
## Usage
```js
const ansiRegex = require('ansi-regex');
ansiRegex().test('\u001B[4mcake\u001B[0m');
//=> true
ansiRegex().test('cake');
//=> false
'\u001B[4mcake\u001B[0m'.match(ansiRegex());
//=> ['\u001B[4m', '\u001B[0m']
'\u001B[4mcake\u001B[0m'.match(ansiRegex({onlyFirst: true}));
//=> ['\u001B[4m']
'\u001B]8;;https://github.com\u0007click\u001B]8;;\u0007'.match(ansiRegex());
//=> ['\u001B]8;;https://github.com\u0007', '\u001B]8;;\u0007']
```
## API
### ansiRegex(options?)
Returns a regex for matching ANSI escape codes.
#### options
Type: `object`
##### onlyFirst
Type: `boolean`<br>
Default: `false` *(Matches any ANSI escape codes in a string)*
Match only the first ANSI escape.
## FAQ
### Why do you test for codes not in the ECMA 48 standard?
Some of the codes we run as a test are codes that we acquired finding various lists of non-standard or manufacturer specific codes. We test for both standard and non-standard codes, as most of them follow the same or similar format and can be safely matched in strings without the risk of removing actual string content. There are a few non-standard control codes that do not follow the traditional format (i.e. they end in numbers) thus forcing us to exclude them from the test because we cannot reliably match them.
On the historical side, those ECMA standards were established in the early 90's whereas the VT100, for example, was designed in the mid/late 70's. At that point in time, control codes were still pretty ungoverned and engineers used them for a multitude of things, namely to activate hardware ports that may have been proprietary. Somewhere else you see a similar 'anarchy' of codes is in the x86 architecture for processors; there are a ton of "interrupts" that can mean different things on certain brands of processors, most of which have been phased out.
## Maintainers
- [Sindre Sorhus](https://github.com/sindresorhus)
- [Josh Junon](https://github.com/qix-)
---
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/npm-ansi-regex?utm_source=npm-ansi-regex&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>

345
node_modules/ansi-styles/index.d.ts generated vendored Normal file
View File

@ -0,0 +1,345 @@
declare type CSSColor =
| 'aliceblue'
| 'antiquewhite'
| 'aqua'
| 'aquamarine'
| 'azure'
| 'beige'
| 'bisque'
| 'black'
| 'blanchedalmond'
| 'blue'
| 'blueviolet'
| 'brown'
| 'burlywood'
| 'cadetblue'
| 'chartreuse'
| 'chocolate'
| 'coral'
| 'cornflowerblue'
| 'cornsilk'
| 'crimson'
| 'cyan'
| 'darkblue'
| 'darkcyan'
| 'darkgoldenrod'
| 'darkgray'
| 'darkgreen'
| 'darkgrey'
| 'darkkhaki'
| 'darkmagenta'
| 'darkolivegreen'
| 'darkorange'
| 'darkorchid'
| 'darkred'
| 'darksalmon'
| 'darkseagreen'
| 'darkslateblue'
| 'darkslategray'
| 'darkslategrey'
| 'darkturquoise'
| 'darkviolet'
| 'deeppink'
| 'deepskyblue'
| 'dimgray'
| 'dimgrey'
| 'dodgerblue'
| 'firebrick'
| 'floralwhite'
| 'forestgreen'
| 'fuchsia'
| 'gainsboro'
| 'ghostwhite'
| 'gold'
| 'goldenrod'
| 'gray'
| 'green'
| 'greenyellow'
| 'grey'
| 'honeydew'
| 'hotpink'
| 'indianred'
| 'indigo'
| 'ivory'
| 'khaki'
| 'lavender'
| 'lavenderblush'
| 'lawngreen'
| 'lemonchiffon'
| 'lightblue'
| 'lightcoral'
| 'lightcyan'
| 'lightgoldenrodyellow'
| 'lightgray'
| 'lightgreen'
| 'lightgrey'
| 'lightpink'
| 'lightsalmon'
| 'lightseagreen'
| 'lightskyblue'
| 'lightslategray'
| 'lightslategrey'
| 'lightsteelblue'
| 'lightyellow'
| 'lime'
| 'limegreen'
| 'linen'
| 'magenta'
| 'maroon'
| 'mediumaquamarine'
| 'mediumblue'
| 'mediumorchid'
| 'mediumpurple'
| 'mediumseagreen'
| 'mediumslateblue'
| 'mediumspringgreen'
| 'mediumturquoise'
| 'mediumvioletred'
| 'midnightblue'
| 'mintcream'
| 'mistyrose'
| 'moccasin'
| 'navajowhite'
| 'navy'
| 'oldlace'
| 'olive'
| 'olivedrab'
| 'orange'
| 'orangered'
| 'orchid'
| 'palegoldenrod'
| 'palegreen'
| 'paleturquoise'
| 'palevioletred'
| 'papayawhip'
| 'peachpuff'
| 'peru'
| 'pink'
| 'plum'
| 'powderblue'
| 'purple'
| 'rebeccapurple'
| 'red'
| 'rosybrown'
| 'royalblue'
| 'saddlebrown'
| 'salmon'
| 'sandybrown'
| 'seagreen'
| 'seashell'
| 'sienna'
| 'silver'
| 'skyblue'
| 'slateblue'
| 'slategray'
| 'slategrey'
| 'snow'
| 'springgreen'
| 'steelblue'
| 'tan'
| 'teal'
| 'thistle'
| 'tomato'
| 'turquoise'
| 'violet'
| 'wheat'
| 'white'
| 'whitesmoke'
| 'yellow'
| 'yellowgreen';
declare namespace ansiStyles {
interface ColorConvert {
/**
The RGB color space.
@param red - (`0`-`255`)
@param green - (`0`-`255`)
@param blue - (`0`-`255`)
*/
rgb(red: number, green: number, blue: number): string;
/**
The RGB HEX color space.
@param hex - A hexadecimal string containing RGB data.
*/
hex(hex: string): string;
/**
@param keyword - A CSS color name.
*/
keyword(keyword: CSSColor): string;
/**
The HSL color space.
@param hue - (`0`-`360`)
@param saturation - (`0`-`100`)
@param lightness - (`0`-`100`)
*/
hsl(hue: number, saturation: number, lightness: number): string;
/**
The HSV color space.
@param hue - (`0`-`360`)
@param saturation - (`0`-`100`)
@param value - (`0`-`100`)
*/
hsv(hue: number, saturation: number, value: number): string;
/**
The HSV color space.
@param hue - (`0`-`360`)
@param whiteness - (`0`-`100`)
@param blackness - (`0`-`100`)
*/
hwb(hue: number, whiteness: number, blackness: number): string;
/**
Use a [4-bit unsigned number](https://en.wikipedia.org/wiki/ANSI_escape_code#3/4-bit) to set text color.
*/
ansi(ansi: number): string;
/**
Use an [8-bit unsigned number](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) to set text color.
*/
ansi256(ansi: number): string;
}
interface CSPair {
/**
The ANSI terminal control sequence for starting this style.
*/
readonly open: string;
/**
The ANSI terminal control sequence for ending this style.
*/
readonly close: string;
}
interface ColorBase {
readonly ansi: ColorConvert;
readonly ansi256: ColorConvert;
readonly ansi16m: ColorConvert;
/**
The ANSI terminal control sequence for ending this color.
*/
readonly close: string;
}
interface Modifier {
/**
Resets the current color chain.
*/
readonly reset: CSPair;
/**
Make text bold.
*/
readonly bold: CSPair;
/**
Emitting only a small amount of light.
*/
readonly dim: CSPair;
/**
Make text italic. (Not widely supported)
*/
readonly italic: CSPair;
/**
Make text underline. (Not widely supported)
*/
readonly underline: CSPair;
/**
Inverse background and foreground colors.
*/
readonly inverse: CSPair;
/**
Prints the text, but makes it invisible.
*/
readonly hidden: CSPair;
/**
Puts a horizontal line through the center of the text. (Not widely supported)
*/
readonly strikethrough: CSPair;
}
interface ForegroundColor {
readonly black: CSPair;
readonly red: CSPair;
readonly green: CSPair;
readonly yellow: CSPair;
readonly blue: CSPair;
readonly cyan: CSPair;
readonly magenta: CSPair;
readonly white: CSPair;
/**
Alias for `blackBright`.
*/
readonly gray: CSPair;
/**
Alias for `blackBright`.
*/
readonly grey: CSPair;
readonly blackBright: CSPair;
readonly redBright: CSPair;
readonly greenBright: CSPair;
readonly yellowBright: CSPair;
readonly blueBright: CSPair;
readonly cyanBright: CSPair;
readonly magentaBright: CSPair;
readonly whiteBright: CSPair;
}
interface BackgroundColor {
readonly bgBlack: CSPair;
readonly bgRed: CSPair;
readonly bgGreen: CSPair;
readonly bgYellow: CSPair;
readonly bgBlue: CSPair;
readonly bgCyan: CSPair;
readonly bgMagenta: CSPair;
readonly bgWhite: CSPair;
/**
Alias for `bgBlackBright`.
*/
readonly bgGray: CSPair;
/**
Alias for `bgBlackBright`.
*/
readonly bgGrey: CSPair;
readonly bgBlackBright: CSPair;
readonly bgRedBright: CSPair;
readonly bgGreenBright: CSPair;
readonly bgYellowBright: CSPair;
readonly bgBlueBright: CSPair;
readonly bgCyanBright: CSPair;
readonly bgMagentaBright: CSPair;
readonly bgWhiteBright: CSPair;
}
}
declare const ansiStyles: {
readonly modifier: ansiStyles.Modifier;
readonly color: ansiStyles.ForegroundColor & ansiStyles.ColorBase;
readonly bgColor: ansiStyles.BackgroundColor & ansiStyles.ColorBase;
readonly codes: ReadonlyMap<number, number>;
} & ansiStyles.BackgroundColor & ansiStyles.ForegroundColor & ansiStyles.Modifier;
export = ansiStyles;

163
node_modules/ansi-styles/index.js generated vendored Normal file
View File

@ -0,0 +1,163 @@
'use strict';
const wrapAnsi16 = (fn, offset) => (...args) => {
const code = fn(...args);
return `\u001B[${code + offset}m`;
};
const wrapAnsi256 = (fn, offset) => (...args) => {
const code = fn(...args);
return `\u001B[${38 + offset};5;${code}m`;
};
const wrapAnsi16m = (fn, offset) => (...args) => {
const rgb = fn(...args);
return `\u001B[${38 + offset};2;${rgb[0]};${rgb[1]};${rgb[2]}m`;
};
const ansi2ansi = n => n;
const rgb2rgb = (r, g, b) => [r, g, b];
const setLazyProperty = (object, property, get) => {
Object.defineProperty(object, property, {
get: () => {
const value = get();
Object.defineProperty(object, property, {
value,
enumerable: true,
configurable: true
});
return value;
},
enumerable: true,
configurable: true
});
};
/** @type {typeof import('color-convert')} */
let colorConvert;
const makeDynamicStyles = (wrap, targetSpace, identity, isBackground) => {
if (colorConvert === undefined) {
colorConvert = require('color-convert');
}
const offset = isBackground ? 10 : 0;
const styles = {};
for (const [sourceSpace, suite] of Object.entries(colorConvert)) {
const name = sourceSpace === 'ansi16' ? 'ansi' : sourceSpace;
if (sourceSpace === targetSpace) {
styles[name] = wrap(identity, offset);
} else if (typeof suite === 'object') {
styles[name] = wrap(suite[targetSpace], offset);
}
}
return styles;
};
function assembleStyles() {
const codes = new Map();
const styles = {
modifier: {
reset: [0, 0],
// 21 isn't widely supported and 22 does the same thing
bold: [1, 22],
dim: [2, 22],
italic: [3, 23],
underline: [4, 24],
inverse: [7, 27],
hidden: [8, 28],
strikethrough: [9, 29]
},
color: {
black: [30, 39],
red: [31, 39],
green: [32, 39],
yellow: [33, 39],
blue: [34, 39],
magenta: [35, 39],
cyan: [36, 39],
white: [37, 39],
// Bright color
blackBright: [90, 39],
redBright: [91, 39],
greenBright: [92, 39],
yellowBright: [93, 39],
blueBright: [94, 39],
magentaBright: [95, 39],
cyanBright: [96, 39],
whiteBright: [97, 39]
},
bgColor: {
bgBlack: [40, 49],
bgRed: [41, 49],
bgGreen: [42, 49],
bgYellow: [43, 49],
bgBlue: [44, 49],
bgMagenta: [45, 49],
bgCyan: [46, 49],
bgWhite: [47, 49],
// Bright color
bgBlackBright: [100, 49],
bgRedBright: [101, 49],
bgGreenBright: [102, 49],
bgYellowBright: [103, 49],
bgBlueBright: [104, 49],
bgMagentaBright: [105, 49],
bgCyanBright: [106, 49],
bgWhiteBright: [107, 49]
}
};
// Alias bright black as gray (and grey)
styles.color.gray = styles.color.blackBright;
styles.bgColor.bgGray = styles.bgColor.bgBlackBright;
styles.color.grey = styles.color.blackBright;
styles.bgColor.bgGrey = styles.bgColor.bgBlackBright;
for (const [groupName, group] of Object.entries(styles)) {
for (const [styleName, style] of Object.entries(group)) {
styles[styleName] = {
open: `\u001B[${style[0]}m`,
close: `\u001B[${style[1]}m`
};
group[styleName] = styles[styleName];
codes.set(style[0], style[1]);
}
Object.defineProperty(styles, groupName, {
value: group,
enumerable: false
});
}
Object.defineProperty(styles, 'codes', {
value: codes,
enumerable: false
});
styles.color.close = '\u001B[39m';
styles.bgColor.close = '\u001B[49m';
setLazyProperty(styles.color, 'ansi', () => makeDynamicStyles(wrapAnsi16, 'ansi16', ansi2ansi, false));
setLazyProperty(styles.color, 'ansi256', () => makeDynamicStyles(wrapAnsi256, 'ansi256', ansi2ansi, false));
setLazyProperty(styles.color, 'ansi16m', () => makeDynamicStyles(wrapAnsi16m, 'rgb', rgb2rgb, false));
setLazyProperty(styles.bgColor, 'ansi', () => makeDynamicStyles(wrapAnsi16, 'ansi16', ansi2ansi, true));
setLazyProperty(styles.bgColor, 'ansi256', () => makeDynamicStyles(wrapAnsi256, 'ansi256', ansi2ansi, true));
setLazyProperty(styles.bgColor, 'ansi16m', () => makeDynamicStyles(wrapAnsi16m, 'rgb', rgb2rgb, true));
return styles;
}
// Make the export immutable
Object.defineProperty(module, 'exports', {
enumerable: true,
get: assembleStyles
});

9
node_modules/ansi-styles/license generated vendored Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

56
node_modules/ansi-styles/package.json generated vendored Normal file
View File

@ -0,0 +1,56 @@
{
"name": "ansi-styles",
"version": "4.3.0",
"description": "ANSI escape codes for styling strings in the terminal",
"license": "MIT",
"repository": "chalk/ansi-styles",
"funding": "https://github.com/chalk/ansi-styles?sponsor=1",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"engines": {
"node": ">=8"
},
"scripts": {
"test": "xo && ava && tsd",
"screenshot": "svg-term --command='node screenshot' --out=screenshot.svg --padding=3 --width=55 --height=3 --at=1000 --no-cursor"
},
"files": [
"index.js",
"index.d.ts"
],
"keywords": [
"ansi",
"styles",
"color",
"colour",
"colors",
"terminal",
"console",
"cli",
"string",
"tty",
"escape",
"formatting",
"rgb",
"256",
"shell",
"xterm",
"log",
"logging",
"command-line",
"text"
],
"dependencies": {
"color-convert": "^2.0.1"
},
"devDependencies": {
"@types/color-convert": "^1.9.0",
"ava": "^2.3.0",
"svg-term-cli": "^2.1.1",
"tsd": "^0.11.0",
"xo": "^0.25.3"
}
}

152
node_modules/ansi-styles/readme.md generated vendored Normal file
View File

@ -0,0 +1,152 @@
# ansi-styles [![Build Status](https://travis-ci.org/chalk/ansi-styles.svg?branch=master)](https://travis-ci.org/chalk/ansi-styles)
> [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors_and_Styles) for styling strings in the terminal
You probably want the higher-level [chalk](https://github.com/chalk/chalk) module for styling your strings.
<img src="screenshot.svg" width="900">
## Install
```
$ npm install ansi-styles
```
## Usage
```js
const style = require('ansi-styles');
console.log(`${style.green.open}Hello world!${style.green.close}`);
// Color conversion between 16/256/truecolor
// NOTE: If conversion goes to 16 colors or 256 colors, the original color
// may be degraded to fit that color palette. This means terminals
// that do not support 16 million colors will best-match the
// original color.
console.log(style.bgColor.ansi.hsl(120, 80, 72) + 'Hello world!' + style.bgColor.close);
console.log(style.color.ansi256.rgb(199, 20, 250) + 'Hello world!' + style.color.close);
console.log(style.color.ansi16m.hex('#abcdef') + 'Hello world!' + style.color.close);
```
## API
Each style has an `open` and `close` property.
## Styles
### Modifiers
- `reset`
- `bold`
- `dim`
- `italic` *(Not widely supported)*
- `underline`
- `inverse`
- `hidden`
- `strikethrough` *(Not widely supported)*
### Colors
- `black`
- `red`
- `green`
- `yellow`
- `blue`
- `magenta`
- `cyan`
- `white`
- `blackBright` (alias: `gray`, `grey`)
- `redBright`
- `greenBright`
- `yellowBright`
- `blueBright`
- `magentaBright`
- `cyanBright`
- `whiteBright`
### Background colors
- `bgBlack`
- `bgRed`
- `bgGreen`
- `bgYellow`
- `bgBlue`
- `bgMagenta`
- `bgCyan`
- `bgWhite`
- `bgBlackBright` (alias: `bgGray`, `bgGrey`)
- `bgRedBright`
- `bgGreenBright`
- `bgYellowBright`
- `bgBlueBright`
- `bgMagentaBright`
- `bgCyanBright`
- `bgWhiteBright`
## Advanced usage
By default, you get a map of styles, but the styles are also available as groups. They are non-enumerable so they don't show up unless you access them explicitly. This makes it easier to expose only a subset in a higher-level module.
- `style.modifier`
- `style.color`
- `style.bgColor`
###### Example
```js
console.log(style.color.green.open);
```
Raw escape codes (i.e. without the CSI escape prefix `\u001B[` and render mode postfix `m`) are available under `style.codes`, which returns a `Map` with the open codes as keys and close codes as values.
###### Example
```js
console.log(style.codes.get(36));
//=> 39
```
## [256 / 16 million (TrueColor) support](https://gist.github.com/XVilka/8346728)
`ansi-styles` uses the [`color-convert`](https://github.com/Qix-/color-convert) package to allow for converting between various colors and ANSI escapes, with support for 256 and 16 million colors.
The following color spaces from `color-convert` are supported:
- `rgb`
- `hex`
- `keyword`
- `hsl`
- `hsv`
- `hwb`
- `ansi`
- `ansi256`
To use these, call the associated conversion function with the intended output, for example:
```js
style.color.ansi.rgb(100, 200, 15); // RGB to 16 color ansi foreground code
style.bgColor.ansi.rgb(100, 200, 15); // RGB to 16 color ansi background code
style.color.ansi256.hsl(120, 100, 60); // HSL to 256 color ansi foreground code
style.bgColor.ansi256.hsl(120, 100, 60); // HSL to 256 color ansi foreground code
style.color.ansi16m.hex('#C0FFEE'); // Hex (RGB) to 16 million color foreground code
style.bgColor.ansi16m.hex('#C0FFEE'); // Hex (RGB) to 16 million color background code
```
## Related
- [ansi-escapes](https://github.com/sindresorhus/ansi-escapes) - ANSI escape codes for manipulating the terminal
## Maintainers
- [Sindre Sorhus](https://github.com/sindresorhus)
- [Josh Junon](https://github.com/qix-)
## For enterprise
Available as part of the Tidelift Subscription.
The maintainers of `ansi-styles` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-ansi-styles?utm_source=npm-ansi-styles&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)

21
node_modules/braces/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-present, Jon Schlinkert.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

586
node_modules/braces/README.md generated vendored Normal file
View File

@ -0,0 +1,586 @@
# braces [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=W8YFZ425KND68) [![NPM version](https://img.shields.io/npm/v/braces.svg?style=flat)](https://www.npmjs.com/package/braces) [![NPM monthly downloads](https://img.shields.io/npm/dm/braces.svg?style=flat)](https://npmjs.org/package/braces) [![NPM total downloads](https://img.shields.io/npm/dt/braces.svg?style=flat)](https://npmjs.org/package/braces) [![Linux Build Status](https://img.shields.io/travis/micromatch/braces.svg?style=flat&label=Travis)](https://travis-ci.org/micromatch/braces)
> Bash-like brace expansion, implemented in JavaScript. Safer than other brace expansion libs, with complete support for the Bash 4.3 braces specification, without sacrificing speed.
Please consider following this project's author, [Jon Schlinkert](https://github.com/jonschlinkert), and consider starring the project to show your :heart: and support.
## Install
Install with [npm](https://www.npmjs.com/):
```sh
$ npm install --save braces
```
## v3.0.0 Released!!
See the [changelog](CHANGELOG.md) for details.
## Why use braces?
Brace patterns make globs more powerful by adding the ability to match specific ranges and sequences of characters.
- **Accurate** - complete support for the [Bash 4.3 Brace Expansion](www.gnu.org/software/bash/) specification (passes all of the Bash braces tests)
- **[fast and performant](#benchmarks)** - Starts fast, runs fast and [scales well](#performance) as patterns increase in complexity.
- **Organized code base** - The parser and compiler are easy to maintain and update when edge cases crop up.
- **Well-tested** - Thousands of test assertions, and passes all of the Bash, minimatch, and [brace-expansion](https://github.com/juliangruber/brace-expansion) unit tests (as of the date this was written).
- **Safer** - You shouldn't have to worry about users defining aggressive or malicious brace patterns that can break your application. Braces takes measures to prevent malicious regex that can be used for DDoS attacks (see [catastrophic backtracking](https://www.regular-expressions.info/catastrophic.html)).
- [Supports lists](#lists) - (aka "sets") `a/{b,c}/d` => `['a/b/d', 'a/c/d']`
- [Supports sequences](#sequences) - (aka "ranges") `{01..03}` => `['01', '02', '03']`
- [Supports steps](#steps) - (aka "increments") `{2..10..2}` => `['2', '4', '6', '8', '10']`
- [Supports escaping](#escaping) - To prevent evaluation of special characters.
## Usage
The main export is a function that takes one or more brace `patterns` and `options`.
```js
const braces = require('braces');
// braces(patterns[, options]);
console.log(braces(['{01..05}', '{a..e}']));
//=> ['(0[1-5])', '([a-e])']
console.log(braces(['{01..05}', '{a..e}'], { expand: true }));
//=> ['01', '02', '03', '04', '05', 'a', 'b', 'c', 'd', 'e']
```
### Brace Expansion vs. Compilation
By default, brace patterns are compiled into strings that are optimized for creating regular expressions and matching.
**Compiled**
```js
console.log(braces('a/{x,y,z}/b'));
//=> ['a/(x|y|z)/b']
console.log(braces(['a/{01..20}/b', 'a/{1..5}/b']));
//=> [ 'a/(0[1-9]|1[0-9]|20)/b', 'a/([1-5])/b' ]
```
**Expanded**
Enable brace expansion by setting the `expand` option to true, or by using [braces.expand()](#expand) (returns an array similar to what you'd expect from Bash, or `echo {1..5}`, or [minimatch](https://github.com/isaacs/minimatch)):
```js
console.log(braces('a/{x,y,z}/b', { expand: true }));
//=> ['a/x/b', 'a/y/b', 'a/z/b']
console.log(braces.expand('{01..10}'));
//=> ['01','02','03','04','05','06','07','08','09','10']
```
### Lists
Expand lists (like Bash "sets"):
```js
console.log(braces('a/{foo,bar,baz}/*.js'));
//=> ['a/(foo|bar|baz)/*.js']
console.log(braces.expand('a/{foo,bar,baz}/*.js'));
//=> ['a/foo/*.js', 'a/bar/*.js', 'a/baz/*.js']
```
### Sequences
Expand ranges of characters (like Bash "sequences"):
```js
console.log(braces.expand('{1..3}')); // ['1', '2', '3']
console.log(braces.expand('a/{1..3}/b')); // ['a/1/b', 'a/2/b', 'a/3/b']
console.log(braces('{a..c}', { expand: true })); // ['a', 'b', 'c']
console.log(braces('foo/{a..c}', { expand: true })); // ['foo/a', 'foo/b', 'foo/c']
// supports zero-padded ranges
console.log(braces('a/{01..03}/b')); //=> ['a/(0[1-3])/b']
console.log(braces('a/{001..300}/b')); //=> ['a/(0{2}[1-9]|0[1-9][0-9]|[12][0-9]{2}|300)/b']
```
See [fill-range](https://github.com/jonschlinkert/fill-range) for all available range-expansion options.
### Steppped ranges
Steps, or increments, may be used with ranges:
```js
console.log(braces.expand('{2..10..2}'));
//=> ['2', '4', '6', '8', '10']
console.log(braces('{2..10..2}'));
//=> ['(2|4|6|8|10)']
```
When the [.optimize](#optimize) method is used, or [options.optimize](#optionsoptimize) is set to true, sequences are passed to [to-regex-range](https://github.com/jonschlinkert/to-regex-range) for expansion.
### Nesting
Brace patterns may be nested. The results of each expanded string are not sorted, and left to right order is preserved.
**"Expanded" braces**
```js
console.log(braces.expand('a{b,c,/{x,y}}/e'));
//=> ['ab/e', 'ac/e', 'a/x/e', 'a/y/e']
console.log(braces.expand('a/{x,{1..5},y}/c'));
//=> ['a/x/c', 'a/1/c', 'a/2/c', 'a/3/c', 'a/4/c', 'a/5/c', 'a/y/c']
```
**"Optimized" braces**
```js
console.log(braces('a{b,c,/{x,y}}/e'));
//=> ['a(b|c|/(x|y))/e']
console.log(braces('a/{x,{1..5},y}/c'));
//=> ['a/(x|([1-5])|y)/c']
```
### Escaping
**Escaping braces**
A brace pattern will not be expanded or evaluted if _either the opening or closing brace is escaped_:
```js
console.log(braces.expand('a\\{d,c,b}e'));
//=> ['a{d,c,b}e']
console.log(braces.expand('a{d,c,b\\}e'));
//=> ['a{d,c,b}e']
```
**Escaping commas**
Commas inside braces may also be escaped:
```js
console.log(braces.expand('a{b\\,c}d'));
//=> ['a{b,c}d']
console.log(braces.expand('a{d\\,c,b}e'));
//=> ['ad,ce', 'abe']
```
**Single items**
Following bash conventions, a brace pattern is also not expanded when it contains a single character:
```js
console.log(braces.expand('a{b}c'));
//=> ['a{b}c']
```
## Options
### options.maxLength
**Type**: `Number`
**Default**: `10,000`
**Description**: Limit the length of the input string. Useful when the input string is generated or your application allows users to pass a string, et cetera.
```js
console.log(braces('a/{b,c}/d', { maxLength: 3 })); //=> throws an error
```
### options.expand
**Type**: `Boolean`
**Default**: `undefined`
**Description**: Generate an "expanded" brace pattern (alternatively you can use the `braces.expand()` method, which does the same thing).
```js
console.log(braces('a/{b,c}/d', { expand: true }));
//=> [ 'a/b/d', 'a/c/d' ]
```
### options.nodupes
**Type**: `Boolean`
**Default**: `undefined`
**Description**: Remove duplicates from the returned array.
### options.rangeLimit
**Type**: `Number`
**Default**: `1000`
**Description**: To prevent malicious patterns from being passed by users, an error is thrown when `braces.expand()` is used or `options.expand` is true and the generated range will exceed the `rangeLimit`.
You can customize `options.rangeLimit` or set it to `Inifinity` to disable this altogether.
**Examples**
```js
// pattern exceeds the "rangeLimit", so it's optimized automatically
console.log(braces.expand('{1..1000}'));
//=> ['([1-9]|[1-9][0-9]{1,2}|1000)']
// pattern does not exceed "rangeLimit", so it's NOT optimized
console.log(braces.expand('{1..100}'));
//=> ['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', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '100']
```
### options.transform
**Type**: `Function`
**Default**: `undefined`
**Description**: Customize range expansion.
**Example: Transforming non-numeric values**
```js
const alpha = braces.expand('x/{a..e}/y', {
transform(value, index) {
// When non-numeric values are passed, "value" is a character code.
return 'foo/' + String.fromCharCode(value) + '-' + index;
},
});
console.log(alpha);
//=> [ 'x/foo/a-0/y', 'x/foo/b-1/y', 'x/foo/c-2/y', 'x/foo/d-3/y', 'x/foo/e-4/y' ]
```
**Example: Transforming numeric values**
```js
const numeric = braces.expand('{1..5}', {
transform(value) {
// when numeric values are passed, "value" is a number
return 'foo/' + value * 2;
},
});
console.log(numeric);
//=> [ 'foo/2', 'foo/4', 'foo/6', 'foo/8', 'foo/10' ]
```
### options.quantifiers
**Type**: `Boolean`
**Default**: `undefined`
**Description**: In regular expressions, quanitifiers can be used to specify how many times a token can be repeated. For example, `a{1,3}` will match the letter `a` one to three times.
Unfortunately, regex quantifiers happen to share the same syntax as [Bash lists](#lists)
The `quantifiers` option tells braces to detect when [regex quantifiers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#quantifiers) are defined in the given pattern, and not to try to expand them as lists.
**Examples**
```js
const braces = require('braces');
console.log(braces('a/b{1,3}/{x,y,z}'));
//=> [ 'a/b(1|3)/(x|y|z)' ]
console.log(braces('a/b{1,3}/{x,y,z}', { quantifiers: true }));
//=> [ 'a/b{1,3}/(x|y|z)' ]
console.log(braces('a/b{1,3}/{x,y,z}', { quantifiers: true, expand: true }));
//=> [ 'a/b{1,3}/x', 'a/b{1,3}/y', 'a/b{1,3}/z' ]
```
### options.keepEscaping
**Type**: `Boolean`
**Default**: `undefined`
**Description**: Do not strip backslashes that were used for escaping from the result.
## What is "brace expansion"?
Brace expansion is a type of parameter expansion that was made popular by unix shells for generating lists of strings, as well as regex-like matching when used alongside wildcards (globs).
In addition to "expansion", braces are also used for matching. In other words:
- [brace expansion](#brace-expansion) is for generating new lists
- [brace matching](#brace-matching) is for filtering existing lists
<details>
<summary><strong>More about brace expansion</strong> (click to expand)</summary>
There are two main types of brace expansion:
1. **lists**: which are defined using comma-separated values inside curly braces: `{a,b,c}`
2. **sequences**: which are defined using a starting value and an ending value, separated by two dots: `a{1..3}b`. Optionally, a third argument may be passed to define a "step" or increment to use: `a{1..100..10}b`. These are also sometimes referred to as "ranges".
Here are some example brace patterns to illustrate how they work:
**Sets**
```
{a,b,c} => a b c
{a,b,c}{1,2} => a1 a2 b1 b2 c1 c2
```
**Sequences**
```
{1..9} => 1 2 3 4 5 6 7 8 9
{4..-4} => 4 3 2 1 0 -1 -2 -3 -4
{1..20..3} => 1 4 7 10 13 16 19
{a..j} => a b c d e f g h i j
{j..a} => j i h g f e d c b a
{a..z..3} => a d g j m p s v y
```
**Combination**
Sets and sequences can be mixed together or used along with any other strings.
```
{a,b,c}{1..3} => a1 a2 a3 b1 b2 b3 c1 c2 c3
foo/{a,b,c}/bar => foo/a/bar foo/b/bar foo/c/bar
```
The fact that braces can be "expanded" from relatively simple patterns makes them ideal for quickly generating test fixtures, file paths, and similar use cases.
## Brace matching
In addition to _expansion_, brace patterns are also useful for performing regular-expression-like matching.
For example, the pattern `foo/{1..3}/bar` would match any of following strings:
```
foo/1/bar
foo/2/bar
foo/3/bar
```
But not:
```
baz/1/qux
baz/2/qux
baz/3/qux
```
Braces can also be combined with [glob patterns](https://github.com/jonschlinkert/micromatch) to perform more advanced wildcard matching. For example, the pattern `*/{1..3}/*` would match any of following strings:
```
foo/1/bar
foo/2/bar
foo/3/bar
baz/1/qux
baz/2/qux
baz/3/qux
```
## Brace matching pitfalls
Although brace patterns offer a user-friendly way of matching ranges or sets of strings, there are also some major disadvantages and potential risks you should be aware of.
### tldr
**"brace bombs"**
- brace expansion can eat up a huge amount of processing resources
- as brace patterns increase _linearly in size_, the system resources required to expand the pattern increase exponentially
- users can accidentally (or intentially) exhaust your system's resources resulting in the equivalent of a DoS attack (bonus: no programming knowledge is required!)
For a more detailed explanation with examples, see the [geometric complexity](#geometric-complexity) section.
### The solution
Jump to the [performance section](#performance) to see how Braces solves this problem in comparison to other libraries.
### Geometric complexity
At minimum, brace patterns with sets limited to two elements have quadradic or `O(n^2)` complexity. But the complexity of the algorithm increases exponentially as the number of sets, _and elements per set_, increases, which is `O(n^c)`.
For example, the following sets demonstrate quadratic (`O(n^2)`) complexity:
```
{1,2}{3,4} => (2X2) => 13 14 23 24
{1,2}{3,4}{5,6} => (2X2X2) => 135 136 145 146 235 236 245 246
```
But add an element to a set, and we get a n-fold Cartesian product with `O(n^c)` complexity:
```
{1,2,3}{4,5,6}{7,8,9} => (3X3X3) => 147 148 149 157 158 159 167 168 169 247 248
249 257 258 259 267 268 269 347 348 349 357
358 359 367 368 369
```
Now, imagine how this complexity grows given that each element is a n-tuple:
```
{1..100}{1..100} => (100X100) => 10,000 elements (38.4 kB)
{1..100}{1..100}{1..100} => (100X100X100) => 1,000,000 elements (5.76 MB)
```
Although these examples are clearly contrived, they demonstrate how brace patterns can quickly grow out of control.
**More information**
Interested in learning more about brace expansion?
- [linuxjournal/bash-brace-expansion](http://www.linuxjournal.com/content/bash-brace-expansion)
- [rosettacode/Brace_expansion](https://rosettacode.org/wiki/Brace_expansion)
- [cartesian product](https://en.wikipedia.org/wiki/Cartesian_product)
</details>
## Performance
Braces is not only screaming fast, it's also more accurate the other brace expansion libraries.
### Better algorithms
Fortunately there is a solution to the ["brace bomb" problem](#brace-matching-pitfalls): _don't expand brace patterns into an array when they're used for matching_.
Instead, convert the pattern into an optimized regular expression. This is easier said than done, and braces is the only library that does this currently.
**The proof is in the numbers**
Minimatch gets exponentially slower as patterns increase in complexity, braces does not. The following results were generated using `braces()` and `minimatch.braceExpand()`, respectively.
| **Pattern** | **braces** | **[minimatch][]** |
| --------------------------- | ------------------- | ---------------------------- |
| `{1..9007199254740991}`[^1] | `298 B` (5ms 459μs) | N/A (freezes) |
| `{1..1000000000000000}` | `41 B` (1ms 15μs) | N/A (freezes) |
| `{1..100000000000000}` | `40 B` (890μs) | N/A (freezes) |
| `{1..10000000000000}` | `39 B` (2ms 49μs) | N/A (freezes) |
| `{1..1000000000000}` | `38 B` (608μs) | N/A (freezes) |
| `{1..100000000000}` | `37 B` (397μs) | N/A (freezes) |
| `{1..10000000000}` | `35 B` (983μs) | N/A (freezes) |
| `{1..1000000000}` | `34 B` (798μs) | N/A (freezes) |
| `{1..100000000}` | `33 B` (733μs) | N/A (freezes) |
| `{1..10000000}` | `32 B` (5ms 632μs) | `78.89 MB` (16s 388ms 569μs) |
| `{1..1000000}` | `31 B` (1ms 381μs) | `6.89 MB` (1s 496ms 887μs) |
| `{1..100000}` | `30 B` (950μs) | `588.89 kB` (146ms 921μs) |
| `{1..10000}` | `29 B` (1ms 114μs) | `48.89 kB` (14ms 187μs) |
| `{1..1000}` | `28 B` (760μs) | `3.89 kB` (1ms 453μs) |
| `{1..100}` | `22 B` (345μs) | `291 B` (196μs) |
| `{1..10}` | `10 B` (533μs) | `20 B` (37μs) |
| `{1..3}` | `7 B` (190μs) | `5 B` (27μs) |
### Faster algorithms
When you need expansion, braces is still much faster.
_(the following results were generated using `braces.expand()` and `minimatch.braceExpand()`, respectively)_
| **Pattern** | **braces** | **[minimatch][]** |
| --------------- | --------------------------- | ---------------------------- |
| `{1..10000000}` | `78.89 MB` (2s 698ms 642μs) | `78.89 MB` (18s 601ms 974μs) |
| `{1..1000000}` | `6.89 MB` (458ms 576μs) | `6.89 MB` (1s 491ms 621μs) |
| `{1..100000}` | `588.89 kB` (20ms 728μs) | `588.89 kB` (156ms 919μs) |
| `{1..10000}` | `48.89 kB` (2ms 202μs) | `48.89 kB` (13ms 641μs) |
| `{1..1000}` | `3.89 kB` (1ms 796μs) | `3.89 kB` (1ms 958μs) |
| `{1..100}` | `291 B` (424μs) | `291 B` (211μs) |
| `{1..10}` | `20 B` (487μs) | `20 B` (72μs) |
| `{1..3}` | `5 B` (166μs) | `5 B` (27μs) |
If you'd like to run these comparisons yourself, see [test/support/generate.js](test/support/generate.js).
## Benchmarks
### Running benchmarks
Install dev dependencies:
```bash
npm i -d && npm benchmark
```
### Latest results
Braces is more accurate, without sacrificing performance.
```bash
● expand - range (expanded)
braces x 53,167 ops/sec ±0.12% (102 runs sampled)
minimatch x 11,378 ops/sec ±0.10% (102 runs sampled)
● expand - range (optimized for regex)
braces x 373,442 ops/sec ±0.04% (100 runs sampled)
minimatch x 3,262 ops/sec ±0.18% (100 runs sampled)
● expand - nested ranges (expanded)
braces x 33,921 ops/sec ±0.09% (99 runs sampled)
minimatch x 10,855 ops/sec ±0.28% (100 runs sampled)
● expand - nested ranges (optimized for regex)
braces x 287,479 ops/sec ±0.52% (98 runs sampled)
minimatch x 3,219 ops/sec ±0.28% (101 runs sampled)
● expand - set (expanded)
braces x 238,243 ops/sec ±0.19% (97 runs sampled)
minimatch x 538,268 ops/sec ±0.31% (96 runs sampled)
● expand - set (optimized for regex)
braces x 321,844 ops/sec ±0.10% (97 runs sampled)
minimatch x 140,600 ops/sec ±0.15% (100 runs sampled)
● expand - nested sets (expanded)
braces x 165,371 ops/sec ±0.42% (96 runs sampled)
minimatch x 337,720 ops/sec ±0.28% (100 runs sampled)
● expand - nested sets (optimized for regex)
braces x 242,948 ops/sec ±0.12% (99 runs sampled)
minimatch x 87,403 ops/sec ±0.79% (96 runs sampled)
```
## About
<details>
<summary><strong>Contributing</strong></summary>
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
</details>
<details>
<summary><strong>Running Tests</strong></summary>
Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
```sh
$ npm install && npm test
```
</details>
<details>
<summary><strong>Building docs</strong></summary>
_(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
To generate the readme, run the following command:
```sh
$ npm install -g verbose/verb#dev verb-generate-readme && verb
```
</details>
### Contributors
| **Commits** | **Contributor** |
| ----------- | ------------------------------------------------------------- |
| 197 | [jonschlinkert](https://github.com/jonschlinkert) |
| 4 | [doowb](https://github.com/doowb) |
| 1 | [es128](https://github.com/es128) |
| 1 | [eush77](https://github.com/eush77) |
| 1 | [hemanth](https://github.com/hemanth) |
| 1 | [wtgtybhertgeghgtwtg](https://github.com/wtgtybhertgeghgtwtg) |
### Author
**Jon Schlinkert**
- [GitHub Profile](https://github.com/jonschlinkert)
- [Twitter Profile](https://twitter.com/jonschlinkert)
- [LinkedIn Profile](https://linkedin.com/in/jonschlinkert)
### License
Copyright © 2019, [Jon Schlinkert](https://github.com/jonschlinkert).
Released under the [MIT License](LICENSE).
---
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.8.0, on April 08, 2019._

170
node_modules/braces/index.js generated vendored Normal file
View File

@ -0,0 +1,170 @@
'use strict';
const stringify = require('./lib/stringify');
const compile = require('./lib/compile');
const expand = require('./lib/expand');
const parse = require('./lib/parse');
/**
* Expand the given pattern or create a regex-compatible string.
*
* ```js
* const braces = require('braces');
* console.log(braces('{a,b,c}', { compile: true })); //=> ['(a|b|c)']
* console.log(braces('{a,b,c}')); //=> ['a', 'b', 'c']
* ```
* @param {String} `str`
* @param {Object} `options`
* @return {String}
* @api public
*/
const braces = (input, options = {}) => {
let output = [];
if (Array.isArray(input)) {
for (const pattern of input) {
const result = braces.create(pattern, options);
if (Array.isArray(result)) {
output.push(...result);
} else {
output.push(result);
}
}
} else {
output = [].concat(braces.create(input, options));
}
if (options && options.expand === true && options.nodupes === true) {
output = [...new Set(output)];
}
return output;
};
/**
* Parse the given `str` with the given `options`.
*
* ```js
* // braces.parse(pattern, [, options]);
* const ast = braces.parse('a/{b,c}/d');
* console.log(ast);
* ```
* @param {String} pattern Brace pattern to parse
* @param {Object} options
* @return {Object} Returns an AST
* @api public
*/
braces.parse = (input, options = {}) => parse(input, options);
/**
* Creates a braces string from an AST, or an AST node.
*
* ```js
* const braces = require('braces');
* let ast = braces.parse('foo/{a,b}/bar');
* console.log(stringify(ast.nodes[2])); //=> '{a,b}'
* ```
* @param {String} `input` Brace pattern or AST.
* @param {Object} `options`
* @return {Array} Returns an array of expanded values.
* @api public
*/
braces.stringify = (input, options = {}) => {
if (typeof input === 'string') {
return stringify(braces.parse(input, options), options);
}
return stringify(input, options);
};
/**
* Compiles a brace pattern into a regex-compatible, optimized string.
* This method is called by the main [braces](#braces) function by default.
*
* ```js
* const braces = require('braces');
* console.log(braces.compile('a/{b,c}/d'));
* //=> ['a/(b|c)/d']
* ```
* @param {String} `input` Brace pattern or AST.
* @param {Object} `options`
* @return {Array} Returns an array of expanded values.
* @api public
*/
braces.compile = (input, options = {}) => {
if (typeof input === 'string') {
input = braces.parse(input, options);
}
return compile(input, options);
};
/**
* Expands a brace pattern into an array. This method is called by the
* main [braces](#braces) function when `options.expand` is true. Before
* using this method it's recommended that you read the [performance notes](#performance))
* and advantages of using [.compile](#compile) instead.
*
* ```js
* const braces = require('braces');
* console.log(braces.expand('a/{b,c}/d'));
* //=> ['a/b/d', 'a/c/d'];
* ```
* @param {String} `pattern` Brace pattern
* @param {Object} `options`
* @return {Array} Returns an array of expanded values.
* @api public
*/
braces.expand = (input, options = {}) => {
if (typeof input === 'string') {
input = braces.parse(input, options);
}
let result = expand(input, options);
// filter out empty strings if specified
if (options.noempty === true) {
result = result.filter(Boolean);
}
// filter out duplicates if specified
if (options.nodupes === true) {
result = [...new Set(result)];
}
return result;
};
/**
* Processes a brace pattern and returns either an expanded array
* (if `options.expand` is true), a highly optimized regex-compatible string.
* This method is called by the main [braces](#braces) function.
*
* ```js
* const braces = require('braces');
* console.log(braces.create('user-{200..300}/project-{a,b,c}-{1..10}'))
* //=> 'user-(20[0-9]|2[1-9][0-9]|300)/project-(a|b|c)-([1-9]|10)'
* ```
* @param {String} `pattern` Brace pattern
* @param {Object} `options`
* @return {Array} Returns an array of expanded values.
* @api public
*/
braces.create = (input, options = {}) => {
if (input === '' || input.length < 3) {
return [input];
}
return options.expand !== true
? braces.compile(input, options)
: braces.expand(input, options);
};
/**
* Expose "braces"
*/
module.exports = braces;

60
node_modules/braces/lib/compile.js generated vendored Normal file
View File

@ -0,0 +1,60 @@
'use strict';
const fill = require('fill-range');
const utils = require('./utils');
const compile = (ast, options = {}) => {
const walk = (node, parent = {}) => {
const invalidBlock = utils.isInvalidBrace(parent);
const invalidNode = node.invalid === true && options.escapeInvalid === true;
const invalid = invalidBlock === true || invalidNode === true;
const prefix = options.escapeInvalid === true ? '\\' : '';
let output = '';
if (node.isOpen === true) {
return prefix + node.value;
}
if (node.isClose === true) {
console.log('node.isClose', prefix, node.value);
return prefix + node.value;
}
if (node.type === 'open') {
return invalid ? prefix + node.value : '(';
}
if (node.type === 'close') {
return invalid ? prefix + node.value : ')';
}
if (node.type === 'comma') {
return node.prev.type === 'comma' ? '' : invalid ? node.value : '|';
}
if (node.value) {
return node.value;
}
if (node.nodes && node.ranges > 0) {
const args = utils.reduce(node.nodes);
const range = fill(...args, { ...options, wrap: false, toRegex: true, strictZeros: true });
if (range.length !== 0) {
return args.length > 1 && range.length > 1 ? `(${range})` : range;
}
}
if (node.nodes) {
for (const child of node.nodes) {
output += walk(child, node);
}
}
return output;
};
return walk(ast);
};
module.exports = compile;

57
node_modules/braces/lib/constants.js generated vendored Normal file
View File

@ -0,0 +1,57 @@
'use strict';
module.exports = {
MAX_LENGTH: 10000,
// Digits
CHAR_0: '0', /* 0 */
CHAR_9: '9', /* 9 */
// Alphabet chars.
CHAR_UPPERCASE_A: 'A', /* A */
CHAR_LOWERCASE_A: 'a', /* a */
CHAR_UPPERCASE_Z: 'Z', /* Z */
CHAR_LOWERCASE_Z: 'z', /* z */
CHAR_LEFT_PARENTHESES: '(', /* ( */
CHAR_RIGHT_PARENTHESES: ')', /* ) */
CHAR_ASTERISK: '*', /* * */
// Non-alphabetic chars.
CHAR_AMPERSAND: '&', /* & */
CHAR_AT: '@', /* @ */
CHAR_BACKSLASH: '\\', /* \ */
CHAR_BACKTICK: '`', /* ` */
CHAR_CARRIAGE_RETURN: '\r', /* \r */
CHAR_CIRCUMFLEX_ACCENT: '^', /* ^ */
CHAR_COLON: ':', /* : */
CHAR_COMMA: ',', /* , */
CHAR_DOLLAR: '$', /* . */
CHAR_DOT: '.', /* . */
CHAR_DOUBLE_QUOTE: '"', /* " */
CHAR_EQUAL: '=', /* = */
CHAR_EXCLAMATION_MARK: '!', /* ! */
CHAR_FORM_FEED: '\f', /* \f */
CHAR_FORWARD_SLASH: '/', /* / */
CHAR_HASH: '#', /* # */
CHAR_HYPHEN_MINUS: '-', /* - */
CHAR_LEFT_ANGLE_BRACKET: '<', /* < */
CHAR_LEFT_CURLY_BRACE: '{', /* { */
CHAR_LEFT_SQUARE_BRACKET: '[', /* [ */
CHAR_LINE_FEED: '\n', /* \n */
CHAR_NO_BREAK_SPACE: '\u00A0', /* \u00A0 */
CHAR_PERCENT: '%', /* % */
CHAR_PLUS: '+', /* + */
CHAR_QUESTION_MARK: '?', /* ? */
CHAR_RIGHT_ANGLE_BRACKET: '>', /* > */
CHAR_RIGHT_CURLY_BRACE: '}', /* } */
CHAR_RIGHT_SQUARE_BRACKET: ']', /* ] */
CHAR_SEMICOLON: ';', /* ; */
CHAR_SINGLE_QUOTE: '\'', /* ' */
CHAR_SPACE: ' ', /* */
CHAR_TAB: '\t', /* \t */
CHAR_UNDERSCORE: '_', /* _ */
CHAR_VERTICAL_LINE: '|', /* | */
CHAR_ZERO_WIDTH_NOBREAK_SPACE: '\uFEFF' /* \uFEFF */
};

113
node_modules/braces/lib/expand.js generated vendored Normal file
View File

@ -0,0 +1,113 @@
'use strict';
const fill = require('fill-range');
const stringify = require('./stringify');
const utils = require('./utils');
const append = (queue = '', stash = '', enclose = false) => {
const result = [];
queue = [].concat(queue);
stash = [].concat(stash);
if (!stash.length) return queue;
if (!queue.length) {
return enclose ? utils.flatten(stash).map(ele => `{${ele}}`) : stash;
}
for (const item of queue) {
if (Array.isArray(item)) {
for (const value of item) {
result.push(append(value, stash, enclose));
}
} else {
for (let ele of stash) {
if (enclose === true && typeof ele === 'string') ele = `{${ele}}`;
result.push(Array.isArray(ele) ? append(item, ele, enclose) : item + ele);
}
}
}
return utils.flatten(result);
};
const expand = (ast, options = {}) => {
const rangeLimit = options.rangeLimit === undefined ? 1000 : options.rangeLimit;
const walk = (node, parent = {}) => {
node.queue = [];
let p = parent;
let q = parent.queue;
while (p.type !== 'brace' && p.type !== 'root' && p.parent) {
p = p.parent;
q = p.queue;
}
if (node.invalid || node.dollar) {
q.push(append(q.pop(), stringify(node, options)));
return;
}
if (node.type === 'brace' && node.invalid !== true && node.nodes.length === 2) {
q.push(append(q.pop(), ['{}']));
return;
}
if (node.nodes && node.ranges > 0) {
const args = utils.reduce(node.nodes);
if (utils.exceedsLimit(...args, options.step, rangeLimit)) {
throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.');
}
let range = fill(...args, options);
if (range.length === 0) {
range = stringify(node, options);
}
q.push(append(q.pop(), range));
node.nodes = [];
return;
}
const enclose = utils.encloseBrace(node);
let queue = node.queue;
let block = node;
while (block.type !== 'brace' && block.type !== 'root' && block.parent) {
block = block.parent;
queue = block.queue;
}
for (let i = 0; i < node.nodes.length; i++) {
const child = node.nodes[i];
if (child.type === 'comma' && node.type === 'brace') {
if (i === 1) queue.push('');
queue.push('');
continue;
}
if (child.type === 'close') {
q.push(append(q.pop(), queue, enclose));
continue;
}
if (child.value && child.type !== 'open') {
queue.push(append(queue.pop(), child.value));
continue;
}
if (child.nodes) {
walk(child, node);
}
}
return queue;
};
return utils.flatten(walk(ast));
};
module.exports = expand;

331
node_modules/braces/lib/parse.js generated vendored Normal file
View File

@ -0,0 +1,331 @@
'use strict';
const stringify = require('./stringify');
/**
* Constants
*/
const {
MAX_LENGTH,
CHAR_BACKSLASH, /* \ */
CHAR_BACKTICK, /* ` */
CHAR_COMMA, /* , */
CHAR_DOT, /* . */
CHAR_LEFT_PARENTHESES, /* ( */
CHAR_RIGHT_PARENTHESES, /* ) */
CHAR_LEFT_CURLY_BRACE, /* { */
CHAR_RIGHT_CURLY_BRACE, /* } */
CHAR_LEFT_SQUARE_BRACKET, /* [ */
CHAR_RIGHT_SQUARE_BRACKET, /* ] */
CHAR_DOUBLE_QUOTE, /* " */
CHAR_SINGLE_QUOTE, /* ' */
CHAR_NO_BREAK_SPACE,
CHAR_ZERO_WIDTH_NOBREAK_SPACE
} = require('./constants');
/**
* parse
*/
const parse = (input, options = {}) => {
if (typeof input !== 'string') {
throw new TypeError('Expected a string');
}
const opts = options || {};
const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
if (input.length > max) {
throw new SyntaxError(`Input length (${input.length}), exceeds max characters (${max})`);
}
const ast = { type: 'root', input, nodes: [] };
const stack = [ast];
let block = ast;
let prev = ast;
let brackets = 0;
const length = input.length;
let index = 0;
let depth = 0;
let value;
/**
* Helpers
*/
const advance = () => input[index++];
const push = node => {
if (node.type === 'text' && prev.type === 'dot') {
prev.type = 'text';
}
if (prev && prev.type === 'text' && node.type === 'text') {
prev.value += node.value;
return;
}
block.nodes.push(node);
node.parent = block;
node.prev = prev;
prev = node;
return node;
};
push({ type: 'bos' });
while (index < length) {
block = stack[stack.length - 1];
value = advance();
/**
* Invalid chars
*/
if (value === CHAR_ZERO_WIDTH_NOBREAK_SPACE || value === CHAR_NO_BREAK_SPACE) {
continue;
}
/**
* Escaped chars
*/
if (value === CHAR_BACKSLASH) {
push({ type: 'text', value: (options.keepEscaping ? value : '') + advance() });
continue;
}
/**
* Right square bracket (literal): ']'
*/
if (value === CHAR_RIGHT_SQUARE_BRACKET) {
push({ type: 'text', value: '\\' + value });
continue;
}
/**
* Left square bracket: '['
*/
if (value === CHAR_LEFT_SQUARE_BRACKET) {
brackets++;
let next;
while (index < length && (next = advance())) {
value += next;
if (next === CHAR_LEFT_SQUARE_BRACKET) {
brackets++;
continue;
}
if (next === CHAR_BACKSLASH) {
value += advance();
continue;
}
if (next === CHAR_RIGHT_SQUARE_BRACKET) {
brackets--;
if (brackets === 0) {
break;
}
}
}
push({ type: 'text', value });
continue;
}
/**
* Parentheses
*/
if (value === CHAR_LEFT_PARENTHESES) {
block = push({ type: 'paren', nodes: [] });
stack.push(block);
push({ type: 'text', value });
continue;
}
if (value === CHAR_RIGHT_PARENTHESES) {
if (block.type !== 'paren') {
push({ type: 'text', value });
continue;
}
block = stack.pop();
push({ type: 'text', value });
block = stack[stack.length - 1];
continue;
}
/**
* Quotes: '|"|`
*/
if (value === CHAR_DOUBLE_QUOTE || value === CHAR_SINGLE_QUOTE || value === CHAR_BACKTICK) {
const open = value;
let next;
if (options.keepQuotes !== true) {
value = '';
}
while (index < length && (next = advance())) {
if (next === CHAR_BACKSLASH) {
value += next + advance();
continue;
}
if (next === open) {
if (options.keepQuotes === true) value += next;
break;
}
value += next;
}
push({ type: 'text', value });
continue;
}
/**
* Left curly brace: '{'
*/
if (value === CHAR_LEFT_CURLY_BRACE) {
depth++;
const dollar = prev.value && prev.value.slice(-1) === '$' || block.dollar === true;
const brace = {
type: 'brace',
open: true,
close: false,
dollar,
depth,
commas: 0,
ranges: 0,
nodes: []
};
block = push(brace);
stack.push(block);
push({ type: 'open', value });
continue;
}
/**
* Right curly brace: '}'
*/
if (value === CHAR_RIGHT_CURLY_BRACE) {
if (block.type !== 'brace') {
push({ type: 'text', value });
continue;
}
const type = 'close';
block = stack.pop();
block.close = true;
push({ type, value });
depth--;
block = stack[stack.length - 1];
continue;
}
/**
* Comma: ','
*/
if (value === CHAR_COMMA && depth > 0) {
if (block.ranges > 0) {
block.ranges = 0;
const open = block.nodes.shift();
block.nodes = [open, { type: 'text', value: stringify(block) }];
}
push({ type: 'comma', value });
block.commas++;
continue;
}
/**
* Dot: '.'
*/
if (value === CHAR_DOT && depth > 0 && block.commas === 0) {
const siblings = block.nodes;
if (depth === 0 || siblings.length === 0) {
push({ type: 'text', value });
continue;
}
if (prev.type === 'dot') {
block.range = [];
prev.value += value;
prev.type = 'range';
if (block.nodes.length !== 3 && block.nodes.length !== 5) {
block.invalid = true;
block.ranges = 0;
prev.type = 'text';
continue;
}
block.ranges++;
block.args = [];
continue;
}
if (prev.type === 'range') {
siblings.pop();
const before = siblings[siblings.length - 1];
before.value += prev.value + value;
prev = before;
block.ranges--;
continue;
}
push({ type: 'dot', value });
continue;
}
/**
* Text
*/
push({ type: 'text', value });
}
// Mark imbalanced braces and brackets as invalid
do {
block = stack.pop();
if (block.type !== 'root') {
block.nodes.forEach(node => {
if (!node.nodes) {
if (node.type === 'open') node.isOpen = true;
if (node.type === 'close') node.isClose = true;
if (!node.nodes) node.type = 'text';
node.invalid = true;
}
});
// get the location of the block on parent.nodes (block's siblings)
const parent = stack[stack.length - 1];
const index = parent.nodes.indexOf(block);
// replace the (invalid) block with it's nodes
parent.nodes.splice(index, 1, ...block.nodes);
}
} while (stack.length > 0);
push({ type: 'eos' });
return ast;
};
module.exports = parse;

32
node_modules/braces/lib/stringify.js generated vendored Normal file
View File

@ -0,0 +1,32 @@
'use strict';
const utils = require('./utils');
module.exports = (ast, options = {}) => {
const stringify = (node, parent = {}) => {
const invalidBlock = options.escapeInvalid && utils.isInvalidBrace(parent);
const invalidNode = node.invalid === true && options.escapeInvalid === true;
let output = '';
if (node.value) {
if ((invalidBlock || invalidNode) && utils.isOpenOrClose(node)) {
return '\\' + node.value;
}
return node.value;
}
if (node.value) {
return node.value;
}
if (node.nodes) {
for (const child of node.nodes) {
output += stringify(child);
}
}
return output;
};
return stringify(ast);
};

122
node_modules/braces/lib/utils.js generated vendored Normal file
View File

@ -0,0 +1,122 @@
'use strict';
exports.isInteger = num => {
if (typeof num === 'number') {
return Number.isInteger(num);
}
if (typeof num === 'string' && num.trim() !== '') {
return Number.isInteger(Number(num));
}
return false;
};
/**
* Find a node of the given type
*/
exports.find = (node, type) => node.nodes.find(node => node.type === type);
/**
* Find a node of the given type
*/
exports.exceedsLimit = (min, max, step = 1, limit) => {
if (limit === false) return false;
if (!exports.isInteger(min) || !exports.isInteger(max)) return false;
return ((Number(max) - Number(min)) / Number(step)) >= limit;
};
/**
* Escape the given node with '\\' before node.value
*/
exports.escapeNode = (block, n = 0, type) => {
const node = block.nodes[n];
if (!node) return;
if ((type && node.type === type) || node.type === 'open' || node.type === 'close') {
if (node.escaped !== true) {
node.value = '\\' + node.value;
node.escaped = true;
}
}
};
/**
* Returns true if the given brace node should be enclosed in literal braces
*/
exports.encloseBrace = node => {
if (node.type !== 'brace') return false;
if ((node.commas >> 0 + node.ranges >> 0) === 0) {
node.invalid = true;
return true;
}
return false;
};
/**
* Returns true if a brace node is invalid.
*/
exports.isInvalidBrace = block => {
if (block.type !== 'brace') return false;
if (block.invalid === true || block.dollar) return true;
if ((block.commas >> 0 + block.ranges >> 0) === 0) {
block.invalid = true;
return true;
}
if (block.open !== true || block.close !== true) {
block.invalid = true;
return true;
}
return false;
};
/**
* Returns true if a node is an open or close node
*/
exports.isOpenOrClose = node => {
if (node.type === 'open' || node.type === 'close') {
return true;
}
return node.open === true || node.close === true;
};
/**
* Reduce an array of text nodes.
*/
exports.reduce = nodes => nodes.reduce((acc, node) => {
if (node.type === 'text') acc.push(node.value);
if (node.type === 'range') node.type = 'text';
return acc;
}, []);
/**
* Flatten an array
*/
exports.flatten = (...args) => {
const result = [];
const flat = arr => {
for (let i = 0; i < arr.length; i++) {
const ele = arr[i];
if (Array.isArray(ele)) {
flat(ele);
continue;
}
if (ele !== undefined) {
result.push(ele);
}
}
return result;
};
flat(args);
return result;
};

77
node_modules/braces/package.json generated vendored Normal file
View File

@ -0,0 +1,77 @@
{
"name": "braces",
"description": "Bash-like brace expansion, implemented in JavaScript. Safer than other brace expansion libs, with complete support for the Bash 4.3 braces specification, without sacrificing speed.",
"version": "3.0.3",
"homepage": "https://github.com/micromatch/braces",
"author": "Jon Schlinkert (https://github.com/jonschlinkert)",
"contributors": [
"Brian Woodward (https://twitter.com/doowb)",
"Elan Shanker (https://github.com/es128)",
"Eugene Sharygin (https://github.com/eush77)",
"hemanth.hm (http://h3manth.com)",
"Jon Schlinkert (http://twitter.com/jonschlinkert)"
],
"repository": "micromatch/braces",
"bugs": {
"url": "https://github.com/micromatch/braces/issues"
},
"license": "MIT",
"files": [
"index.js",
"lib"
],
"main": "index.js",
"engines": {
"node": ">=8"
},
"scripts": {
"test": "mocha",
"benchmark": "node benchmark"
},
"dependencies": {
"fill-range": "^7.1.1"
},
"devDependencies": {
"ansi-colors": "^3.2.4",
"bash-path": "^2.0.1",
"gulp-format-md": "^2.0.0",
"mocha": "^6.1.1"
},
"keywords": [
"alpha",
"alphabetical",
"bash",
"brace",
"braces",
"expand",
"expansion",
"filepath",
"fill",
"fs",
"glob",
"globbing",
"letter",
"match",
"matches",
"matching",
"number",
"numerical",
"path",
"range",
"ranges",
"sh"
],
"verb": {
"toc": false,
"layout": "default",
"tasks": [
"readme"
],
"lint": {
"reflinks": true
},
"plugins": [
"gulp-format-md"
]
}
}

63
node_modules/camelcase/index.d.ts generated vendored Normal file
View File

@ -0,0 +1,63 @@
declare namespace camelcase {
interface Options {
/**
Uppercase the first character: `foo-bar` `FooBar`.
@default false
*/
readonly pascalCase?: boolean;
}
}
declare const camelcase: {
/**
Convert a dash/dot/underscore/space separated string to camelCase or PascalCase: `foo-bar` `fooBar`.
@param input - String to convert to camel case.
@example
```
import camelCase = require('camelcase');
camelCase('foo-bar');
//=> 'fooBar'
camelCase('foo_bar');
//=> 'fooBar'
camelCase('Foo-Bar');
//=> 'fooBar'
camelCase('Foo-Bar', {pascalCase: true});
//=> 'FooBar'
camelCase('--foo.bar', {pascalCase: false});
//=> 'fooBar'
camelCase('foo bar');
//=> 'fooBar'
console.log(process.argv[3]);
//=> '--foo-bar'
camelCase(process.argv[3]);
//=> 'fooBar'
camelCase(['foo', 'bar']);
//=> 'fooBar'
camelCase(['__foo__', '--bar'], {pascalCase: true});
//=> 'FooBar'
```
*/
(input: string | ReadonlyArray<string>, options?: camelcase.Options): string;
// TODO: Remove this for the next major release, refactor the whole definition to:
// declare function camelcase(
// input: string | ReadonlyArray<string>,
// options?: camelcase.Options
// ): string;
// export = camelcase;
default: typeof camelcase;
};
export = camelcase;

76
node_modules/camelcase/index.js generated vendored Normal file
View File

@ -0,0 +1,76 @@
'use strict';
const preserveCamelCase = string => {
let isLastCharLower = false;
let isLastCharUpper = false;
let isLastLastCharUpper = false;
for (let i = 0; i < string.length; i++) {
const character = string[i];
if (isLastCharLower && /[a-zA-Z]/.test(character) && character.toUpperCase() === character) {
string = string.slice(0, i) + '-' + string.slice(i);
isLastCharLower = false;
isLastLastCharUpper = isLastCharUpper;
isLastCharUpper = true;
i++;
} else if (isLastCharUpper && isLastLastCharUpper && /[a-zA-Z]/.test(character) && character.toLowerCase() === character) {
string = string.slice(0, i - 1) + '-' + string.slice(i - 1);
isLastLastCharUpper = isLastCharUpper;
isLastCharUpper = false;
isLastCharLower = true;
} else {
isLastCharLower = character.toLowerCase() === character && character.toUpperCase() !== character;
isLastLastCharUpper = isLastCharUpper;
isLastCharUpper = character.toUpperCase() === character && character.toLowerCase() !== character;
}
}
return string;
};
const camelCase = (input, options) => {
if (!(typeof input === 'string' || Array.isArray(input))) {
throw new TypeError('Expected the input to be `string | string[]`');
}
options = Object.assign({
pascalCase: false
}, options);
const postProcess = x => options.pascalCase ? x.charAt(0).toUpperCase() + x.slice(1) : x;
if (Array.isArray(input)) {
input = input.map(x => x.trim())
.filter(x => x.length)
.join('-');
} else {
input = input.trim();
}
if (input.length === 0) {
return '';
}
if (input.length === 1) {
return options.pascalCase ? input.toUpperCase() : input.toLowerCase();
}
const hasUpperCase = input !== input.toLowerCase();
if (hasUpperCase) {
input = preserveCamelCase(input);
}
input = input
.replace(/^[_.\- ]+/, '')
.toLowerCase()
.replace(/[_.\- ]+(\w|$)/g, (_, p1) => p1.toUpperCase())
.replace(/\d+(\w|$)/g, m => m.toUpperCase());
return postProcess(input);
};
module.exports = camelCase;
// TODO: Remove this for the next major release
module.exports.default = camelCase;

9
node_modules/camelcase/license generated vendored Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

43
node_modules/camelcase/package.json generated vendored Normal file
View File

@ -0,0 +1,43 @@
{
"name": "camelcase",
"version": "5.3.1",
"description": "Convert a dash/dot/underscore/space separated string to camelCase or PascalCase: `foo-bar` → `fooBar`",
"license": "MIT",
"repository": "sindresorhus/camelcase",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"engines": {
"node": ">=6"
},
"scripts": {
"test": "xo && ava && tsd"
},
"files": [
"index.js",
"index.d.ts"
],
"keywords": [
"camelcase",
"camel-case",
"camel",
"case",
"dash",
"hyphen",
"dot",
"underscore",
"separator",
"string",
"text",
"convert",
"pascalcase",
"pascal-case"
],
"devDependencies": {
"ava": "^1.4.1",
"tsd": "^0.7.1",
"xo": "^0.24.0"
}
}

99
node_modules/camelcase/readme.md generated vendored Normal file
View File

@ -0,0 +1,99 @@
# camelcase [![Build Status](https://travis-ci.org/sindresorhus/camelcase.svg?branch=master)](https://travis-ci.org/sindresorhus/camelcase)
> Convert a dash/dot/underscore/space separated string to camelCase or PascalCase: `foo-bar``fooBar`
---
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/npm-camelcase?utm_source=npm-camelcase&utm_medium=referral&utm_campaign=readme">Get professional support for 'camelcase' with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>
---
## Install
```
$ npm install camelcase
```
## Usage
```js
const camelCase = require('camelcase');
camelCase('foo-bar');
//=> 'fooBar'
camelCase('foo_bar');
//=> 'fooBar'
camelCase('Foo-Bar');
//=> 'fooBar'
camelCase('Foo-Bar', {pascalCase: true});
//=> 'FooBar'
camelCase('--foo.bar', {pascalCase: false});
//=> 'fooBar'
camelCase('foo bar');
//=> 'fooBar'
console.log(process.argv[3]);
//=> '--foo-bar'
camelCase(process.argv[3]);
//=> 'fooBar'
camelCase(['foo', 'bar']);
//=> 'fooBar'
camelCase(['__foo__', '--bar'], {pascalCase: true});
//=> 'FooBar'
```
## API
### camelCase(input, [options])
#### input
Type: `string` `string[]`
String to convert to camel case.
#### options
Type: `Object`
##### pascalCase
Type: `boolean`<br>
Default: `false`
Uppercase the first character: `foo-bar``FooBar`
## Security
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
## Related
- [decamelize](https://github.com/sindresorhus/decamelize) - The inverse of this module
- [uppercamelcase](https://github.com/SamVerschueren/uppercamelcase) - Like this module, but to PascalCase instead of camelCase
- [titleize](https://github.com/sindresorhus/titleize) - Capitalize every word in string
- [humanize-string](https://github.com/sindresorhus/humanize-string) - Convert a camelized/dasherized/underscored string into a humanized one
## License
MIT © [Sindre Sorhus](https://sindresorhus.com)

21
node_modules/chokidar/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2012 Paul Miller (https://paulmillr.com), Elan Shanker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

305
node_modules/chokidar/README.md generated vendored Normal file
View File

@ -0,0 +1,305 @@
# Chokidar [![Weekly downloads](https://img.shields.io/npm/dw/chokidar.svg)](https://github.com/paulmillr/chokidar)
> Minimal and efficient cross-platform file watching library
## Why?
There are many reasons to prefer Chokidar to raw fs.watch / fs.watchFile in 2024:
- Events are properly reported
- macOS events report filenames
- events are not reported twice
- changes are reported as add / change / unlink instead of useless `rename`
- Atomic writes are supported, using `atomic` option
- Some file editors use them
- Chunked writes are supported, using `awaitWriteFinish` option
- Large files are commonly written in chunks
- File / dir filtering is supported
- Symbolic links are supported
- Recursive watching is always supported, instead of partial when using raw events
- Includes a way to limit recursion depth
Chokidar relies on the Node.js core `fs` module, but when using
`fs.watch` and `fs.watchFile` for watching, it normalizes the events it
receives, often checking for truth by getting file stats and/or dir contents.
The `fs.watch`-based implementation is the default, which
avoids polling and keeps CPU usage down. Be advised that chokidar will initiate
watchers recursively for everything within scope of the paths that have been
specified, so be judicious about not wasting system resources by watching much
more than needed. For some cases, `fs.watchFile`, which utilizes polling and uses more resources, is used.
Made for [Brunch](https://brunch.io/) in 2012,
it is now used in [~30 million repositories](https://www.npmjs.com/browse/depended/chokidar) and
has proven itself in production environments.
**Sep 2024 update:** v4 is out! It decreases dependency count from 13 to 1, removes
support for globs, adds support for ESM / Common.js modules, and bumps minimum node.js version from v8 to v14.
Check out [upgrading](#upgrading).
## Getting started
Install with npm:
```sh
npm install chokidar
```
Use it in your code:
```javascript
import chokidar from 'chokidar';
// One-liner for current directory
chokidar.watch('.').on('all', (event, path) => {
console.log(event, path);
});
// Extended options
// ----------------
// Initialize watcher.
const watcher = chokidar.watch('file, dir, or array', {
ignored: (path, stats) => stats?.isFile() && !path.endsWith('.js'), // only watch js files
persistent: true
});
// Something to use when events are received.
const log = console.log.bind(console);
// Add event listeners.
watcher
.on('add', path => log(`File ${path} has been added`))
.on('change', path => log(`File ${path} has been changed`))
.on('unlink', path => log(`File ${path} has been removed`));
// More possible events.
watcher
.on('addDir', path => log(`Directory ${path} has been added`))
.on('unlinkDir', path => log(`Directory ${path} has been removed`))
.on('error', error => log(`Watcher error: ${error}`))
.on('ready', () => log('Initial scan complete. Ready for changes'))
.on('raw', (event, path, details) => { // internal
log('Raw event info:', event, path, details);
});
// 'add', 'addDir' and 'change' events also receive stat() results as second
// argument when available: https://nodejs.org/api/fs.html#fs_class_fs_stats
watcher.on('change', (path, stats) => {
if (stats) console.log(`File ${path} changed size to ${stats.size}`);
});
// Watch new files.
watcher.add('new-file');
watcher.add(['new-file-2', 'new-file-3']);
// Get list of actual paths being watched on the filesystem
let watchedPaths = watcher.getWatched();
// Un-watch some files.
await watcher.unwatch('new-file');
// Stop watching. The method is async!
await watcher.close().then(() => console.log('closed'));
// Full list of options. See below for descriptions.
// Do not use this example!
chokidar.watch('file', {
persistent: true,
// ignore .txt files
ignored: (file) => file.endsWith('.txt'),
// watch only .txt files
// ignored: (file, _stats) => _stats?.isFile() && !file.endsWith('.txt'),
awaitWriteFinish: true, // emit single event when chunked writes are completed
atomic: true, // emit proper events when "atomic writes" (mv _tmp file) are used
// The options also allow specifying custom intervals in ms
// awaitWriteFinish: {
// stabilityThreshold: 2000,
// pollInterval: 100
// },
// atomic: 100,
interval: 100,
binaryInterval: 300,
cwd: '.',
depth: 99,
followSymlinks: true,
ignoreInitial: false,
ignorePermissionErrors: false,
usePolling: false,
alwaysStat: false,
});
```
`chokidar.watch(paths, [options])`
* `paths` (string or array of strings). Paths to files, dirs to be watched
recursively.
* `options` (object) Options object as defined below:
#### Persistence
* `persistent` (default: `true`). Indicates whether the process
should continue to run as long as files are being watched.
#### Path filtering
* `ignored` function, regex, or path. Defines files/paths to be ignored.
The whole relative or absolute path is tested, not just filename. If a function with two arguments
is provided, it gets called twice per path - once with a single argument (the path), second
time with two arguments (the path and the
[`fs.Stats`](https://nodejs.org/api/fs.html#fs_class_fs_stats)
object of that path).
* `ignoreInitial` (default: `false`). If set to `false` then `add`/`addDir` events are also emitted for matching paths while
instantiating the watching as chokidar discovers these file paths (before the `ready` event).
* `followSymlinks` (default: `true`). When `false`, only the
symlinks themselves will be watched for changes instead of following
the link references and bubbling events through the link's path.
* `cwd` (no default). The base directory from which watch `paths` are to be
derived. Paths emitted with events will be relative to this.
#### Performance
* `usePolling` (default: `false`).
Whether to use fs.watchFile (backed by polling), or fs.watch. If polling
leads to high CPU utilization, consider setting this to `false`. It is
typically necessary to **set this to `true` to successfully watch files over
a network**, and it may be necessary to successfully watch files in other
non-standard situations. Setting to `true` explicitly on MacOS overrides the
`useFsEvents` default. You may also set the CHOKIDAR_USEPOLLING env variable
to true (1) or false (0) in order to override this option.
* _Polling-specific settings_ (effective when `usePolling: true`)
* `interval` (default: `100`). Interval of file system polling, in milliseconds. You may also
set the CHOKIDAR_INTERVAL env variable to override this option.
* `binaryInterval` (default: `300`). Interval of file system
polling for binary files.
([see list of binary extensions](https://github.com/sindresorhus/binary-extensions/blob/master/binary-extensions.json))
* `alwaysStat` (default: `false`). If relying upon the
[`fs.Stats`](https://nodejs.org/api/fs.html#fs_class_fs_stats)
object that may get passed with `add`, `addDir`, and `change` events, set
this to `true` to ensure it is provided even in cases where it wasn't
already available from the underlying watch events.
* `depth` (default: `undefined`). If set, limits how many levels of
subdirectories will be traversed.
* `awaitWriteFinish` (default: `false`).
By default, the `add` event will fire when a file first appears on disk, before
the entire file has been written. Furthermore, in some cases some `change`
events will be emitted while the file is being written. In some cases,
especially when watching for large files there will be a need to wait for the
write operation to finish before responding to a file creation or modification.
Setting `awaitWriteFinish` to `true` (or a truthy value) will poll file size,
holding its `add` and `change` events until the size does not change for a
configurable amount of time. The appropriate duration setting is heavily
dependent on the OS and hardware. For accurate detection this parameter should
be relatively high, making file watching much less responsive.
Use with caution.
* *`options.awaitWriteFinish` can be set to an object in order to adjust
timing params:*
* `awaitWriteFinish.stabilityThreshold` (default: 2000). Amount of time in
milliseconds for a file size to remain constant before emitting its event.
* `awaitWriteFinish.pollInterval` (default: 100). File size polling interval, in milliseconds.
#### Errors
* `ignorePermissionErrors` (default: `false`). Indicates whether to watch files
that don't have read permissions if possible. If watching fails due to `EPERM`
or `EACCES` with this set to `true`, the errors will be suppressed silently.
* `atomic` (default: `true` if `useFsEvents` and `usePolling` are `false`).
Automatically filters out artifacts that occur when using editors that use
"atomic writes" instead of writing directly to the source file. If a file is
re-added within 100 ms of being deleted, Chokidar emits a `change` event
rather than `unlink` then `add`. If the default of 100 ms does not work well
for you, you can override it by setting `atomic` to a custom value, in
milliseconds.
### Methods & Events
`chokidar.watch()` produces an instance of `FSWatcher`. Methods of `FSWatcher`:
* `.add(path / paths)`: Add files, directories for tracking.
Takes an array of strings or just one string.
* `.on(event, callback)`: Listen for an FS event.
Available events: `add`, `addDir`, `change`, `unlink`, `unlinkDir`, `ready`,
`raw`, `error`.
Additionally `all` is available which gets emitted with the underlying event
name and path for every event other than `ready`, `raw`, and `error`. `raw` is internal, use it carefully.
* `.unwatch(path / paths)`: Stop watching files or directories.
Takes an array of strings or just one string.
* `.close()`: **async** Removes all listeners from watched files. Asynchronous, returns Promise. Use with `await` to ensure bugs don't happen.
* `.getWatched()`: Returns an object representing all the paths on the file
system being watched by this `FSWatcher` instance. The object's keys are all the
directories (using absolute paths unless the `cwd` option was used), and the
values are arrays of the names of the items contained in each directory.
### CLI
Check out third party [chokidar-cli](https://github.com/open-cli-tools/chokidar-cli),
which allows to execute a command on each change, or get a stdio stream of change events.
## Troubleshooting
Sometimes, Chokidar runs out of file handles, causing `EMFILE` and `ENOSP` errors:
* `bash: cannot set terminal process group (-1): Inappropriate ioctl for device bash: no job control in this shell`
* `Error: watch /home/ ENOSPC`
There are two things that can cause it.
1. Exhausted file handles for generic fs operations
- Can be solved by using [graceful-fs](https://www.npmjs.com/package/graceful-fs),
which can monkey-patch native `fs` module used by chokidar: `let fs = require('fs'); let grfs = require('graceful-fs'); grfs.gracefulify(fs);`
- Can also be solved by tuning OS: `echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p`.
2. Exhausted file handles for `fs.watch`
- Can't seem to be solved by graceful-fs or OS tuning
- It's possible to start using `usePolling: true`, which will switch backend to resource-intensive `fs.watchFile`
All fsevents-related issues (`WARN optional dep failed`, `fsevents is not a constructor`) are solved by upgrading to v4+.
## Changelog
- **v4 (Sep 2024):** remove glob support and bundled fsevents. Decrease dependency count from 13 to 1. Rewrite in typescript. Bumps minimum node.js requirement to v14+
- **v3 (Apr 2019):** massive CPU & RAM consumption improvements; reduces deps / package size by a factor of 17x and bumps Node.js requirement to v8.16+.
- **v2 (Dec 2017):** globs are now posix-style-only. Tons of bugfixes.
- **v1 (Apr 2015):** glob support, symlink support, tons of bugfixes. Node 0.8+ is supported
- **v0.1 (Apr 2012):** Initial release, extracted from [Brunch](https://github.com/brunch/brunch/blob/9847a065aea300da99bd0753f90354cde9de1261/src/helpers.coffee#L66)
### Upgrading
If you've used globs before and want do replicate the functionality with v4:
```js
// v3
chok.watch('**/*.js');
chok.watch("./directory/**/*");
// v4
chok.watch('.', {
ignored: (path, stats) => stats?.isFile() && !path.endsWith('.js'), // only watch js files
});
chok.watch('./directory');
// other way
import { glob } from 'node:fs/promises';
const watcher = watch(await Array.fromAsync(glob('**/*.js')));
// unwatching
// v3
chok.unwatch('**/*.js');
// v4
chok.unwatch(await glob('**/*.js'));
```
## Also
Why was chokidar named this way? What's the meaning behind it?
>Chowkidar is a transliteration of a Hindi word meaning 'watchman, gatekeeper', चौकीदार. This ultimately comes from Sanskrit _ चतुष्क_ (crossway, quadrangle, consisting-of-four). This word is also used in other languages like Urdu as (چوکیدار) which is widely used in Pakistan and India.
## License
MIT (c) Paul Miller (<https://paulmillr.com>), see [LICENSE](LICENSE) file.

90
node_modules/chokidar/esm/handler.d.ts generated vendored Normal file
View File

@ -0,0 +1,90 @@
import type { WatchEventType, Stats, FSWatcher as NativeFsWatcher } from 'fs';
import type { FSWatcher, WatchHelper, Throttler } from './index.js';
import type { EntryInfo } from 'readdirp';
export type Path = string;
export declare const STR_DATA = "data";
export declare const STR_END = "end";
export declare const STR_CLOSE = "close";
export declare const EMPTY_FN: () => void;
export declare const IDENTITY_FN: (val: unknown) => unknown;
export declare const isWindows: boolean;
export declare const isMacos: boolean;
export declare const isLinux: boolean;
export declare const isFreeBSD: boolean;
export declare const isIBMi: boolean;
export declare const EVENTS: {
readonly ALL: "all";
readonly READY: "ready";
readonly ADD: "add";
readonly CHANGE: "change";
readonly ADD_DIR: "addDir";
readonly UNLINK: "unlink";
readonly UNLINK_DIR: "unlinkDir";
readonly RAW: "raw";
readonly ERROR: "error";
};
export type EventName = (typeof EVENTS)[keyof typeof EVENTS];
export type FsWatchContainer = {
listeners: (path: string) => void | Set<any>;
errHandlers: (err: unknown) => void | Set<any>;
rawEmitters: (ev: WatchEventType, path: string, opts: unknown) => void | Set<any>;
watcher: NativeFsWatcher;
watcherUnusable?: boolean;
};
export interface WatchHandlers {
listener: (path: string) => void;
errHandler: (err: unknown) => void;
rawEmitter: (ev: WatchEventType, path: string, opts: unknown) => void;
}
/**
* @mixin
*/
export declare class NodeFsHandler {
fsw: FSWatcher;
_boundHandleError: (error: unknown) => void;
constructor(fsW: FSWatcher);
/**
* Watch file for changes with fs_watchFile or fs_watch.
* @param path to file or dir
* @param listener on fs change
* @returns closer for the watcher instance
*/
_watchWithNodeFs(path: string, listener: (path: string, newStats?: any) => void | Promise<void>): (() => void) | undefined;
/**
* Watch a file and emit add event if warranted.
* @returns closer for the watcher instance
*/
_handleFile(file: Path, stats: Stats, initialAdd: boolean): (() => void) | undefined;
/**
* Handle symlinks encountered while reading a dir.
* @param entry returned by readdirp
* @param directory path of dir being read
* @param path of this item
* @param item basename of this item
* @returns true if no more processing is needed for this entry.
*/
_handleSymlink(entry: EntryInfo, directory: string, path: Path, item: string): Promise<boolean | undefined>;
_handleRead(directory: string, initialAdd: boolean, wh: WatchHelper, target: Path, dir: Path, depth: number, throttler: Throttler): Promise<unknown> | undefined;
/**
* Read directory to add / remove files from `@watched` list and re-read it on change.
* @param dir fs path
* @param stats
* @param initialAdd
* @param depth relative to user-supplied path
* @param target child path targeted for watch
* @param wh Common watch helpers for this path
* @param realpath
* @returns closer for the watcher instance.
*/
_handleDir(dir: string, stats: Stats, initialAdd: boolean, depth: number, target: string, wh: WatchHelper, realpath: string): Promise<(() => void) | undefined>;
/**
* Handle added file, directory, or glob pattern.
* Delegates call to _handleFile / _handleDir after checks.
* @param path to file or ir
* @param initialAdd was the file added at watch instantiation?
* @param priorWh depth relative to user-supplied path
* @param depth Child path actually targeted for watch
* @param target Child path actually targeted for watch
*/
_addToNodeFs(path: string, initialAdd: boolean, priorWh: WatchHelper | undefined, depth: number, target?: string): Promise<string | false | undefined>;
}

Some files were not shown because too many files have changed in this diff Show More