not finished
This commit is contained in:
parent
776bb227e6
commit
9cd81f73fa
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
lib/vscode
|
||||
node_modules
|
||||
dist
|
24
README.md
Normal file
24
README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# vscode-cloud
|
||||
|
||||
Run VS Code in the cloud.
|
||||
|
||||
## Contributing
|
||||
|
||||
### Getting the source
|
||||
|
||||
```
|
||||
git clone https://github.com/codercom/vscode-cloud
|
||||
```
|
||||
|
||||
### Installing dependencies
|
||||
|
||||
```
|
||||
cd vscode-cloud
|
||||
yarn
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
```
|
||||
yarn start
|
||||
```
|
19
package.json
Normal file
19
package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@coder/vscode-cloud",
|
||||
"repository": "https://github.com/codercom/vscode-cloud",
|
||||
"author": "Coder",
|
||||
"license": "TBD",
|
||||
"description": "VS Code in the cloud.",
|
||||
"scripts": {
|
||||
"vscode:clone": "mkdir -p ./lib && test -d ./lib/vscode || git clone https://github.com/Microsoft/vscode/ ./lib/vscode",
|
||||
"vscode:install": "cd ./lib/vscode && git checkout tags/1.30.1 && yarn",
|
||||
"vscode": "npm-run-all vscode:*",
|
||||
"packages:install": "cd ./packages && yarn",
|
||||
"postinstall": "npm-run-all --parallel vscode packages:install",
|
||||
"start": "webpack-dev-server --config ./webpack.config.app.js",
|
||||
"test": "cd ./packages && yarn test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"npm-run-all": "^4.1.5"
|
||||
}
|
||||
}
|
86
packages/app/src/index.html
Normal file
86
packages/app/src/index.html
Normal file
@ -0,0 +1,86 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>VS Code</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="overlay">
|
||||
<div id="logo">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 313.7 220.7">
|
||||
<g stroke="none">
|
||||
<path d="M303,96.2c-6.1,0-10.2-3.5-10.2-10.7V44c0-26.5-11.1-41.1-39.8-41.1h-13.3v28h4.1c11.3,0,16.7,6.1,16.7,17.1
|
||||
v36.7c0,15.9,4.8,22.4,15.4,25.8c-10.6,3.2-15.4,9.8-15.4,25.8c0,9.1,0,18.2,0,27.2c0,7.6,0,15-2,22.6c-2,7-5.4,13.7-10,19.5
|
||||
c-2.6,3.3-5.6,6.1-8.9,8.7v3.7h13.3c28.7,0,39.8-14.6,39.8-41.1v-41.5c0-7.4,3.9-10.7,10.2-10.7h7.6v-28h-7.4L303,96.2L303,96.2z" />
|
||||
<path d="M212.3,45.1H171c-0.9,0-1.7-0.7-1.7-1.7v-3.2c0-0.9,0.7-1.7,1.7-1.7h41.3c0.9,0,1.7,0.7,1.7,1.7v3.2
|
||||
C214,44.3,213.2,45.1,212.3,45.1z" />
|
||||
<path d="M219.2,85.1h-30c-0.9,0-1.7-0.7-1.7-1.7v-3.1c0-0.9,0.7-1.7,1.7-1.7h30c0.9,0,1.7,0.7,1.7,1.7v3.2
|
||||
C220.8,84.2,220.1,85.1,219.2,85.1z" />
|
||||
<path d="M231,65.1h-59.9c-0.9,0-1.7-0.7-1.7-1.7v-3.1c0-0.9,0.7-1.7,1.7-1.7H231c0.9,0,1.7,0.7,1.7,1.7v3.2
|
||||
C232.7,64.1,232,65.1,231,65.1z" />
|
||||
<path d="M123.4,54.1c4.1,0,8.2,0.4,12,1.3v-7.6c0-10.7,5.6-17.1,16.7-17.1h4.1v-28h-13.3c-28.7,0-39.8,14.6-39.8,41.1
|
||||
v13.7C109.5,55.4,116.3,54.1,123.4,54.1z" />
|
||||
|
||||
<path d="M243.6,155c-3-23.5-21.1-43.2-44.5-47.6c-6.5-1.3-13-1.5-19.3-0.4c-0.2,0-0.2-0.2-0.4-0.2
|
||||
c-10.2-21.3-32.1-35.4-55.8-35.4c-23.7,0-45.4,13.7-55.8,35c-0.2,0-0.2,0.2-0.4,0.2c-6.7-0.7-13.3-0.4-20,1.3
|
||||
c-23,5.6-40.4,24.8-43.6,48.2c-0.4,2.4-0.6,4.8-0.6,7c0,7,4.8,13.5,11.9,14.5c8.7,1.3,16.3-5.4,16.1-13.9c0-1.3,0-2.8,0.2-4.1
|
||||
c1.5-11.9,10.6-21.9,22.4-24.7c3.7-0.9,7.4-1.1,10.9-0.6C76,135.8,87.1,130,91.9,120c3.5-7.4,9.1-13.9,16.5-17.4
|
||||
c8.2-3.9,17.4-4.4,25.9-1.5c8.9,3.2,15.6,9.8,19.6,18.2c4.3,8.2,6.3,13.9,15.4,15c3.7,0.6,14.1,0.4,18,0.2c7.6,0,15.2,2.6,20.6,8
|
||||
c3.5,3.7,6.1,8.3,7.2,13.5c1.7,8.3-0.4,16.7-5.4,23c-3.5,4.4-8.3,7.8-13.7,9.3c-2.6,0.7-5.2,0.9-7.8,0.9c-1.5,0-3.5,0-5.9,0
|
||||
c-7.4,0-23.2,0-35,0c-8.2,0-14.6-6.5-14.6-14.6v-27.4v-26.9c0-2.2-1.9-4.1-4.1-4.1h-5.7c-11.3,0.2-20.4,12.8-20.4,26.1
|
||||
s0,48.7,0,48.7c0,14.5,11.7,26.1,26.1,26.1c0,0,64.3-0.2,65.2-0.2c14.8-1.5,28.5-9.1,37.8-20.8C241.2,185,245.5,170.2,243.6,155z" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div id="status">
|
||||
<div id="progress">
|
||||
<div id="fill"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="activitybar">
|
||||
<a href="/projects">
|
||||
<svg viewBox="0 0 39 21" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Assets" stroke="none" stroke-width="1" fill-rule="evenodd">
|
||||
<g>
|
||||
<g id="Group-19" transform="translate(1.000000, 1.500000)">
|
||||
<g id="Group" transform="translate(12.000000, 0.000000)">
|
||||
<path d="M24.2814371,7.68292683 C23.7874251,7.68292683 23.4580838,7.39329268 23.4580838,6.79878049 L23.4580838,3.38414634 C23.4580838,1.20426829 22.5598802,0 20.239521,0 L19.1616766,0 L19.1616766,2.30182927 L19.491018,2.30182927 C20.4041916,2.30182927 20.8383234,2.80487805 20.8383234,3.70426829 L20.8383234,6.72256098 C20.8383234,8.03353659 21.2275449,8.56707317 22.0808383,8.84146341 C21.2275449,9.10060976 20.8383234,9.64939024 20.8383234,10.9603659 C20.8383234,11.7073171 20.8383234,12.4542683 20.8383234,13.2012195 C20.8383234,13.8262195 20.8383234,14.4359756 20.6736527,15.0609756 C20.508982,15.6402439 20.239521,16.1890244 19.8652695,16.6615854 C19.6556886,16.9359756 19.4161677,17.1646341 19.1467066,17.3780488 L19.1467066,17.6829268 L20.2245509,17.6829268 C22.5449102,17.6829268 23.4431138,16.4786585 23.4431138,14.2987805 L23.4431138,10.8841463 C23.4431138,10.2743902 23.757485,10 24.2664671,10 L24.8802395,10 L24.8802395,7.69817073 L24.2814371,7.69817073 L24.2814371,7.68292683 Z"
|
||||
id="Shape"></path>
|
||||
<path d="M16.9610778,3.47560976 L13.6227545,3.47560976 C13.5479042,3.47560976 13.488024,3.41463415 13.488024,3.33841463 L13.488024,3.07926829 C13.488024,3.00304878 13.5479042,2.94207317 13.6227545,2.94207317 L16.9610778,2.94207317 C17.0359281,2.94207317 17.0958084,3.00304878 17.0958084,3.07926829 L17.0958084,3.33841463 C17.0958084,3.41463415 17.0359281,3.47560976 16.9610778,3.47560976 Z"
|
||||
id="Shape"></path>
|
||||
<path d="M17.5149701,6.76829268 L15.0898204,6.76829268 C15.0149701,6.76829268 14.9550898,6.70731707 14.9550898,6.63109756 L14.9550898,6.37195122 C14.9550898,6.29573171 15.0149701,6.2347561 15.0898204,6.2347561 L17.5149701,6.2347561 C17.5898204,6.2347561 17.6497006,6.29573171 17.6497006,6.37195122 L17.6497006,6.63109756 C17.6497006,6.69207317 17.5898204,6.76829268 17.5149701,6.76829268 Z"
|
||||
id="Shape"></path>
|
||||
<path d="M18.4730539,5.12195122 L13.6227545,5.12195122 C13.5479042,5.12195122 13.488024,5.06097561 13.488024,4.9847561 L13.488024,4.72560976 C13.488024,4.64939024 13.5479042,4.58841463 13.6227545,4.58841463 L18.4580838,4.58841463 C18.5329341,4.58841463 18.5928144,4.64939024 18.5928144,4.72560976 L18.5928144,4.9847561 C18.6077844,5.04573171 18.5479042,5.12195122 18.4730539,5.12195122 Z"
|
||||
id="Shape"></path>
|
||||
<path d="M9.7754491,4.22256098 C10.1047904,4.22256098 10.4341317,4.25304878 10.748503,4.32926829 L10.748503,3.70426829 C10.748503,2.82012195 11.1976048,2.30182927 12.0958084,2.30182927 L12.4251497,2.30182927 L12.4251497,0 L11.3473054,0 C9.02694611,0 8.12874251,1.20426829 8.12874251,3.38414634 L8.12874251,4.51219512 C8.65269461,4.32926829 9.20658683,4.22256098 9.7754491,4.22256098 Z"
|
||||
id="Shape"></path>
|
||||
<path d="M19.491018,12.5152439 C19.251497,10.5792683 17.7844311,8.96341463 15.8982036,8.59756098 C15.3742515,8.49085366 14.8502994,8.47560976 14.3413174,8.56707317 C14.3263473,8.56707317 14.3263473,8.55182927 14.3113772,8.55182927 C13.488024,6.79878049 11.7215569,5.6402439 9.80538922,5.6402439 C7.88922156,5.6402439 6.13772455,6.76829268 5.2994012,8.52134146 C5.28443114,8.52134146 5.28443114,8.53658537 5.26946108,8.53658537 C4.73053892,8.47560976 4.19161677,8.50609756 3.65269461,8.64329268 C1.79640719,9.10060976 0.389221557,10.6859756 0.134730539,12.6067073 C0.104790419,12.804878 0.0898203593,13.0030488 0.0898203593,13.1859756 C0.0898203593,13.7652439 0.479041916,14.2987805 1.04790419,14.375 C1.75149701,14.4817073 2.36526946,13.9329268 2.3502994,13.2317073 C2.3502994,13.125 2.3502994,13.0030488 2.36526946,12.8963415 C2.48502994,11.9207317 3.21856287,11.097561 4.17664671,10.8689024 C4.4760479,10.7926829 4.7754491,10.777439 5.05988024,10.8231707 C5.97305389,10.945122 6.87125749,10.472561 7.26047904,9.64939024 C7.54491018,9.03963415 7.99401198,8.50609756 8.59281437,8.21646341 C9.25149701,7.89634146 10,7.85060976 10.6886228,8.0945122 C11.4071856,8.35365854 11.9461078,8.90243902 12.2754491,9.58841463 C12.6197605,10.2591463 12.7844311,10.7317073 13.5179641,10.8231707 C13.8173653,10.8689024 14.6556886,10.8536585 14.9700599,10.8384146 C15.5838323,10.8384146 16.1976048,11.0518293 16.6317365,11.4939024 C16.9161677,11.7987805 17.1257485,12.179878 17.2155689,12.6067073 C17.3502994,13.2926829 17.1856287,13.9786585 16.7814371,14.4969512 C16.497006,14.8628049 16.1077844,15.1371951 15.6736527,15.2591463 C15.4640719,15.320122 15.254491,15.3353659 15.0449102,15.3353659 C14.9251497,15.3353659 14.760479,15.3353659 14.5658683,15.3353659 C13.9670659,15.3353659 12.6946108,15.3353659 11.7365269,15.3353659 C11.0778443,15.3353659 10.5538922,14.8018293 10.5538922,14.1310976 L10.5538922,11.875 L10.5538922,9.66463415 C10.5538922,9.48170732 10.4041916,9.32926829 10.2245509,9.32926829 L9.76047904,9.32926829 C8.84730539,9.3445122 8.11377246,10.3810976 8.11377246,11.4786585 C8.11377246,12.5762195 8.11377246,15.4878049 8.11377246,15.4878049 C8.11377246,16.6768293 9.05688623,17.6371951 10.2245509,17.6371951 C10.2245509,17.6371951 15.4191617,17.6219512 15.494012,17.6219512 C16.6916168,17.5 17.7994012,16.875 18.5479042,15.9146341 C19.2964072,14.9847561 19.6407186,13.7652439 19.491018,12.5152439 Z"
|
||||
id="Shape"></path>
|
||||
</g>
|
||||
<path d="M3.99099026,8.1 L10.4,8.1 L10.4,9.9 L3.99099026,9.9 L6.45459415,12.3636039 L5.18180195,13.6363961 L0.545405845,9 L5.18180195,4.3636039 L6.45459415,5.6363961 L3.99099026,8.1 Z"
|
||||
id="Combined-Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="statusbar"></div>
|
||||
<div class="message">
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const overlay = document.getElementById("overlay");
|
||||
const logo = document.getElementById("logo");
|
||||
overlay.addEventListener("mousemove", (event) => {
|
||||
const xPos = ((event.clientX - logo.offsetLeft) / 24).toFixed(2);
|
||||
const yPos = ((logo.offsetTop - event.clientY) / 24).toFixed(2);
|
||||
|
||||
logo.style.transform = `perspective(200px) rotateX(${yPos}deg) rotateY(${xPos}deg)`;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
144
packages/app/src/index.scss
Normal file
144
packages/app/src/index.scss
Normal file
@ -0,0 +1,144 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
align-items: center;
|
||||
background-color: #252526;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
justify-content: center;
|
||||
left: 0;
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transition: 150ms opacity ease;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#overlay>.message {
|
||||
color: white;
|
||||
margin-top: 10px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#overlay.error>.message {
|
||||
color: white;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
#overlay>.activitybar {
|
||||
background-color: rgb(44, 44, 44);
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
#overlay>.activitybar svg {
|
||||
fill: white;
|
||||
margin-left: 2px;
|
||||
margin-top: 2px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
#overlay.error>#status {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#overlay>.statusbar {
|
||||
background-color: rgb(0, 122, 204);
|
||||
bottom: 0;
|
||||
cursor: default;
|
||||
height: 22px;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#logo {
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
#logo>svg {
|
||||
fill: rgb(0, 122, 204);
|
||||
opacity: 1;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
#status {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 2px 10px -2px rgba(0, 0, 0, 0.75);
|
||||
color: white;
|
||||
font-size: 0.9em;
|
||||
margin-top: 15px;
|
||||
min-width: 100px;
|
||||
position: relative;
|
||||
transition: 300ms opacity ease;
|
||||
}
|
||||
|
||||
#status>#progress {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
bottom: 0;
|
||||
height: 3px;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@-moz-keyframes statusProgress {
|
||||
0% {
|
||||
background-position: 0% 50%
|
||||
}
|
||||
|
||||
50% {
|
||||
background-position: 100% 50%
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 0% 50%
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes statusProgress {
|
||||
0% {
|
||||
background-position: 0% 50%
|
||||
}
|
||||
|
||||
50% {
|
||||
background-position: 100% 50%
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 0% 50%
|
||||
}
|
||||
}
|
||||
|
||||
#status>#progress>#fill {
|
||||
animation: statusProgress 2s ease infinite;
|
||||
background-size: 400% 400%;
|
||||
background: linear-gradient(270deg, #007acc, #0016cc);
|
||||
height: 100%;
|
||||
transition: 500ms width ease;
|
||||
width: 0%;
|
||||
}
|
4
packages/disposable/package.json
Normal file
4
packages/disposable/package.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@coder/disposable",
|
||||
"main": "src/disposable.ts"
|
||||
}
|
5
packages/disposable/src/disposable.ts
Normal file
5
packages/disposable/src/disposable.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface IDisposable {
|
||||
|
||||
dispose(): void;
|
||||
|
||||
}
|
8
packages/electron-browser/package.json
Normal file
8
packages/electron-browser/package.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "@coder/electron-browser",
|
||||
"description": "A browser implementation of Electron's API.",
|
||||
"main": "src/index.ts",
|
||||
"devDependencies": {
|
||||
"electron": "^4.0.1"
|
||||
}
|
||||
}
|
68
packages/electron-browser/src/dialog.scss
Normal file
68
packages/electron-browser/src/dialog.scss
Normal file
@ -0,0 +1,68 @@
|
||||
.msgbox {
|
||||
padding-top: 25px;
|
||||
padding-left: 40px;
|
||||
padding-right: 40px;
|
||||
padding-bottom: 25px;
|
||||
background: #242424;
|
||||
-webkit-box-shadow: 0px 0px 10px -3px rgba(0,0,0,0.75);
|
||||
-moz-box-shadow: 0px 0px 10px -3px rgba(0,0,0,0.75);
|
||||
box-shadow: 0px 0px 10px -3px rgba(0,0,0,0.75);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.msgbox.input {
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.msgbox > .input {
|
||||
background: #141414;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 25px;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.msgbox > .msg {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.msgbox > .detail {
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.msgbox > .errors {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.msgbox > .errors {
|
||||
color: #f44747;
|
||||
}
|
||||
|
||||
.msgbox > .button-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.msgbox > .button-wrapper > button {
|
||||
flex: 1;
|
||||
border-radius: 2px;
|
||||
padding: 10px;
|
||||
color: white;
|
||||
background: #3d3d3d;
|
||||
border: 0px;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.msgbox > .button-wrapper > button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.msgbox > .button-wrapper > button:not(:last-child) {
|
||||
margin-right: 8px;
|
||||
}
|
194
packages/electron-browser/src/dialog.ts
Normal file
194
packages/electron-browser/src/dialog.ts
Normal file
@ -0,0 +1,194 @@
|
||||
import { IDisposable } from "@coder/disposable";
|
||||
import { Emitter } from "@coder/emitter";
|
||||
|
||||
import "./dialog.scss";
|
||||
|
||||
/**
|
||||
* Dialog options.
|
||||
*/
|
||||
export interface IDialogOptions {
|
||||
message?: string;
|
||||
detail?: string;
|
||||
buttons?: string[];
|
||||
input?: {
|
||||
value: string;
|
||||
selection?: {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface IDialogAction {
|
||||
buttonIndex?: number;
|
||||
key?: IKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pressed keys.
|
||||
*/
|
||||
export enum IKey {
|
||||
Enter = "Enter",
|
||||
Escape = "Escape",
|
||||
}
|
||||
|
||||
export class Dialog {
|
||||
|
||||
private options: IDialogOptions;
|
||||
private overlay: HTMLElement;
|
||||
private cachedActiveElement: HTMLElement;
|
||||
private input: HTMLInputElement;
|
||||
private actionEmitter: Emitter<IDialogAction>;
|
||||
private errors: HTMLElement;
|
||||
private buttons: HTMLElement[];
|
||||
|
||||
public constructor(options: IDialogOptions) {
|
||||
this.options = options;
|
||||
|
||||
this.actionEmitter = new Emitter();
|
||||
|
||||
const msgBox = document.createElement("div");
|
||||
msgBox.classList.add("msgbox");
|
||||
|
||||
if (this.options.message) {
|
||||
const messageDiv = document.createElement("div");
|
||||
messageDiv.classList.add("msg");
|
||||
messageDiv.innerText = this.options.message;
|
||||
msgBox.appendChild(messageDiv);
|
||||
}
|
||||
|
||||
if (this.options.detail) {
|
||||
const detailDiv = document.createElement("div");
|
||||
detailDiv.classList.add("detail");
|
||||
detailDiv.innerText = this.options.detail;
|
||||
msgBox.appendChild(detailDiv);
|
||||
}
|
||||
|
||||
if (this.options.input) {
|
||||
msgBox.classList.add("input");
|
||||
this.input = document.createElement("input");
|
||||
this.input.classList.add("input");
|
||||
this.input.value = this.options.input.value;
|
||||
this.input.addEventListener("keydown", (event) => {
|
||||
if (event.key === IKey.Enter) {
|
||||
event.preventDefault();
|
||||
this.actionEmitter.emit({
|
||||
buttonIndex: undefined,
|
||||
key: IKey.Enter,
|
||||
});
|
||||
}
|
||||
});
|
||||
msgBox.appendChild(this.input);
|
||||
}
|
||||
|
||||
this.errors = document.createElement("div");
|
||||
this.errors.classList.add("errors");
|
||||
msgBox.appendChild(this.errors);
|
||||
|
||||
if (this.options.buttons && this.options.buttons.length > 0) {
|
||||
this.buttons = this.options.buttons.map((buttonText, buttonIndex) => {
|
||||
const button = document.createElement("button");
|
||||
button.innerText = buttonText;
|
||||
button.addEventListener("click", () => {
|
||||
this.actionEmitter.emit({
|
||||
buttonIndex,
|
||||
key: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
return button;
|
||||
});
|
||||
|
||||
const buttonWrapper = document.createElement("div");
|
||||
buttonWrapper.classList.add("button-wrapper");
|
||||
this.buttons.forEach((b) => buttonWrapper.appendChild(b));
|
||||
msgBox.appendChild(buttonWrapper);
|
||||
}
|
||||
|
||||
|
||||
this.overlay = document.createElement("div");
|
||||
this.overlay.style.cssText = `display: flex; align-items: center; justify-content: center; top: 0; left: 0; right: 0; bottom: 0; z-index: 15; position: absolute; background: rgba(0, 0, 0, 0.4); opacity: 0; transition: 300ms opacity ease;`;
|
||||
this.overlay.appendChild(msgBox);
|
||||
|
||||
setTimeout(() => {
|
||||
this.overlay.style.opacity = "1";
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a function to be called when the user performs an action.
|
||||
*/
|
||||
public onAction(callback: (action: IDialogAction) => void): IDisposable {
|
||||
return this.actionEmitter.event(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Input value if this dialog has an input.
|
||||
*/
|
||||
public get inputValue(): string {
|
||||
return this.input ? this.input.value : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display or remove an error.
|
||||
*/
|
||||
public set error(error: string) {
|
||||
while (this.errors.lastChild) {
|
||||
this.errors.removeChild(this.errors.lastChild);
|
||||
}
|
||||
if (error) {
|
||||
const errorDiv = document.createElement("error");
|
||||
errorDiv.innerText = error;
|
||||
this.errors.appendChild(errorDiv);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the dialog.
|
||||
*/
|
||||
public show(): void {
|
||||
if (!this.cachedActiveElement) {
|
||||
this.cachedActiveElement = document.activeElement as HTMLElement;
|
||||
document.body.appendChild(this.overlay);
|
||||
document.addEventListener("keydown", this.onKeydown);
|
||||
if (this.input) {
|
||||
this.input.focus();
|
||||
if (this.options.input.selection) {
|
||||
this.input.setSelectionRange(
|
||||
this.options.input.selection.start,
|
||||
this.options.input.selection.end
|
||||
);
|
||||
}
|
||||
} else if (this.buttons) {
|
||||
this.buttons[0].focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the dialog and clean up.
|
||||
*/
|
||||
public hide(): void {
|
||||
if (this.cachedActiveElement) {
|
||||
this.overlay.remove();
|
||||
document.removeEventListener("keydown", this.onKeydown);
|
||||
this.cachedActiveElement.focus();
|
||||
this.cachedActiveElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture escape.
|
||||
*/
|
||||
private onKeydown = (event: KeyboardEvent): void => {
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.actionEmitter.emit({
|
||||
buttonIndex: undefined,
|
||||
key: IKey.Escape,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
352
packages/electron-browser/src/electron.ts
Normal file
352
packages/electron-browser/src/electron.ts
Normal file
@ -0,0 +1,352 @@
|
||||
import * as electron from "electron";
|
||||
import { EventEmitter } from "events";
|
||||
import * as fs from "fs";
|
||||
import { getFetchUrl } from "../src/coder/api";
|
||||
import { escapePath } from "../src/coder/common";
|
||||
import { wush } from "../src/coder/server";
|
||||
import { IKey, Dialog } from "./dialog";
|
||||
|
||||
(global as any).getOpenUrls = () => {
|
||||
return [];
|
||||
};
|
||||
|
||||
const oldCreateElement = document.createElement;
|
||||
|
||||
document.createElement = (tagName: string) => {
|
||||
const createElement = (tagName: string) => {
|
||||
return oldCreateElement.call(document, tagName);
|
||||
};
|
||||
|
||||
if (tagName === "webview") {
|
||||
const view = createElement("iframe") as HTMLIFrameElement;
|
||||
view.style.border = "0px";
|
||||
const frameID = Math.random().toString();
|
||||
view.addEventListener("error", (event) => {
|
||||
console.log("Got iframe error", event.error, event.message);
|
||||
});
|
||||
window.addEventListener("message", (event) => {
|
||||
if (!event.data || !event.data.id) {
|
||||
return;
|
||||
}
|
||||
if (event.data.id !== frameID) {
|
||||
return;
|
||||
}
|
||||
const e = new CustomEvent("ipc-message");
|
||||
(e as any).channel = event.data.channel;
|
||||
(e as any).args = event.data.data;
|
||||
view.dispatchEvent(e);
|
||||
});
|
||||
view.sandbox.add("allow-same-origin", "allow-scripts", "allow-popups", "allow-forms");
|
||||
Object.defineProperty(view, "preload", {
|
||||
set: (url: string) => {
|
||||
view.onload = () => {
|
||||
view.contentDocument.body.id = frameID;
|
||||
view.contentDocument.body.parentElement.style.overflow = "hidden";
|
||||
const script = document.createElement("script");
|
||||
script.src = url;
|
||||
view.contentDocument.head.appendChild(script);
|
||||
};
|
||||
},
|
||||
});
|
||||
(view as any).getWebContents = () => undefined;
|
||||
(view as any).send = (channel: string, ...args) => {
|
||||
if (args[0] && typeof args[0] === "object" && args[0].contents) {
|
||||
args[0].contents = (args[0].contents as string).replace(/"(file:\/\/[^"]*)"/g, (m) => `"${getFetchUrl(m)}"`);
|
||||
args[0].contents = (args[0].contents as string).replace(/"vscode-resource:([^"]*)"/g, (m) => `"${getFetchUrl(m)}"`);
|
||||
}
|
||||
view.contentWindow.postMessage({
|
||||
channel,
|
||||
data: args,
|
||||
id: frameID,
|
||||
}, "*");
|
||||
};
|
||||
return view;
|
||||
}
|
||||
|
||||
return createElement(tagName);
|
||||
};
|
||||
|
||||
const rendererToMainEmitter = new EventEmitter();
|
||||
const mainToRendererEmitter = new EventEmitter();
|
||||
|
||||
module.exports = {
|
||||
clipboard: {
|
||||
has: () => {
|
||||
return false;
|
||||
},
|
||||
writeText: (value: string) => {
|
||||
// Taken from https://hackernoon.com/copying-text-to-clipboard-with-javascript-df4d4988697f
|
||||
const active = document.activeElement as HTMLElement;
|
||||
const el = document.createElement('textarea'); // Create a <textarea> element
|
||||
el.value = value; // Set its value to the string that you want copied
|
||||
el.setAttribute('readonly', ''); // Make it readonly to be tamper-proof
|
||||
el.style.position = 'absolute';
|
||||
el.style.left = '-9999px'; // Move outside the screen to make it invisible
|
||||
document.body.appendChild(el); // Append the <textarea> element to the HTML document
|
||||
const selected =
|
||||
document.getSelection().rangeCount > 0 // Check if there is any content selected previously
|
||||
? document.getSelection().getRangeAt(0) // Store selection if found
|
||||
: false; // Mark as false to know no selection existed before
|
||||
el.select(); // Select the <textarea> content
|
||||
document.execCommand('copy'); // Copy - only works as a result of a user action (e.g. click events)
|
||||
document.body.removeChild(el); // Remove the <textarea> element
|
||||
if (selected) { // If a selection existed before copying
|
||||
document.getSelection().removeAllRanges(); // Unselect everything on the HTML document
|
||||
document.getSelection().addRange(selected); // Restore the original selection
|
||||
}
|
||||
active.focus();
|
||||
},
|
||||
},
|
||||
dialog: {
|
||||
showSaveDialog: (_: void, options: Electron.SaveDialogOptions, callback: (filename: string) => void): void => {
|
||||
const defaultPath = options.defaultPath || "/untitled";
|
||||
const fileIndex = defaultPath.lastIndexOf("/");
|
||||
const extensionIndex = defaultPath.lastIndexOf(".");
|
||||
const saveDialogOptions = {
|
||||
buttons: ["Cancel", "Save"],
|
||||
detail: "Enter a path for this file",
|
||||
input: {
|
||||
value: defaultPath,
|
||||
selection: {
|
||||
start: fileIndex === -1 ? 0 : fileIndex + 1,
|
||||
end: extensionIndex === -1 ? defaultPath.length : extensionIndex,
|
||||
},
|
||||
},
|
||||
message: "Save file",
|
||||
};
|
||||
|
||||
const dialog = new Dialog(saveDialogOptions);
|
||||
dialog.onAction((action) => {
|
||||
if (action.key !== IKey.Enter && action.buttonIndex !== 1) {
|
||||
dialog.hide();
|
||||
return callback(undefined);
|
||||
}
|
||||
|
||||
const filePath = dialog.inputValue.replace(/\/+$/, "");
|
||||
const split = filePath.split("/");
|
||||
const fileName = split.pop();
|
||||
const parentName = split.pop() || "/";
|
||||
if (fileName === "") {
|
||||
dialog.error = "You must enter a file name.";
|
||||
return;
|
||||
}
|
||||
|
||||
fs.stat(filePath, (error, stats) => {
|
||||
if (error && error.code === "ENOENT") {
|
||||
dialog.hide();
|
||||
callback(filePath);
|
||||
} else if (error) {
|
||||
dialog.error = error.message;
|
||||
} else if (stats.isDirectory()) {
|
||||
dialog.error = `A directory named "${fileName}" already exists.`;
|
||||
} else {
|
||||
dialog.error = undefined;
|
||||
|
||||
const confirmDialog = new Dialog({
|
||||
message: `A file named "${fileName}" already exists. Do you want to replace it?`,
|
||||
detail: `The file already exists in "${parentName}". Replacing it will overwrite its contents.`,
|
||||
buttons: ["Cancel", "Replace"],
|
||||
});
|
||||
|
||||
confirmDialog.onAction((action) => {
|
||||
if (action.buttonIndex === 1) {
|
||||
confirmDialog.hide();
|
||||
return callback(filePath);
|
||||
}
|
||||
|
||||
confirmDialog.hide();
|
||||
dialog.show();
|
||||
});
|
||||
|
||||
dialog.hide();
|
||||
confirmDialog.show();
|
||||
}
|
||||
});
|
||||
});
|
||||
dialog.show();
|
||||
},
|
||||
showOpenDialog: () => {
|
||||
console.log("Trying to show the open dialog");
|
||||
},
|
||||
showMessageBox: (_: void, options: Electron.MessageBoxOptions, callback: (button: number, checked: boolean) => void): void => {
|
||||
const dialog = new Dialog(options);
|
||||
dialog.onAction((action) => {
|
||||
dialog.hide();
|
||||
callback(action.buttonIndex, false);
|
||||
});
|
||||
dialog.show();
|
||||
},
|
||||
},
|
||||
remote: {
|
||||
dialog: {
|
||||
showOpenDialog: () => {
|
||||
console.log("Trying to remotely open");
|
||||
},
|
||||
},
|
||||
},
|
||||
webFrame: {
|
||||
getZoomFactor: () => {
|
||||
return 1;
|
||||
},
|
||||
getZoomLevel: () => {
|
||||
return 1;
|
||||
},
|
||||
setZoomLevel: () => {
|
||||
return;
|
||||
},
|
||||
},
|
||||
screen: {
|
||||
getAllDisplays: () => {
|
||||
return [{
|
||||
bounds: {
|
||||
x: 1000,
|
||||
y: 1000,
|
||||
},
|
||||
}];
|
||||
},
|
||||
},
|
||||
app: {
|
||||
isAccessibilitySupportEnabled: () => {
|
||||
return false;
|
||||
},
|
||||
setAsDefaultProtocolClient: () => {
|
||||
|
||||
},
|
||||
send: (str) => {
|
||||
console.log("APP Trying to send", str);
|
||||
//
|
||||
},
|
||||
on: () => {
|
||||
//
|
||||
},
|
||||
once: () => {
|
||||
//
|
||||
},
|
||||
},
|
||||
// ipcRenderer communicates with ipcMain
|
||||
ipcRenderer: {
|
||||
send: (str, ...args) => {
|
||||
rendererToMainEmitter.emit(str, {
|
||||
sender: module.exports.ipcMain,
|
||||
}, ...args);
|
||||
},
|
||||
on: (str, listener) => {
|
||||
mainToRendererEmitter.on(str, listener);
|
||||
},
|
||||
once: (str, listener) => {
|
||||
mainToRendererEmitter.once(str, listener);
|
||||
},
|
||||
removeListener: (str, listener) => {
|
||||
mainToRendererEmitter.removeListener(str, listener);
|
||||
},
|
||||
},
|
||||
ipcMain: {
|
||||
send: (str, ...args) => {
|
||||
mainToRendererEmitter.emit(str, {
|
||||
sender: module.exports.ipcRenderer,
|
||||
}, ...args);
|
||||
},
|
||||
on: (str, listener) => {
|
||||
rendererToMainEmitter.on(str, listener);
|
||||
},
|
||||
once: (str, listener) => {
|
||||
rendererToMainEmitter.once(str, listener);
|
||||
},
|
||||
},
|
||||
shell: {
|
||||
moveItemToTrash: async (path) => {
|
||||
const response = await wush.execute({
|
||||
command: `trash-put --trash-dir ${escapePath("~/.Trash")} ${escapePath(path)}`,
|
||||
}).done();
|
||||
return response.wasSuccessful();
|
||||
},
|
||||
},
|
||||
BrowserWindow: class {
|
||||
|
||||
public webContents = {
|
||||
on: () => {
|
||||
|
||||
},
|
||||
session: {
|
||||
webRequest: {
|
||||
onBeforeRequest: () => {
|
||||
|
||||
},
|
||||
|
||||
onBeforeSendHeaders: () => {
|
||||
|
||||
},
|
||||
|
||||
onHeadersReceived: () => {
|
||||
|
||||
},
|
||||
}
|
||||
},
|
||||
removeAllListeners: () => {
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
public static getFocusedWindow() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public isMaximized() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public isFullScreen() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public setMenuBarVisibility(visibility) {
|
||||
console.log("We are setting the menu bar to ", visibility);
|
||||
}
|
||||
|
||||
public setAutoHideMenuBar() {
|
||||
|
||||
}
|
||||
|
||||
public on() {
|
||||
|
||||
}
|
||||
|
||||
public setTitle(value: string): void {
|
||||
document.title = value;
|
||||
}
|
||||
},
|
||||
toggleFullScreen: () => {
|
||||
const doc = document as any;
|
||||
const isInFullScreen = doc.fullscreenElement
|
||||
|| doc.webkitFullscreenElement
|
||||
|| doc.mozFullScreenElement
|
||||
|| doc.msFullscreenElement;
|
||||
|
||||
const body = doc.body;
|
||||
if (!isInFullScreen) {
|
||||
if (body.requestFullscreen) {
|
||||
body.requestFullscreen();
|
||||
} else if (body.mozRequestFullScreen) {
|
||||
body.mozRequestFullScreen();
|
||||
} else if (body.webkitRequestFullScreen) {
|
||||
body.webkitRequestFullScreen();
|
||||
} else if (body.msRequestFullscreen) {
|
||||
body.msRequestFullscreen();
|
||||
}
|
||||
} else {
|
||||
if (doc.exitFullscreen) {
|
||||
doc.exitFullscreen();
|
||||
} else if (doc.webkitExitFullscreen) {
|
||||
doc.webkitExitFullscreen();
|
||||
} else if (doc.mozCancelFullScreen) {
|
||||
doc.mozCancelFullScreen();
|
||||
} else if (doc.msExitFullscreen) {
|
||||
doc.msExitFullscreen();
|
||||
}
|
||||
}
|
||||
},
|
||||
focusWindow: () => {
|
||||
console.log("focusing window");
|
||||
window.focus();
|
||||
},
|
||||
};
|
958
packages/electron-browser/yarn.lock
Normal file
958
packages/electron-browser/yarn.lock
Normal file
@ -0,0 +1,958 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@types/node@^10.12.18":
|
||||
version "10.12.18"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67"
|
||||
integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==
|
||||
|
||||
ajv@^6.5.5:
|
||||
version "6.6.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.2.tgz#caceccf474bf3fc3ce3b147443711a24063cc30d"
|
||||
integrity sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==
|
||||
dependencies:
|
||||
fast-deep-equal "^2.0.1"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ansi-regex@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
|
||||
integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
|
||||
|
||||
array-find-index@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
|
||||
integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=
|
||||
|
||||
asn1@~0.2.3:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
|
||||
dependencies:
|
||||
safer-buffer "~2.1.0"
|
||||
|
||||
assert-plus@1.0.0, assert-plus@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
||||
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
|
||||
|
||||
aws-sign2@~0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
||||
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
|
||||
|
||||
aws4@^1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
||||
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
||||
|
||||
bcrypt-pbkdf@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
||||
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
|
||||
dependencies:
|
||||
tweetnacl "^0.14.3"
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
||||
|
||||
builtin-modules@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
|
||||
integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
|
||||
|
||||
camelcase-keys@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7"
|
||||
integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc=
|
||||
dependencies:
|
||||
camelcase "^2.0.0"
|
||||
map-obj "^1.0.0"
|
||||
|
||||
camelcase@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
|
||||
integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=
|
||||
|
||||
caseless@~0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
|
||||
|
||||
code-point-at@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
||||
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
|
||||
|
||||
combined-stream@^1.0.6, combined-stream@~1.0.6:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828"
|
||||
integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
concat-stream@1.6.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
|
||||
integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^2.2.2"
|
||||
typedarray "^0.0.6"
|
||||
|
||||
core-util-is@1.0.2, core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
||||
|
||||
currently-unhandled@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
|
||||
integrity sha1-mI3zP+qxke95mmE2nddsF635V+o=
|
||||
dependencies:
|
||||
array-find-index "^1.0.1"
|
||||
|
||||
dashdash@^1.12.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
||||
integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
debug@2.6.9, debug@^2.1.3, debug@^2.2.0:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^3.0.0:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
||||
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
decamelize@^1.1.2:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
|
||||
|
||||
deep-extend@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
||||
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
|
||||
|
||||
ecc-jsbn@~0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
|
||||
integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
|
||||
dependencies:
|
||||
jsbn "~0.1.0"
|
||||
safer-buffer "^2.1.0"
|
||||
|
||||
electron-download@^4.1.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.1.tgz#02e69556705cc456e520f9e035556ed5a015ebe8"
|
||||
integrity sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg==
|
||||
dependencies:
|
||||
debug "^3.0.0"
|
||||
env-paths "^1.0.0"
|
||||
fs-extra "^4.0.1"
|
||||
minimist "^1.2.0"
|
||||
nugget "^2.0.1"
|
||||
path-exists "^3.0.0"
|
||||
rc "^1.2.1"
|
||||
semver "^5.4.1"
|
||||
sumchecker "^2.0.2"
|
||||
|
||||
electron@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-4.0.1.tgz#c41eaee9e081c2e5e4a4a4a761b7577a77d2eb18"
|
||||
integrity sha512-kBWDLn1Vq8Tm6+/HpQc8gkjX7wJyQI8v/lf2kAirfi0Q4cXh6vBjozFvV1U/9gGCbyKnIDM+m8/wpyJIjg4w7g==
|
||||
dependencies:
|
||||
"@types/node" "^10.12.18"
|
||||
electron-download "^4.1.0"
|
||||
extract-zip "^1.0.3"
|
||||
|
||||
env-paths@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0"
|
||||
integrity sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=
|
||||
|
||||
error-ex@^1.2.0:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
|
||||
integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
|
||||
dependencies:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
extend@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
||||
|
||||
extract-zip@^1.0.3:
|
||||
version "1.6.7"
|
||||
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9"
|
||||
integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=
|
||||
dependencies:
|
||||
concat-stream "1.6.2"
|
||||
debug "2.6.9"
|
||||
mkdirp "0.5.1"
|
||||
yauzl "2.4.1"
|
||||
|
||||
extsprintf@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||
integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
|
||||
|
||||
extsprintf@^1.2.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
||||
|
||||
fast-deep-equal@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
|
||||
integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
|
||||
|
||||
fast-json-stable-stringify@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
|
||||
integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
|
||||
|
||||
fd-slicer@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
|
||||
integrity sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
find-up@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
|
||||
integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=
|
||||
dependencies:
|
||||
path-exists "^2.0.0"
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
forever-agent@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
|
||||
|
||||
form-data@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
|
||||
integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.6"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
fs-extra@^4.0.1:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
|
||||
integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==
|
||||
dependencies:
|
||||
graceful-fs "^4.1.2"
|
||||
jsonfile "^4.0.0"
|
||||
universalify "^0.1.0"
|
||||
|
||||
get-stdin@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
|
||||
integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=
|
||||
|
||||
getpass@^0.1.1:
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
|
||||
integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
graceful-fs@^4.1.2, graceful-fs@^4.1.6:
|
||||
version "4.1.15"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
||||
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
|
||||
|
||||
har-schema@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
|
||||
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
|
||||
|
||||
har-validator@~5.1.0:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
|
||||
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
|
||||
dependencies:
|
||||
ajv "^6.5.5"
|
||||
har-schema "^2.0.0"
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047"
|
||||
integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==
|
||||
|
||||
http-signature@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
|
||||
integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
jsprim "^1.2.2"
|
||||
sshpk "^1.7.0"
|
||||
|
||||
indent-string@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
|
||||
integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=
|
||||
dependencies:
|
||||
repeating "^2.0.0"
|
||||
|
||||
inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||
|
||||
ini@~1.3.0:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
|
||||
|
||||
is-arrayish@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
|
||||
integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
|
||||
|
||||
is-builtin-module@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
|
||||
integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74=
|
||||
dependencies:
|
||||
builtin-modules "^1.0.0"
|
||||
|
||||
is-finite@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
|
||||
integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=
|
||||
dependencies:
|
||||
number-is-nan "^1.0.0"
|
||||
|
||||
is-fullwidth-code-point@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
|
||||
integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs=
|
||||
dependencies:
|
||||
number-is-nan "^1.0.0"
|
||||
|
||||
is-typedarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
||||
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
|
||||
|
||||
is-utf8@^0.2.0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
|
||||
integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
|
||||
|
||||
isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
||||
|
||||
isstream@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
|
||||
|
||||
jsbn@~0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
||||
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
|
||||
|
||||
json-schema-traverse@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
||||
|
||||
json-schema@0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
|
||||
integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
|
||||
|
||||
json-stringify-safe@~5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
||||
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
|
||||
|
||||
jsonfile@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
||||
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsprim@^1.2.2:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
|
||||
integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=
|
||||
dependencies:
|
||||
assert-plus "1.0.0"
|
||||
extsprintf "1.3.0"
|
||||
json-schema "0.2.3"
|
||||
verror "1.10.0"
|
||||
|
||||
load-json-file@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
|
||||
integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=
|
||||
dependencies:
|
||||
graceful-fs "^4.1.2"
|
||||
parse-json "^2.2.0"
|
||||
pify "^2.0.0"
|
||||
pinkie-promise "^2.0.0"
|
||||
strip-bom "^2.0.0"
|
||||
|
||||
loud-rejection@^1.0.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
|
||||
integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=
|
||||
dependencies:
|
||||
currently-unhandled "^0.4.1"
|
||||
signal-exit "^3.0.0"
|
||||
|
||||
map-obj@^1.0.0, map-obj@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
|
||||
integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=
|
||||
|
||||
meow@^3.1.0:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
|
||||
integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=
|
||||
dependencies:
|
||||
camelcase-keys "^2.0.0"
|
||||
decamelize "^1.1.2"
|
||||
loud-rejection "^1.0.0"
|
||||
map-obj "^1.0.1"
|
||||
minimist "^1.1.3"
|
||||
normalize-package-data "^2.3.4"
|
||||
object-assign "^4.0.1"
|
||||
read-pkg-up "^1.0.1"
|
||||
redent "^1.0.0"
|
||||
trim-newlines "^1.0.0"
|
||||
|
||||
mime-db@~1.37.0:
|
||||
version "1.37.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8"
|
||||
integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==
|
||||
|
||||
mime-types@^2.1.12, mime-types@~2.1.19:
|
||||
version "2.1.21"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96"
|
||||
integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==
|
||||
dependencies:
|
||||
mime-db "~1.37.0"
|
||||
|
||||
minimist@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
|
||||
|
||||
minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
|
||||
|
||||
mkdirp@0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
|
||||
dependencies:
|
||||
minimist "0.0.8"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||
|
||||
ms@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
||||
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
|
||||
|
||||
normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
|
||||
integrity sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==
|
||||
dependencies:
|
||||
hosted-git-info "^2.1.4"
|
||||
is-builtin-module "^1.0.0"
|
||||
semver "2 || 3 || 4 || 5"
|
||||
validate-npm-package-license "^3.0.1"
|
||||
|
||||
nugget@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0"
|
||||
integrity sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=
|
||||
dependencies:
|
||||
debug "^2.1.3"
|
||||
minimist "^1.1.0"
|
||||
pretty-bytes "^1.0.2"
|
||||
progress-stream "^1.1.0"
|
||||
request "^2.45.0"
|
||||
single-line-log "^1.1.2"
|
||||
throttleit "0.0.2"
|
||||
|
||||
number-is-nan@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
|
||||
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
|
||||
|
||||
oauth-sign@~0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
||||
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
|
||||
|
||||
object-assign@^4.0.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
||||
|
||||
object-keys@~0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336"
|
||||
integrity sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=
|
||||
|
||||
parse-json@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
|
||||
integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=
|
||||
dependencies:
|
||||
error-ex "^1.2.0"
|
||||
|
||||
path-exists@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
|
||||
integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=
|
||||
dependencies:
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
path-exists@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
|
||||
integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
|
||||
|
||||
path-type@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
|
||||
integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=
|
||||
dependencies:
|
||||
graceful-fs "^4.1.2"
|
||||
pify "^2.0.0"
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
|
||||
|
||||
performance-now@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
|
||||
|
||||
pify@^2.0.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
|
||||
|
||||
pinkie-promise@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
||||
integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o=
|
||||
dependencies:
|
||||
pinkie "^2.0.0"
|
||||
|
||||
pinkie@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
||||
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
|
||||
|
||||
pretty-bytes@^1.0.2:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84"
|
||||
integrity sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=
|
||||
dependencies:
|
||||
get-stdin "^4.0.1"
|
||||
meow "^3.1.0"
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
||||
integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
|
||||
|
||||
progress-stream@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-1.2.0.tgz#2cd3cfea33ba3a89c9c121ec3347abe9ab125f77"
|
||||
integrity sha1-LNPP6jO6OonJwSHsM0er6asSX3c=
|
||||
dependencies:
|
||||
speedometer "~0.1.2"
|
||||
through2 "~0.2.3"
|
||||
|
||||
psl@^1.1.24:
|
||||
version "1.1.31"
|
||||
resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184"
|
||||
integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==
|
||||
|
||||
punycode@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
|
||||
|
||||
punycode@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
qs@~6.5.2:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
||||
|
||||
rc@^1.2.1:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
|
||||
dependencies:
|
||||
deep-extend "^0.6.0"
|
||||
ini "~1.3.0"
|
||||
minimist "^1.2.0"
|
||||
strip-json-comments "~2.0.1"
|
||||
|
||||
read-pkg-up@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
|
||||
integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=
|
||||
dependencies:
|
||||
find-up "^1.0.0"
|
||||
read-pkg "^1.0.0"
|
||||
|
||||
read-pkg@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
|
||||
integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=
|
||||
dependencies:
|
||||
load-json-file "^1.0.0"
|
||||
normalize-package-data "^2.3.2"
|
||||
path-type "^1.0.0"
|
||||
|
||||
readable-stream@^2.2.2:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.3"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~2.0.0"
|
||||
safe-buffer "~5.1.1"
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readable-stream@~1.1.9:
|
||||
version "1.1.14"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
|
||||
integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
redent@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
|
||||
integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=
|
||||
dependencies:
|
||||
indent-string "^2.1.0"
|
||||
strip-indent "^1.0.1"
|
||||
|
||||
repeating@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
|
||||
integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=
|
||||
dependencies:
|
||||
is-finite "^1.0.0"
|
||||
|
||||
request@^2.45.0:
|
||||
version "2.88.0"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
|
||||
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
|
||||
dependencies:
|
||||
aws-sign2 "~0.7.0"
|
||||
aws4 "^1.8.0"
|
||||
caseless "~0.12.0"
|
||||
combined-stream "~1.0.6"
|
||||
extend "~3.0.2"
|
||||
forever-agent "~0.6.1"
|
||||
form-data "~2.3.2"
|
||||
har-validator "~5.1.0"
|
||||
http-signature "~1.2.0"
|
||||
is-typedarray "~1.0.0"
|
||||
isstream "~0.1.2"
|
||||
json-stringify-safe "~5.0.1"
|
||||
mime-types "~2.1.19"
|
||||
oauth-sign "~0.9.0"
|
||||
performance-now "^2.1.0"
|
||||
qs "~6.5.2"
|
||||
safe-buffer "^5.1.2"
|
||||
tough-cookie "~2.4.3"
|
||||
tunnel-agent "^0.6.0"
|
||||
uuid "^3.3.2"
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.4.1:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
|
||||
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
|
||||
|
||||
signal-exit@^3.0.0:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
||||
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
|
||||
|
||||
single-line-log@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/single-line-log/-/single-line-log-1.1.2.tgz#c2f83f273a3e1a16edb0995661da0ed5ef033364"
|
||||
integrity sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=
|
||||
dependencies:
|
||||
string-width "^1.0.1"
|
||||
|
||||
spdx-correct@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4"
|
||||
integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==
|
||||
dependencies:
|
||||
spdx-expression-parse "^3.0.0"
|
||||
spdx-license-ids "^3.0.0"
|
||||
|
||||
spdx-exceptions@^2.1.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977"
|
||||
integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==
|
||||
|
||||
spdx-expression-parse@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0"
|
||||
integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==
|
||||
dependencies:
|
||||
spdx-exceptions "^2.1.0"
|
||||
spdx-license-ids "^3.0.0"
|
||||
|
||||
spdx-license-ids@^3.0.0:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz#81c0ce8f21474756148bbb5f3bfc0f36bf15d76e"
|
||||
integrity sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==
|
||||
|
||||
speedometer@~0.1.2:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d"
|
||||
integrity sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0=
|
||||
|
||||
sshpk@^1.7.0:
|
||||
version "1.16.0"
|
||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.0.tgz#1d4963a2fbffe58050aa9084ca20be81741c07de"
|
||||
integrity sha512-Zhev35/y7hRMcID/upReIvRse+I9SVhyVre/KTJSJQWMz3C3+G+HpO7m1wK/yckEtujKZ7dS4hkVxAnmHaIGVQ==
|
||||
dependencies:
|
||||
asn1 "~0.2.3"
|
||||
assert-plus "^1.0.0"
|
||||
bcrypt-pbkdf "^1.0.0"
|
||||
dashdash "^1.12.0"
|
||||
ecc-jsbn "~0.1.1"
|
||||
getpass "^0.1.1"
|
||||
jsbn "~0.1.0"
|
||||
safer-buffer "^2.0.2"
|
||||
tweetnacl "~0.14.0"
|
||||
|
||||
string-width@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
|
||||
integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=
|
||||
dependencies:
|
||||
code-point-at "^1.0.0"
|
||||
is-fullwidth-code-point "^1.0.0"
|
||||
strip-ansi "^3.0.0"
|
||||
|
||||
string_decoder@~0.10.x:
|
||||
version "0.10.31"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
|
||||
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
|
||||
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
strip-ansi@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
|
||||
integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
|
||||
dependencies:
|
||||
ansi-regex "^2.0.0"
|
||||
|
||||
strip-bom@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
|
||||
integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=
|
||||
dependencies:
|
||||
is-utf8 "^0.2.0"
|
||||
|
||||
strip-indent@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
|
||||
integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=
|
||||
dependencies:
|
||||
get-stdin "^4.0.1"
|
||||
|
||||
strip-json-comments@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
||||
|
||||
sumchecker@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e"
|
||||
integrity sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4=
|
||||
dependencies:
|
||||
debug "^2.2.0"
|
||||
|
||||
throttleit@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf"
|
||||
integrity sha1-z+34jmDADdlpe2H90qg0OptoDq8=
|
||||
|
||||
through2@~0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f"
|
||||
integrity sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=
|
||||
dependencies:
|
||||
readable-stream "~1.1.9"
|
||||
xtend "~2.1.1"
|
||||
|
||||
tough-cookie@~2.4.3:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
|
||||
integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
|
||||
dependencies:
|
||||
psl "^1.1.24"
|
||||
punycode "^1.4.1"
|
||||
|
||||
trim-newlines@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
|
||||
integrity sha1-WIeWa7WCpFA6QetST301ARgVphM=
|
||||
|
||||
tunnel-agent@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
||||
integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||
version "0.14.5"
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
|
||||
|
||||
typedarray@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
universalify@^0.1.0:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||
|
||||
uri-js@^4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
|
||||
integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
|
||||
uuid@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
|
||||
|
||||
validate-npm-package-license@^3.0.1:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
|
||||
integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
|
||||
dependencies:
|
||||
spdx-correct "^3.0.0"
|
||||
spdx-expression-parse "^3.0.0"
|
||||
|
||||
verror@1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
|
||||
integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
core-util-is "1.0.2"
|
||||
extsprintf "^1.2.0"
|
||||
|
||||
xtend@~2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b"
|
||||
integrity sha1-bv7MKk2tjmlixJAbM3znuoe10os=
|
||||
dependencies:
|
||||
object-keys "~0.4.0"
|
||||
|
||||
yauzl@2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005"
|
||||
integrity sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=
|
||||
dependencies:
|
||||
fd-slicer "~1.0.1"
|
5
packages/ide/package.json
Normal file
5
packages/ide/package.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "@coder/ide",
|
||||
"description": "Browser-based IDE client abstraction.",
|
||||
"main": "src/index.ts"
|
||||
}
|
94
packages/ide/src/client.ts
Normal file
94
packages/ide/src/client.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import { field, logger, time, Time } from "@coder/logger";
|
||||
import { escapePath } from "@coder/node-browser";
|
||||
|
||||
export interface IClientOptions {
|
||||
mkDirs?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Client represents a general abstraction of an IDE client.
|
||||
*
|
||||
* Everything the client provides is asynchronous so you can wait on what
|
||||
* you need from it without blocking anything else.
|
||||
*
|
||||
* It also provides task management to help asynchronously load and time
|
||||
* external code.
|
||||
*/
|
||||
export class Client {
|
||||
|
||||
public readonly mkDirs: Promise<void>;
|
||||
private start: Time | undefined;
|
||||
private readonly progressElement: HTMLElement | undefined;
|
||||
private tasks: string[];
|
||||
private finishedTaskCount: number;
|
||||
|
||||
public constructor(options: IClientOptions) {
|
||||
this.tasks = [];
|
||||
this.finishedTaskCount = 0;
|
||||
this.progressElement = typeof document !== "undefined"
|
||||
? document.querySelector("#status > #progress > #fill") as HTMLElement
|
||||
: undefined;
|
||||
|
||||
this.mkDirs = this.wrapTask("Creating directories", 100, async () => {
|
||||
if (options.mkDirs && options.mkDirs.length > 0) {
|
||||
await promisify(exec)(`mkdir -p ${options.mkDirs.map(escapePath).join(" ")}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a task in some logging, timing, and progress updates. Can optionally
|
||||
* wait on other tasks which won't count towards this task's time.
|
||||
*/
|
||||
public async wrapTask<T>(description: string, duration: number, task: () => Promise<T>): Promise<T>;
|
||||
public async wrapTask<T, V>(description: string, duration: number, task: (v: V) => Promise<T>, t: Promise<V>): Promise<T>;
|
||||
public async wrapTask<T, V1, V2>(description: string, duration: number, task: (v1: V1, v2: V2) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>): Promise<T>;
|
||||
public async wrapTask<T, V1, V2, V3>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>): Promise<T>;
|
||||
public async wrapTask<T, V1, V2, V3, V4>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>): Promise<T>;
|
||||
public async wrapTask<T, V1, V2, V3, V4, V5>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4, v5: V5) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>, t5: Promise<V5>): Promise<T>;
|
||||
public async wrapTask<T, V1, V2, V3, V4, V5, V6>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4, v5: V5, v6: V6) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>, t5: Promise<V5>, t6: Promise<V6>): Promise<T>;
|
||||
public async wrapTask<T>(
|
||||
description: string, duration: number = 100, task: (...args: any[]) => Promise<T>, ...after: Array<Promise<any>> // tslint:disable-line no-any
|
||||
): Promise<T> {
|
||||
this.tasks.push(description);
|
||||
if (!this.start) {
|
||||
this.start = time(1000);
|
||||
}
|
||||
const updateProgress = (): void => {
|
||||
if (this.progressElement) {
|
||||
this.progressElement.style.width = `${Math.round((this.finishedTaskCount / (this.tasks.length + this.finishedTaskCount)) * 100)}%`;
|
||||
}
|
||||
};
|
||||
updateProgress();
|
||||
|
||||
let start: Time | undefined;
|
||||
try {
|
||||
const waitFor = await (after && after.length > 0 ? Promise.all(after) : Promise.resolve([]));
|
||||
start = time(duration);
|
||||
logger.info(description);
|
||||
const value = await task(...waitFor);
|
||||
logger.info(`Finished "${description}"`, field("duration", start));
|
||||
const index = this.tasks.indexOf(description);
|
||||
if (index !== -1) {
|
||||
this.tasks.splice(index, 1);
|
||||
}
|
||||
++this.finishedTaskCount;
|
||||
updateProgress();
|
||||
if (this.tasks.length === 0) {
|
||||
logger.info("Finished all queued tasks", field("duration", this.start), field("count", this.finishedTaskCount));
|
||||
this.start = undefined;
|
||||
}
|
||||
|
||||
return value;
|
||||
} catch (error) {
|
||||
logger.error(`Failed "${description}"`, field("duration", typeof start !== "undefined" ? start : "not started"), field("error", error));
|
||||
if (this.progressElement) {
|
||||
this.progressElement.style.backgroundColor = "red";
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
4
packages/ide/src/index.ts
Normal file
4
packages/ide/src/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./client";
|
||||
export * from "./retry";
|
||||
export * from "./upload";
|
||||
export * from "./uri";
|
344
packages/ide/src/retry.ts
Normal file
344
packages/ide/src/retry.ts
Normal file
@ -0,0 +1,344 @@
|
||||
import { logger } from "@coder/logger";
|
||||
|
||||
/**
|
||||
* Handle for a notification that allows it to be closed and updated.
|
||||
*/
|
||||
export interface INotificationHandle {
|
||||
|
||||
/**
|
||||
* Closes the notification.
|
||||
*/
|
||||
close(): void;
|
||||
|
||||
/**
|
||||
* Update the message.
|
||||
*/
|
||||
updateMessage(message: string): void;
|
||||
|
||||
/**
|
||||
* Update the buttons.
|
||||
*/
|
||||
updateButtons(buttons: INotificationButton[]): void;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification severity.
|
||||
*/
|
||||
enum Severity {
|
||||
Ignore = 0,
|
||||
Info = 1,
|
||||
Warning = 2,
|
||||
Error = 3,
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification button.
|
||||
*/
|
||||
export interface INotificationButton {
|
||||
label: string;
|
||||
run(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional notification service.
|
||||
*/
|
||||
export interface INotificationService {
|
||||
|
||||
/**
|
||||
* Show a notification.
|
||||
*/
|
||||
prompt(severity: Severity, message: string, buttons: INotificationButton[], onCancel: () => void): INotificationHandle;
|
||||
|
||||
}
|
||||
|
||||
interface IRetryItem {
|
||||
count?: number;
|
||||
delay?: number; // In seconds.
|
||||
end?: number; // In ms.
|
||||
fn(): any | Promise<any>; // tslint:disable-line no-any can have different return values
|
||||
timeout?: number | NodeJS.Timer;
|
||||
running?: boolean;
|
||||
showInNotification: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry services. Handles multiple services so when a connection drops the
|
||||
* user doesn't get a separate notification for each service.
|
||||
*
|
||||
* Attempts to restart services silently up to a maximum number of tries, then
|
||||
* starts waiting for a delay that grows exponentially with each attempt with a
|
||||
* cap on the delay. Once the delay is long enough, it will show a notification
|
||||
* to the user explaining what is happening with an option to immediately retry.
|
||||
*/
|
||||
export class Retry {
|
||||
|
||||
private items: Map<string, IRetryItem>;
|
||||
|
||||
// Times are in seconds.
|
||||
private readonly retryMinDelay = 1;
|
||||
private readonly retryMaxDelay = 10;
|
||||
private readonly maxImmediateRetries = 5;
|
||||
private readonly retryExponent = 1.5;
|
||||
private blocked: string | boolean | undefined;
|
||||
|
||||
private notificationHandle: INotificationHandle | undefined;
|
||||
private updateDelay = 1;
|
||||
private updateTimeout: number | NodeJS.Timer | undefined;
|
||||
private notificationThreshold = 3;
|
||||
|
||||
// Time in milliseconds to wait before restarting a service. (See usage below
|
||||
// for reasoning.)
|
||||
private waitDelay = 50;
|
||||
|
||||
public constructor(private notificationService?: INotificationService) {
|
||||
this.items = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set notification service.
|
||||
*/
|
||||
public setNotificationService(notificationService?: INotificationService): void {
|
||||
this.notificationService = notificationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Block retries when we know they will fail (for example when starting Wush
|
||||
* back up). If a name is passed, that service will still be allowed to retry
|
||||
* (unless we have already blocked).
|
||||
*
|
||||
* Blocking without a name will override a block with a name.
|
||||
*/
|
||||
public block(name?: string): void {
|
||||
if (!this.blocked || !name) {
|
||||
this.blocked = name || true;
|
||||
this.items.forEach((item) => {
|
||||
this.stopItem(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unblock retries and run any that are pending.
|
||||
*/
|
||||
public unblock(): void {
|
||||
this.blocked = false;
|
||||
this.items.forEach((item, name) => {
|
||||
if (item.running) {
|
||||
this.runItem(name, item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a function to retry that starts/connects to a service.
|
||||
*
|
||||
* If the function returns a promise, it will automatically be retried,
|
||||
* recover, & unblock after calling `run` once (otherwise they need to be
|
||||
* called manually).
|
||||
*/
|
||||
// tslint:disable-next-line no-any can have different return values
|
||||
public register(name: string, fn: () => any | Promise<any>, showInNotification: boolean = true): void {
|
||||
if (this.items.has(name)) {
|
||||
throw new Error(`"${name}" is already registered`);
|
||||
}
|
||||
this.items.set(name, { fn, showInNotification });
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a function to retry.
|
||||
*/
|
||||
public unregister(name: string): void {
|
||||
if (!this.items.has(name)) {
|
||||
throw new Error(`"${name}" is not registered`);
|
||||
}
|
||||
this.items.delete(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry a service.
|
||||
*/
|
||||
public run(name: string): void {
|
||||
if (!this.items.has(name)) {
|
||||
throw new Error(`"${name}" is not registered`);
|
||||
}
|
||||
|
||||
const item = this.items.get(name)!;
|
||||
if (item.running) {
|
||||
throw new Error(`"${name}" is already retrying`);
|
||||
}
|
||||
|
||||
item.running = true;
|
||||
// This timeout is for the case when the connection drops; this allows time
|
||||
// for the Wush service to come in and block everything because some other
|
||||
// services might make it here first and try to restart, which will fail.
|
||||
setTimeout(() => {
|
||||
if (this.blocked && this.blocked !== name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!item.count || item.count < this.maxImmediateRetries) {
|
||||
return this.runItem(name, item);
|
||||
}
|
||||
|
||||
if (!item.delay) {
|
||||
item.delay = this.retryMinDelay;
|
||||
} else {
|
||||
item.delay = Math.ceil(item.delay * this.retryExponent);
|
||||
if (item.delay > this.retryMaxDelay) {
|
||||
item.delay = this.retryMaxDelay;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Retrying ${name.toLowerCase()} in ${item.delay}s`);
|
||||
const itemDelayMs = item.delay * 1000;
|
||||
item.end = Date.now() + itemDelayMs;
|
||||
item.timeout = setTimeout(() => this.runItem(name, item), itemDelayMs);
|
||||
|
||||
this.updateNotification();
|
||||
}, this.waitDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset a service after a successfully recovering.
|
||||
*/
|
||||
public recover(name: string): void {
|
||||
if (!this.items.has(name)) {
|
||||
throw new Error(`"${name}" is not registered`);
|
||||
}
|
||||
|
||||
const item = this.items.get(name)!;
|
||||
if (typeof item.timeout === "undefined" && !item.running && typeof item.count !== "undefined") {
|
||||
logger.info(`Recovered connection to ${name.toLowerCase()}`);
|
||||
item.delay = undefined;
|
||||
item.count = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an item.
|
||||
*/
|
||||
private runItem(name: string, item: IRetryItem): void {
|
||||
if (!item.count) {
|
||||
item.count = 1;
|
||||
} else {
|
||||
++item.count;
|
||||
}
|
||||
|
||||
const retryCountText = item.count <= this.maxImmediateRetries
|
||||
? `[${item.count}/${this.maxImmediateRetries}]`
|
||||
: `[${item.count}]`;
|
||||
logger.info(`Retrying ${name.toLowerCase()} ${retryCountText}...`);
|
||||
|
||||
const endItem = (): void => {
|
||||
this.stopItem(item);
|
||||
item.running = false;
|
||||
};
|
||||
|
||||
try {
|
||||
const maybePromise = item.fn();
|
||||
if (maybePromise instanceof Promise) {
|
||||
maybePromise.then(() => {
|
||||
endItem();
|
||||
this.recover(name);
|
||||
if (this.blocked === name) {
|
||||
this.unblock();
|
||||
}
|
||||
}).catch(() => {
|
||||
endItem();
|
||||
this.run(name);
|
||||
});
|
||||
} else {
|
||||
endItem();
|
||||
}
|
||||
} catch (error) {
|
||||
// Prevent an exception from causing the item to never run again.
|
||||
endItem();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update, close, or show the notification.
|
||||
*/
|
||||
private updateNotification(): void {
|
||||
if (!this.notificationService) {
|
||||
return;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line no-any because NodeJS.Timer is valid.
|
||||
clearTimeout(this.updateTimeout as any);
|
||||
|
||||
const now = Date.now();
|
||||
const items = Array.from(this.items.entries()).filter(([_, item]) => {
|
||||
return item.showInNotification
|
||||
&& typeof item.end !== "undefined"
|
||||
&& item.end > now
|
||||
&& item.delay && item.delay >= this.notificationThreshold;
|
||||
}).sort((a, b) => {
|
||||
return a[1] < b[1] ? -1 : 1;
|
||||
});
|
||||
|
||||
if (items.length === 0) {
|
||||
if (this.notificationHandle) {
|
||||
this.notificationHandle.close();
|
||||
this.notificationHandle = undefined;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const join = (arr: string[]): string => {
|
||||
const last = arr.pop()!; // Assume length > 0.
|
||||
|
||||
return arr.length > 0 ? `${arr.join(", ")} and ${last}` : last;
|
||||
};
|
||||
|
||||
const servicesStr = join(items.map(([name, _]) => name.toLowerCase()));
|
||||
const message = `Lost connection to ${servicesStr}. Retrying in ${
|
||||
join(items.map(([_, item]) => `${Math.ceil((item.end! - now) / 1000)}s`))
|
||||
}.`;
|
||||
|
||||
const buttons = [{
|
||||
label: `Retry ${items.length > 1 ? "Services" : items[0][0]} Now`,
|
||||
run: (): void => {
|
||||
logger.info(`Forcing ${servicesStr} to restart now`);
|
||||
items.forEach(([name, item]) => {
|
||||
this.runItem(name, item);
|
||||
});
|
||||
this.updateNotification();
|
||||
},
|
||||
}];
|
||||
|
||||
if (!this.notificationHandle) {
|
||||
this.notificationHandle = this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
message,
|
||||
buttons,
|
||||
() => {
|
||||
this.notificationHandle = undefined;
|
||||
// tslint:disable-next-line no-any because NodeJS.Timer is valid.
|
||||
clearTimeout(this.updateTimeout as any);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
this.notificationHandle.updateMessage(message);
|
||||
this.notificationHandle.updateButtons(buttons);
|
||||
}
|
||||
|
||||
this.updateTimeout = setTimeout(() => this.updateNotification(), this.updateDelay * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop an item's timer.
|
||||
*/
|
||||
private stopItem(item: IRetryItem): void {
|
||||
// tslint:disable-next-line no-any because NodeJS.Timer is valid.
|
||||
clearTimeout(item.timeout as any);
|
||||
item.timeout = undefined;
|
||||
item.end = undefined;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const retry = new Retry();
|
367
packages/ide/src/upload.ts
Normal file
367
packages/ide/src/upload.ts
Normal file
@ -0,0 +1,367 @@
|
||||
import { exec } from "child_process";
|
||||
import { appendFile } from "fs";
|
||||
import { promisify } from "util";
|
||||
import { logger, Logger } from "@coder/logger";
|
||||
import { escapePath } from "@coder/node-browser";
|
||||
import { IURI } from "./uri";
|
||||
|
||||
/**
|
||||
* Represents an uploadable directory, so we can query for existing files once.
|
||||
*/
|
||||
interface IUploadableDirectory {
|
||||
existingFiles: string[];
|
||||
filesToUpload: Map<string, File>;
|
||||
preparePromise?: Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* There doesn't seem to be a provided type for entries, so here is an
|
||||
* incomplete version.
|
||||
*/
|
||||
interface IEntry {
|
||||
name: string;
|
||||
isFile: boolean;
|
||||
file: (cb: (file: File) => void) => void;
|
||||
createReader: () => ({
|
||||
readEntries: (cb: (entries: Array<IEntry>) => void) => void;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updatable progress.
|
||||
*/
|
||||
interface IProgress {
|
||||
|
||||
/**
|
||||
* Report progress. Progress is the completed percentage from 0 to 100.
|
||||
*/
|
||||
report(progress: number): void;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Service for reporting progress.
|
||||
*/
|
||||
interface IProgressService {
|
||||
|
||||
/**
|
||||
* Start a new progress bar that resolves & disappears when the task finishes.
|
||||
*/
|
||||
start<T>(title:string, task: (progress: IProgress) => Promise<T>): Promise<T>;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Service for notifications.
|
||||
*/
|
||||
interface INotificationService {
|
||||
|
||||
/**
|
||||
* Display an error message.
|
||||
*/
|
||||
error(error: Error): void;
|
||||
|
||||
/**
|
||||
* Ask for a decision.
|
||||
*/
|
||||
prompt(message: string, choices: string[]): Promise<string>;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles file uploads.
|
||||
*/
|
||||
export class Upload {
|
||||
|
||||
private readonly maxParallelUploads = 100;
|
||||
private readonly readSize = 32000; // ~32kb max while reading in the file.
|
||||
private readonly packetSize = 32000; // ~32kb max when writing.
|
||||
private readonly notificationService: INotificationService;
|
||||
private readonly progressService: IProgressService;
|
||||
private readonly logger: Logger;
|
||||
private readonly currentlyUploadingFiles: Map<string, File>;
|
||||
private readonly queueByDirectory: Map<string, IUploadableDirectory>;
|
||||
private progress: IProgress | undefined;
|
||||
private uploadPromise: Promise<string[]> | undefined;
|
||||
private resolveUploadPromise: (() => void) | undefined;
|
||||
private finished: number;
|
||||
private uploadedFilePaths: string[];
|
||||
private total: number;
|
||||
|
||||
public constructor(notificationService: INotificationService, progressService: IProgressService) {
|
||||
this.notificationService = notificationService;
|
||||
this.progressService = progressService;
|
||||
this.logger = logger.named("Upload");
|
||||
this.currentlyUploadingFiles = new Map();
|
||||
this.queueByDirectory = new Map();
|
||||
this.uploadedFilePaths = [];
|
||||
this.finished = 0;
|
||||
this.total = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload dropped files. This will try to upload everything it can. Errors
|
||||
* will show via notifications. If an upload operation is ongoing, the files
|
||||
* will be added to that operation.
|
||||
*/
|
||||
public async uploadDropped(event: DragEvent, uploadDir: IURI): Promise<string[]> {
|
||||
this.addDirectory(uploadDir.path);
|
||||
await this.queueFiles(event, uploadDir);
|
||||
this.logger.debug( // -1 so we don't include the uploadDir itself.
|
||||
`Uploading ${this.queueByDirectory.size - 1} directories and ${this.total} files`,
|
||||
);
|
||||
await this.prepareDirectories();
|
||||
if (!this.uploadPromise) {
|
||||
this.uploadPromise = this.progressService.start("Uploading files...", (progress) => {
|
||||
return new Promise((resolve): void => {
|
||||
this.progress = progress;
|
||||
this.resolveUploadPromise = (): void => {
|
||||
const uploaded = this.uploadedFilePaths;
|
||||
this.uploadPromise = undefined;
|
||||
this.resolveUploadPromise = undefined;
|
||||
this.uploadedFilePaths = [];
|
||||
this.finished = 0;
|
||||
this.total = 0;
|
||||
resolve(uploaded);
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
this.uploadFiles();
|
||||
|
||||
return this.uploadPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel all file uploads.
|
||||
*/
|
||||
public async cancel(): Promise<void> {
|
||||
this.currentlyUploadingFiles.clear();
|
||||
this.queueByDirectory.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create directories and get existing files.
|
||||
* On failure, show the error and remove the failed directory from the queue.
|
||||
*/
|
||||
private async prepareDirectories(): Promise<void> {
|
||||
await Promise.all(Array.from(this.queueByDirectory).map(([path, dir]) => {
|
||||
if (!dir.preparePromise) {
|
||||
dir.preparePromise = this.prepareDirectory(path, dir);
|
||||
}
|
||||
|
||||
return dir.preparePromise;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a directory and get existing files.
|
||||
* On failure, show the error and remove the directory from the queue.
|
||||
*/
|
||||
private async prepareDirectory(path: string, dir: IUploadableDirectory): Promise<void> {
|
||||
await Promise.all([
|
||||
promisify(exec)(`mkdir -p ${escapePath(path)}`).catch((error) => {
|
||||
const message = error.message.toLowerCase();
|
||||
if (message.includes("file exists")) {
|
||||
throw new Error(`Unable to create directory at ${path} because a file exists there`);
|
||||
}
|
||||
throw new Error(error.message || `Unable to upload ${path}`);
|
||||
}),
|
||||
// Only get files, so we don't show an override option that will just
|
||||
// fail anyway.
|
||||
promisify(exec)(`find ${escapePath(path)} -maxdepth 1 -not -type d`).then((stdio) => {
|
||||
dir.existingFiles = stdio.stdout.split("\n");
|
||||
}),
|
||||
]).catch((error) => {
|
||||
this.queueByDirectory.delete(path);
|
||||
this.notificationService.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload as many files as possible. When finished, resolve the upload promise.
|
||||
*/
|
||||
private uploadFiles(): void {
|
||||
const finishFileUpload = (path: string): void => {
|
||||
++this.finished;
|
||||
this.currentlyUploadingFiles.delete(path);
|
||||
this.progress!.report(Math.floor((this.finished / this.total) * 100));
|
||||
this.uploadFiles();
|
||||
};
|
||||
while (this.queueByDirectory.size > 0 && this.currentlyUploadingFiles.size < this.maxParallelUploads) {
|
||||
const [dirPath, dir] = this.queueByDirectory.entries().next().value;
|
||||
if (dir.filesToUpload.size === 0) {
|
||||
this.queueByDirectory.delete(dirPath);
|
||||
continue;
|
||||
}
|
||||
const [filePath, item] = dir.filesToUpload.entries().next().value;
|
||||
this.currentlyUploadingFiles.set(filePath, item);
|
||||
dir.filesToUpload.delete(filePath);
|
||||
this.uploadFile(filePath, item, dir.existingFiles).then(() => {
|
||||
finishFileUpload(filePath);
|
||||
}).catch((error) => {
|
||||
this.notificationService.error(error);
|
||||
finishFileUpload(filePath);
|
||||
});
|
||||
}
|
||||
if (this.queueByDirectory.size === 0 && this.currentlyUploadingFiles.size === 0) {
|
||||
this.resolveUploadPromise!();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a file.
|
||||
*/
|
||||
private async uploadFile(path: string, file: File, existingFiles: string[]): Promise<void> {
|
||||
if (existingFiles.includes(path)) {
|
||||
const choice = await this.notificationService.prompt(`${path} already exists. Overwrite?`, ["Yes", "No"]);
|
||||
if (choice !== "Yes") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await new Promise(async (resolve, reject): Promise<void> => {
|
||||
let readOffset = 0;
|
||||
const reader = new FileReader();
|
||||
const seek = (): void => {
|
||||
const slice = file.slice(readOffset, readOffset + this.readSize);
|
||||
readOffset += this.readSize;
|
||||
reader.readAsArrayBuffer(slice);
|
||||
};
|
||||
|
||||
const rm = async (): Promise<void> => {
|
||||
await promisify(exec)(`rm -f ${escapePath(path)}`);
|
||||
};
|
||||
|
||||
await rm();
|
||||
|
||||
reader.addEventListener("load", async () => {
|
||||
const buffer = new Uint8Array(reader.result as ArrayBuffer);
|
||||
let bufferOffset = 0;
|
||||
|
||||
while (bufferOffset <= buffer.length) {
|
||||
// Got canceled while sending data.
|
||||
if (!this.currentlyUploadingFiles.has(path)) {
|
||||
await rm();
|
||||
|
||||
return resolve();
|
||||
}
|
||||
const data = buffer.slice(bufferOffset, bufferOffset + this.packetSize);
|
||||
|
||||
try {
|
||||
await promisify(appendFile)(path, data);
|
||||
} catch (error) {
|
||||
await rm();
|
||||
|
||||
const message = error.message.toLowerCase();
|
||||
if (message.includes("no space")) {
|
||||
return reject(new Error("You are out of disk space"));
|
||||
} else if (message.includes("is a directory")) {
|
||||
return reject(new Error(`Unable to upload ${path} because there is a directory there`));
|
||||
}
|
||||
|
||||
return reject(new Error(error.message || `Unable to upload ${path}`));
|
||||
}
|
||||
|
||||
bufferOffset += this.packetSize;
|
||||
}
|
||||
|
||||
if (readOffset >= file.size) {
|
||||
this.uploadedFilePaths.push(path);
|
||||
|
||||
return resolve();
|
||||
}
|
||||
|
||||
seek();
|
||||
});
|
||||
|
||||
seek();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue files from a drop event. We have to get the files first; we can't do
|
||||
* it in tandem with uploading or the entries will disappear.
|
||||
*/
|
||||
private async queueFiles(event: DragEvent, uploadDir: IURI): Promise<void> {
|
||||
if (!event.dataTransfer || !event.dataTransfer.items) {
|
||||
return;
|
||||
}
|
||||
const promises: Array<Promise<void>> = [];
|
||||
for (let i = 0; i < event.dataTransfer.items.length; i++) {
|
||||
const item = event.dataTransfer.items[i];
|
||||
if (typeof item.webkitGetAsEntry === "function") {
|
||||
promises.push(this.traverseItem(item.webkitGetAsEntry(), uploadDir.fsPath).catch(this.notificationService.error));
|
||||
} else {
|
||||
const file = item.getAsFile();
|
||||
if (file) {
|
||||
this.addFile(uploadDir.fsPath, uploadDir.fsPath + "/" + file.name, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses an entry and add files to the queue.
|
||||
*/
|
||||
private async traverseItem(entry: IEntry, parentPath: string): Promise<void> {
|
||||
if (entry.isFile) {
|
||||
return new Promise<void>((resolve): void => {
|
||||
entry.file((file) => {
|
||||
this.addFile(
|
||||
parentPath,
|
||||
parentPath + "/" + file.name,
|
||||
file,
|
||||
);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
parentPath += "/" + entry.name;
|
||||
this.addDirectory(parentPath);
|
||||
|
||||
await new Promise((resolve): void => {
|
||||
const promises: Array<Promise<void>> = [];
|
||||
const dirReader = entry.createReader();
|
||||
// According to the spec, readEntries() must be called until it calls
|
||||
// the callback with an empty array.
|
||||
const readEntries = (): void => {
|
||||
dirReader.readEntries((entries) => {
|
||||
if (entries.length === 0) {
|
||||
Promise.all(promises).then(resolve).catch((error) => {
|
||||
this.notificationService.error(error);
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
promises.push(...entries.map((child) => this.traverseItem(child, parentPath)));
|
||||
readEntries();
|
||||
}
|
||||
});
|
||||
};
|
||||
readEntries();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file to the queue.
|
||||
*/
|
||||
private addFile(parentPath: string, path: string, file: File): void {
|
||||
++this.total;
|
||||
this.addDirectory(parentPath);
|
||||
this.queueByDirectory.get(parentPath)!.filesToUpload.set(path, file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a directory to the queue.
|
||||
*/
|
||||
private addDirectory(path: string): void {
|
||||
if (!this.queueByDirectory.has(path)) {
|
||||
this.queueByDirectory.set(path, {
|
||||
existingFiles: [],
|
||||
filesToUpload: new Map(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
45
packages/ide/src/uri.ts
Normal file
45
packages/ide/src/uri.ts
Normal file
@ -0,0 +1,45 @@
|
||||
export interface IURI {
|
||||
|
||||
readonly path: string;
|
||||
readonly fsPath: string;
|
||||
readonly scheme: string;
|
||||
|
||||
}
|
||||
|
||||
export interface IURIFactory {
|
||||
|
||||
/**
|
||||
* Convert the object to an instance of a real URI.
|
||||
*/
|
||||
create<T extends IURI>(uri: IURI): T;
|
||||
file(path: string): IURI;
|
||||
parse(raw: string): IURI;
|
||||
|
||||
}
|
||||
|
||||
let activeUriFactory: IURIFactory;
|
||||
|
||||
/**
|
||||
* Get the active URI factory
|
||||
*/
|
||||
export const getFactory = (): IURIFactory => {
|
||||
if (!activeUriFactory) {
|
||||
throw new Error("default uri factory not set");
|
||||
}
|
||||
|
||||
return activeUriFactory;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the active URI factory.
|
||||
*/
|
||||
export const setUriFactory = (factory: IURIFactory): void => {
|
||||
activeUriFactory = factory;
|
||||
};
|
||||
|
||||
export interface IUriSwitcher {
|
||||
|
||||
strip(uri: IURI): IURI;
|
||||
prepend(uri: IURI): IURI;
|
||||
|
||||
}
|
28
packages/logger/README.md
Normal file
28
packages/logger/README.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Logger
|
||||
|
||||
Beautiful client logging inspired by https://github.com/uber-go/zap.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```javascript
|
||||
import { field, logger } from "@coder/logger";
|
||||
|
||||
logger.info("Loading container",
|
||||
field("container_id", container.id_str),
|
||||
field("organization_id", organization.id_str));
|
||||
```
|
||||
|
||||
## Formatting
|
||||
|
||||
By default the logger uses a different formatter depending on whether it detects
|
||||
it is running in the browser or not. A custom formatter can be set:
|
||||
|
||||
```javascript
|
||||
import { logger, Formatter } from "@coder/logger";
|
||||
|
||||
class MyFormatter extends Formatter {
|
||||
// implementation ...
|
||||
}
|
||||
|
||||
logger.formatter = new MyFormatter();
|
||||
```
|
5
packages/logger/package.json
Normal file
5
packages/logger/package.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "@coder/logger",
|
||||
"description": "Beautiful client logging inspired by https://github.com/uber-go/zap.",
|
||||
"main": "src/index.ts"
|
||||
}
|
1
packages/logger/src/index.ts
Normal file
1
packages/logger/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./logger";
|
16
packages/logger/src/logger.test.ts
Normal file
16
packages/logger/src/logger.test.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { field, logger, BrowserFormatter, Time } from "./logger";
|
||||
|
||||
describe("Logger", () => {
|
||||
it("should use server formatter", () => {
|
||||
logger.info("test", field("key", "value"), field("time", new Time(100, Date.now())));
|
||||
logger.named("name").debug("test name");
|
||||
logger.named("another name").warn("another test name");
|
||||
});
|
||||
|
||||
it("should use browser formatter", () => {
|
||||
logger.formatter = new BrowserFormatter();
|
||||
logger.info("test", field("key", "value"), field("time", new Time(100, Date.now())));
|
||||
logger.named("name").debug("test name");
|
||||
logger.named("another name").warn("another test name");
|
||||
});
|
||||
});
|
367
packages/logger/src/logger.ts
Normal file
367
packages/logger/src/logger.ts
Normal file
@ -0,0 +1,367 @@
|
||||
/**
|
||||
* A field to log.
|
||||
*/
|
||||
export class Field<T> {
|
||||
|
||||
public constructor(
|
||||
public readonly identifier: string,
|
||||
public readonly value: T,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Convert field to JSON.
|
||||
*/
|
||||
public toJSON(): object {
|
||||
return {
|
||||
identifier: this.identifier,
|
||||
value: this.value,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the time something takes.
|
||||
*/
|
||||
export class Time {
|
||||
|
||||
public constructor(
|
||||
public readonly expected: number,
|
||||
public readonly ms: number,
|
||||
) { }
|
||||
|
||||
}
|
||||
|
||||
export type FieldArray = Array<Field<any>>; // tslint:disable-line no-any
|
||||
|
||||
/**
|
||||
* Creates a time field
|
||||
*/
|
||||
export const time = (expected: number): Time => {
|
||||
return new Time(expected, Date.now());
|
||||
};
|
||||
|
||||
export const field = <T>(name: string, value: T): Field<T> => {
|
||||
return new Field(name, value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hashes a string.
|
||||
*/
|
||||
const djb2 = (str: string): number => {
|
||||
let hash = 5381;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash = ((hash << 5) + hash) + str.charCodeAt(i); /* hash * 33 + c */
|
||||
}
|
||||
|
||||
return hash;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert rgb to hex.
|
||||
*/
|
||||
const rgbToHex = (r: number, g: number, b: number): string => {
|
||||
const integer = ((Math.round(r) & 0xFF) << 16)
|
||||
+ ((Math.round(g) & 0xFF) << 8)
|
||||
+ (Math.round(b) & 0xFF);
|
||||
|
||||
const str = integer.toString(16);
|
||||
|
||||
return "#" + "000000".substring(str.length) + str;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert fully-formed hex to rgb.
|
||||
*/
|
||||
const hexToRgb = (hex: string): [number, number, number] => {
|
||||
const integer = parseInt(hex.substr(1), 16);
|
||||
|
||||
return [
|
||||
(integer >> 16) & 0xFF,
|
||||
(integer >> 8) & 0xFF,
|
||||
integer & 0xFF,
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a deterministic color from a string using hashing.
|
||||
*/
|
||||
const hashStringToColor = (str: string): string => {
|
||||
const hash = djb2(str);
|
||||
|
||||
return rgbToHex(
|
||||
(hash & 0xFF0000) >> 16,
|
||||
(hash & 0x00FF00) >> 8,
|
||||
hash & 0x0000FF,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* This formats & builds text for logging.
|
||||
* It should only be used to build one log item at a time since it stores the
|
||||
* currently built items and appends to that.
|
||||
*/
|
||||
export abstract class Formatter {
|
||||
|
||||
protected format: string = "";
|
||||
protected args: string[] = [];
|
||||
|
||||
/**
|
||||
* Add a tag.
|
||||
*/
|
||||
public abstract tag(name: string, color: string): void;
|
||||
|
||||
/**
|
||||
* Add string or arbitrary variable.
|
||||
*/
|
||||
public abstract push(arg: string, color?: string, weight?: string): void;
|
||||
public abstract push(arg: any): void; // tslint:disable-line no-any
|
||||
|
||||
/**
|
||||
* Flush out the built arguments.
|
||||
*/
|
||||
public flush(): any[] { // tslint:disable-line no-any
|
||||
const args = [this.format, ...this.args];
|
||||
this.format = "";
|
||||
this.args = [];
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the format string for the value type.
|
||||
*/
|
||||
protected getType(arg: any): string { // tslint:disable-line no-any
|
||||
switch (typeof arg) {
|
||||
case "object":
|
||||
return "%o";
|
||||
case "number":
|
||||
return "%d";
|
||||
default:
|
||||
return "%s";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Browser formatter.
|
||||
*/
|
||||
export class BrowserFormatter extends Formatter {
|
||||
|
||||
public tag(name: string, color:string): void {
|
||||
this.format += `%c ${name} `;
|
||||
this.args.push(
|
||||
`border: 1px solid #222; background-color: ${color}; padding-top: 1px;`
|
||||
+ " padding-bottom: 1px; font-size: 12px; font-weight: bold; color: white;"
|
||||
+ (name.length === 4 ? "padding-left: 3px; padding-right: 4px;" : ""),
|
||||
);
|
||||
}
|
||||
|
||||
public push(arg: any, color: string = "inherit", weight: string = "normal"): void { // tslint:disable-line no-any
|
||||
if (color || weight) {
|
||||
this.format += "%c";
|
||||
this.args.push(
|
||||
(color ? `color: ${color};` : "") +
|
||||
(weight ? `font-weight: ${weight};` : ""),
|
||||
);
|
||||
}
|
||||
this.format += this.getType(arg);
|
||||
this.args.push(arg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Server (Node) formatter.
|
||||
*/
|
||||
export class ServerFormatter extends Formatter {
|
||||
|
||||
public tag(name: string, color: string): void {
|
||||
const [r, g, b] = hexToRgb(color);
|
||||
this.format += "\u001B[1m";
|
||||
this.format += `\u001B[48;2;${r};${g};${b}m ${name} \u001B[0m`;
|
||||
}
|
||||
|
||||
public push(arg: any, color?: string, weight?: string): void { // tslint:disable-line no-any
|
||||
if (weight === "bold") {
|
||||
this.format += "\u001B[1m";
|
||||
}
|
||||
if (color) {
|
||||
const [r, g, b] = hexToRgb(color);
|
||||
this.format += `\u001B[38;2;${r};${g};${b}m`;
|
||||
}
|
||||
this.format += this.getType(arg);
|
||||
if (weight || color) {
|
||||
this.format += "\u001B[0m";
|
||||
}
|
||||
this.args.push(arg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for logging.
|
||||
*/
|
||||
export class Logger {
|
||||
|
||||
private readonly nameColor?: string;
|
||||
private muted: boolean;
|
||||
|
||||
public constructor(
|
||||
private _formatter: Formatter,
|
||||
private readonly name?: string,
|
||||
private readonly defaultFields?: FieldArray,
|
||||
) {
|
||||
this.muted = false;
|
||||
if (name) {
|
||||
this.nameColor = hashStringToColor(name);
|
||||
}
|
||||
}
|
||||
|
||||
public set formatter(formatter: Formatter) {
|
||||
this._formatter = formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supresses all output
|
||||
*/
|
||||
public mute(): void {
|
||||
this.muted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs information.
|
||||
*/
|
||||
public info(msg: string, ...fields: FieldArray): void {
|
||||
this.handle({
|
||||
type: "info",
|
||||
message: msg,
|
||||
fields,
|
||||
tagColor: "#008FBF",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a warning.
|
||||
*/
|
||||
public warn(msg: string, ...fields: FieldArray): void {
|
||||
this.handle({
|
||||
type: "warn",
|
||||
message: msg,
|
||||
fields,
|
||||
tagColor: "#919E00",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a debug message.
|
||||
*/
|
||||
public debug(msg: string, ...fields: FieldArray): void {
|
||||
this.handle({
|
||||
type: "debug",
|
||||
message: msg,
|
||||
fields,
|
||||
tagColor: "#84009E",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs an error.
|
||||
*/
|
||||
public error(msg: string, ...fields: FieldArray): void {
|
||||
this.handle({
|
||||
type: "error",
|
||||
message: msg,
|
||||
fields,
|
||||
tagColor: "#B00000",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sub-logger with a name.
|
||||
* Each name is deterministically generated a color.
|
||||
*/
|
||||
public named(name: string, ...fields: FieldArray): Logger {
|
||||
const l = new Logger(this._formatter, name, fields);
|
||||
if (this.muted) {
|
||||
l.mute();
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a message.
|
||||
*/
|
||||
private handle(options: {
|
||||
type: "info" | "warn" | "debug" | "error";
|
||||
message: string;
|
||||
fields?: FieldArray;
|
||||
tagColor: string;
|
||||
}): void {
|
||||
if (this.muted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const passedFields = options.fields || [];
|
||||
const fields = this.defaultFields
|
||||
? passedFields.concat(this.defaultFields)
|
||||
: passedFields;
|
||||
|
||||
const now = Date.now();
|
||||
let times: Array<Field<Time>> = [];
|
||||
const hasFields = fields && fields.length > 0;
|
||||
if (hasFields) {
|
||||
times = fields.filter((f) => f.value instanceof Time);
|
||||
}
|
||||
|
||||
// Format is:
|
||||
// [TAG] [NAME?] MESSAGE TIME?
|
||||
// field1 (type)?: value
|
||||
// field2 (type)?: value
|
||||
this._formatter.tag(options.type.toUpperCase(), options.tagColor);
|
||||
if (this.name && this.nameColor) {
|
||||
this._formatter.push(" ");
|
||||
this._formatter.tag(this.name.toUpperCase(), this.nameColor);
|
||||
}
|
||||
this._formatter.push(" " + options.message);
|
||||
if (times.length > 0) {
|
||||
times.forEach((time) => {
|
||||
const diff = now - time.value.ms;
|
||||
const expPer = diff / time.value.expected;
|
||||
const min = 125 * (1 - expPer);
|
||||
const max = 125 + min;
|
||||
const green = expPer < 1 ? max : min;
|
||||
const red = expPer >= 1 ? max : min;
|
||||
this._formatter.push(` ${time.identifier}=`, "#3794ff");
|
||||
this._formatter.push(`${diff}ms`, rgbToHex(red > 0 ? red : 0, green > 0 ? green : 0, 0));
|
||||
});
|
||||
}
|
||||
|
||||
// tslint:disable no-console
|
||||
if (hasFields) {
|
||||
console.groupCollapsed(...this._formatter.flush());
|
||||
fields.forEach((field) => {
|
||||
this._formatter.push(field.identifier, "#3794ff", "bold");
|
||||
if (typeof field.value !== "undefined" && field.value.constructor && field.value.constructor.name) {
|
||||
this._formatter.push(` (${field.value.constructor.name})`);
|
||||
}
|
||||
this._formatter.push(": ");
|
||||
this._formatter.push(field.value);
|
||||
console.log(...this._formatter.flush());
|
||||
});
|
||||
console.groupEnd();
|
||||
} else {
|
||||
console.log(...this._formatter.flush());
|
||||
}
|
||||
// tslint:enable no-console
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const logger = new Logger(
|
||||
typeof process === "undefined" || typeof process.stdout === "undefined"
|
||||
? new BrowserFormatter()
|
||||
: new ServerFormatter(),
|
||||
);
|
4
packages/logger/yarn.lock
Normal file
4
packages/logger/yarn.lock
Normal file
@ -0,0 +1,4 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
5
packages/node-browser/package.json
Normal file
5
packages/node-browser/package.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "@coder/node-browser",
|
||||
"description": "A browser implementation of async Node APIs.",
|
||||
"main": "src/index.ts"
|
||||
}
|
185
packages/node-browser/src/child_process.ts
Normal file
185
packages/node-browser/src/child_process.ts
Normal file
@ -0,0 +1,185 @@
|
||||
import * as cp from "child_process";
|
||||
import * as stream from "stream";
|
||||
import * as events from "events";
|
||||
import * as net from "net";
|
||||
import { wush, Session } from "@coder/wush";
|
||||
import { useBuffer, throwUnimplementedError, throwSyncError } from "./util";
|
||||
|
||||
/**
|
||||
* Readable stream.
|
||||
*/
|
||||
class Readable extends stream.Readable {
|
||||
|
||||
/**
|
||||
* Read a chunk.
|
||||
*/
|
||||
public _read(_size: number): void {
|
||||
// There is nothing to actually read.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of ChildProcess for the browser.
|
||||
*/
|
||||
class ChildProcess extends events.EventEmitter implements cp.ChildProcess {
|
||||
|
||||
public connected: boolean = true;
|
||||
public killed: boolean = false;
|
||||
public pid = 0;
|
||||
public stdin: stream.Writable;
|
||||
public stdout: Readable;
|
||||
public stderr: Readable;
|
||||
public stdio: [stream.Writable, stream.Readable, stream.Readable];
|
||||
|
||||
private emitter = new events.EventEmitter();
|
||||
|
||||
public constructor(private session: Session) {
|
||||
super();
|
||||
|
||||
this.emitter = new events.EventEmitter();
|
||||
this.stdin = new stream.Writable();
|
||||
this.stdin._write = (
|
||||
chunk: any, // tslint:disable-line no-any so we can match the Node API.
|
||||
_encoding: string,
|
||||
callback: (error?: Error) => void,
|
||||
): void => {
|
||||
session.sendStdin(chunk.toString());
|
||||
callback();
|
||||
};
|
||||
this.stdout = new Readable();
|
||||
this.stderr = new Readable();
|
||||
this.stdio = [this.stdin, this.stdout, this.stderr];
|
||||
|
||||
session.onDone((exitCode) => {
|
||||
this.emitter.emit("exit", exitCode);
|
||||
});
|
||||
|
||||
session.onDisconnect(() => {
|
||||
this.emitter.emit("exit", -1);
|
||||
});
|
||||
|
||||
session.onStdout((data) => {
|
||||
this.stdout.emit("data", data);
|
||||
});
|
||||
|
||||
session.onStderr((data) => {
|
||||
this.stderr.emit("data", data);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill the session.
|
||||
*/
|
||||
public kill(): void {
|
||||
this.session.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Not implemented.
|
||||
*/
|
||||
public disconnect(): void {
|
||||
throwUnimplementedError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Not implemented.
|
||||
*/
|
||||
public ref(): void {
|
||||
throwUnimplementedError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Not implemented.
|
||||
*/
|
||||
public unref(): void {
|
||||
throwUnimplementedError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Not implemented.
|
||||
*/
|
||||
public send(
|
||||
_message: any, // tslint:disable-line no-any so we can match the Node API.
|
||||
_sendHandle?: net.Socket | net.Server | ((error: Error) => void),
|
||||
_options?: cp.MessageOptions | ((error: Error) => void),
|
||||
_callback?: (error: Error) => void,
|
||||
): boolean {
|
||||
throw throwUnimplementedError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add event listener.
|
||||
*/
|
||||
public on(
|
||||
eventName: string,
|
||||
callback: (...args: any[]) => void, // tslint:disable-line no-any so we can match the Node API.
|
||||
): this {
|
||||
this.emitter.on(eventName, callback);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// tslint:disable only-arrow-functions
|
||||
function exec(
|
||||
command: string,
|
||||
options?: { encoding?: BufferEncoding | string | "buffer" | null } & cp.ExecOptions | null | ((error: Error | null, stdout: string, stderr: string) => void) | ((error: Error | null, stdout: Buffer, stderr: Buffer) => void),
|
||||
callback?: ((error: Error | null, stdout: string, stderr: string) => void) | ((error: Error | null, stdout: Buffer, stderr: Buffer) => void),
|
||||
): cp.ChildProcess {
|
||||
const process = new ChildProcess(wush.execute({ command }));
|
||||
|
||||
let stdout = "";
|
||||
process.stdout.on("data", (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
let stderr = "";
|
||||
process.stderr.on("data", (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
process.on("exit", (exitCode) => {
|
||||
const error = exitCode !== 0 ? new Error(stderr) : null;
|
||||
if (typeof options === "function") {
|
||||
callback = options;
|
||||
}
|
||||
// @ts-ignore not sure how to make this work.
|
||||
callback(
|
||||
error,
|
||||
useBuffer(options) ? Buffer.from(stdout) : stdout,
|
||||
useBuffer(options) ? Buffer.from(stderr) : stderr,
|
||||
);
|
||||
});
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
function fork(modulePath: string): cp.ChildProcess {
|
||||
return new ChildProcess(wush.execute({
|
||||
command: `node ${modulePath}`,
|
||||
}));
|
||||
}
|
||||
|
||||
function spawn(_command: string, _args?: ReadonlyArray<string>, _options?: cp.SpawnOptions): cp.ChildProcess {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
// tslint:enable only-arrow-functions
|
||||
|
||||
// To satisfy the types.
|
||||
// tslint:disable no-any
|
||||
exec.__promisify__ = undefined as any;
|
||||
// tslint:enable no-any
|
||||
|
||||
const exp: typeof cp = {
|
||||
exec,
|
||||
execFile: throwUnimplementedError,
|
||||
execFileSync: throwSyncError,
|
||||
execSync: throwSyncError,
|
||||
fork,
|
||||
spawn,
|
||||
spawnSync: throwSyncError,
|
||||
};
|
||||
|
||||
export = exp;
|
1
packages/node-browser/src/empty.ts
Normal file
1
packages/node-browser/src/empty.ts
Normal file
@ -0,0 +1 @@
|
||||
module.exports = {};
|
715
packages/node-browser/src/fs.ts
Normal file
715
packages/node-browser/src/fs.ts
Normal file
@ -0,0 +1,715 @@
|
||||
import { ChildProcess } from "child_process";
|
||||
import * as fs from "fs";
|
||||
import { EventEmitter } from "events";
|
||||
import { promisify } from "util";
|
||||
import { Writable } from "stream";
|
||||
import { exec } from "./child_process";
|
||||
import {
|
||||
bashCommand, throwUnimplementedError, throwSyncError, escapePath,
|
||||
useBuffer, NewlineInputBuffer, Queue,
|
||||
} from "./util";
|
||||
|
||||
/**
|
||||
* An open file.
|
||||
*/
|
||||
interface IOpenFile {
|
||||
readonly path: fs.PathLike;
|
||||
position: number | undefined;
|
||||
}
|
||||
|
||||
type ReaddirCallback = (error?: NodeJS.ErrnoException, files?: string[]) => void;
|
||||
|
||||
/**
|
||||
* Queue for readdir.
|
||||
*/
|
||||
class ReaddirQueue extends Queue<ReaddirCallback> {
|
||||
|
||||
public async run(items: Map<string, ReaddirCallback[]>): Promise<void> {
|
||||
const keys = Array.from(items.keys());
|
||||
try {
|
||||
const stdio = await promisify(exec)(`bash -c '${keys.map((key) => `cd ${escapePath(key)} && ls -1a; echo;`).join(" ")}'`);
|
||||
stdio.stdout.trim().split("\n\n").forEach((split, index) => {
|
||||
const path = keys[index];
|
||||
const cbs = items.get(path);
|
||||
if (split.indexOf("does not exist") !== -1) {
|
||||
cbs.forEach((cb) => {
|
||||
cb({
|
||||
code: "ENOENT",
|
||||
message: "No such file or directory " + path,
|
||||
name: "Not found",
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const files = split.trim().split("\n");
|
||||
cbs.forEach((cb) => {
|
||||
cb(undefined, files.filter((f) => f !== "." && f !== ".."));
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
items.forEach((cbs) => cbs.forEach((cb) => cb(new Error("failed to ls"))));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type StatCallback = (error?: NodeJS.ErrnoException, stats?: fs.Stats) => void;
|
||||
|
||||
/**
|
||||
* Queue for stat.
|
||||
*/
|
||||
class StatQueue extends Queue<StatCallback> {
|
||||
|
||||
public constructor() {
|
||||
super(100);
|
||||
}
|
||||
|
||||
public async run(items: Map<string, StatCallback[]>): Promise<void> {
|
||||
try {
|
||||
const stats = await this.stat(Array.from(items.keys()));
|
||||
items.forEach((callbacks, path) => {
|
||||
if (stats.has(path)) {
|
||||
callbacks.forEach((cb) => {
|
||||
cb(undefined, stats.get(path));
|
||||
});
|
||||
} else {
|
||||
callbacks.forEach((cb) => {
|
||||
cb({
|
||||
code: "ENOENT",
|
||||
message: "No such file or directory " + path,
|
||||
name: "Not found",
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
items.forEach((callbacks) => {
|
||||
callbacks.forEach((cb) => {
|
||||
cb({
|
||||
code: "ECMDFAIL",
|
||||
message: "failed to stat",
|
||||
name: "failed to stat",
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform stat on multiple paths. Invalid files are ignored.
|
||||
*/
|
||||
private async stat(paths: string[]): Promise<Map<string, fs.Stats>> {
|
||||
const map = new Map<string, fs.Stats>();
|
||||
const pathsStr = paths.map(escapePath).join(" ");
|
||||
const resp = await promisify(exec)(
|
||||
`bash -c "stat ${pathsStr} -c \\\\'%n\\\\',%s,%F,%Y,%a,%g,%u,%X,%W,%d,%i,%b,%B,%Z,%h,%t"`,
|
||||
);
|
||||
resp.stdout.split("\n").forEach((stat) => {
|
||||
const matches = stat.trim().match(/(^'.*'|[^',\s]+)(?=\s*,|\s*$)/g);
|
||||
if (!matches || matches.length < 16) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = matches[0].substring(1, matches[0].length -1);
|
||||
const size = parseInt(matches[1], 10);
|
||||
const fileType = matches[2];
|
||||
const mtime = new Date(parseInt(matches[3], 10) * 1000);
|
||||
const mode = parseInt(matches[4], 10);
|
||||
const gid = parseInt(matches[5], 10);
|
||||
const uid = parseInt(matches[6], 10);
|
||||
const atime = new Date(parseInt(matches[7], 10) * 1000);
|
||||
const birthtime = new Date(parseInt(matches[8], 10) * 1000);
|
||||
const dev = parseInt(matches[9], 10);
|
||||
const ino = parseInt(matches[10], 10);
|
||||
const blocks = parseInt(matches[11], 10);
|
||||
const blksize = parseInt(matches[12], 10);
|
||||
const ctime = new Date(parseInt(matches[13], 10) * 1000);
|
||||
const nlink = parseInt(matches[14], 10);
|
||||
const rdev = parseInt(matches[15], 10);
|
||||
|
||||
map.set(name, {
|
||||
atime: atime,
|
||||
atimeMs: atime.getTime(),
|
||||
birthtime,
|
||||
birthtimeMs: birthtime.getTime(),
|
||||
blksize,
|
||||
blocks,
|
||||
ctime,
|
||||
ctimeMs: ctime.getTime(),
|
||||
dev,
|
||||
gid,
|
||||
ino,
|
||||
isBlockDevice: (): boolean => fileType === "block special file",
|
||||
isCharacterDevice: (): boolean => fileType === "character special file",
|
||||
isDirectory: (): boolean => fileType === "directory",
|
||||
isFIFO: (): boolean => fileType === "fifo",
|
||||
isFile: (): boolean => fileType === "regular file",
|
||||
isSocket: (): boolean => fileType === "socket",
|
||||
isSymbolicLink: (): boolean => fileType === "symbolic link",
|
||||
mode,
|
||||
mtime,
|
||||
mtimeMs: mtime.getTime(),
|
||||
nlink,
|
||||
rdev,
|
||||
size,
|
||||
uid,
|
||||
});
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type ReadFileCallback = (err: NodeJS.ErrnoException, content: string) => void;
|
||||
|
||||
/**
|
||||
* Queue for readFile.
|
||||
*/
|
||||
class ReadFileQueue extends Queue<ReadFileCallback> {
|
||||
|
||||
public constructor() {
|
||||
super(100);
|
||||
}
|
||||
|
||||
public async run(items: Map<string, ReadFileCallback[]>): Promise<void> {
|
||||
try {
|
||||
throwUnimplementedError();
|
||||
} catch (error) {
|
||||
items.forEach((cbs) => cbs.forEach((cb) => cb(error, "")));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Watcher extends EventEmitter implements fs.FSWatcher {
|
||||
|
||||
public constructor(private readonly process: ChildProcess) {
|
||||
super();
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.process.kill();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class WriteStream extends Writable implements fs.WriteStream {
|
||||
|
||||
public path: string;
|
||||
|
||||
private process: ChildProcess;
|
||||
|
||||
public constructor(path: fs.PathLike) {
|
||||
super();
|
||||
|
||||
this.path = path.toString();
|
||||
this.process = exec(`cat > ${escapePath(this.path)}`);
|
||||
setTimeout(() => {
|
||||
// Set timeout so listeners have time to register.
|
||||
this.emit("open");
|
||||
}, 0);
|
||||
this.process.on("exit", () => {
|
||||
this.emit("close");
|
||||
});
|
||||
}
|
||||
|
||||
public get bytesWritten(): number {
|
||||
throw throwUnimplementedError();
|
||||
}
|
||||
|
||||
// tslint:disable-next-line no-any
|
||||
public _write(chunk: any, _encoding: string, callback: () => void): void {
|
||||
this.process.stdin.write(chunk);
|
||||
callback();
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.process.kill();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Used to identify files by descriptor.
|
||||
let lastFileDescriptor = 0;
|
||||
const readdirQueue = new ReaddirQueue();
|
||||
const readFileQueue = new ReadFileQueue();
|
||||
const statQueue = new StatQueue();
|
||||
const openFiles = new Map<number, IOpenFile>();
|
||||
|
||||
// tslint:disable only-arrow-functions
|
||||
// A common pattern is to exec and call the callback with an error or null.
|
||||
function execAndCallback(command: string, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
promisify(exec)(command).then(() => {
|
||||
callback(null as any); // tslint:disable-line no-any
|
||||
}).catch((error) => {
|
||||
callback(error);
|
||||
});
|
||||
}
|
||||
|
||||
function appendFile(
|
||||
path: fs.PathLike | number,
|
||||
data: any, // tslint:disable-line no-any
|
||||
options?: { encoding?: string | null; mode?: string | number; flag?: string; } | string | undefined | null | ((err: NodeJS.ErrnoException) => void),
|
||||
callback?: (err: NodeJS.ErrnoException) => void,
|
||||
): void {
|
||||
if (typeof options === "function") {
|
||||
callback = options;
|
||||
}
|
||||
|
||||
if (typeof path === "number") {
|
||||
if (!openFiles.has(path)) {
|
||||
// @ts-ignore not sure how to make this work.
|
||||
return callback(new Error("not open"), undefined as any); // tslint:disable-line no-any
|
||||
}
|
||||
path = openFiles.get(path).path;
|
||||
}
|
||||
|
||||
const process = exec(`${data ? "cat >>" : "touch"} ${escapePath(path.toString())}`, (error) => {
|
||||
callback!(error as any); // tslint:disable-line no-any
|
||||
});
|
||||
if (data) {
|
||||
process.stdin.write(data);
|
||||
}
|
||||
}
|
||||
|
||||
function close(fd: number, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
if (!openFiles.has(fd)) {
|
||||
return callback(new Error("file wasnt open"));
|
||||
}
|
||||
|
||||
openFiles.delete(fd);
|
||||
callback(null as any); // tslint:disable-line no-any
|
||||
}
|
||||
|
||||
function createWriteStream(path: fs.PathLike, _options?: string | {
|
||||
flags?: string;
|
||||
encoding?: string;
|
||||
fd?: number;
|
||||
mode?: number;
|
||||
}): fs.WriteStream {
|
||||
return new WriteStream(path);
|
||||
}
|
||||
|
||||
function exists(path: fs.PathLike, callback: (exists: boolean) => void): void {
|
||||
const pathStr = escapePath(path.toString());
|
||||
const command = bashCommand(
|
||||
`if [ -d ${pathStr} ]; then echo true;`
|
||||
+ ` elif [ -f ${pathStr} ]; then echo true;`
|
||||
+ ` elif [ -s ${pathStr} ]; then echo true;`
|
||||
+ "fi",
|
||||
);
|
||||
promisify(exec)(command).then((stdio) => {
|
||||
callback(stdio.stdout.trim() === "true");
|
||||
}).catch(() => {
|
||||
callback(false);
|
||||
});
|
||||
}
|
||||
|
||||
function fstat(fd: number, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void {
|
||||
if (!openFiles.has(fd)) {
|
||||
return callback(new Error("not open"), null as any); // tslint:disable-line no-any
|
||||
}
|
||||
stat(openFiles.get(fd).path, callback);
|
||||
}
|
||||
|
||||
function futimes(
|
||||
fd: number,
|
||||
atime: string | number | Date,
|
||||
mtime: string | number | Date,
|
||||
callback: (err: NodeJS.ErrnoException) => void,
|
||||
): void {
|
||||
if (!openFiles.has(fd)) {
|
||||
return callback(new Error("not opened"));
|
||||
}
|
||||
|
||||
const openFile = openFiles.get(fd);
|
||||
const command = [
|
||||
{ flag: "a", time: atime },
|
||||
{ flag: "m", time: mtime },
|
||||
]
|
||||
.filter((item) => !!item.time)
|
||||
.map((item) => `touch -${item.flag} --date="${item.time}" ${escapePath(openFile.path.toString())}`)
|
||||
.join(";");
|
||||
|
||||
if (command.length === 0) {
|
||||
return callback(new Error("atime or mtime required"));
|
||||
}
|
||||
|
||||
execAndCallback(command, callback);
|
||||
}
|
||||
|
||||
function lstat(path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void {
|
||||
stat(path, callback);
|
||||
}
|
||||
|
||||
function mkdir(
|
||||
path: fs.PathLike, mode: number | string | undefined | null | ((err: NodeJS.ErrnoException) => void),
|
||||
callback?: (err: NodeJS.ErrnoException) => void,
|
||||
): void {
|
||||
execAndCallback(
|
||||
`mkdir -p ${escapePath(path.toString())}`,
|
||||
typeof mode === "function" ? mode : callback!,
|
||||
);
|
||||
}
|
||||
|
||||
function open(
|
||||
path: fs.PathLike,
|
||||
flags: string | number, mode: number | string | undefined | null | ((err: NodeJS.ErrnoException, fd: number) => void),
|
||||
callback?: (err: NodeJS.ErrnoException, fd: number) => void,
|
||||
): void {
|
||||
if (typeof mode === "function") {
|
||||
callback = mode;
|
||||
}
|
||||
|
||||
// Don't touch if read-only.
|
||||
const promise = flags !== "r"
|
||||
? promisify(exec)(`touch ${escapePath(path.toString())}`)
|
||||
.then(() => Promise.resolve())
|
||||
.catch((error) => {
|
||||
if (error.message.indexOf("No such file or directory") !== -1) {
|
||||
return Promise.reject({
|
||||
code: "ENOENT",
|
||||
message: "No such file or directory " + path,
|
||||
name: "Not found",
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
})
|
||||
: Promise.resolve();
|
||||
|
||||
promise.then(() => {
|
||||
const id = lastFileDescriptor++;
|
||||
openFiles.set(id, {
|
||||
path,
|
||||
position: undefined,
|
||||
});
|
||||
callback!(null as any, id); // tslint:disable-line no-any
|
||||
}).catch((error) => {
|
||||
callback!(error, undefined as any); // tslint:disable-line no-any
|
||||
});
|
||||
}
|
||||
|
||||
function read<TBuffer extends Buffer | Uint8Array>(
|
||||
fd: number,
|
||||
buffer: TBuffer,
|
||||
offset: number,
|
||||
length: number,
|
||||
position: number | null,
|
||||
callback?: (err: NodeJS.ErrnoException, bytesRead: number, buffer: TBuffer) => void,
|
||||
): void {
|
||||
if (!openFiles.has(fd)) {
|
||||
if (callback) {
|
||||
// tslint:disable-next-line no-any
|
||||
callback(new Error("not opened"), undefined as any, undefined as any);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const hasPosition = typeof position === "number";
|
||||
const openFile = openFiles.get(fd);
|
||||
|
||||
if (!hasPosition) {
|
||||
position = openFile.position || 0;
|
||||
}
|
||||
|
||||
readFile(openFile.path, (error, data) => {
|
||||
if (error) {
|
||||
if (callback) {
|
||||
// tslint:disable-next-line no-any
|
||||
callback(error, undefined as any, undefined as any);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const output = data.slice(position!, position! + length);
|
||||
if (output.length !== 0) {
|
||||
buffer.set(output, offset);
|
||||
}
|
||||
|
||||
if (!hasPosition) {
|
||||
if (typeof openFile.position !== "undefined") {
|
||||
openFile.position += output.length;
|
||||
} else {
|
||||
openFile.position = output.length;
|
||||
}
|
||||
openFiles.set(fd, openFile);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(null as any, output.length, buffer); // tslint:disable-line no-any
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function readFile(
|
||||
path: fs.PathLike | number,
|
||||
options: { encoding?: string | null; flag?: string; } | string | undefined | null | ((err: NodeJS.ErrnoException, data: Buffer) => void),
|
||||
callback?: ((err: NodeJS.ErrnoException, data: Buffer | string) => void) | ((err: NodeJS.ErrnoException, data: Buffer) => void) | ((err: NodeJS.ErrnoException, data: string) => void),
|
||||
): void {
|
||||
if (typeof options === "function") {
|
||||
callback = options;
|
||||
}
|
||||
|
||||
if (typeof path === "number") {
|
||||
if (!openFiles.has(path)) {
|
||||
// @ts-ignore not sure how to make this work.
|
||||
return callback(new Error("not open"), undefined as any); // tslint:disable-line no-any
|
||||
}
|
||||
path = openFiles.get(path).path;
|
||||
}
|
||||
|
||||
readFileQueue.add(path.toString(), (error, result) => {
|
||||
// @ts-ignore not sure how to make this work.
|
||||
callback(
|
||||
error,
|
||||
result && useBuffer(options) ? Buffer.from(result) : result,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function readdir(
|
||||
path: fs.PathLike,
|
||||
options: { encoding?: string | null } | string | undefined | null | ((err: NodeJS.ErrnoException, files: string[]) => void),
|
||||
callback?: ((err: NodeJS.ErrnoException, files: string[]) => void) | ((err: NodeJS.ErrnoException, files: Buffer[]) => void) | ((err: NodeJS.ErrnoException, files: Array<string | Buffer>) => void),
|
||||
): void {
|
||||
if (typeof options === "function") {
|
||||
callback = options;
|
||||
}
|
||||
readdirQueue.add(path.toString(), (error, files) => {
|
||||
if (typeof options === "function") {
|
||||
callback = options;
|
||||
}
|
||||
// @ts-ignore not sure how to make this work.
|
||||
callback(
|
||||
error,
|
||||
files && useBuffer(options)
|
||||
? files.map((f) => Buffer.from(f))
|
||||
: files,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function realpath(
|
||||
path: fs.PathLike,
|
||||
options: { encoding?: string | null } | string | undefined | null | ((err: NodeJS.ErrnoException, resolvedPath: string) => void),
|
||||
callback?: ((err: NodeJS.ErrnoException, resolvedPath: string) => void) | ((err: NodeJS.ErrnoException, resolvedPath: Buffer) => void) | ((err: NodeJS.ErrnoException, resolvedPath: string | Buffer) => void),
|
||||
): void {
|
||||
if (typeof options === "function") {
|
||||
callback = options;
|
||||
}
|
||||
// @ts-ignore not sure how to make this work.
|
||||
callback(
|
||||
null,
|
||||
useBuffer(options) ? Buffer.from(path.toString()) : path.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
function rename(oldPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
promisify(exec)(`mv ${escapePath(oldPath.toString())} ${escapePath(newPath.toString())}`).then(() => {
|
||||
callback(null as any); // tslint:disable-line no-any
|
||||
}).catch((error) => {
|
||||
callback(error.message.indexOf("No such file or directory") !== -1 ? {
|
||||
code: "ENOENT",
|
||||
message: "No such file or directory " + oldPath,
|
||||
name: "Not found",
|
||||
} : error);
|
||||
});
|
||||
}
|
||||
|
||||
function rmdir(path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
execAndCallback(`rmdir ${escapePath(path.toString())}`, callback);
|
||||
}
|
||||
|
||||
function stat(path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void {
|
||||
statQueue.add(path.toString(), (error, stats) => {
|
||||
callback(error as any, stats as any); // tslint:disable-line no-any
|
||||
});
|
||||
}
|
||||
|
||||
function unlink(path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void {
|
||||
execAndCallback(`unlink ${escapePath(path.toString())}`, callback);
|
||||
}
|
||||
|
||||
function watch(
|
||||
filename: fs.PathLike,
|
||||
options: { encoding?: string | null, persistent?: boolean, recursive?: boolean } | string | undefined | null | ((event: string, filename: string) => void),
|
||||
listener?: ((event: string, filename: string) => void) | ((event: string, filename: Buffer) => void),
|
||||
): fs.FSWatcher {
|
||||
const buffer = new NewlineInputBuffer((msg): void => {
|
||||
msg = msg.trim();
|
||||
const index = msg.lastIndexOf(":");
|
||||
const events = msg.substring(index + 1).split(",");
|
||||
const baseFilename = msg.substring(0, index).split("/").pop();
|
||||
events.forEach((event) => {
|
||||
switch (event) {
|
||||
// Rename is emitted when a file appears or disappears in the directory.
|
||||
case "CREATE":
|
||||
case "DELETE":
|
||||
case "MOVED_FROM":
|
||||
case "MOVED_TO":
|
||||
watcher.emit("rename", baseFilename);
|
||||
break;
|
||||
case "CLOSE_WRITE":
|
||||
watcher.emit("change", baseFilename);
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const process = exec(`inotifywait ${escapePath(filename.toString())} -m --format "%w%f:%e"`);
|
||||
process.on("exit", (exitCode) => {
|
||||
watcher.emit("error", new Error(`process terminated unexpectedly with code ${exitCode}`));
|
||||
});
|
||||
process.stdout.on("data", (data) => {
|
||||
buffer.push(data);
|
||||
});
|
||||
|
||||
const watcher = new Watcher(process);
|
||||
watcher.on("change", (filename) => {
|
||||
// @ts-ignore not sure how to make this work.
|
||||
listener("change", useBuffer(options) ? Buffer.from(filename) : filename);
|
||||
});
|
||||
watcher.on("rename", (filename) => {
|
||||
// @ts-ignore not sure how to make this work.
|
||||
listener("rename", useBuffer(options) ? Buffer.from(filename) : filename);
|
||||
});
|
||||
|
||||
return watcher;
|
||||
}
|
||||
|
||||
function writeFile(
|
||||
path: fs.PathLike | number,
|
||||
data: any, // tslint:disable-line no-any
|
||||
options: { encoding?: string | null; mode?: number | string; flag?: string; } | string | undefined | null | ((err: NodeJS.ErrnoException) => void),
|
||||
callback?: (err: NodeJS.ErrnoException) => void,
|
||||
): void {
|
||||
if (typeof options === "function") {
|
||||
callback = options;
|
||||
}
|
||||
|
||||
if (typeof path === "number") {
|
||||
if (!openFiles.has(path)) {
|
||||
// @ts-ignore not sure how to make this work.
|
||||
return callback(new Error("not open"), undefined as any); // tslint:disable-line no-any
|
||||
}
|
||||
path = openFiles.get(path).path;
|
||||
}
|
||||
|
||||
const process = exec(`${data ? "cat >" : "touch"} ${escapePath(path.toString())}`, (error) => {
|
||||
callback!(error as any); // tslint:disable-line no-any
|
||||
});
|
||||
if (data) {
|
||||
process.stdin.write(data);
|
||||
}
|
||||
}
|
||||
// tslint:enable only-arrow-functions
|
||||
|
||||
// Just to satisfy the types.
|
||||
// tslint:disable no-any
|
||||
appendFile.__promisify__ = undefined as any;
|
||||
close.__promisify__ = undefined as any;
|
||||
exists.__promisify__ = undefined as any;
|
||||
fstat.__promisify__ = undefined as any;
|
||||
futimes.__promisify__ = undefined as any;
|
||||
lstat.__promisify__ = undefined as any;
|
||||
mkdir.__promisify__ = undefined as any;
|
||||
open.__promisify__ = undefined as any;
|
||||
read.__promisify__ = undefined as any;
|
||||
readFile.__promisify__ = undefined as any;
|
||||
readdir.__promisify__ = undefined as any;
|
||||
realpath.__promisify__ = undefined as any;
|
||||
rename.__promisify__ = undefined as any;
|
||||
rmdir.__promisify__ = undefined as any;
|
||||
stat.__promisify__ = undefined as any;
|
||||
unlink.__promisify__ = undefined as any;
|
||||
writeFile.__promisify__ = undefined as any;
|
||||
// tslint:enable no-any
|
||||
|
||||
const exp: typeof fs = {
|
||||
constants: fs.constants,
|
||||
Stats: fs.Stats,
|
||||
ReadStream: fs.ReadStream,
|
||||
WriteStream: fs.WriteStream,
|
||||
|
||||
access: throwUnimplementedError,
|
||||
accessSync: throwSyncError,
|
||||
appendFile,
|
||||
appendFileSync: throwSyncError,
|
||||
chmod: throwUnimplementedError,
|
||||
chmodSync: throwSyncError,
|
||||
chown: throwUnimplementedError,
|
||||
chownSync: throwSyncError,
|
||||
close,
|
||||
copyFile: throwUnimplementedError,
|
||||
copyFileSync: throwSyncError,
|
||||
closeSync: throwSyncError,
|
||||
createReadStream: throwUnimplementedError,
|
||||
createWriteStream,
|
||||
exists,
|
||||
existsSync: throwSyncError,
|
||||
fchmod: throwUnimplementedError,
|
||||
fchmodSync: throwSyncError,
|
||||
fchown: throwUnimplementedError,
|
||||
fchownSync: throwSyncError,
|
||||
fdatasync: throwUnimplementedError,
|
||||
fdatasyncSync: throwSyncError,
|
||||
fstat,
|
||||
fstatSync: throwSyncError,
|
||||
fsync: throwUnimplementedError,
|
||||
fsyncSync: throwSyncError,
|
||||
ftruncate: throwUnimplementedError,
|
||||
ftruncateSync: throwSyncError,
|
||||
futimes,
|
||||
futimesSync: throwSyncError,
|
||||
lchmod: throwUnimplementedError,
|
||||
lchmodSync: throwSyncError,
|
||||
lchown: throwUnimplementedError,
|
||||
lchownSync: throwSyncError,
|
||||
link: throwUnimplementedError,
|
||||
linkSync: throwSyncError,
|
||||
lstat,
|
||||
lstatSync: throwSyncError,
|
||||
mkdir,
|
||||
mkdirSync: throwSyncError,
|
||||
mkdtemp: throwUnimplementedError,
|
||||
mkdtempSync: throwSyncError,
|
||||
open,
|
||||
openSync: throwSyncError,
|
||||
read,
|
||||
readFile,
|
||||
readFileSync: throwSyncError,
|
||||
readSync: throwSyncError,
|
||||
readdir,
|
||||
readdirSync: throwSyncError,
|
||||
readlink: throwUnimplementedError,
|
||||
readlinkSync: throwSyncError,
|
||||
realpath,
|
||||
realpathSync: throwSyncError,
|
||||
rename,
|
||||
renameSync: throwSyncError,
|
||||
rmdir,
|
||||
rmdirSync: throwSyncError,
|
||||
stat,
|
||||
statSync: throwSyncError,
|
||||
symlink: throwUnimplementedError,
|
||||
symlinkSync: throwSyncError,
|
||||
truncate: throwUnimplementedError,
|
||||
truncateSync: throwSyncError,
|
||||
unlink,
|
||||
unlinkSync: throwSyncError,
|
||||
unwatchFile: throwUnimplementedError,
|
||||
utimes: throwUnimplementedError,
|
||||
utimesSync: throwSyncError,
|
||||
watch,
|
||||
watchFile: throwUnimplementedError,
|
||||
write: throwUnimplementedError,
|
||||
writeFile,
|
||||
writeFileSync: throwSyncError,
|
||||
writeSync: throwSyncError,
|
||||
};
|
||||
|
||||
export = exp;
|
2
packages/node-browser/src/index.ts
Normal file
2
packages/node-browser/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { bashCommand, escapePath, isBrowserEnvironment } from "./util";
|
||||
export { bashCommand, escapePath, isBrowserEnvironment };
|
70
packages/node-browser/src/net.ts
Normal file
70
packages/node-browser/src/net.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import * as net from "net";
|
||||
|
||||
/**
|
||||
* Implementation of Socket for the browser.
|
||||
*/
|
||||
class Socket extends net.Socket {
|
||||
|
||||
public connect(): this {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Server for the browser.
|
||||
*/
|
||||
class Server extends net.Server {
|
||||
|
||||
public listen(
|
||||
_port?: number | any | net.ListenOptions, // tslint:disable-line no-any so we can match the Node API.
|
||||
_hostname?: string | number | Function,
|
||||
_backlog?: number | Function,
|
||||
_listeningListener?: Function,
|
||||
): this {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// tslint:disable only-arrow-functions
|
||||
function connect(): net.Socket {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
function createConnection(): net.Socket {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
function isIP(_input: string): number {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
function isIPv4(_input: string): boolean {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
function isIPv6(_input: string): boolean {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
function createServer(
|
||||
_options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean } | ((socket: net.Socket) => void),
|
||||
_connectionListener?: (socket: net.Socket) => void,
|
||||
): Server {
|
||||
return new Server();
|
||||
}
|
||||
// tslint:enable only-arrow-functions
|
||||
|
||||
const exp: typeof net = {
|
||||
Socket,
|
||||
Server,
|
||||
connect,
|
||||
createConnection,
|
||||
isIP,
|
||||
isIPv4,
|
||||
isIPv6,
|
||||
createServer,
|
||||
};
|
||||
|
||||
export = exp;
|
137
packages/node-browser/src/util.ts
Normal file
137
packages/node-browser/src/util.ts
Normal file
@ -0,0 +1,137 @@
|
||||
// The type doesn't matter for these since we're just throwing.
|
||||
// tslint:disable no-any
|
||||
export const throwUnimplementedError = (): any => {
|
||||
throw new Error("not implemented");
|
||||
};
|
||||
// In case the types except the promisify property.
|
||||
throwUnimplementedError.__promisify__ = undefined as any;
|
||||
// This one seems to be a mistake in the types for `link`.
|
||||
throwUnimplementedError.link = undefined as any;
|
||||
export const throwSyncError = (): any => {
|
||||
throw new Error("sync is not supported");
|
||||
};
|
||||
// tslint:enable no-any
|
||||
|
||||
/**
|
||||
* Return true if the options specify to use a Buffer instead of string.
|
||||
*/
|
||||
export const useBuffer = (options: { encoding?: string | null } | string | undefined | null | Function): boolean => {
|
||||
return options === "buffer"
|
||||
|| (!!options && typeof options !== "string" && typeof options !== "function"
|
||||
&& (options.encoding === "buffer" || options.encoding === null));
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a command with bash.
|
||||
*/
|
||||
export const bashCommand = (command: string): string => {
|
||||
return `bash -c "${command.replace(/"/g, "\\\"")}"`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return true if we're in a browser environment (including web workers).
|
||||
*/
|
||||
export const isBrowserEnvironment = (): boolean => {
|
||||
return typeof process === "undefined" || typeof process.stdout === "undefined";
|
||||
};
|
||||
|
||||
/**
|
||||
* Escape a path. This prevents any issues with file names that have quotes,
|
||||
* spaces, braces, etc.
|
||||
*/
|
||||
export const escapePath = (path: string): string => {
|
||||
return `'${path.replace(/'/g, "'\\''")}'`;
|
||||
};
|
||||
|
||||
/**
|
||||
* This queues up items then runs on all the items at once after a timeout. Each
|
||||
* item has a callback that expects the response for that item which is the
|
||||
* extending class's responsibility to call.
|
||||
*
|
||||
* You can also specify a maximum number of items to keep in the queue.
|
||||
*/
|
||||
export abstract class Queue<T> {
|
||||
|
||||
private items: Map<string, T[]>;
|
||||
private timeout: number | NodeJS.Timer | undefined;
|
||||
private max: number | undefined;
|
||||
private timeoutDelay = 1;
|
||||
|
||||
public constructor(max?: number) {
|
||||
this.items = new Map();
|
||||
this.run = run;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to the queue.
|
||||
*/
|
||||
public add(key: string, callback: T): void {
|
||||
if (this.items.has(key)) {
|
||||
this.items.get(key)!.push(callback);
|
||||
} else {
|
||||
this.items.set(key, [callback]);
|
||||
}
|
||||
|
||||
const run = (): void => {
|
||||
// tslint:disable-next-line no-any because NodeJS.Timer is valid.
|
||||
clearTimeout(this.timeout as any);
|
||||
this.timeout = undefined;
|
||||
const newMap = new Map(this.items);
|
||||
this.items.clear();
|
||||
this.run(newMap);
|
||||
};
|
||||
|
||||
if (typeof this.max !== "undefined" && this.items.size >= this.max) {
|
||||
return run();
|
||||
}
|
||||
|
||||
if (typeof this.timeout === "undefined") {
|
||||
this.timeout = setTimeout(() => {
|
||||
run();
|
||||
}, this.timeoutDelay);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run on the specified items then call their callbacks.
|
||||
*/
|
||||
protected abstract run(items: Map<string, T[]>): void;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for safely taking input and turning it into separate messages.
|
||||
* Assumes that messages are split by newlines.
|
||||
*/
|
||||
export class NewlineInputBuffer {
|
||||
|
||||
private callback: (msg: string) => void;
|
||||
private buffer: string | undefined;
|
||||
|
||||
public constructor(callback: (msg: string) => void) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add data to be buffered.
|
||||
*/
|
||||
public push(data: string | Uint8Array): void {
|
||||
let input = typeof data === "string" ? data : data.toString();
|
||||
if (this.buffer) {
|
||||
input = this.buffer + input;
|
||||
this.buffer = undefined;
|
||||
}
|
||||
const lines = input.split("\n");
|
||||
const length = lines.length - 1;
|
||||
const lastLine = lines[length];
|
||||
if (lastLine.length > 0) {
|
||||
this.buffer = lastLine;
|
||||
}
|
||||
lines.pop(); // This is either the line we buffered or an empty string.
|
||||
for (let i = 0; i < length; ++i) {
|
||||
this.callback(lines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
4
packages/node-browser/yarn.lock
Normal file
4
packages/node-browser/yarn.lock
Normal file
@ -0,0 +1,4 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
45
packages/package.json
Normal file
45
packages/package.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"scripts": {
|
||||
"build:rules": "cd ./rules && yarn build",
|
||||
"postinstall": "ts-node ./scripts/install-packages.ts && yarn build:rules",
|
||||
"test": "jest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^23.3.12",
|
||||
"@types/node": "10.5.2",
|
||||
"@types/webpack-env": "1.13.6",
|
||||
"jest": "^23.6.0",
|
||||
"ts-jest": "^23.10.5",
|
||||
"ts-node": "^7.0.1",
|
||||
"tslint": "5.11.0",
|
||||
"tslint-language-service": "0.9.9",
|
||||
"typescript": "3.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"xmlhttprequest": "1.8.0"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"json"
|
||||
],
|
||||
"setupFiles": [
|
||||
"<rootDir>/scripts/test-setup.js"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"^.+\\.(s?css|png|svg)$": "<rootDir>/scripts/dummy.js",
|
||||
"@coder/(.*)/testing": "<rootDir>/packages/$1/testing",
|
||||
"@coder/(.*)": "<rootDir>/packages/$1/src"
|
||||
},
|
||||
"transform": {
|
||||
"^.+\\.tsx?$": "ts-jest"
|
||||
},
|
||||
"testPathIgnorePatterns": [
|
||||
"/node_modules/",
|
||||
"/logger/"
|
||||
],
|
||||
"testRegex": ".*\\.test\\.tsx?"
|
||||
}
|
||||
}
|
20
packages/requirefs/package.json
Normal file
20
packages/requirefs/package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "requirefs",
|
||||
"description": "",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"benchmark": "ts-node ./test/*.bench.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"jszip": "2.6.0",
|
||||
"path": "0.12.7",
|
||||
"resolve": "1.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/benchmark": "^1.0.31",
|
||||
"@types/jszip": "3.1.4",
|
||||
"@types/resolve": "0.0.8",
|
||||
"benchmark": "^2.1.4",
|
||||
"text-encoding": "0.6.4"
|
||||
}
|
||||
}
|
1
packages/requirefs/src/index.ts
Normal file
1
packages/requirefs/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./requirefs";
|
170
packages/requirefs/src/requirefs.ts
Normal file
170
packages/requirefs/src/requirefs.ts
Normal file
@ -0,0 +1,170 @@
|
||||
import * as JSZip from "jszip";
|
||||
import * as path from "path";
|
||||
import * as resolve from "resolve";
|
||||
import { Tar } from "./tarReader";
|
||||
const textDecoder = new (typeof TextDecoder === "undefined" ? require("text-encoding").TextDecoder : TextDecoder)();
|
||||
|
||||
export interface IFileReader {
|
||||
exists(path: string): boolean;
|
||||
read(path: string): Uint8Array;
|
||||
}
|
||||
|
||||
/**
|
||||
* RequireFS allows users to require from a file system.
|
||||
*/
|
||||
export class RequireFS {
|
||||
|
||||
private readonly reader: IFileReader;
|
||||
private readonly customModules: Map<string, { exports: object }>;
|
||||
private readonly requireCache: Map<string, { exports: object }>;
|
||||
private baseDir: string | undefined;
|
||||
|
||||
public constructor(reader: IFileReader) {
|
||||
this.reader = reader;
|
||||
this.customModules = new Map();
|
||||
this.requireCache = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a base-directory to nest from.
|
||||
*/
|
||||
public basedir(path: string): void {
|
||||
this.baseDir = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide custom modules to the require instance.
|
||||
*/
|
||||
// tslint:disable-next-line:no-any
|
||||
public provide(module: string, value: any): void {
|
||||
if (this.customModules.has(module)) {
|
||||
throw new Error("custom module has already been registered with this name");
|
||||
}
|
||||
|
||||
this.customModules.set(module, value);
|
||||
}
|
||||
|
||||
public readFile(target: string, type?: "string"): string;
|
||||
public readFile(target: string, type?: "buffer"): Buffer;
|
||||
|
||||
/**
|
||||
* Read a file and returns its contents.
|
||||
*/
|
||||
public readFile(target: string, type?: "string" | "buffer"): string | Buffer {
|
||||
target = path.normalize(target);
|
||||
const read = this.reader.read(target);
|
||||
|
||||
return type === "string" ? textDecoder.decode(read) : Buffer.from(read);
|
||||
}
|
||||
|
||||
/**
|
||||
* Require a path from a file system.
|
||||
*/
|
||||
// tslint:disable-next-line:no-any
|
||||
public require(target: string): any {
|
||||
target = path.normalize(target);
|
||||
|
||||
return this.doRequire([target], `./${path.basename(target)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do require for a caller. Needed for resolving relative paths.
|
||||
*/
|
||||
private doRequire(callers: string[], resolvePath: string): object {
|
||||
if (this.customModules.has(resolvePath)) {
|
||||
return this.customModules.get(resolvePath)!.exports;
|
||||
}
|
||||
|
||||
const caller = callers[callers.length - 1];
|
||||
const reader = this.reader;
|
||||
|
||||
const newRelative = this.realizePath(caller, resolvePath);
|
||||
if (this.requireCache.has(newRelative)) {
|
||||
return this.requireCache.get(newRelative)!.exports;
|
||||
}
|
||||
|
||||
const module = {
|
||||
exports: {},
|
||||
};
|
||||
this.requireCache.set(newRelative, module);
|
||||
|
||||
const content = textDecoder.decode(reader.read(newRelative));
|
||||
if (newRelative.endsWith(".json")) {
|
||||
module.exports = JSON.parse(content);
|
||||
} else {
|
||||
eval("'use strict'; " + content);
|
||||
}
|
||||
|
||||
return module.exports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find a module from a path
|
||||
*/
|
||||
private realizePath(caller: string, fullRelative: string): string {
|
||||
const stripPrefix = (path: string): string => {
|
||||
if (path.startsWith("/")) {
|
||||
path = path.substr(1);
|
||||
}
|
||||
if (path.endsWith("/")) {
|
||||
path = path.substr(0, path.length - 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
};
|
||||
const callerDirname = path.dirname(caller);
|
||||
const resolvedPath = resolve.sync(fullRelative, {
|
||||
basedir: this.baseDir ? callerDirname.startsWith(this.baseDir) ? callerDirname : path.join(this.baseDir, callerDirname) : callerDirname,
|
||||
extensions: [".js"],
|
||||
readFileSync: (file: string): string => {
|
||||
return this.readFile(stripPrefix(file));
|
||||
},
|
||||
isFile: (file: string): boolean => {
|
||||
return this.reader.exists(stripPrefix(file));
|
||||
},
|
||||
});
|
||||
|
||||
return stripPrefix(resolvedPath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const fromTar = (content: Uint8Array): RequireFS => {
|
||||
const tar = Tar.fromUint8Array(content);
|
||||
|
||||
return new RequireFS({
|
||||
exists: (path: string): boolean => {
|
||||
return tar.files.has(path);
|
||||
},
|
||||
read: (path: string): Uint8Array => {
|
||||
const file = tar.files.get(path);
|
||||
if (!file) {
|
||||
throw new Error(`file "${path}" not found`);
|
||||
}
|
||||
|
||||
return file.read();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const fromZip = (content: Uint8Array): RequireFS => {
|
||||
const zip = new JSZip(content);
|
||||
|
||||
return new RequireFS({
|
||||
exists: (fsPath: string): boolean => {
|
||||
const file = zip.file(fsPath);
|
||||
|
||||
return typeof file !== "undefined" && file !== null;
|
||||
},
|
||||
read: (fsPath: string): Uint8Array => {
|
||||
const file = zip.file(fsPath);
|
||||
if (!file) {
|
||||
throw new Error(`file "${fsPath}" not found`);
|
||||
}
|
||||
|
||||
// TODO: Should refactor to allow a promise.
|
||||
// tslint:disable-next-line no-any
|
||||
return zip.file(fsPath).async("uint8array") as any;
|
||||
},
|
||||
});
|
||||
};
|
285
packages/requirefs/src/tarReader.ts
Normal file
285
packages/requirefs/src/tarReader.ts
Normal file
@ -0,0 +1,285 @@
|
||||
import * as path from "path";
|
||||
const textDecoder = new (typeof TextDecoder === "undefined" ? require("text-encoding").TextDecoder : TextDecoder)();
|
||||
|
||||
/**
|
||||
* Tar represents a tar archive.
|
||||
*/
|
||||
export class Tar {
|
||||
|
||||
/**
|
||||
* Return a tar object from a Uint8Array.
|
||||
*/
|
||||
public static fromUint8Array(array: Uint8Array): Tar {
|
||||
const reader = new Reader(array);
|
||||
|
||||
const tar = new Tar();
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const file = TarFile.fromReader(reader);
|
||||
if (file) {
|
||||
tar._files.set(path.normalize(file.name), file);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.message === "EOF") {
|
||||
break;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
reader.unclamp();
|
||||
|
||||
return tar;
|
||||
}
|
||||
|
||||
private readonly _files: Map<string, TarFile>;
|
||||
|
||||
private constructor() {
|
||||
this._files = new Map();
|
||||
}
|
||||
|
||||
public get files(): ReadonlyMap<string, TarFile> {
|
||||
return this._files;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a tar files location within a reader
|
||||
*/
|
||||
export class TarFile {
|
||||
|
||||
/**
|
||||
* Locate a tar file from a reader.
|
||||
*/
|
||||
public static fromReader(reader: Reader): TarFile | undefined {
|
||||
const firstByte = reader.peek(1)[0];
|
||||
// If the first byte is nil, we know it isn't a filename
|
||||
if (firstByte === 0x00) {
|
||||
// The tar header is 512 bytes large. Its safe to skip here
|
||||
// because we know this block is not a header
|
||||
reader.skip(512);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let name = reader.readString(100);
|
||||
|
||||
reader.skip(8); // 100->108 mode
|
||||
reader.skip(8); // 108->116 uid
|
||||
reader.skip(8); // 116->124 gid
|
||||
|
||||
const rawSize = reader.read(12); // 124->136 size
|
||||
|
||||
reader.skip(12); // 136->148 mtime
|
||||
|
||||
if (reader.jump(345).readByte()) {
|
||||
name = reader.jump(345).readString(155) + "/" + name;
|
||||
}
|
||||
|
||||
const nums: number[] = [];
|
||||
rawSize.forEach((a) => nums.push(a));
|
||||
|
||||
const parseSize = (): number => {
|
||||
let offset = 0;
|
||||
// While 48 (ASCII value of 0), the byte is nil and considered padding.
|
||||
while (offset < rawSize.length && nums[offset] === 48) {
|
||||
offset++;
|
||||
}
|
||||
const clamp = (index: number, len: number, defaultValue: number): number => {
|
||||
if (typeof index !== "number") {
|
||||
return defaultValue;
|
||||
}
|
||||
// Coerce index to an integer.
|
||||
index = ~~index;
|
||||
if (index >= len) {
|
||||
return len;
|
||||
}
|
||||
if (index >= 0) {
|
||||
return index;
|
||||
}
|
||||
index += len;
|
||||
if (index >= 0) {
|
||||
return index;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Checks for the index of the POSIX file-size terminating char.
|
||||
// Falls back to GNU's tar format. If neither characters are found
|
||||
// the index will default to the end of the file size buffer.
|
||||
let i = nums.indexOf(32, offset);
|
||||
if (i === -1) {
|
||||
i = nums.indexOf(0, offset);
|
||||
if (i === -1) {
|
||||
i = rawSize.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
const end = clamp(i, rawSize.length, rawSize.length - 1);
|
||||
if (end === offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return parseInt(textDecoder.decode(rawSize.slice(offset, end)), 8);
|
||||
};
|
||||
|
||||
const size = parseSize();
|
||||
|
||||
const overflow = ((): number => {
|
||||
let newSize = size;
|
||||
newSize &= 511;
|
||||
|
||||
return newSize && 512 - newSize;
|
||||
})();
|
||||
|
||||
reader.jump(512);
|
||||
const offset = reader.offset;
|
||||
reader.skip(overflow + size);
|
||||
reader.clamp();
|
||||
|
||||
const tarFile = new TarFile(reader, {
|
||||
offset,
|
||||
name,
|
||||
size,
|
||||
});
|
||||
|
||||
return tarFile;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
private readonly reader: Reader,
|
||||
private readonly data: {
|
||||
name: string;
|
||||
size: number;
|
||||
offset: number;
|
||||
},
|
||||
) { }
|
||||
|
||||
public get name(): string {
|
||||
return this.data.name;
|
||||
}
|
||||
|
||||
public get size(): number {
|
||||
return this.data.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file type is a file.
|
||||
*/
|
||||
public isFile(): boolean {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the file as a string.
|
||||
*/
|
||||
public readAsString(): string {
|
||||
return textDecoder.decode(this.read());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the file as Uint8Array.
|
||||
*/
|
||||
public read(): Uint8Array {
|
||||
return this.reader.jump(this.data.offset).read(this.data.size);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads within a Uint8Array.
|
||||
*/
|
||||
export class Reader {
|
||||
|
||||
private array: Uint8Array;
|
||||
private _offset: number;
|
||||
private lastClamp: number;
|
||||
|
||||
public constructor(array: Uint8Array) {
|
||||
this.array = array;
|
||||
this._offset = 0;
|
||||
this.lastClamp = 0;
|
||||
}
|
||||
|
||||
public get offset(): number {
|
||||
return this._offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip the specified amount of bytes.
|
||||
*/
|
||||
public skip(amount: number): boolean {
|
||||
if (this._offset + amount > this.array.length) {
|
||||
throw new Error("EOF");
|
||||
}
|
||||
this._offset += amount;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamp the reader at a position.
|
||||
*/
|
||||
public clamp(): void {
|
||||
this.lastClamp = this._offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unclamp the reader.
|
||||
*/
|
||||
public unclamp(): void {
|
||||
this.lastClamp = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Jump to a specific offset.
|
||||
*/
|
||||
public jump(offset: number): Reader {
|
||||
this._offset = offset + this.lastClamp;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Peek the amount of bytes.
|
||||
*/
|
||||
public peek(amount: number): Uint8Array {
|
||||
return this.array.slice(this.offset, this.offset + amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a string.
|
||||
*/
|
||||
public readString(amount: number): string {
|
||||
// Replacing the 0s removes all nil bytes from the str
|
||||
return textDecoder.decode(this.read(amount)).replace(/\0/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a byte in the array.
|
||||
*/
|
||||
public readByte(): number {
|
||||
const data = this.array[this._offset];
|
||||
this._offset++;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the amount of bytes.
|
||||
*/
|
||||
public read(amount: number): Uint8Array {
|
||||
if (this._offset > this.array.length) {
|
||||
throw new Error("EOF");
|
||||
}
|
||||
|
||||
const data = this.array.slice(this._offset, this._offset + amount);
|
||||
this._offset += amount;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
3
packages/requirefs/test/.gitignore
vendored
Normal file
3
packages/requirefs/test/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
!lib/node_modules
|
||||
*.tar
|
||||
*.zip
|
1
packages/requirefs/test/lib/chained-1.js
Normal file
1
packages/requirefs/test/lib/chained-1.js
Normal file
@ -0,0 +1 @@
|
||||
exports = require("./chained-2");
|
1
packages/requirefs/test/lib/chained-2.js
Normal file
1
packages/requirefs/test/lib/chained-2.js
Normal file
@ -0,0 +1 @@
|
||||
exports = require("./chained-3");
|
1
packages/requirefs/test/lib/chained-3.js
Normal file
1
packages/requirefs/test/lib/chained-3.js
Normal file
@ -0,0 +1 @@
|
||||
exports.text = "moo";
|
1
packages/requirefs/test/lib/customModule.js
Normal file
1
packages/requirefs/test/lib/customModule.js
Normal file
@ -0,0 +1 @@
|
||||
exports = require("donkey");
|
1
packages/requirefs/test/lib/individual.js
Normal file
1
packages/requirefs/test/lib/individual.js
Normal file
@ -0,0 +1 @@
|
||||
exports.frog = "hi";
|
3
packages/requirefs/test/lib/nodeResolve.js
Normal file
3
packages/requirefs/test/lib/nodeResolve.js
Normal file
@ -0,0 +1,3 @@
|
||||
const frogger = require("frogger");
|
||||
|
||||
exports = frogger;
|
1
packages/requirefs/test/lib/node_modules/frogger/index.js
generated
vendored
Normal file
1
packages/requirefs/test/lib/node_modules/frogger/index.js
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
exports.banana = "potato";
|
1
packages/requirefs/test/lib/scope.js
Normal file
1
packages/requirefs/test/lib/scope.js
Normal file
@ -0,0 +1 @@
|
||||
exports = coder.test;
|
1
packages/requirefs/test/lib/subfolder.js
Normal file
1
packages/requirefs/test/lib/subfolder.js
Normal file
@ -0,0 +1 @@
|
||||
exports.orangeColor = require("./subfolder/oranges").orange;
|
1
packages/requirefs/test/lib/subfolder/goingUp.js
Normal file
1
packages/requirefs/test/lib/subfolder/goingUp.js
Normal file
@ -0,0 +1 @@
|
||||
exports = require("../individual");
|
1
packages/requirefs/test/lib/subfolder/oranges.js
Normal file
1
packages/requirefs/test/lib/subfolder/oranges.js
Normal file
@ -0,0 +1 @@
|
||||
exports.orange = "blue";
|
48
packages/requirefs/test/requirefs.bench.ts
Normal file
48
packages/requirefs/test/requirefs.bench.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import * as benchmark from "benchmark";
|
||||
import { performance } from "perf_hooks";
|
||||
import { TestCaseArray, isMac } from "./requirefs.util";
|
||||
|
||||
const files = [
|
||||
"./individual.js", "./chained-1", "./subfolder",
|
||||
"./subfolder/goingUp", "./nodeResolve",
|
||||
];
|
||||
const toBench = new TestCaseArray();
|
||||
|
||||
// Limits the amount of time taken for each test,
|
||||
// but increases uncertainty.
|
||||
benchmark.options.maxTime = 0.5;
|
||||
|
||||
let suite = new benchmark.Suite();
|
||||
let _start = 0;
|
||||
const addMany = (names: string[]): benchmark.Suite => {
|
||||
for (let name of names) {
|
||||
for (let file of files) {
|
||||
suite = suite.add(`${name} -> ${file}`, async () => {
|
||||
let rfs = await toBench.byName(name).rfs;
|
||||
rfs.require(file);
|
||||
});
|
||||
}
|
||||
}
|
||||
_start = performance.now();
|
||||
return suite;
|
||||
}
|
||||
// Returns mean time per operation, in microseconds (10^-6s).
|
||||
const mean = (c: any): number => {
|
||||
return Number((c.stats.mean * 10e+5).toFixed(5));
|
||||
};
|
||||
|
||||
// Swap out the tar command for gtar, when on MacOS.
|
||||
let testNames = ["zip", "bsdtar", isMac ? "gtar" : "tar"];
|
||||
addMany(testNames).on("cycle", (event: benchmark.Event) => {
|
||||
console.log(String(event.target) + ` (~${mean(event.target)} μs/op)`);
|
||||
}).on("complete", () => {
|
||||
const slowest = suite.filter("slowest").shift();
|
||||
const fastest = suite.filter("fastest").shift();
|
||||
console.log(`===\nFastest is ${fastest.name} with ~${mean(fastest)} μs/op`);
|
||||
if (slowest.name !== fastest.name) {
|
||||
console.log(`Slowest is ${slowest.name} with ~${mean(slowest)} μs/op`);
|
||||
}
|
||||
const d = ((performance.now() - _start)/1000).toFixed(2);
|
||||
console.log(`Benchmark took ${d} s`);
|
||||
})
|
||||
.run({ "async": true });
|
56
packages/requirefs/test/requirefs.test.ts
Normal file
56
packages/requirefs/test/requirefs.test.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { RequireFS } from "../src/requirefs";
|
||||
import { TestCaseArray, isMac } from "./requirefs.util";
|
||||
|
||||
const toTest = new TestCaseArray();
|
||||
|
||||
describe("requirefs", () => {
|
||||
for (let i = 0; i < toTest.length(); i++) {
|
||||
const testCase = toTest.byID(i);
|
||||
if (!isMac && testCase.name === "gtar") {
|
||||
break;
|
||||
}
|
||||
if (isMac && testCase.name === "tar") {
|
||||
break;
|
||||
}
|
||||
|
||||
describe(testCase.name, () => {
|
||||
let rfs: RequireFS;
|
||||
beforeAll(async () => {
|
||||
rfs = await testCase.rfs;
|
||||
});
|
||||
|
||||
it("should parse individual module", () => {
|
||||
expect(rfs.require("./individual.js").frog).toEqual("hi");
|
||||
});
|
||||
|
||||
it("should parse chained modules", () => {
|
||||
expect(rfs.require("./chained-1").text).toEqual("moo");
|
||||
});
|
||||
|
||||
it("should parse through subfolders", () => {
|
||||
expect(rfs.require("./subfolder").orangeColor).toEqual("blue");
|
||||
});
|
||||
|
||||
it("should be able to move up directories", () => {
|
||||
expect(rfs.require("./subfolder/goingUp").frog).toEqual("hi");
|
||||
});
|
||||
|
||||
it("should resolve node_modules", () => {
|
||||
expect(rfs.require("./nodeResolve").banana).toEqual("potato");
|
||||
});
|
||||
|
||||
it("should access global scope", () => {
|
||||
// tslint:disable-next-line no-any for testing
|
||||
(window as any).coder = {
|
||||
test: "hi",
|
||||
};
|
||||
expect(rfs.require("./scope")).toEqual("hi");
|
||||
});
|
||||
|
||||
it("should find custom module", () => {
|
||||
rfs.provide("donkey", "ok");
|
||||
expect(rfs.require("./customModule")).toEqual("ok");
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
112
packages/requirefs/test/requirefs.util.ts
Normal file
112
packages/requirefs/test/requirefs.util.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import * as cp from "child_process";
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import { fromTar, RequireFS, fromZip } from "../src/requirefs";
|
||||
|
||||
export const isMac = os.platform() === "darwin";
|
||||
|
||||
/**
|
||||
* Encapsulates a RequireFS Promise and the
|
||||
* name of the test case it will be used in.
|
||||
*/
|
||||
interface TestCase {
|
||||
rfs: Promise<RequireFS>;
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* TestCaseArray allows tests and benchmarks to share
|
||||
* test cases while limiting redundancy.
|
||||
*/
|
||||
export class TestCaseArray {
|
||||
private cases: Array<TestCase> = [];
|
||||
|
||||
constructor(cases?: Array<TestCase>) {
|
||||
if (!cases) {
|
||||
this.cases = TestCaseArray.defaults();
|
||||
return
|
||||
}
|
||||
this.cases = cases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns default test cases. MacOS users need to have `gtar` binary
|
||||
* in order to run GNU-tar tests and benchmarks.
|
||||
*/
|
||||
public static defaults(): Array<TestCase> {
|
||||
let cases: Array<TestCase> = [
|
||||
TestCaseArray.newCase("cd lib && zip -r ../lib.zip ./*", "lib.zip", async (c) => fromZip(c), "zip"),
|
||||
TestCaseArray.newCase("cd lib && bsdtar cvf ../lib.tar ./*", "lib.tar", async (c) => fromTar(c), "bsdtar"),
|
||||
];
|
||||
if (isMac) {
|
||||
const gtarInstalled: boolean = cp.execSync("which tar").length > 0;
|
||||
if (gtarInstalled) {
|
||||
cases.push(TestCaseArray.newCase("cd lib && gtar cvf ../lib.tar ./*", "lib.tar", async (c) => fromTar(c), "gtar"));
|
||||
} else {
|
||||
throw new Error("failed to setup gtar test case, gtar binary is necessary to test GNU-tar on MacOS");
|
||||
}
|
||||
} else {
|
||||
cases.push(TestCaseArray.newCase("cd lib && tar cvf ../lib.tar ./*", "lib.tar", async (c) => fromTar(c), "tar"));
|
||||
}
|
||||
return cases;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a test case prepared with the provided RequireFS Promise.
|
||||
* @param command Command to run immediately. For setup.
|
||||
* @param targetFile File to be read and handled by prepare function.
|
||||
* @param prepare Run on target file contents before test.
|
||||
* @param name Test case name.
|
||||
*/
|
||||
public static newCase(command: string, targetFile: string, prepare: (content: Uint8Array) => Promise<RequireFS>, name: string): TestCase {
|
||||
cp.execSync(command, { cwd: __dirname });
|
||||
const content = fs.readFileSync(path.join(__dirname, targetFile));
|
||||
return {
|
||||
name,
|
||||
rfs: prepare(content),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns updated TestCaseArray instance, with a new test case.
|
||||
* @see TestCaseArray.newCase
|
||||
*/
|
||||
public add(command: string, targetFile: string, prepare: (content: Uint8Array) => Promise<RequireFS>, name: string): TestCaseArray {
|
||||
this.cases.push(TestCaseArray.newCase(command, targetFile, prepare, name));
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a test case by index.
|
||||
* @param id Test case index.
|
||||
*/
|
||||
public byID(id: number): TestCase {
|
||||
if (!this.cases[id]) {
|
||||
if (id < 0 || id >= this.cases.length) {
|
||||
throw new Error(`test case index "${id}" out of bounds`);
|
||||
}
|
||||
throw new Error(`test case at index "${id}" not found`);
|
||||
}
|
||||
return this.cases[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a test case by name.
|
||||
* @param name Test case name.
|
||||
*/
|
||||
public byName(name: string): TestCase {
|
||||
let c = this.cases.find((c) => c.name === name);
|
||||
if (!c) {
|
||||
throw new Error(`test case "${name}" not found`);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of test cases.
|
||||
*/
|
||||
public length(): number {
|
||||
return this.cases.length;
|
||||
}
|
||||
}
|
99
packages/requirefs/yarn.lock
Normal file
99
packages/requirefs/yarn.lock
Normal file
@ -0,0 +1,99 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@types/benchmark@^1.0.31":
|
||||
version "1.0.31"
|
||||
resolved "https://registry.yarnpkg.com/@types/benchmark/-/benchmark-1.0.31.tgz#2dd3514e93396f362ba5551a7c9ff0da405c1d38"
|
||||
integrity sha512-F6fVNOkGEkSdo/19yWYOwVKGvzbTeWkR/XQYBKtGBQ9oGRjBN9f/L4aJI4sDcVPJO58Y1CJZN8va9V2BhrZapA==
|
||||
|
||||
"@types/jszip@3.1.4":
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/jszip/-/jszip-3.1.4.tgz#9b81e3901a6988e9459ac27abf483e6b892251af"
|
||||
integrity sha512-UaVbz4buRlBEolZYrxqkrGDOypugYlbqGNrUFB4qBaexrLypTH0jyvaF5jolNy5D+5C4kKV1WJ3Yx9cn/JH8oA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node@*":
|
||||
version "10.11.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.11.3.tgz#c055536ac8a5e871701aa01914be5731539d01ee"
|
||||
integrity sha512-3AvcEJAh9EMatxs+OxAlvAEs7OTy6AG94mcH1iqyVDwVVndekLxzwkWQ/Z4SDbY6GO2oyUXyWW8tQ4rENSSQVQ==
|
||||
|
||||
"@types/resolve@0.0.8":
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194"
|
||||
integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
benchmark@^2.1.4:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629"
|
||||
integrity sha1-CfPeMckWQl1JjMLuVloOvzwqVik=
|
||||
dependencies:
|
||||
lodash "^4.17.4"
|
||||
platform "^1.3.3"
|
||||
|
||||
inherits@2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||
|
||||
jszip@2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/jszip/-/jszip-2.6.0.tgz#7fb3e9c2f11c8a9840612db5dabbc8cf3a7534b7"
|
||||
integrity sha1-f7PpwvEciphAYS212rvIzzp1NLc=
|
||||
dependencies:
|
||||
pako "~1.0.0"
|
||||
|
||||
lodash@^4.17.4:
|
||||
version "4.17.11"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
|
||||
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
|
||||
|
||||
pako@~1.0.0:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258"
|
||||
integrity sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==
|
||||
|
||||
path-parse@^1.0.5:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
|
||||
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
|
||||
|
||||
path@0.12.7:
|
||||
version "0.12.7"
|
||||
resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f"
|
||||
integrity sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=
|
||||
dependencies:
|
||||
process "^0.11.1"
|
||||
util "^0.10.3"
|
||||
|
||||
platform@^1.3.3:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444"
|
||||
integrity sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==
|
||||
|
||||
process@^0.11.1:
|
||||
version "0.11.10"
|
||||
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
|
||||
integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
|
||||
|
||||
resolve@1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
|
||||
integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==
|
||||
dependencies:
|
||||
path-parse "^1.0.5"
|
||||
|
||||
text-encoding@0.6.4:
|
||||
version "0.6.4"
|
||||
resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
|
||||
integrity sha1-45mpgiV6J22uQou5KEXLcb3CbRk=
|
||||
|
||||
util@^0.10.3:
|
||||
version "0.10.4"
|
||||
resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901"
|
||||
integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==
|
||||
dependencies:
|
||||
inherits "2.0.3"
|
7
packages/rules/package.json
Normal file
7
packages/rules/package.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "@coder/rules",
|
||||
"description": "Custom linting rules.",
|
||||
"scripts": {
|
||||
"build": "tsc -p ."
|
||||
}
|
||||
}
|
37
packages/rules/src/curlyStatementNewlinesRule.ts
Normal file
37
packages/rules/src/curlyStatementNewlinesRule.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import * as ts from "typescript";
|
||||
import * as Lint from "tslint";
|
||||
|
||||
/**
|
||||
* Curly statement newlines rule.
|
||||
*/
|
||||
export class Rule extends Lint.Rules.AbstractRule {
|
||||
|
||||
public static FAILURE_STRING = "curly statements must separate with newlines";
|
||||
|
||||
/**
|
||||
* Apply the rule.
|
||||
*/
|
||||
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
|
||||
return this.applyWithWalker(new CurlyStatementNewlinesWalker(sourceFile, this.getOptions()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Curly statement newlines walker.
|
||||
*/
|
||||
class CurlyStatementNewlinesWalker extends Lint.RuleWalker {
|
||||
|
||||
/**
|
||||
* Visit if statements.
|
||||
*/
|
||||
public visitIfStatement(node: ts.IfStatement): void {
|
||||
const splitLength = node.getFullText().split("\n").length;
|
||||
if (splitLength <= 2) {
|
||||
this.addFailureAt(node.getStart(), node.getWidth(), Rule.FAILURE_STRING);
|
||||
}
|
||||
|
||||
super.visitIfStatement(node);
|
||||
}
|
||||
|
||||
}
|
12
packages/rules/tsconfig.json
Normal file
12
packages/rules/tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"sourceMap": false,
|
||||
"declaration": false,
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": [
|
||||
"."
|
||||
]
|
||||
}
|
4
packages/rules/yarn.lock
Normal file
4
packages/rules/yarn.lock
Normal file
@ -0,0 +1,4 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
1
packages/scripts/dummy.js
Normal file
1
packages/scripts/dummy.js
Normal file
@ -0,0 +1 @@
|
||||
// This is for ignoring CSS and images when running tests with Jest.
|
42
packages/scripts/install-packages.ts
Normal file
42
packages/scripts/install-packages.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { exec } from "child_process";
|
||||
import { existsSync, readdirSync } from "fs";
|
||||
import { join, resolve } from "path";
|
||||
import { logger, field } from "../logger";
|
||||
|
||||
/**
|
||||
* Install dependencies for a single package.
|
||||
*/
|
||||
const doInstall = (pkg: string, path: string): void => {
|
||||
logger.info(`Installing "${pkg}" dependencies...`);
|
||||
exec("yarn", {
|
||||
cwd: path,
|
||||
maxBuffer: 1024 * 1024 * 10,
|
||||
}, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
logger.error(
|
||||
`Failed to install "${pkg}" dependencies`,
|
||||
field("error", error),
|
||||
field("stdout", stdout),
|
||||
field("stderr", stderr),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
logger.info(`Successfully grabbed \"${pkg}\" dependencies!`);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Install dependencies for all packages.
|
||||
*/
|
||||
const handlePackages = (dir: string): void => {
|
||||
readdirSync(dir).forEach((pkg) => {
|
||||
const pkgDir = join(dir, pkg);
|
||||
const pkgJsonPath = join(pkgDir, "package.json");
|
||||
if (existsSync(pkgJsonPath)) {
|
||||
doInstall(pkg, pkgDir);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handlePackages(resolve(__dirname, ".."));
|
3
packages/scripts/test-setup.js
Normal file
3
packages/scripts/test-setup.js
Normal file
@ -0,0 +1,3 @@
|
||||
global.requestAnimationFrame = (cb) => {
|
||||
setTimeout(cb, 0);
|
||||
};
|
4
packages/vscode/package.json
Normal file
4
packages/vscode/package.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@coder/vscode",
|
||||
"description": "VS Code implementation of the browser-based IDE client."
|
||||
}
|
277
packages/vscode/src/element/augment.ts
Normal file
277
packages/vscode/src/element/augment.ts
Normal file
@ -0,0 +1,277 @@
|
||||
|
||||
export function classSplice(element: HTMLElement, removeClasses: string, addClasses: string): HTMLElement {
|
||||
if (removeClasses) { removeClasses.split(/\s+/g).forEach((className) => element.classList.remove(className)); }
|
||||
if (addClasses) { addClasses.split(/\s+/g).forEach((className) => element.classList.add(className)); }
|
||||
return element;
|
||||
}
|
||||
|
||||
export type Side = "LEFT" | "RIGHT" | "TOP" | "BOTTOM";
|
||||
export type BoundaryPos = [Side, Side];
|
||||
export interface IBoundary {
|
||||
top: number;
|
||||
left: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
}
|
||||
|
||||
export type PointPos = ["LEFT" | "CENTER" | "RIGHT", "TOP" | "CENTER" | "BOTTOM"];
|
||||
|
||||
export class FloaterPositioning {
|
||||
private static positionClasses = [
|
||||
"--boundary_top_left",
|
||||
"--boundary_top_right",
|
||||
"--boundary_left_top",
|
||||
"--boundary_right_top",
|
||||
"--boundary_left_bottom",
|
||||
"--boundary_right_bottom",
|
||||
"--boundary_bottom_left",
|
||||
"--boundary_bottom_right",
|
||||
|
||||
"--point_top_left",
|
||||
"--point_top_center",
|
||||
"--point_top_right",
|
||||
"--point_center_left",
|
||||
"--point_center_center",
|
||||
"--point_center_right",
|
||||
"--point_bottom_left",
|
||||
"--point_bottom_center",
|
||||
"--point_bottom_right",
|
||||
].join(" ");
|
||||
|
||||
public readonly target: HTMLElement;
|
||||
constructor(target: HTMLElement) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
// this function was surprisingly difficult
|
||||
public moveToBoundary(boundary: IBoundary, pos: BoundaryPos, keepInBounds: boolean = true) {
|
||||
if (keepInBounds) {
|
||||
const height = this.target.offsetHeight;
|
||||
const width = this.target.offsetWidth;
|
||||
if (height === 0 && width === 0) {
|
||||
throw new Error("target must be added to page before it can be in bounds positioned");
|
||||
}
|
||||
const flip = {
|
||||
BOTTOM: "TOP",
|
||||
LEFT: "RIGHT",
|
||||
RIGHT: "LEFT",
|
||||
TOP: "BOTTOM",
|
||||
} as any;
|
||||
|
||||
const getOverlap = (side: string, strong: boolean) => {
|
||||
switch (side) {
|
||||
case "BOTTOM": return ((strong ? boundary.bottom : boundary.top) + height) - window.innerHeight;
|
||||
case "TOP": return 0 - (strong ? boundary.top : boundary.bottom) - height;
|
||||
case "RIGHT": return ((strong ? boundary.right : boundary.left) + width) - window.innerWidth;
|
||||
case "LEFT": return 0 - (strong ? boundary.left : boundary.right) - width;
|
||||
}
|
||||
};
|
||||
|
||||
const firstA = getOverlap(pos[0], true);
|
||||
if (firstA > 0) {
|
||||
const firstB = getOverlap(flip[pos[0]], true);
|
||||
if (firstB < firstA) {
|
||||
pos[0] = flip[pos[0]];
|
||||
}
|
||||
}
|
||||
|
||||
const secA = getOverlap(pos[1], false);
|
||||
if (secA > 0) {
|
||||
const secB = getOverlap(flip[pos[1]], false);
|
||||
if (secB < secA) {
|
||||
pos[1] = flip[pos[1]];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
classSplice(this.target, FloaterPositioning.positionClasses, undefined);
|
||||
this.target.classList.add(`--boundary_${pos.map((val) => val.toLowerCase()).join("_")}`);
|
||||
|
||||
const displayPos: IBoundary = {} as any;
|
||||
switch (pos[0]) {
|
||||
case "BOTTOM": displayPos.top = boundary.bottom; break;
|
||||
case "TOP": displayPos.bottom = window.innerHeight - boundary.top; break;
|
||||
case "LEFT": displayPos.right = window.innerWidth - boundary.left; break;
|
||||
case "RIGHT": displayPos.left = boundary.right; break;
|
||||
}
|
||||
switch (pos[1]) {
|
||||
case "BOTTOM": displayPos.top = boundary.top; break;
|
||||
case "TOP": displayPos.bottom = window.innerHeight - boundary.bottom; break;
|
||||
case "LEFT": displayPos.right = window.innerWidth - boundary.right; break;
|
||||
case "RIGHT": displayPos.left = boundary.left; break;
|
||||
}
|
||||
this.applyPos(displayPos);
|
||||
}
|
||||
|
||||
public moveToPoint(point: { top: number, left: number }, pos: PointPos, keepInBounds: boolean = true): void {
|
||||
if (keepInBounds) {
|
||||
const height = this.target.offsetHeight;
|
||||
const width = this.target.offsetWidth;
|
||||
if (height === 0 && width === 0) {
|
||||
throw new Error("target must be added to page before it can be in bounds positioned");
|
||||
}
|
||||
const flip = {
|
||||
BOTTOM: "TOP",
|
||||
LEFT: "RIGHT",
|
||||
RIGHT: "LEFT",
|
||||
TOP: "BOTTOM",
|
||||
} as any;
|
||||
|
||||
const getOverlap = (side: string) => {
|
||||
switch (side) {
|
||||
case "BOTTOM": return (point.top + height) - window.innerHeight;
|
||||
case "TOP": return -1 * (point.top - height);
|
||||
case "RIGHT": return (point.left + width) - window.innerWidth;
|
||||
case "LEFT": return -1 * (point.left - width);
|
||||
default: return 0;
|
||||
}
|
||||
};
|
||||
|
||||
const xAlign = pos[0];
|
||||
const normalXOffset = getOverlap(xAlign);
|
||||
if (normalXOffset > 0 && normalXOffset > getOverlap(flip[xAlign])) {
|
||||
pos[0] = flip[xAlign];
|
||||
}
|
||||
|
||||
const yAlign = pos[1];
|
||||
const normalYOffset = getOverlap(yAlign);
|
||||
if (normalYOffset > 0 && normalYOffset > getOverlap(flip[yAlign])) {
|
||||
pos[1] = flip[yAlign];
|
||||
}
|
||||
}
|
||||
|
||||
const displayPos: IBoundary = {} as any;
|
||||
let centerX = false;
|
||||
let centerY = false;
|
||||
switch (pos[0]) {
|
||||
case "CENTER": centerX = true;
|
||||
case "RIGHT": displayPos.left = point.left; break;
|
||||
case "LEFT": displayPos.right = window.innerWidth - point.left; break;
|
||||
}
|
||||
switch (pos[1]) {
|
||||
case "CENTER": centerY = true;
|
||||
case "BOTTOM": displayPos.top = point.top; break;
|
||||
case "TOP": displayPos.bottom = window.innerHeight - point.top; break;
|
||||
}
|
||||
|
||||
classSplice(this.target, FloaterPositioning.positionClasses, undefined);
|
||||
this.target.classList.add(`--point_${pos.map((val) => val.toLowerCase()).reverse().join("_")}`);
|
||||
|
||||
this.applyPos(displayPos);
|
||||
this.target.style.transform = `${centerX ? "translateX(-50)" : ""} ${centerY ? "translateY(-50)" : ""}`;
|
||||
}
|
||||
|
||||
private applyPos(pos: IBoundary) {
|
||||
this.target.style.top = pos.top !== undefined ? (pos.top + "px") : "";
|
||||
this.target.style.bottom = pos.bottom !== undefined ? (pos.bottom + "px") : "";
|
||||
this.target.style.left = pos.left !== undefined ? (pos.left + "px") : "";
|
||||
this.target.style.right = pos.right !== undefined ? (pos.right + "px") : "";
|
||||
}
|
||||
}
|
||||
|
||||
export type Boolable = ((item: HTMLElement) => boolean) | boolean;
|
||||
|
||||
export interface IMakeChildrenSelectableArgs {
|
||||
maxSelectable?: number;
|
||||
selectOnKeyHover?: Boolable;
|
||||
selectOnMouseHover?: Boolable;
|
||||
onHover?: (selectedItem: HTMLElement) => void;
|
||||
onSelect: (selectedItem: HTMLElement, wasAlreadySelected?: boolean) => void;
|
||||
isItemSelectable?: (item: HTMLElement) => boolean;
|
||||
}
|
||||
|
||||
export class SelectableChildren {
|
||||
|
||||
public readonly target: HTMLElement;
|
||||
private keyHoveredItem: HTMLElement;
|
||||
private _selectedItem: HTMLElement;
|
||||
private selectOnMouseHover: Boolable;
|
||||
private onHover: (selectedItem: HTMLElement) => void;
|
||||
private onSelect: (selectedItem: HTMLElement) => void;
|
||||
private isItemSelectable: (item: HTMLElement) => boolean;
|
||||
|
||||
constructor(target: HTMLElement, args: IMakeChildrenSelectableArgs) {
|
||||
this.target = target;
|
||||
|
||||
this.onHover = args.onHover;
|
||||
this.onSelect = args.onSelect;
|
||||
this.selectOnMouseHover = args.selectOnMouseHover || false;
|
||||
this.isItemSelectable = args.isItemSelectable;
|
||||
|
||||
// this.target.addEventListener("keydown", (event) => this.onTargetKeydown(event));
|
||||
this.target.addEventListener("mousemove", (event) => this.onTargetMousemove(event));
|
||||
|
||||
Array.from(this.target.children).forEach((child: HTMLElement) => this.registerChild(child));
|
||||
}
|
||||
|
||||
public registerChild(child: HTMLElement) {
|
||||
child.addEventListener("mouseover", (event) => this.onItemHover(child, event));
|
||||
child.addEventListener("mousedown", (event) => this.onItemMousedown(child, event));
|
||||
}
|
||||
|
||||
public get selectedItem() { return this._selectedItem; }
|
||||
|
||||
public unsetSelection() {
|
||||
if (this.selectedItem) { this.selectedItem.classList.remove("--is_selected"); }
|
||||
this._selectedItem = undefined;
|
||||
}
|
||||
|
||||
public trySelectItem(item: HTMLElement): boolean {
|
||||
if (this.checkItemSelectable(item) === false) { return false; }
|
||||
const alreadySelected = item === this.selectedItem;
|
||||
if (!alreadySelected) {
|
||||
this.unsetSelection();
|
||||
this._selectedItem = item;
|
||||
this.selectedItem.classList.add("--is_selected");
|
||||
this.onSelect(this.selectedItem);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public updateAllItemIsSelectableStates() {
|
||||
this.updateItemIsSelectableState(Array.from(this.target.childNodes) as any);
|
||||
}
|
||||
|
||||
public updateItemIsSelectableState(itemOrItems?: HTMLElement | HTMLElement[]) {
|
||||
const items: HTMLElement[] = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
|
||||
|
||||
items.forEach((item) => {
|
||||
if (!this.isItemSelectable || this.isItemSelectable(item)) {
|
||||
item.classList.remove("--not_selectable");
|
||||
} else {
|
||||
item.classList.add("--not_selectable");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private checkItemSelectable(item: HTMLElement): boolean {
|
||||
this.updateItemIsSelectableState(item);
|
||||
return item.classList.contains("--not_selectable") === false;
|
||||
}
|
||||
|
||||
private onTargetMousemove(event: MouseEvent) {
|
||||
classSplice(this.target, "--key_naving", "--mouse_naving");
|
||||
if (this.keyHoveredItem) {
|
||||
this.keyHoveredItem.classList.remove("--key_hovered");
|
||||
this.keyHoveredItem = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private onItemHover(item: HTMLElement, event: Event) {
|
||||
if (this.onHover) { this.onHover(item); }
|
||||
if (
|
||||
this.checkItemSelectable(item)
|
||||
&& typeof this.selectOnMouseHover === "boolean"
|
||||
? this.selectOnMouseHover
|
||||
: (this.selectOnMouseHover as any)(item)
|
||||
) {
|
||||
this.trySelectItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
private onItemMousedown(item: HTMLElement, event: Event) {
|
||||
this.trySelectItem(item);
|
||||
}
|
||||
|
||||
}
|
68
packages/vscode/src/element/contextmenu.css
Normal file
68
packages/vscode/src/element/contextmenu.css
Normal file
@ -0,0 +1,68 @@
|
||||
.context-menu-overlay {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.command-menu {
|
||||
position: fixed;
|
||||
background-color: var(--floater, rgba(67, 67, 61, 1));
|
||||
border: 2px solid rgba(66, 66, 60, 1);
|
||||
color: var(--fg, rgb(216, 216, 216));
|
||||
font-size: 14px;
|
||||
box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.1);
|
||||
/* border-radius: 4px; */
|
||||
overflow: hidden;
|
||||
}
|
||||
.command-menu.--boundary_bottom_right, .command-menu.--boundary_right_bottom, .command-menu.--point_bottom_right {
|
||||
border-top-left-radius: 0px;
|
||||
}
|
||||
.command-menu.--boundary_bottom_left, .command-menu.--boundary_left_bottom, .command-menu.--point_bottom_left {
|
||||
border-top-right-radius: 0px;
|
||||
}
|
||||
.command-menu.--boundary_top_right, .command-menu.--boundary_right_top, .command-menu.--point_top_right {
|
||||
border-bottom-left-radius: 0px;
|
||||
}
|
||||
.command-menu.--boundary_top_left, .command-menu.--boundary_left_top, .command-menu.--point_top_left {
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
.command-menu .menuitem {
|
||||
white-space: nowrap;
|
||||
padding: 5px 20px;
|
||||
cursor: pointer;
|
||||
min-width: 150px;
|
||||
}
|
||||
.command-menu .menuitem:not(.--not_selectable):not(.--is_selected):hover {
|
||||
background: var(--floaterHover, rgba(0, 0, 0, 0.2));
|
||||
}
|
||||
.command-menu .menuitem.--is_selected {
|
||||
background: var(--floaterActive, rgba(0, 0, 0, 0.25));
|
||||
}
|
||||
.command-menu .menuitem:not(.--non_selection_item).--not_selectable {
|
||||
color: var(--fgFade7, rgba(255, 255, 255, 0.3));
|
||||
cursor: unset;
|
||||
}
|
||||
.command-menu .menuitem.entry {
|
||||
display: flex;
|
||||
}
|
||||
.command-menu .menuitem.spacer {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
.command-menu .menuitem.spacer > hr {
|
||||
margin: 0px;
|
||||
border: none;
|
||||
background: var(--fgFade7, rgba(47, 47, 41, 1));
|
||||
opacity: 0.4;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.command-menu .menuitem.entry > .keybind {
|
||||
margin-left: auto;
|
||||
padding-left: 50px;
|
||||
font-size: 12px;
|
||||
opacity: 0.6;
|
||||
}
|
250
packages/vscode/src/element/contextmenu.ts
Normal file
250
packages/vscode/src/element/contextmenu.ts
Normal file
@ -0,0 +1,250 @@
|
||||
/**
|
||||
* SHOULD BE MOVED. THIS IS NOT A UI SECTION
|
||||
*/
|
||||
|
||||
import * as augment from './augment';
|
||||
import "./contextmenu.css";
|
||||
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
|
||||
|
||||
export enum MenuItemType {
|
||||
COMMAND,
|
||||
SUB_MENU,
|
||||
SPACER,
|
||||
CUSTOM_ITEM,
|
||||
GENERATIVE_SUBMENU,
|
||||
}
|
||||
|
||||
export interface IMenuItem {
|
||||
type: MenuItemType;
|
||||
domNode: HTMLElement;
|
||||
priority: number;
|
||||
selectOnHover: boolean;
|
||||
refreshDomNode?: () => void;
|
||||
isSelectable?: (() => boolean) | boolean;
|
||||
onSelect?: () => void;
|
||||
}
|
||||
|
||||
export class ContextMenuManager {
|
||||
|
||||
private readonly domNode: FastDomNode<HTMLDivElement>;
|
||||
|
||||
public constructor() {
|
||||
this.domNode = createFastDomNode(document.createElement("div"));
|
||||
this.domNode.setClassName("context-menu-overlay");
|
||||
// this.display = false;
|
||||
this.domNode.domNode.addEventListener("mousedown", (event) => {
|
||||
event.preventDefault();
|
||||
if (event.target === this.domNode.domNode) {
|
||||
this.display = false;
|
||||
}
|
||||
});
|
||||
this.domNode.domNode.addEventListener("closeAllContextMenus", (event) => {
|
||||
this.display = false;
|
||||
event.stopPropagation();
|
||||
});
|
||||
this.domNode.domNode.addEventListener("contextMenuActive", (event) => {
|
||||
// this.clearStackTill(event.target as HTMLElement);
|
||||
event.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
public onceClose(cb: () => void): void {
|
||||
const l = () => {
|
||||
cb();
|
||||
this.domNode.domNode.removeEventListener("closed", l);
|
||||
};
|
||||
this.domNode.domNode.addEventListener("closed", l);
|
||||
}
|
||||
|
||||
public set display(value: boolean) {
|
||||
if (value) {
|
||||
document.body.appendChild(this.domNode.domNode);
|
||||
} else {
|
||||
this.domNode.domNode.remove();
|
||||
this.domNode.domNode.dispatchEvent(new Event("closed"));
|
||||
}
|
||||
}
|
||||
|
||||
public displayMenuAtBoundary<T>(
|
||||
menu: ContextMenu,
|
||||
boundary: augment.IBoundary,
|
||||
positioning: augment.BoundaryPos = ["BOTTOM", "RIGHT"],
|
||||
clearStack: boolean = true,
|
||||
): void {
|
||||
this.displayMenu(menu, clearStack);
|
||||
menu.positioningAugment.moveToBoundary(boundary, positioning);
|
||||
}
|
||||
|
||||
public displayMenuAtPoint<T>(
|
||||
menu: ContextMenu,
|
||||
point: { top: number, left: number },
|
||||
positioning: augment.PointPos = ["RIGHT", "BOTTOM"],
|
||||
clearStack: boolean = true,
|
||||
): void {
|
||||
this.displayMenu(menu, clearStack);
|
||||
menu.positioningAugment.moveToPoint(point, positioning);
|
||||
}
|
||||
|
||||
private displayMenu(menu: ContextMenu, clearStack: boolean) {
|
||||
while (this.domNode.domNode.lastChild) {
|
||||
this.domNode.domNode.removeChild(this.domNode.domNode.lastChild);
|
||||
}
|
||||
this.domNode.appendChild(menu.domNode);
|
||||
this.display = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class ContextMenu {
|
||||
|
||||
public readonly id: string;
|
||||
public readonly positioningAugment: augment.FloaterPositioning;
|
||||
public readonly selectionAugment: augment.SelectableChildren;
|
||||
public readonly domNode: FastDomNode<HTMLDivElement>;
|
||||
private readonly manager: ContextMenuManager;
|
||||
|
||||
private cachedActive: HTMLElement;
|
||||
private domNodeToItemMap: Map<HTMLElement, IMenuItem>;
|
||||
private items: IMenuItem[];
|
||||
|
||||
constructor(id: string, manager: ContextMenuManager) {
|
||||
this.id = id;
|
||||
this.manager = manager;
|
||||
this.items = [];
|
||||
this.domNodeToItemMap = new Map();
|
||||
this.domNode = createFastDomNode(document.createElement("div"));
|
||||
this.domNode.setClassName("command-menu");
|
||||
this.positioningAugment = new augment.FloaterPositioning(this.domNode.domNode);
|
||||
|
||||
const selectOnHover = (itemDomNode: HTMLElement) => this.domNodeToItemMap.get(itemDomNode).selectOnHover;
|
||||
this.selectionAugment = new augment.SelectableChildren(this.domNode.domNode, {
|
||||
isItemSelectable: (itemDomNode) => {
|
||||
const item = this.domNodeToItemMap.get(itemDomNode);
|
||||
return typeof item.isSelectable === "boolean" ? item.isSelectable : item.isSelectable();
|
||||
},
|
||||
onHover: (itemDomNode) => {
|
||||
const item = this.domNodeToItemMap.get(itemDomNode);
|
||||
if (item.type !== MenuItemType.SUB_MENU && item.type !== MenuItemType.GENERATIVE_SUBMENU) {
|
||||
this.domNode.domNode.dispatchEvent(new Event("contextMenuActive", { bubbles: true }));
|
||||
this.selectionAugment.unsetSelection();
|
||||
}
|
||||
},
|
||||
onSelect: (itemDomNode) => {
|
||||
const item = this.domNodeToItemMap.get(itemDomNode);
|
||||
if (item.onSelect) { item.onSelect(); }
|
||||
},
|
||||
selectOnKeyHover: selectOnHover,
|
||||
selectOnMouseHover: selectOnHover,
|
||||
});
|
||||
}
|
||||
|
||||
public set display(onOff: boolean) {
|
||||
if (onOff === true) {
|
||||
this.cachedActive = document.activeElement as HTMLElement;
|
||||
if (this.cachedActive) {
|
||||
this.cachedActive.blur();
|
||||
}
|
||||
this.items.forEach((item) => !!item.refreshDomNode ? item.refreshDomNode() : null);
|
||||
this.selectionAugment.updateAllItemIsSelectableStates();
|
||||
} else if (this.cachedActive) {
|
||||
this.cachedActive.focus();
|
||||
this.cachedActive = null;
|
||||
}
|
||||
this.domNode.domNode.style.display = onOff ? "" : "none";
|
||||
}
|
||||
|
||||
public addSpacer(priority: number) {
|
||||
const rootNode = createFastDomNode(document.createElement("div"));
|
||||
rootNode.setClassName("menuitem spacer");
|
||||
const hrNode = createFastDomNode(document.createElement("hr"));
|
||||
rootNode.appendChild(hrNode);
|
||||
this.appendMenuItem({
|
||||
domNode: rootNode.domNode,
|
||||
isSelectable: false,
|
||||
priority,
|
||||
selectOnHover: false,
|
||||
type: MenuItemType.SPACER,
|
||||
});
|
||||
}
|
||||
|
||||
public addEntry(priority: number, label: string, accelerator: string, enabled: boolean, callback: () => void) {
|
||||
const domNode = createFastDomNode(document.createElement("div"));
|
||||
domNode.setClassName("menuitem entry");
|
||||
const labelNode = createFastDomNode(document.createElement("div"));
|
||||
labelNode.setClassName("entrylabel");
|
||||
labelNode.domNode.innerText = label;
|
||||
domNode.appendChild(labelNode);
|
||||
|
||||
if (accelerator) {
|
||||
const accelNode = createFastDomNode(document.createElement("div"));
|
||||
accelNode.setClassName("keybind");
|
||||
accelNode.domNode.innerText = accelerator;
|
||||
domNode.appendChild(accelNode);
|
||||
}
|
||||
|
||||
|
||||
const menuItem: IMenuItem = {
|
||||
domNode: domNode.domNode,
|
||||
isSelectable: () => enabled,
|
||||
onSelect: () => {
|
||||
if (this.cachedActive) {
|
||||
this.cachedActive.focus();
|
||||
this.cachedActive = null;
|
||||
}
|
||||
callback();
|
||||
domNode.domNode.dispatchEvent(new Event("closeAllContextMenus", { bubbles: true }));
|
||||
},
|
||||
priority,
|
||||
selectOnHover: false,
|
||||
type: MenuItemType.COMMAND,
|
||||
};
|
||||
this.appendMenuItem(menuItem);
|
||||
}
|
||||
|
||||
public addSubMenu(priority: number, subMenu: ContextMenu, label: string, description?: string) {
|
||||
const rootNode = createFastDomNode(document.createElement("div"));
|
||||
rootNode.setClassName("menuitem");
|
||||
const subLabel = createFastDomNode(document.createElement("div"));
|
||||
subLabel.setClassName("seg submenulabel");
|
||||
subLabel.domNode.innerText = label;
|
||||
const subArrow = createFastDomNode(document.createElement("div"));
|
||||
subArrow.setClassName("seg submenuarrow");
|
||||
subArrow.domNode.innerText = "->";
|
||||
rootNode.appendChild(subLabel);
|
||||
rootNode.appendChild(subArrow);
|
||||
this.appendMenuItem({
|
||||
domNode: rootNode.domNode,
|
||||
isSelectable: true,
|
||||
onSelect: () => {
|
||||
this.manager.displayMenuAtBoundary(subMenu, rootNode.domNode.getBoundingClientRect(), ["RIGHT", "BOTTOM"], false);
|
||||
},
|
||||
priority,
|
||||
selectOnHover: true,
|
||||
type: MenuItemType.SUB_MENU,
|
||||
});
|
||||
}
|
||||
|
||||
// used for generative sub menu... needs to be less public
|
||||
public removeAllItems() {
|
||||
while (this.items.length) {
|
||||
const removeMe = this.items.pop();
|
||||
removeMe.domNode.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private appendMenuItem(item: IMenuItem) {
|
||||
this.items.push(item);
|
||||
this.domNodeToItemMap.set(item.domNode, item);
|
||||
this.selectionAugment.registerChild(item.domNode);
|
||||
this.items = this.items.sort((a, b) => a.priority - b.priority);
|
||||
this.sortDomNode();
|
||||
}
|
||||
|
||||
private sortDomNode() {
|
||||
while (this.domNode.domNode.lastChild) {
|
||||
this.domNode.domNode.removeChild(this.domNode.domNode.lastChild);
|
||||
}
|
||||
this.items.forEach((item) => this.domNode.domNode.appendChild(item.domNode));
|
||||
}
|
||||
|
||||
}
|
55
packages/vscode/src/entry.ts
Normal file
55
packages/vscode/src/entry.ts
Normal file
@ -0,0 +1,55 @@
|
||||
const loadTime = time(2500);
|
||||
|
||||
import { URI } from "vs/base/common/uri";
|
||||
import { field, logger, time } from "@coder/logger";
|
||||
import { Client, IURI, setUriFactory } from "@coder/ide";
|
||||
import "./firefox";
|
||||
import "./setup";
|
||||
|
||||
setUriFactory({
|
||||
// TODO: not sure why this is an error.
|
||||
// tslint:disable-next-line no-any
|
||||
create: <URI>(uri: IURI): URI => URI.from(uri) as any,
|
||||
file: (path: string): IURI => URI.file(path),
|
||||
parse: (raw: string): IURI => URI.parse(raw),
|
||||
});
|
||||
|
||||
export const client = new Client({
|
||||
mkDirs: [
|
||||
"~/vscode/extensions",
|
||||
"~/.config/User",
|
||||
],
|
||||
});
|
||||
|
||||
const overlayElement = document.getElementById("overlay");
|
||||
const msgElement = overlayElement
|
||||
? overlayElement.querySelector(".message") as HTMLElement
|
||||
: undefined;
|
||||
|
||||
const importTime = time(1500);
|
||||
import(/* webpackPrefetch: true */ "./workbench").then((module) => {
|
||||
logger.info("Loaded workbench bundle", field("duration", importTime));
|
||||
const initTime = time(1500);
|
||||
|
||||
return module.initialize(client).then(() => {
|
||||
logger.info("Initialized workbench", field("duration", initTime));
|
||||
logger.info("Load completed", field("duration", loadTime));
|
||||
if (overlayElement) {
|
||||
overlayElement.style.opacity = "0";
|
||||
overlayElement.addEventListener("transitionend", () => {
|
||||
overlayElement.remove();
|
||||
});
|
||||
}
|
||||
});
|
||||
}).catch((error) => {
|
||||
logger.error(error);
|
||||
if (overlayElement) {
|
||||
overlayElement.classList.add("error");
|
||||
}
|
||||
if (msgElement) {
|
||||
msgElement.innerText = `Failed to load: ${error.message}. Retrying in 3 seconds...`;
|
||||
}
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 3000);
|
||||
});
|
6
packages/vscode/src/fill/css.js
Normal file
6
packages/vscode/src/fill/css.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = function(source) {
|
||||
if (this.resourcePath.endsWith(".ts")) {
|
||||
this.resourcePath = this.resourcePath.replace(".ts", ".css");
|
||||
}
|
||||
return `module.exports = require("${this.resourcePath}");`;
|
||||
};
|
8
packages/vscode/src/fill/native-keymap.ts
Normal file
8
packages/vscode/src/fill/native-keymap.ts
Normal file
@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
getCurrentKeyboardLayout: (): null => {
|
||||
return null;
|
||||
},
|
||||
getKeyMap: (): undefined[] => {
|
||||
return [];
|
||||
},
|
||||
};
|
73
packages/vscode/src/fill/node-pty.ts
Normal file
73
packages/vscode/src/fill/node-pty.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import * as cp from "child_process";
|
||||
import { EventEmitter } from "events";
|
||||
import * as nodePty from "node-pty";
|
||||
|
||||
type nodePtyType = typeof nodePty;
|
||||
|
||||
/**
|
||||
* Implementation of nodePty for the browser.
|
||||
*/
|
||||
class Pty implements nodePty.IPty {
|
||||
|
||||
private readonly emitter: EventEmitter;
|
||||
|
||||
public constructor(file: string, args: string[] | string, options: nodePty.IPtyForkOptions) {
|
||||
this.emitter = new EventEmitter();
|
||||
const session = wush.execute({
|
||||
command: `${file} ${Array.isArray(args) ? args.join(" ") : args}`,
|
||||
directory: options.cwd,
|
||||
environment: {
|
||||
...(options.env || {}),
|
||||
TERM: "xterm-color",
|
||||
},
|
||||
size: options && options.cols && options.rows ? {
|
||||
columns: options.cols,
|
||||
rows: options.rows,
|
||||
} : {
|
||||
columns: 100,
|
||||
rows: 100,
|
||||
},
|
||||
});
|
||||
this.on("write", (data) => session.sendStdin(data));
|
||||
this.on("kill", (exitCode) => session.close());
|
||||
this.on("resize", (columns, rows) => session.setSize({ columns, rows }));
|
||||
session.onStdout((data) => this.emitter.emit("data", data));
|
||||
session.onStderr((data) => this.emitter.emit("data", data));
|
||||
session.onDone((exitCode) => this.emitter.emit("exit", exitCode));
|
||||
}
|
||||
|
||||
public get pid(): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public get process(): string {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
public on(event: string, listener: (...args) => void): void {
|
||||
this.emitter.on(event, listener);
|
||||
}
|
||||
|
||||
public resize(columns: number, rows: number): void {
|
||||
this.emitter.emit("resize", columns, rows);
|
||||
}
|
||||
|
||||
public write(data: string): void {
|
||||
this.emitter.emit("write", data);
|
||||
}
|
||||
|
||||
public kill(signal?: string): void {
|
||||
this.emitter.emit("kill", signal);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const ptyType: nodePtyType = {
|
||||
|
||||
spawn: (file: string, args: string[] | string, options: nodePty.IPtyForkOptions): nodePty.IPty => {
|
||||
return new Pty(file, args, options);
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
module.exports = ptyType;
|
9
packages/vscode/src/firefox.scss
Normal file
9
packages/vscode/src/firefox.scss
Normal file
@ -0,0 +1,9 @@
|
||||
// Using @supports to keep the Firefox fixes completely separate from vscode's
|
||||
// CSS that is tailored for Chrome.
|
||||
@supports (-moz-appearance:none) {
|
||||
/* Fix buttons getting cut off on notifications. */
|
||||
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-buttons-container .monaco-button.monaco-text-button {
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
}
|
||||
}
|
20
packages/vscode/src/firefox.ts
Normal file
20
packages/vscode/src/firefox.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import "./firefox.scss";
|
||||
|
||||
if (!("toElement" in MouseEvent.prototype)) {
|
||||
Object.defineProperty(MouseEvent.prototype, "toElement", {
|
||||
get: function (): EventTarget | null {
|
||||
// @ts-ignore
|
||||
const event = this as MouseEvent;
|
||||
switch (event.type) {
|
||||
case "mouseup":
|
||||
case "focusin":
|
||||
case "mousenter":
|
||||
case "mouseover":
|
||||
case "dragenter":
|
||||
return event.target;
|
||||
default:
|
||||
return event.relatedTarget;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
58
packages/vscode/src/storageService.ts
Normal file
58
packages/vscode/src/storageService.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
|
||||
export class StorageService implements IStorageService {
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
private _globalObject: object;
|
||||
private _workspaceObject: object;
|
||||
|
||||
public constructor(globalState: object, workspaceState: object) {
|
||||
this._globalObject = globalState;
|
||||
this._workspaceObject = workspaceState;
|
||||
}
|
||||
|
||||
public get globalObject() {
|
||||
return this._globalObject;
|
||||
}
|
||||
|
||||
public get workspaceObject() {
|
||||
return this._workspaceObject;
|
||||
}
|
||||
|
||||
public store(key: string, value: any, scope?: StorageScope): void {
|
||||
this.getObject(scope)[key] = value;
|
||||
}
|
||||
|
||||
public remove(key: string, scope?: StorageScope): void {
|
||||
delete this.getObject(scope)[key];
|
||||
}
|
||||
|
||||
public get(key: string, scope?: StorageScope, defaultValue?: string): string {
|
||||
return this.getObject(scope)[key] || defaultValue;
|
||||
}
|
||||
|
||||
public getInteger(key: string, scope?: StorageScope, defaultValue?: number): number {
|
||||
return parseInt(this.get(key, scope), 10) || defaultValue;
|
||||
}
|
||||
|
||||
public getBoolean(key: string, scope?: StorageScope, defaultValue?: boolean): boolean {
|
||||
const v = this.get(key, scope);
|
||||
if (typeof v !== "undefined") {
|
||||
return v === 'true';
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private getObject(scope = StorageScope.GLOBAL): object {
|
||||
switch (scope) {
|
||||
case StorageScope.GLOBAL:
|
||||
return this._globalObject;
|
||||
case StorageScope.WORKSPACE:
|
||||
return this._workspaceObject;
|
||||
default:
|
||||
throw new Error("unsupported storage scope");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
59
packages/vscode/src/upload.ts
Normal file
59
packages/vscode/src/upload.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { Upload as BaseUpload, IURI } from "@coder/ide";
|
||||
import { client } from "./entry";
|
||||
import { INotificationService, Severity } from "vs/platform/notification/common/notification";
|
||||
import { IProgressService2, ProgressLocation } from "vs/workbench/services/progress/common/progress";
|
||||
|
||||
export class Upload extends BaseUpload {
|
||||
|
||||
public constructor(
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IProgressService2 progressService: IProgressService2,
|
||||
) {
|
||||
super({
|
||||
error: (error) => {
|
||||
notificationService.error(error);
|
||||
},
|
||||
prompt: (message, choices) => {
|
||||
return new Promise((resolve) => {
|
||||
notificationService.prompt(
|
||||
Severity.Error,
|
||||
message,
|
||||
choices.map((label) => ({
|
||||
label,
|
||||
run: () => {
|
||||
resolve(label);
|
||||
},
|
||||
})),
|
||||
() => {
|
||||
resolve(undefined);
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
}, {
|
||||
start: (title, task) => {
|
||||
let lastProgress = 0;
|
||||
|
||||
return progressService.withProgress({
|
||||
location: ProgressLocation.Notification,
|
||||
title,
|
||||
cancellable: true,
|
||||
}, (progress) => {
|
||||
return task({
|
||||
report: (p) => {
|
||||
progress.report({ increment: p - lastProgress });
|
||||
lastProgress = p;
|
||||
},
|
||||
});
|
||||
}, () => {
|
||||
this.cancel();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async uploadDropped(event: DragEvent, uri?: IURI): Promise<string[]> {
|
||||
return super.uploadDropped(event, uri || (await client.workspace).mountUri);
|
||||
}
|
||||
|
||||
}
|
245
packages/vscode/src/workbench.ts
Normal file
245
packages/vscode/src/workbench.ts
Normal file
@ -0,0 +1,245 @@
|
||||
import * as fs from "fs";
|
||||
import {
|
||||
Client, Emitter, getFactory, IPosition, IFileConflict, ConflictResolution,
|
||||
Event,
|
||||
IDisposable,
|
||||
IDocumentContentChangedEvent, IURI, IRange, escapePath,
|
||||
IOrphanedChangedEvent,
|
||||
} from 'coder/common';
|
||||
import { Protocol } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ITextModel, TrackedRangeStickiness, IModelDeltaDecoration } from 'vs/editor/common/model';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu';
|
||||
import { Workbench } from 'vs/workbench/electron-browser/workbench';
|
||||
import { StorageService } from 'coder/storageService';
|
||||
import { IContentData, IFileService, FileOperationError, FileOperationResult, FileSystemProviderCapabilities, IStat, FileType } from 'vs/platform/files/common/files';
|
||||
import { onInstantiation as onFileServiceInstantiation } from 'vs/workbench/services/files/electron-browser/fileService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { EventEmitter } from 'events';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { CONFLICT_RESOLUTION_SCHEME } from 'vs/workbench/parts/files/electron-browser/saveErrorHandler';
|
||||
import { ITextFileService, ModelState } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { field, logger } from 'coder/logger';
|
||||
import { events } from 'coder/analytics';
|
||||
import { IDecorationsService } from 'vs/workbench/services/decorations/browser/decorations';
|
||||
import { registerCollaboratorDecorations } from 'coder/collaborators';
|
||||
import { IInitData as ISharedProcessInitData } from 'vs/code/electron-browser/sharedProcess/sharedProcessClient';
|
||||
import { LogLevel } from 'vs/platform/log/common/log';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { toLocalISOString } from 'vs/base/common/date';
|
||||
import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
let protoResolve: (protocol: Protocol) => void;
|
||||
export const protocolPromise = new Promise<Protocol>((res) => {
|
||||
protoResolve = res;
|
||||
});
|
||||
let storageResolve: (storageService: StorageService) => void;
|
||||
export const getStorageService = new Promise<StorageService>((res) => {
|
||||
storageResolve = res;
|
||||
});
|
||||
export let systemExtensionsLocation: string;
|
||||
export let forkedBinLocation: string;
|
||||
|
||||
const hasNativeClipboard = typeof navigator !== "undefined" && typeof (navigator as any).clipboard !== "undefined" && typeof (navigator as any).clipboard.readText !== "undefined";
|
||||
let isEnabled: boolean = false;
|
||||
const clipboardEnabledEmitter = new Emitter<boolean>();
|
||||
export const nativeClipboard: {
|
||||
readonly contextKey: RawContextKey<boolean>;
|
||||
readonly instance: {
|
||||
readText(): Promise<string>;
|
||||
writeText(value: string): Promise<void>;
|
||||
};
|
||||
readonly onChange: Event<boolean>;
|
||||
readonly isEnabled: boolean;
|
||||
} = {
|
||||
contextKey: new RawContextKey('nativeClipboard', hasNativeClipboard),
|
||||
instance: hasNativeClipboard ? (navigator as any).clipboard : undefined,
|
||||
get onChange(): Event<boolean> {
|
||||
return clipboardEnabledEmitter.event;
|
||||
},
|
||||
get isEnabled(): boolean {
|
||||
return isEnabled;
|
||||
}
|
||||
};
|
||||
|
||||
let workbench: Workbench;
|
||||
|
||||
function getModelService(): IModelService {
|
||||
return workbench.workbenchParams.serviceCollection.get<IModelService>(IModelService) as IModelService;
|
||||
}
|
||||
|
||||
function getCodeEditorService(): ICodeEditorService {
|
||||
return workbench.workbenchParams.serviceCollection.get(ICodeEditorService) as ICodeEditorService;
|
||||
}
|
||||
|
||||
function getFileService(): IFileService {
|
||||
return workbench.workbenchParams.serviceCollection.get(IFileService) as IFileService;
|
||||
}
|
||||
|
||||
function getTextFileService(): ITextFileService {
|
||||
return workbench.workbenchParams.serviceCollection.get(ITextFileService) as ITextFileService;
|
||||
}
|
||||
|
||||
function getNotificationService(): INotificationService {
|
||||
return workbench.workbenchParams.serviceCollection.get(INotificationService) as INotificationService;
|
||||
}
|
||||
|
||||
export const initialize = async (client: Client): Promise<void> {
|
||||
window.addEventListener("contextmenu", (event) => {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
const storageServicePromise = client.wrapTask("Set configurations", 5, async (state) => {
|
||||
const storageService = new StorageService(state.global, state.workspace);
|
||||
storageResolve(storageService);
|
||||
|
||||
return storageService;
|
||||
}, client.state);
|
||||
|
||||
// Set up window ID for logging. We'll also use a static logging directory
|
||||
// otherwise we'd have to get the log directory back from the currently
|
||||
// running shared process. This allows us to not wait for that. Each window
|
||||
// will still have its own logging within that directory.
|
||||
const windowId = parseInt(toLocalISOString(new Date()).replace(/[-:.TZ]/g, ""), 10);
|
||||
process.env.VSCODE_LOGS = "/tmp/vscode-logs";
|
||||
|
||||
client.wrapTask("Start shared process", 5, async (api, wush, mountPath) => {
|
||||
const session = wush.execute({
|
||||
command: "bash -c 'VSCODE_ALLOW_IO=true"
|
||||
+ " AMD_ENTRYPOINT=vs/code/electron-browser/sharedProcess/sharedProcessClient"
|
||||
+ ` nice -n -17 ${nodePath} ${bootstrapForkLocation} --client'`,
|
||||
});
|
||||
|
||||
const sharedProcessLogger = logger.named("shr proc");
|
||||
session.onStderr((data) => {
|
||||
sharedProcessLogger.error("stderr: " + data);
|
||||
});
|
||||
|
||||
session.onDone(() => {
|
||||
workbenchPromise.then(() => {
|
||||
getNotificationService().prompt(
|
||||
Severity.Error,
|
||||
"Shared process terminated unexpectedly.",
|
||||
[{
|
||||
label: "Reload IDE",
|
||||
run: (): void => {
|
||||
window.location.reload();
|
||||
},
|
||||
}],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const protocol = Protocol.fromStdio({
|
||||
onMessage: (cb) => {
|
||||
session.onStdout((data) => {
|
||||
cb(Buffer.from(data as any));
|
||||
}, true);
|
||||
},
|
||||
sendMessage: (data) => {
|
||||
session.sendStdin(data);
|
||||
},
|
||||
});
|
||||
|
||||
await new Promise((resolve) => {
|
||||
const listener = protocol.onMessage((message) => {
|
||||
const messageStr = message.toString();
|
||||
sharedProcessLogger.debug(messageStr);
|
||||
switch (messageStr) {
|
||||
case "handshake:hello":
|
||||
protocol.send(Buffer.from(JSON.stringify({
|
||||
// Using the mount path so if we get a new mount, it spins up a new shared
|
||||
// process since it or the builtin extensions could contain changes.
|
||||
sharedIPCHandle: `/tmp/vscode-shared${mountPath.replace(/\//g, "-")}.sock`,
|
||||
serviceUrl: api.environment.appURL("extensions-api"),
|
||||
logsDir: process.env.VSCODE_LOGS,
|
||||
nodePath,
|
||||
bootstrapForkLocation,
|
||||
args: {},
|
||||
windowId,
|
||||
logLevel: LogLevel.Info,
|
||||
} as ISharedProcessInitData)));
|
||||
break;
|
||||
case "handshake:ready":
|
||||
listener.dispose();
|
||||
resolve();
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
protoResolve(protocol);
|
||||
}, client.api, client.wush, mountPromise);
|
||||
|
||||
const { startup, URI } = require('vs/workbench/workbench.main');
|
||||
|
||||
require("os").homedir = () => {
|
||||
// TODO: update this as well as folderURL
|
||||
return "/root";
|
||||
};
|
||||
require("path").posix = require("path");
|
||||
|
||||
registerContextMenuListener();
|
||||
|
||||
const workbenchPromise = client.wrapTask("Start workbench", 1000, async (workspace, mountPath) => {
|
||||
const workbenchShellPromise = startup({
|
||||
machineId: "1",
|
||||
windowId,
|
||||
logLevel: LogLevel.Info,
|
||||
mainPid: 1,
|
||||
appRoot: mountPath,
|
||||
execPath: "/tmp",
|
||||
userEnv: {},
|
||||
nodeCachedDataDir: "/tmp",
|
||||
perfEntries: [],
|
||||
_: undefined,
|
||||
folderUri: URI.file(workspace.mountUri.path),
|
||||
});
|
||||
|
||||
const workbenchShell = await workbenchShellPromise;
|
||||
workbench = workbenchShell.workbench;
|
||||
|
||||
const contextKeys = workbench.workbenchParams.serviceCollection.get(IContextKeyService) as IContextKeyService;
|
||||
const bounded = nativeClipboard.contextKey.bindTo(contextKeys);
|
||||
|
||||
const navigatorClip = (navigator as any).clipboard;
|
||||
const navigatorPerms = (navigator as any).permissions;
|
||||
if (navigatorClip && navigatorPerms) {
|
||||
navigatorPerms.query({
|
||||
name: "clipboard-read",
|
||||
}).then((permissionStatus) => {
|
||||
const updateStatus = () => {
|
||||
if (permissionStatus.state === "denied") {
|
||||
isEnabled = false;
|
||||
clipboardEnabledEmitter.emit(false);
|
||||
bounded.set(false);
|
||||
} else {
|
||||
isEnabled = true;
|
||||
clipboardEnabledEmitter.emit(true);
|
||||
bounded.set(true);
|
||||
}
|
||||
};
|
||||
|
||||
updateStatus();
|
||||
|
||||
permissionStatus.onchange = () => {
|
||||
updateStatus();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const decorations = workbench.workbenchParams.serviceCollection.get(IDecorationsService) as IDecorationsService;
|
||||
await registerCollaboratorDecorations(client, decorations);
|
||||
|
||||
return workbenchShell;
|
||||
}, client.workspace.then((w) => w.connect()), mountPromise, client.mkDirs);
|
||||
|
||||
await workbenchPromise;
|
||||
};
|
3787
packages/yarn.lock
Normal file
3787
packages/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
26
tsconfig.json
Normal file
26
tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"baseUrl": ".",
|
||||
"rootDir": ".",
|
||||
"outDir": "dist",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"experimentalDecorators": true,
|
||||
"paths": {
|
||||
"@coder/*": [
|
||||
"./packages/*/src"
|
||||
],
|
||||
"vs/*": [
|
||||
"./lib/vscode/src/vs/*"
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "tslint-language-service"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
86
tslint.json
Normal file
86
tslint.json
Normal file
@ -0,0 +1,86 @@
|
||||
{
|
||||
"rulesDirectory": "./packages/rules/dist",
|
||||
"rules": {
|
||||
"only-arrow-functions": true,
|
||||
"curly-statement-newlines": true,
|
||||
"adjacent-overload-signatures": true,
|
||||
"align": true,
|
||||
"await-promise": [true, "Thenable"],
|
||||
"class-name": true,
|
||||
"eofline": true,
|
||||
"import-spacing": true,
|
||||
"no-angle-bracket-type-assertion": true,
|
||||
"no-bitwise": false,
|
||||
"no-any": true,
|
||||
"newline-before-return": true,
|
||||
"no-console": true,
|
||||
"no-duplicate-imports": true,
|
||||
"no-consecutive-blank-lines": true,
|
||||
"no-empty": true,
|
||||
"no-floating-promises": true,
|
||||
"no-return-await": true,
|
||||
"no-var-keyword": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-redundant-jsdoc": true,
|
||||
"no-implicit-dependencies": false,
|
||||
"no-boolean-literal-compare": true,
|
||||
"deprecation": true,
|
||||
"semicolon": true,
|
||||
"one-line": [
|
||||
true,
|
||||
"check-catch",
|
||||
"check-finally",
|
||||
"check-else",
|
||||
"check-whitespace",
|
||||
"check-open-brace"
|
||||
],
|
||||
"completed-docs": {
|
||||
"options": [
|
||||
true,
|
||||
"enums",
|
||||
"functions",
|
||||
"methods",
|
||||
"classes"
|
||||
],
|
||||
"severity": "warning"
|
||||
},
|
||||
"no-unused-expression": [
|
||||
true,
|
||||
"allow-fast-null-checks"
|
||||
],
|
||||
"curly": [
|
||||
true
|
||||
],
|
||||
"quotemark": [
|
||||
true,
|
||||
"double",
|
||||
"avoid-escape",
|
||||
"avoid-template"
|
||||
],
|
||||
"trailing-comma": [
|
||||
true,
|
||||
{
|
||||
"multiline": "always",
|
||||
"singleline": "never",
|
||||
"esSpecCompliant": true
|
||||
}
|
||||
],
|
||||
"space-before-function-paren": [
|
||||
false,
|
||||
"always"
|
||||
],
|
||||
"member-access": [
|
||||
true,
|
||||
"check-accessor",
|
||||
"check-constructor",
|
||||
"check-parameter-property"
|
||||
],
|
||||
"typedef": [
|
||||
true,
|
||||
"call-signature",
|
||||
"arrow-call-signature",
|
||||
"parameter",
|
||||
"property-declaration"
|
||||
]
|
||||
}
|
||||
}
|
194
webpack.config.app.js
Normal file
194
webpack.config.app.js
Normal file
@ -0,0 +1,194 @@
|
||||
const path = require("path");
|
||||
|
||||
const sourcePath = "./src";
|
||||
const entryFile = "./coder/entry.ts";
|
||||
const isCi = typeof process.env.CI !== "undefined";
|
||||
const environment = process.env.NODE_ENV || "development";
|
||||
const minify = isCi;
|
||||
const compatibility = isCi;
|
||||
const HappyPack = require("happypack");
|
||||
const webpack = require("webpack");
|
||||
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
|
||||
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
|
||||
const WriteFilePlugin = require("write-file-webpack-plugin");
|
||||
const PreloadWebpackPlugin = require("preload-webpack-plugin");
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
|
||||
module.exports = {
|
||||
context: path.join(__dirname, sourcePath),
|
||||
devtool: "eval", // "cheap-module-eval-source-map",
|
||||
entry: entryFile,
|
||||
mode: isCi ? "production" : "development",
|
||||
output: {
|
||||
chunkFilename: "[name]-[hash:6].bundle.js",
|
||||
path: path.resolve(__dirname, "./dist"),
|
||||
publicPath: process.env.BUILD_ID ? `/${process.env.BUILD_ID}/ide/` : "/ide/",
|
||||
filename: "[hash:6].bundle.js",
|
||||
globalObject: "this",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
loader: "string-replace-loader",
|
||||
test: /\.(js|ts)/,
|
||||
options: {
|
||||
multiple: [
|
||||
{
|
||||
search: "require\\.toUrl\\(",
|
||||
replace: "requireToUrl(",
|
||||
flags: "g",
|
||||
},
|
||||
{
|
||||
search: "require\\.__\\$__nodeRequire",
|
||||
replace: "require",
|
||||
flags: "g",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(js)/,
|
||||
exclude: /test/,
|
||||
},
|
||||
{
|
||||
test: /\.(node|txt|d\.ts|test.ts|perf.data.js|jxs)/,
|
||||
loader: "ignore-loader",
|
||||
},
|
||||
{
|
||||
use: [{
|
||||
loader: "happypack/loader?id=ts",
|
||||
}],
|
||||
test: /(^.?|\.[^d]|[^.]d|[^.][^d])\.tsx?$/,
|
||||
}, {
|
||||
exclude: /test/,
|
||||
test: /\.s?css$/,
|
||||
// This is required otherwise it'll fail to resolve
|
||||
// CSS in common
|
||||
include: __dirname,
|
||||
use: [true ? {
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
} : "style-loader", require.resolve("css-loader"), require.resolve("sass-loader")],
|
||||
}, {
|
||||
test: /\.(svg|png|ttf|woff|eot)$/,
|
||||
use: ["file-loader"]
|
||||
}, {
|
||||
test: /\.wasm$/,
|
||||
type: "javascript/auto",
|
||||
}
|
||||
],
|
||||
noParse: /(\.test\.tsx?)|(\.test\.jsx?)/,
|
||||
},
|
||||
node: {
|
||||
// electron: "empty",
|
||||
// fs: "empty",
|
||||
// child_process: "empty",
|
||||
|
||||
module: "empty",
|
||||
// net: "empty",
|
||||
crypto: "empty",
|
||||
tls: "empty",
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"gc-signals": path.resolve(__dirname, "./fill/empty.ts"),
|
||||
"native-keymap": path.resolve(__dirname, "./fill/native-keymap.ts"),
|
||||
"windows-process-tree": path.resolve(__dirname, "./fill/empty.ts"),
|
||||
"windows-mutex": path.resolve(__dirname, "./fill/empty.ts"),
|
||||
"selenium-webdriver": path.resolve(__dirname, "./fill/empty.ts"),
|
||||
"windows-foreground-love": path.resolve(__dirname, "./fill/empty.ts"),
|
||||
"vscode-fsevents": path.resolve(__dirname, "./fill/empty.ts"),
|
||||
"vsda": path.resolve(__dirname, "./fill/empty.ts"),
|
||||
"vscode": path.resolve(__dirname, "./fill/empty.ts"),
|
||||
"coder$": path.resolve(__dirname, "./fill/empty.ts"),
|
||||
|
||||
"crypto": "crypto-browserify",
|
||||
"spdlog": path.resolve(__dirname, "./fill/spdlog.ts"),
|
||||
"child_process": path.resolve(__dirname, "./fill/child_process.ts"),
|
||||
"electron": path.resolve(__dirname, "./fill/electron.ts"),
|
||||
"fs": path.resolve(__dirname, "./fill/fs.ts"),
|
||||
"http": "http-browserify",
|
||||
"node-pty": path.resolve(__dirname, "./fill/node-pty.ts"),
|
||||
"os": "os-browserify",
|
||||
"net": path.resolve(__dirname, "./fill/net.ts"),
|
||||
"coder": path.resolve(__dirname, "./src/coder"),
|
||||
"vs": path.resolve(__dirname, "./src/vs"),
|
||||
"util": path.resolve(__dirname, "./node_modules/util"),
|
||||
"@coder": path.resolve(__dirname, "../../"),
|
||||
},
|
||||
extensions: [".js", ".jsx", ".ts", ".tsx", ".json", ".css"],
|
||||
mainFiles: [
|
||||
"index",
|
||||
"src/index",
|
||||
],
|
||||
modules: [
|
||||
path.resolve(__dirname, "./node_modules"),
|
||||
"../node_modules",
|
||||
path.resolve(__dirname, "../../../"),
|
||||
],
|
||||
},
|
||||
resolveLoader: {
|
||||
alias: {
|
||||
"vs/css": path.resolve(__dirname, "./fill/css.js"),
|
||||
},
|
||||
modules: [
|
||||
path.resolve(__dirname, "./node_modules"),
|
||||
],
|
||||
},
|
||||
devServer: {
|
||||
contentBase: sourcePath,
|
||||
compress: true,
|
||||
host: "0.0.0.0",
|
||||
hot: true,
|
||||
historyApiFallback: true,
|
||||
port: 9966,
|
||||
inline: true,
|
||||
disableHostCheck: true,
|
||||
stats: {
|
||||
warnings: false
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: "./index.html",
|
||||
}),
|
||||
new HappyPack({
|
||||
id: "ts",
|
||||
threads: 2,
|
||||
loaders: [
|
||||
{
|
||||
path: "ts-loader",
|
||||
query: {
|
||||
happyPackMode: true,
|
||||
},
|
||||
}
|
||||
],
|
||||
}),
|
||||
// new BundleAnalyzerPlugin(),
|
||||
new WriteFilePlugin({
|
||||
exitOnErrors: false,
|
||||
}),
|
||||
new PreloadWebpackPlugin({
|
||||
rel: "preload",
|
||||
as: "script",
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': `"${environment}"`,
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: "[name].css",
|
||||
chunkFilename: "[id].css",
|
||||
}),
|
||||
// minify ? new UglifyJsPlugin({
|
||||
// cache: true,
|
||||
// parallel: true,
|
||||
// sourceMap: false,
|
||||
// }) : undefined,
|
||||
// new ForkTsCheckerWebpackPlugin({
|
||||
// checkSyntacticErrors: true,
|
||||
// tsconfig: path.resolve(__dirname, "./src/tsconfig.json"),
|
||||
// }),
|
||||
],
|
||||
target: "web",
|
||||
};
|
180
webpack.config.bootstrap.js
Normal file
180
webpack.config.bootstrap.js
Normal file
@ -0,0 +1,180 @@
|
||||
const path = require("path");
|
||||
|
||||
const sourcePath = "./src";
|
||||
const entryFile = "./bootstrap-fork.js";
|
||||
const minify = false;
|
||||
const HappyPack = require("happypack");
|
||||
const webpack = require("webpack");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
|
||||
|
||||
module.exports = (env) => {
|
||||
const afterCompileCommand = env && env.afterCompileCommand;
|
||||
return {
|
||||
context: path.join(__dirname, sourcePath),
|
||||
entry: entryFile,
|
||||
mode: minify ? "production" : "development",
|
||||
target: "node",
|
||||
output: {
|
||||
chunkFilename: "[name].bundle.js",
|
||||
path: path.resolve(__dirname, "./bin"),
|
||||
publicPath: "/",
|
||||
filename: "entry.bundle.js",
|
||||
// libraryTarget: "amd",
|
||||
globalObject: "this",
|
||||
},
|
||||
module: {
|
||||
rules: [{
|
||||
loader: "string-replace-loader",
|
||||
test: /\.(js|ts)$/,
|
||||
options: {
|
||||
multiple: [
|
||||
{
|
||||
search: "require\\.toUrl\\(",
|
||||
replace: "requireToUrl(",
|
||||
flags: "g",
|
||||
},
|
||||
{
|
||||
search: "require\\.__\\$__nodeRequire",
|
||||
replace: "require",
|
||||
flags: "g",
|
||||
},
|
||||
],
|
||||
},
|
||||
}, {
|
||||
use: [{
|
||||
loader: "happypack/loader?id=ts",
|
||||
}],
|
||||
test: /(^.?|\.[^d]|[^.]d|[^.][^d])\.tsx?$/,
|
||||
}, {
|
||||
test: /\.s?css$/,
|
||||
use: [{
|
||||
loader: "style-loader",
|
||||
}, {
|
||||
loader: "css-loader",
|
||||
}],
|
||||
}, {
|
||||
test: /\.(svg|png|ttf|woff|eot)$/,
|
||||
use: ["file-loader"]
|
||||
}, {
|
||||
test: /\.wasm$/,
|
||||
type: "javascript/auto",
|
||||
}, {
|
||||
// Ignore a bunch of file types we don't have loaders for. Also ignore
|
||||
// test directories, some files with invalid JSON, and files we don't
|
||||
// actually require but throw warnings or errors. This all seems to be a
|
||||
// case of dynamic loading including things we won't require.
|
||||
// This also results in the bundle being significantly smaller which
|
||||
// makes uglify much faster.
|
||||
test: /(\/vs\/code\/electron-main\/)|(\/test\/)|(OSSREADME\.json$)|(\.(test\.ts|test\.js|d\.ts|qwoff|node|html|txt|exe|wuff|md|sh|scpt|less)$)/,
|
||||
use: ["ignore-loader"]
|
||||
}],
|
||||
},
|
||||
// node: {
|
||||
// // electron: "empty",
|
||||
// // fs: "empty",
|
||||
// // child_process: "empty",
|
||||
|
||||
// module: "empty",
|
||||
// // net: "empty",
|
||||
// crypto: "empty",
|
||||
// tls: "empty",
|
||||
// },
|
||||
// externals: [
|
||||
// function(context, request, callback) {
|
||||
// process.stderr.write("requiring external " + request + "\n");
|
||||
// callback();
|
||||
// },
|
||||
// ],
|
||||
resolve: {
|
||||
alias: {
|
||||
"gc-signals": path.resolve(__dirname, "./fill/empty.ts"),
|
||||
"native-keymap": path.resolve(__dirname, "./fill/native-keymap.ts"),
|
||||
"windows-process-tree": path.resolve(__dirname, "./fill/empty.ts"),
|
||||
"electron": path.resolve(__dirname, "./fill/electron.ts"),
|
||||
// "crypto": "crypto-browserify",
|
||||
// "child_process": path.resolve(__dirname, "./fill/child_process.ts"),
|
||||
// "fs": path.resolve(__dirname, "./fill/fs.ts"),
|
||||
// "http": "http-browserify",
|
||||
// "node-pty": path.resolve(__dirname, "./fill/node-pty.ts"),
|
||||
// "os": "os-browserify",
|
||||
// "net": path.resolve(__dirname, "./fill/net.ts"),
|
||||
// TODO: The real spdlog doesn't work and keeps saying the path argument is undefined.
|
||||
"spdlog": path.resolve(__dirname, "./fill/spdlog.ts"),
|
||||
"coder": path.resolve(__dirname, "./src/coder"),
|
||||
"vs": path.resolve(__dirname, "./src/vs"),
|
||||
},
|
||||
extensions: [".js", ".ts", ".json", ".css"],
|
||||
mainFiles: [
|
||||
"index",
|
||||
"src/index",
|
||||
],
|
||||
modules: [
|
||||
"../node_modules",
|
||||
path.resolve(__dirname, "../../../"),
|
||||
],
|
||||
},
|
||||
resolveLoader: {
|
||||
alias: {
|
||||
"vs/css": path.resolve(__dirname, "./fill/css.js"),
|
||||
},
|
||||
},
|
||||
// devServer: {
|
||||
// contentBase: sourcePath,
|
||||
// compress: true,
|
||||
// host: "0.0.0.0",
|
||||
// hot: true,
|
||||
// historyApiFallback: true,
|
||||
// port: 9966,
|
||||
// inline: true,
|
||||
// disableHostCheck: true,
|
||||
// stats: {
|
||||
// warnings: false
|
||||
// },
|
||||
// },
|
||||
plugins: [
|
||||
// new HtmlWebpackPlugin({
|
||||
// template: "./index.html",
|
||||
// }),
|
||||
new HappyPack({
|
||||
id: "ts",
|
||||
threads: 2,
|
||||
loaders: [
|
||||
{
|
||||
path: "ts-loader",
|
||||
query: {
|
||||
happyPackMode: true,
|
||||
},
|
||||
}
|
||||
],
|
||||
}),
|
||||
new webpack.ProgressPlugin((percentage, msg) => {
|
||||
if (percentage === 1) {
|
||||
if (afterCompileCommand) {
|
||||
require("child_process").execSync(afterCompileCommand, {
|
||||
stdio: "inherit"
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
// new UglifyJsPlugin(),
|
||||
// new BundleAnalyzerPlugin(),
|
||||
// new WriteFilePlugin({
|
||||
// exitOnErrors: false,
|
||||
// }),
|
||||
// new PreloadWebpackPlugin({
|
||||
// rel: "preload",
|
||||
// as: "script",
|
||||
// }),
|
||||
// new ForkTsCheckerWebpackPlugin({
|
||||
// checkSyntacticErrors: true,
|
||||
// tsconfig: path.resolve(__dirname, "./src/tsconfig.json"),
|
||||
// }),
|
||||
],
|
||||
stats: {
|
||||
all: false, // Fallback for options not defined.
|
||||
errors: true,
|
||||
warnings: true,
|
||||
},
|
||||
};
|
||||
};
|
388
yarn.lock
Normal file
388
yarn.lock
Normal file
@ -0,0 +1,388 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
array-filter@~0.0.0:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec"
|
||||
integrity sha1-fajPLiZijtcygDWB/SH2fKzS7uw=
|
||||
|
||||
array-map@~0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662"
|
||||
integrity sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=
|
||||
|
||||
array-reduce@~0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b"
|
||||
integrity sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
builtin-modules@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
|
||||
integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
|
||||
|
||||
chalk@^2.4.1:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
color-convert@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
|
||||
dependencies:
|
||||
color-name "1.1.3"
|
||||
|
||||
color-name@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
||||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||
|
||||
cross-spawn@^6.0.5:
|
||||
version "6.0.5"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
|
||||
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
|
||||
dependencies:
|
||||
nice-try "^1.0.4"
|
||||
path-key "^2.0.1"
|
||||
semver "^5.5.0"
|
||||
shebang-command "^1.2.0"
|
||||
which "^1.2.9"
|
||||
|
||||
define-properties@^1.1.2:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
|
||||
integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
|
||||
dependencies:
|
||||
object-keys "^1.0.12"
|
||||
|
||||
error-ex@^1.3.1:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
|
||||
integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
|
||||
dependencies:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
es-abstract@^1.4.3:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
|
||||
integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==
|
||||
dependencies:
|
||||
es-to-primitive "^1.2.0"
|
||||
function-bind "^1.1.1"
|
||||
has "^1.0.3"
|
||||
is-callable "^1.1.4"
|
||||
is-regex "^1.0.4"
|
||||
object-keys "^1.0.12"
|
||||
|
||||
es-to-primitive@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377"
|
||||
integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==
|
||||
dependencies:
|
||||
is-callable "^1.1.4"
|
||||
is-date-object "^1.0.1"
|
||||
is-symbol "^1.0.2"
|
||||
|
||||
escape-string-regexp@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||
|
||||
function-bind@^1.0.2, function-bind@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||
|
||||
graceful-fs@^4.1.2:
|
||||
version "4.1.15"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
||||
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||
|
||||
has-symbols@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
|
||||
integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=
|
||||
|
||||
has@^1.0.1, has@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
|
||||
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047"
|
||||
integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==
|
||||
|
||||
is-arrayish@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
|
||||
integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
|
||||
|
||||
is-builtin-module@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
|
||||
integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74=
|
||||
dependencies:
|
||||
builtin-modules "^1.0.0"
|
||||
|
||||
is-callable@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
|
||||
integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==
|
||||
|
||||
is-date-object@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
|
||||
integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=
|
||||
|
||||
is-regex@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
|
||||
integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=
|
||||
dependencies:
|
||||
has "^1.0.1"
|
||||
|
||||
is-symbol@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38"
|
||||
integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==
|
||||
dependencies:
|
||||
has-symbols "^1.0.0"
|
||||
|
||||
isexe@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
|
||||
|
||||
json-parse-better-errors@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
|
||||
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
|
||||
|
||||
jsonify@~0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
|
||||
integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
|
||||
|
||||
load-json-file@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
|
||||
integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs=
|
||||
dependencies:
|
||||
graceful-fs "^4.1.2"
|
||||
parse-json "^4.0.0"
|
||||
pify "^3.0.0"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
memorystream@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
|
||||
integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI=
|
||||
|
||||
minimatch@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
nice-try@^1.0.4:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
||||
|
||||
normalize-package-data@^2.3.2:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
|
||||
integrity sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==
|
||||
dependencies:
|
||||
hosted-git-info "^2.1.4"
|
||||
is-builtin-module "^1.0.0"
|
||||
semver "2 || 3 || 4 || 5"
|
||||
validate-npm-package-license "^3.0.1"
|
||||
|
||||
npm-run-all@^4.1.5:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba"
|
||||
integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
chalk "^2.4.1"
|
||||
cross-spawn "^6.0.5"
|
||||
memorystream "^0.3.1"
|
||||
minimatch "^3.0.4"
|
||||
pidtree "^0.3.0"
|
||||
read-pkg "^3.0.0"
|
||||
shell-quote "^1.6.1"
|
||||
string.prototype.padend "^3.0.0"
|
||||
|
||||
object-keys@^1.0.12:
|
||||
version "1.0.12"
|
||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2"
|
||||
integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==
|
||||
|
||||
parse-json@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
|
||||
integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=
|
||||
dependencies:
|
||||
error-ex "^1.3.1"
|
||||
json-parse-better-errors "^1.0.1"
|
||||
|
||||
path-key@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
|
||||
integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
|
||||
|
||||
path-type@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
|
||||
integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==
|
||||
dependencies:
|
||||
pify "^3.0.0"
|
||||
|
||||
pidtree@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.0.tgz#f6fada10fccc9f99bf50e90d0b23d72c9ebc2e6b"
|
||||
integrity sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg==
|
||||
|
||||
pify@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
|
||||
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
|
||||
|
||||
read-pkg@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
|
||||
integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=
|
||||
dependencies:
|
||||
load-json-file "^4.0.0"
|
||||
normalize-package-data "^2.3.2"
|
||||
path-type "^3.0.0"
|
||||
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.5.0:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
|
||||
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
|
||||
|
||||
shebang-command@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
|
||||
integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=
|
||||
dependencies:
|
||||
shebang-regex "^1.0.0"
|
||||
|
||||
shebang-regex@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
|
||||
integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
|
||||
|
||||
shell-quote@^1.6.1:
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767"
|
||||
integrity sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=
|
||||
dependencies:
|
||||
array-filter "~0.0.0"
|
||||
array-map "~0.0.0"
|
||||
array-reduce "~0.0.0"
|
||||
jsonify "~0.0.0"
|
||||
|
||||
spdx-correct@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4"
|
||||
integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==
|
||||
dependencies:
|
||||
spdx-expression-parse "^3.0.0"
|
||||
spdx-license-ids "^3.0.0"
|
||||
|
||||
spdx-exceptions@^2.1.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977"
|
||||
integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==
|
||||
|
||||
spdx-expression-parse@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0"
|
||||
integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==
|
||||
dependencies:
|
||||
spdx-exceptions "^2.1.0"
|
||||
spdx-license-ids "^3.0.0"
|
||||
|
||||
spdx-license-ids@^3.0.0:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz#81c0ce8f21474756148bbb5f3bfc0f36bf15d76e"
|
||||
integrity sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==
|
||||
|
||||
string.prototype.padend@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz#f3aaef7c1719f170c5eab1c32bf780d96e21f2f0"
|
||||
integrity sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA=
|
||||
dependencies:
|
||||
define-properties "^1.1.2"
|
||||
es-abstract "^1.4.3"
|
||||
function-bind "^1.0.2"
|
||||
|
||||
strip-bom@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
|
||||
integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
|
||||
|
||||
supports-color@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
validate-npm-package-license@^3.0.1:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
|
||||
integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
|
||||
dependencies:
|
||||
spdx-correct "^3.0.0"
|
||||
spdx-expression-parse "^3.0.0"
|
||||
|
||||
which@^1.2.9:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
Loading…
Reference in New Issue
Block a user