diff --git a/Dockerfile.dev b/Dockerfile.dev index fc15487..efe6904 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -2,6 +2,9 @@ FROM ubuntu:focal ARG UID +COPY scripts/docker-install-phase0.bash /tmp/ +RUN /tmp/docker-install-phase0.bash + COPY scripts/docker-install-phase1.bash /tmp/ RUN /tmp/docker-install-phase1.bash @@ -31,6 +34,7 @@ RUN /tmp/docker-install-phase6.bash "$UID" USER docker WORKDIR /home/docker +RUN chmod go-rwx /home/docker EXPOSE 6119 EXPOSE 6120 diff --git a/Dockerfile.prod b/Dockerfile.prod index 063ce53..a69919d 100644 --- a/Dockerfile.prod +++ b/Dockerfile.prod @@ -4,6 +4,9 @@ FROM ubuntu:focal # prod, it's not actually read by anything. ARG UID +COPY scripts/docker-install-phase0.bash /tmp/ +RUN /tmp/docker-install-phase0.bash + COPY scripts/docker-install-phase1.bash /tmp/ RUN /tmp/docker-install-phase1.bash @@ -33,6 +36,7 @@ RUN /tmp/docker-install-phase6.bash USER docker WORKDIR /home/docker +RUN chmod go-rwx /home/docker EXPOSE 6119 EXPOSE 6120 @@ -40,7 +44,7 @@ ENTRYPOINT ["/usr/local/bin/pid1.bash"] COPY scripts/pid1.bash /usr/local/bin/ CMD ["yarn", "run", "server"] -RUN mkdir /tmp/riju +RUN mkdir /tmp/riju /tmp/riju/scripts COPY --chown=docker:docker package.json yarn.lock /tmp/riju/ RUN cd /tmp/riju && yarn install COPY --chown=docker:docker webpack.config.js tsconfig.json tsconfig-webpack.json /tmp/riju/ @@ -48,10 +52,11 @@ COPY --chown=docker:docker frontend /tmp/riju/frontend RUN cd /tmp/riju && yarn run frontend COPY --chown=docker:docker backend /tmp/riju/backend RUN cd /tmp/riju && yarn run backend +COPY --chown=docker:docker scripts/compile-system.bash /tmp/riju/scripts COPY --chown=docker:docker system /tmp/riju/system RUN cd /tmp/riju && RIJU_PRIVILEGED=1 yarn run system COPY --chown=docker:docker . /home/docker/src -RUN cp -R /tmp/riju/* /home/docker/src/ && rm -rf /tmp/riju +RUN sudo cp -a /tmp/riju/* /home/docker/src/ && rm -rf /tmp/riju WORKDIR /home/docker/src RUN sudo deluser docker sudo diff --git a/Makefile b/Makefile index 2a7ef42..5ee1504 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ image-prod: ## Build Docker image for production .PHONY: docker docker: image-dev ## Run shell with source code and deps inside Docker - scripts/docker.bash run -it --rm -v "$(PWD):/home/docker/src" -p 6119:6119 -p 6120:6120 riju bash + scripts/docker.bash run -it --rm -v "$(PWD):/home/docker/src" -p 6119:6119 -p 6120:6120 -h riju riju bash .PHONY: deploy deploy: ## Deploy current master from GitHub to production diff --git a/README.md b/README.md index e5c140b..7aee9be 100644 --- a/README.md +++ b/README.md @@ -18,18 +18,18 @@ documenting it until it has reached feature-completeness. To run the webserver, all you need is Yarn. Just run `yarn install` as usual to install dependencies. For production, it's: - $ yarn backend - $ yarn frontend - $ yarn system + $ yarn backend |- or run all three with 'yarn build' + $ yarn frontend | + $ yarn system | $ yarn server For development with file watching and automatic server rebooting and all that, it's: - $ yarn backend-dev - $ yarn frontend-dev - $ yarn system-dev - $ yarn server-dev + $ yarn backend-dev |- or run all four with 'yarn dev' + $ yarn frontend-dev | + $ yarn system-dev | + $ yarn server-dev | The webserver listens on `localhost:6119`. Now, although the server itself will work, the only languages that will work are the ones that diff --git a/backend/src/api.ts b/backend/src/api.ts index c3b5e59..9897468 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -1,49 +1,48 @@ -import * as fs from "fs"; import * as path from "path"; import * as WebSocket from "ws"; -import * as mkdirp from "mkdirp"; -import * as nodeCleanup from "node-cleanup"; import * as pty from "node-pty"; import { IPty } from "node-pty"; -import * as tmp from "tmp"; import { v4 as getUUID } from "uuid"; +import { PRIVILEGED } from "./config"; import { LangConfig, langs } from "./langs"; import { borrowUser } from "./users"; +import { callPrivileged, getEnv, spawnPrivileged } from "./util"; export class Session { - id: string; + uuid: string; code: string; config: LangConfig; term: { pty: IPty | null; live: boolean }; ws: WebSocket; - tmpdir: string | null; - tmpdirCleanup: (() => void) | null; + homedir: string | null; uid: number | null; uidCleanup: (() => Promise) | null; - log = (msg: string) => console.log(`[${this.id}] ${msg}`); + log = (msg: string) => console.log(`[${this.uuid}] ${msg}`); constructor(ws: WebSocket, lang: string) { - this.id = getUUID(); + this.uuid = getUUID(); this.log(`Creating session, language ${lang}`); this.ws = ws; this.config = langs[lang]; this.term = { pty: null, live: false }; this.code = ""; - this.tmpdir = null; - this.tmpdirCleanup = null; + this.homedir = null; this.uid = null; this.uidCleanup = null; ws.on("message", this.handleClientMessage); ws.on("close", () => - this.cleanup().catch((err) => - this.log(`Error during session cleanup: ${err}`) - ) + this.cleanup().catch((err) => { + this.log(`Error during session cleanup`); + console.log(err); + }) ); - nodeCleanup(); - this.run().catch((err) => this.log(`Error while running: ${err}`)); + this.run().catch((err) => { + this.log(`Error while setting up environment for pty`); + console.log(err); + }); } handleClientMessage = (event: string) => { let msg: any; @@ -81,9 +80,18 @@ export class Session { ({ uid: this.uid, cleanup: this.uidCleanup } = await borrowUser( this.log )); + this.log(`Borrowed uid ${this.uid}`); } - this.log(`Borrowed uid ${this.uid}`); - const { name, repl, main, suffix, compile, run, hacks } = this.config; + const { + name, + repl, + main, + suffix, + alwaysCreate, + compile, + run, + hacks, + } = this.config; if (this.term.pty) { this.term.pty.kill(); this.term.live = false; @@ -93,20 +101,9 @@ export class Session { } catch (err) { // } - if (this.tmpdir == null) { - ({ path: this.tmpdir, cleanup: this.tmpdirCleanup } = await new Promise( - (resolve, reject) => - tmp.dir( - { unsafeCleanup: true, dir: "riju" }, - (err, path, cleanup) => { - if (err) { - reject(err); - } else { - resolve({ path, cleanup }); - } - } - ) - )); + if (this.homedir == null) { + this.homedir = `/tmp/riju/${this.uuid}`; + await callPrivileged(["setup", `${this.uid}`, this.uuid], this.log); } let cmdline: string; if (!run) { @@ -117,22 +114,33 @@ export class Session { code += suffix; } if (main.includes("/")) { - await mkdirp(path.dirname(path.resolve(this.tmpdir!, main))); + await spawnPrivileged( + this.uid, + this.uuid, + ["mkdir", "-p", path.dirname(path.resolve(this.homedir, main))], + this.log + ); } - await new Promise((resolve, reject) => - fs.writeFile(path.resolve(this.tmpdir!, main), code, (err) => { - if (err) { - reject(err); - } else { - resolve(); - } - }) + await spawnPrivileged( + this.uid, + this.uuid, + ["sh", "-c", `cat > ${path.resolve(this.homedir, main)}`], + this.log, + { input: code } ); cmdline = run; if (compile) { cmdline = `( ${compile} ) && ( ${run} )`; } } else if (repl) { + if (alwaysCreate) { + await spawnPrivileged( + this.uid, + this.uuid, + ["touch", `${path.resolve(this.homedir, main)}`], + this.log + ); + } cmdline = repl; } else { cmdline = `echo '${name} has no REPL, press Run to see it in action'`; @@ -140,32 +148,38 @@ export class Session { if (hacks && hacks.includes("ghci-config") && run) { if (this.code) { const contents = ":load Main\nmain\n"; - await new Promise((resolve, reject) => { - fs.writeFile(path.resolve(this.tmpdir!, ".ghci"), contents, (err) => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); + await spawnPrivileged( + this.uid, + this.uuid, + ["sh", "-c", `cat > ${path.resolve(this.homedir, ".ghci")}`], + this.log, + { input: contents } + ); } else { - await new Promise((resolve, reject) => - fs.unlink(path.resolve(this.tmpdir!, ".ghci"), (err) => { - if (err && err.code !== "ENOENT") { - reject(err); - } else { - resolve(); - } - }) + await spawnPrivileged( + this.uid, + this.uuid, + ["rm", "-f", path.resolve(this.homedir, ".ghci")], + this.log ); } } + const args = PRIVILEGED + ? [ + "/home/docker/src/system/out/riju-system-privileged", + "spawn", + `${this.uid}`, + `${this.uuid}`, + "bash", + "-c", + cmdline, + ] + : ["bash", "-c", cmdline]; + const env = getEnv(this.uuid); const term = { - pty: pty.spawn("bash", ["-c", cmdline], { + pty: pty.spawn(args[0], args.slice(1), { name: "xterm-color", - cwd: this.tmpdir!, - env: process.env as { [key: string]: string }, + env, }), live: true, }; @@ -186,8 +200,8 @@ export class Session { }; cleanup = async () => { this.log(`Cleaning up session`); - if (this.tmpdirCleanup) { - this.tmpdirCleanup(); + if (this.homedir) { + await callPrivileged(["teardown", this.uuid], this.log); } if (this.uidCleanup) { await this.uidCleanup(); diff --git a/backend/src/config.ts b/backend/src/config.ts new file mode 100644 index 0000000..133630c --- /dev/null +++ b/backend/src/config.ts @@ -0,0 +1,3 @@ +import * as process from "process"; + +export const PRIVILEGED = process.env.RIJU_PRIVILEGED ? true : false; diff --git a/backend/src/langs.ts b/backend/src/langs.ts index 021d4f8..3859c38 100644 --- a/backend/src/langs.ts +++ b/backend/src/langs.ts @@ -6,6 +6,7 @@ export interface LangConfig { main: string; prefix?: string; suffix?: string; + alwaysCreate?: boolean; compile?: string; run: string; template: string; @@ -220,7 +221,7 @@ int main() { monacoLang: "csharp", main: "main.cs", compile: "mcs main.cs", - run: "./main.exe", + run: "mono main.exe", template: `class main { static void Main(string[] args) { System.Console.WriteLine("Hello, world!"); @@ -326,9 +327,9 @@ output = "Hello, world!" aliases: ["elv"], name: "Elvish", monacoLang: "plaintext", - repl: "SHELL=/usr/bin/elvish HOME=. elvish", + repl: `SHELL=/usr/bin/elvish HOME="$PWD" elvish`, main: ".elvish/rc.elv", - run: "SHELL=/usr/bin/elvish HOME=. elvish", + run: `SHELL=/usr/bin/elvish HOME="$PWD" elvish`, template: `echo "Hello, world!" `, }, @@ -451,8 +452,8 @@ main = putStrLn "Hello, world!" repl: "ink", main: "main.ink", run: "ink main.ink; ink", - template: `std := load('../../opt/ink/std') -str := load('../../opt/ink/str') + template: `std := load('../../../opt/ink/std') +str := load('../../../opt/ink/str') log := std.log @@ -535,9 +536,9 @@ PLEASE GIVE UP aliases: ["kshell"], name: "Ksh", monacoLang: "shell", - repl: "SHELL=/usr/bin/ksh HOME=. ksh", + repl: `SHELL=/usr/bin/ksh HOME="$PWD" ksh`, main: ".kshrc", - run: "SHELL=/usr/bin/ksh HOME=. ksh", + run: `SHELL=/usr/bin/ksh HOME="$PWD" ksh`, template: `echo "Hello, world!" `, }, @@ -830,9 +831,9 @@ binding_irb.run(IRB.conf) aliases: ["shell", "posix", "posixsh", "ash", "dash", "posh"], name: "Sh", monacoLang: "shell", - repl: "SHELL=/usr/bin/sh HOME=. posh -l", + repl: `SHELL=/usr/bin/sh HOME="$PWD" posh -l`, main: ".profile", - run: "SHELL=/usr/bin/sh HOME=. posh -l", + run: `SHELL=/usr/bin/sh HOME="$PWD" posh -l`, template: `echo "Hello, world!" `, }, @@ -992,7 +993,7 @@ END monacoLang: "tcl", repl: "tclsh", main: ".tclshrc", - run: "HOME=. tclsh", + run: `HOME="$PWD" tclsh`, template: `puts {Hello, world!} `, }, @@ -1000,9 +1001,9 @@ END aliases: ["tcshell", "tcshrc"], name: "Tcsh", monacoLang: "shell", - repl: "SHELL=/usr/bin/tcsh HOME=. tcsh", + repl: `SHELL=/usr/bin/tcsh HOME="$PWD" tcsh`, main: ".tcshrc", - run: "SHELL=/usr/bin/tcsh HOME=. tcsh", + run: `SHELL=/usr/bin/tcsh HOME="$PWD" tcsh`, template: `echo "Hello, world!" `, }, @@ -1104,7 +1105,8 @@ message: monacoLang: "shell", repl: "SHELL=/usr/bin/zsh zsh", main: ".zshrc", - run: "SHELL=/usr/bin/zsh ZDOTDIR=. zsh", + alwaysCreate: true, + run: `SHELL=/usr/bin/zsh ZDOTDIR="$PWD" zsh`, template: `echo "Hello, world!" `, }, diff --git a/backend/src/users.ts b/backend/src/users.ts index 6e3c8f4..0742a6b 100644 --- a/backend/src/users.ts +++ b/backend/src/users.ts @@ -1,16 +1,17 @@ import { spawn } from "child_process"; import * as fs from "fs"; -import * as process from "process"; import * as AsyncLock from "async-lock"; import * as _ from "lodash"; import * as parsePasswd from "parse-passwd"; +import { PRIVILEGED } from "./config"; +import { callPrivileged } from "./util"; + // Keep in sync with system/src/riju-system-privileged.c const MIN_UID = 2000; const MAX_UID = 65000; -const PRIVILEGED = process.env.RIJU_PRIVILEGED ? true : false; const CUR_UID = parseInt(process.env.UID || "") || null; let availIds: number[] | null = null; @@ -29,7 +30,7 @@ async function readExistingUsers(log: (msg: string) => void) { }) ) ) - .filter(({ username }) => username.startsWith("riju_user")) + .filter(({ username }) => username.startsWith("riju")) .map(({ uid }) => parseInt(uid)) .filter((uid) => !isNaN(uid) && uid >= MIN_UID && uid < MAX_UID); nextId = (_.max(availIds) || MIN_UID - 1) + 1; @@ -40,33 +41,11 @@ async function createUser(log: (msg: string) => void): Promise { if (nextId! >= MAX_UID) { throw new Error("too many users"); } - return await new Promise((resolve, reject) => { - const uid = nextId!; - const useradd = spawn("system/out/riju-system-privileged", [ - "useradd", - `${uid}`, - ]); - let output = ""; - useradd.stdout.on("data", (data) => { - output += `${data}`; - }); - useradd.stderr.on("data", (data) => { - output += `${data}`; - }); - useradd.on("close", (code) => { - output = output.trim(); - if (output) { - log("Output from useradd:\n" + output); - } - if (code === 0) { - log(`Created new user with ID ${uid}`); - nextId! += 1; - resolve(uid); - } else { - reject(`useradd failed with error code ${code}`); - } - }); - }); + const uid = nextId!; + await callPrivileged(["useradd", `${uid}`], log); + log(`Created new user with ID ${uid}`); + nextId! += 1; + return uid; } export async function borrowUser(log: (msg: string) => void) { diff --git a/backend/src/util.ts b/backend/src/util.ts new file mode 100644 index 0000000..235252c --- /dev/null +++ b/backend/src/util.ts @@ -0,0 +1,83 @@ +import { spawn, SpawnOptions } from "child_process"; +import * as process from "process"; + +interface Options extends SpawnOptions { + input?: string; +} + +export function getEnv(uuid: string) { + const cwd = `/tmp/riju/${uuid}`; + return { + HOME: cwd, + HOSTNAME: "riju", + LANG: "C.UTF-8", + LC_ALL: "C.UTF-8", + PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/bin", + PWD: cwd, + SHELL: "/usr/bin/bash", + TERM: "xterm-color", + }; +} + +export async function call( + args: string[], + log: (msg: string) => void, + options?: Options +) { + options = options || {}; + const input = options.input; + delete options.input; + const proc = spawn(args[0], args.slice(1), options); + if (input) { + proc.stdin!.end(input); + } + let output = ""; + proc.stdout!.on("data", (data: Buffer) => { + output += `${data}`; + }); + proc.stderr!.on("data", (data: Buffer) => { + output += `${data}`; + }); + await new Promise((resolve, reject) => { + proc.on("error", reject); + proc.on("close", (code: number) => { + output = output.trim(); + if (output) { + log(`Output from ${args[0]}:\n` + output); + } + if (code === 0) { + resolve(); + } else { + reject(`command ${args[0]} failed with error code ${code}`); + } + }); + }); +} + +export async function callPrivileged( + args: string[], + log: (msg: string) => void, + options?: Options +) { + await call( + ["/home/docker/src/system/out/riju-system-privileged"].concat(args), + log, + options + ); +} + +export async function spawnPrivileged( + uid: number, + uuid: string, + args: string[], + log: (msg: string) => void, + options?: Options +) { + options = options || {}; + options.env = getEnv(uuid); + await callPrivileged( + ["spawn", `${uid}`, `${uuid}`].concat(args), + log, + options + ); +} diff --git a/frontend/src/app.ts b/frontend/src/app.ts index 95deb8d..28bdd2f 100644 --- a/frontend/src/app.ts +++ b/frontend/src/app.ts @@ -10,92 +10,98 @@ interface RijuConfig { template: string; } -const config: RijuConfig = (window as any).rijuConfig; +async function main() { + const config: RijuConfig = (window as any).rijuConfig; -const term = new Terminal(); -const fitAddon = new FitAddon(); -term.loadAddon(fitAddon); -term.open(document.getElementById("terminal")!); + const term = new Terminal(); + const fitAddon = new FitAddon(); + term.loadAddon(fitAddon); + term.open(document.getElementById("terminal")!); -fitAddon.fit(); -window.addEventListener("resize", () => fitAddon.fit()); + fitAddon.fit(); + window.addEventListener("resize", () => fitAddon.fit()); -term.write("Connecting to server..."); - -const initialRetryDelayMs = 200; -let retryDelayMs = initialRetryDelayMs; - -function tryConnect() { - console.log("Connecting to server..."); - socket = new WebSocket( - (document.location.protocol === "http:" ? "ws://" : "wss://") + - document.location.host + - `/api/v1/ws?lang=${encodeURIComponent(config.id)}` + await new Promise((resolve) => + term.write("Connecting to server...", resolve) ); - socket.addEventListener("open", () => { - console.log("Successfully connected to server"); - }); - socket.addEventListener("message", (event: MessageEvent) => { - let message: any; - try { - message = JSON.parse(event.data); - } catch (err) { - console.error("Malformed message from server:", event.data); - return; - } - if (message?.event && message?.event !== "error") { - retryDelayMs = initialRetryDelayMs; - } - switch (message?.event) { - case "terminalClear": - term.reset(); + + const initialRetryDelayMs = 200; + let retryDelayMs = initialRetryDelayMs; + + function tryConnect() { + console.log("Connecting to server..."); + socket = new WebSocket( + (document.location.protocol === "http:" ? "ws://" : "wss://") + + document.location.host + + `/api/v1/ws?lang=${encodeURIComponent(config.id)}` + ); + socket.addEventListener("open", () => { + console.log("Successfully connected to server"); + }); + socket.addEventListener("message", (event: MessageEvent) => { + let message: any; + try { + message = JSON.parse(event.data); + } catch (err) { + console.error("Malformed message from server:", event.data); return; - case "terminalOutput": - if (typeof message.output !== "string") { + } + if (message?.event && message?.event !== "error") { + retryDelayMs = initialRetryDelayMs; + } + switch (message?.event) { + case "terminalClear": + term.reset(); + return; + case "terminalOutput": + if (typeof message.output !== "string") { + console.error("Unexpected message from server:", message); + return; + } + term.write(message.output); + return; + default: console.error("Unexpected message from server:", message); return; - } - term.write(message.output); - return; - default: - console.error("Unexpected message from server:", message); - return; - } + } + }); + socket.addEventListener("close", (event: CloseEvent) => { + if (event.wasClean) { + console.log("Connection closed cleanly"); + } else { + console.error("Connection died"); + } + scheduleConnect(); + }); + } + + function scheduleConnect() { + const delay = retryDelayMs * Math.random(); + console.log(`Trying to reconnect in ${Math.floor(delay)}ms`); + setTimeout(tryConnect, delay); + retryDelayMs *= 2; + } + + let socket: WebSocket | null = null; + tryConnect(); + + term.onData( + (data) => + socket && + socket.send(JSON.stringify({ event: "terminalInput", input: data })) + ); + + const editor = monaco.editor.create(document.getElementById("editor")!, { + minimap: { enabled: false }, + scrollbar: { verticalScrollbarSize: 0 }, }); - socket.addEventListener("close", (event: CloseEvent) => { - if (event.wasClean) { - console.log("Connection closed cleanly"); - } else { - console.error("Connection died"); - } - scheduleConnect(); + window.addEventListener("resize", () => editor.layout()); + editor.getModel()!.setValue(config.template); + monaco.editor.setModelLanguage(editor.getModel()!, config.monacoLang); + + document.getElementById("runButton")!.addEventListener("click", () => { + socket?.send(JSON.stringify({ event: "runCode", code: editor.getValue() })); }); } -function scheduleConnect() { - const delay = retryDelayMs * Math.random(); - console.log(`Trying to reconnect in ${Math.floor(delay)}ms`); - setTimeout(tryConnect, delay); - retryDelayMs *= 2; -} - -let socket: WebSocket | null = null; -tryConnect(); - -term.onData( - (data) => - socket && - socket.send(JSON.stringify({ event: "terminalInput", input: data })) -); - -const editor = monaco.editor.create(document.getElementById("editor")!, { - minimap: { enabled: false }, - scrollbar: { verticalScrollbarSize: 0 }, -}); -window.addEventListener("resize", () => editor.layout()); -editor.getModel()!.setValue(config.template); -monaco.editor.setModelLanguage(editor.getModel()!, config.monacoLang); - -document.getElementById("runButton")!.addEventListener("click", () => { - socket?.send(JSON.stringify({ event: "runCode", code: editor.getValue() })); -}); +main().catch(console.error); diff --git a/package.json b/package.json index 3f23bc1..5b3d502 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "riju", - "version": "0", + "version": "0.0.0", "license": "MIT", "private": true, "dependencies": { @@ -11,6 +11,7 @@ "@types/lodash": "^4.14.155", "@types/mkdirp": "^1.0.1", "@types/parse-passwd": "^1.0.0", + "@types/rimraf": "^3.0.0", "@types/tmp": "^0.2.0", "@types/uuid": "^8.0.0", "app-root-path": "^3.0.0", @@ -21,13 +22,12 @@ "express-ws": "^4.0.0", "file-loader": "^6.0.0", "lodash": "^4.17.15", - "mkdirp": "^1.0.4", "monaco-editor": "^0.20.0", "node-cleanup": "^2.1.2", "node-pty": "^0.9.0", + "npm-run-all": "^4.1.5", "parse-passwd": "^1.0.0", "style-loader": "^1.2.1", - "tmp": "^0.2.1", "ts-loader": "^7.0.5", "typescript": "^3.9.5", "uuid": "^8.1.0", @@ -38,12 +38,14 @@ }, "scripts": { "backend": "tsc", - "backend-dev": "tsc --watch", + "backend-dev": "tsc --watch --preserveWatchOutput", "frontend": "webpack --production", "frontend-dev": "webpack --development --watch", "server": "scripts/setup.bash && node backend/out/server.js", "server-dev": "watchexec -w backend/out -r 'scripts/setup.bash && node backend/out/server.js'", "system": "scripts/compile-system.bash", - "system-dev": "watchexec -w system/src -n scripts/compile-system.bash" + "system-dev": "watchexec -w system/src -n scripts/compile-system.bash", + "build": "run-s backend frontend system", + "dev": "run-p backend-dev frontend-dev system-dev server-dev" } } diff --git a/scripts/docker-install-phase0.bash b/scripts/docker-install-phase0.bash new file mode 100755 index 0000000..7382789 --- /dev/null +++ b/scripts/docker-install-phase0.bash @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail +set -x + +export DEBIAN_FRONTEND=noninteractive +apt-get update +(yes || true) | unminimize +rm -rf /var/lib/apt/lists/* + +rm "$0" diff --git a/scripts/docker-install-phase1.bash b/scripts/docker-install-phase1.bash index fa3932b..5f47de2 100755 --- a/scripts/docker-install-phase1.bash +++ b/scripts/docker-install-phase1.bash @@ -2,6 +2,7 @@ set -e set -o pipefail +set -x dpkg --add-architecture i386 diff --git a/scripts/docker-install-phase2.bash b/scripts/docker-install-phase2.bash index 5ec38e5..ac8fdf2 100755 --- a/scripts/docker-install-phase2.bash +++ b/scripts/docker-install-phase2.bash @@ -2,6 +2,7 @@ set -e set -o pipefail +set -x packages=" diff --git a/scripts/docker-install-phase3a.bash b/scripts/docker-install-phase3a.bash index 770409d..ea98b4c 100755 --- a/scripts/docker-install-phase3a.bash +++ b/scripts/docker-install-phase3a.bash @@ -2,6 +2,7 @@ set -e set -o pipefail +set -x packages=" diff --git a/scripts/docker-install-phase3b.bash b/scripts/docker-install-phase3b.bash index b4ced0b..60e9aad 100755 --- a/scripts/docker-install-phase3b.bash +++ b/scripts/docker-install-phase3b.bash @@ -2,6 +2,7 @@ set -e set -o pipefail +set -x packages=" diff --git a/scripts/docker-install-phase3c.bash b/scripts/docker-install-phase3c.bash index 6ca413b..e14bf0c 100755 --- a/scripts/docker-install-phase3c.bash +++ b/scripts/docker-install-phase3c.bash @@ -2,6 +2,7 @@ set -e set -o pipefail +set -x packages=" diff --git a/scripts/docker-install-phase3d.bash b/scripts/docker-install-phase3d.bash index 74f09b7..dca024b 100755 --- a/scripts/docker-install-phase3d.bash +++ b/scripts/docker-install-phase3d.bash @@ -2,6 +2,7 @@ set -e set -o pipefail +set -x packages=" diff --git a/scripts/docker-install-phase4.bash b/scripts/docker-install-phase4.bash index 69fe156..8274235 100755 --- a/scripts/docker-install-phase4.bash +++ b/scripts/docker-install-phase4.bash @@ -2,6 +2,7 @@ set -e set -o pipefail +set -x npm config set unsafe-perm true PERL_MM_USE_DEFAULT=1 cpan App::cpanminus diff --git a/scripts/docker-install-phase5.bash b/scripts/docker-install-phase5.bash index 4ccbe29..ea3cce3 100755 --- a/scripts/docker-install-phase5.bash +++ b/scripts/docker-install-phase5.bash @@ -2,6 +2,7 @@ set -e set -o pipefail +set -x # Needed for project infrastructure cd /tmp diff --git a/scripts/docker-install-phase6.bash b/scripts/docker-install-phase6.bash index cc0efa2..eefee5a 100755 --- a/scripts/docker-install-phase6.bash +++ b/scripts/docker-install-phase6.bash @@ -2,19 +2,22 @@ set -e set -o pipefail +set -x uid="$1" rm -rf /tmp/hsperfdata_root if [[ -n "$uid" ]] && (( "$uid" != 0 )); then - useradd --uid="$uid" --create-home --groups sudo docker - passwd -d docker + useradd --uid="$uid" --password "!" --create-home --groups sudo docker else - useradd --create-home --groups sudo docker - passwd -d docker + useradd --password "!" --create-home --groups sudo docker fi +tee /etc/sudoers.d/99-passwordless >/dev/null <<"EOF" +%sudo ALL=(ALL:ALL) NOPASSWD: ALL +EOF + touch /home/docker/.zshrc chown docker:docker /home/docker/.zshrc diff --git a/scripts/riju-serve.bash b/scripts/riju-serve.bash index 13e4bb4..123fbbf 100755 --- a/scripts/riju-serve.bash +++ b/scripts/riju-serve.bash @@ -18,4 +18,4 @@ else fi docker run ${it} -e TLS -e TLS_PRIVATE_KEY -e TLS_CERTIFICATE \ - --rm -p 0.0.0.0:80:6119 -p 0.0.0.0:443:6120 riju:prod + --rm -p 0.0.0.0:80:6119 -p 0.0.0.0:443:6120 -h riju riju:prod diff --git a/scripts/setup.bash b/scripts/setup.bash index b884776..08923fb 100755 --- a/scripts/setup.bash +++ b/scripts/setup.bash @@ -4,4 +4,7 @@ set -e set -o pipefail mkdir -p /tmp/riju -rm -rf /tmp/riju/* +if [[ -x system/out/riju-system-privileged ]]; then + system/out/riju-system-privileged teardown "*" +fi +chmod a=x,u=rwx /tmp/riju diff --git a/system/src/riju-system-privileged.c b/system/src/riju-system-privileged.c index 6ada086..8ad72d1 100644 --- a/system/src/riju-system-privileged.c +++ b/system/src/riju-system-privileged.c @@ -1,14 +1,18 @@ #define _GNU_SOURCE +#include +#include #include #include #include +#include +#include #include // Keep in sync with backend/src/users.ts const int MIN_UID = 2000; const int MAX_UID = 65000; -void die(const char *msg) +void die(char *msg) { fprintf(stderr, "%s\n", msg); exit(1); @@ -17,19 +21,84 @@ void die(const char *msg) void die_with_usage() { die("usage:\n" - " riju-system-privileged useradd UID"); + " riju-system-privileged useradd UID\n" + " riju-system-privileged spawn UID CMDLINE...\n" + " riju-system-privileged setup UID UUID\n" + " riju-system-privileged teardown UUID"); +} + +int parseUID(char *str) +{ + char *endptr; + long uid = strtol(str, &endptr, 10); + if (!*str || *endptr) + die("uid must be an integer"); + if (uid < MIN_UID || uid >= MAX_UID) + die("uid is out of range"); + return uid; +} + +char *parseUUID(char *uuid) +{ + if (!*uuid) + die("illegal uuid"); + for (char *ptr = uuid; *ptr; ++ptr) + if (!((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= '0' && *ptr <= '9') || *ptr == '-')) + die("illegal uuid"); + return uuid; } void useradd(int uid) { char *cmdline; - if (asprintf(&cmdline, "useradd -M -N -l -r -u %1$d riju_user%1$d", uid) < 0) { + if (asprintf(&cmdline, "groupadd -g %1$d riju%1$d", uid) < 0) die("asprintf failed"); - } int status = system(cmdline); - if (status) { + if (status) + die("groupadd failed"); + if (asprintf(&cmdline, "useradd -M -N -l -r -u %1$d -g %1$d -p '!' riju%1$d", uid) < 0) + die("asprintf failed"); + status = system(cmdline); + if (status) die("useradd failed"); - } +} + +void spawn(int uid, char *uuid, char **cmdline) +{ + char *cwd; + if (asprintf(&cwd, "/tmp/riju/%s", uuid) < 0) + die("asprintf failed"); + if (chdir(cwd) < 0) + die("chdir failed"); + if (setgid(uid) < 0) + die("setgid failed"); + if (setgroups(0, NULL) < 0) + die("setgroups failed"); + if (setuid(uid) < 0) + die("setuid failed"); + umask(077); + execvp(cmdline[0], cmdline); + die("execvp failed"); +} + +void setup(int uid, char *uuid) +{ + char *cmdline; + if (asprintf(&cmdline, "install -d -o riju%1$d -g riju%1$d -m 700 /tmp/riju/%2$s", uid, uuid) < 0) + die("asprintf failed"); + int status = system(cmdline); + if (status) + die("install failed"); +} + +void teardown(char *uuid) +{ + char *cmdline; + if (asprintf(&cmdline, "rm -rf /tmp/riju/%s", uuid) < 0) + die("asprintf failed"); + int status = system(cmdline); + if (status) + die("rm failed"); } int main(int argc, char **argv) @@ -40,15 +109,28 @@ int main(int argc, char **argv) if (!strcmp(argv[1], "useradd")) { if (argc != 3) die_with_usage(); - char *endptr; - long uid = strtol(argv[2], &endptr, 10); - if (!argv[2] || *endptr) { - die("uid must be an integer"); - } - if (uid < MIN_UID || uid >= MAX_UID) { - die("uid is out of range"); - } - useradd(uid); + useradd(parseUID(argv[2])); + return 0; + } + if (!strcmp(argv[1], "spawn")) { + if (argc < 5) + die_with_usage(); + spawn(parseUID(argv[2]), parseUUID(argv[3]), &argv[4]); + return 0; + } + if (!strcmp(argv[1], "setup")) { + if (argc != 4) + die_with_usage(); + int uid = parseUID(argv[2]); + char *uuid = parseUUID(argv[3]); + setup(uid, uuid); + return 0; + } + if (!strcmp(argv[1], "teardown")) { + if (argc != 3) + die_with_usage(); + char *uuid = strcmp(argv[2], "*") ? parseUUID(argv[2]) : "*"; + teardown(uuid); return 0; } die_with_usage(); diff --git a/yarn.lock b/yarn.lock index a312345..72b89b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -55,6 +55,14 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/glob@*": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.2.tgz#06ca26521353a545d94a0adc74f38a59d232c987" + integrity sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + "@types/json-schema@^7.0.4": version "7.0.4" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" @@ -70,6 +78,11 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.2.tgz#857a118d8634c84bba7ae14088e4508490cd5da5" integrity sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q== +"@types/minimatch@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + "@types/mkdirp@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-1.0.1.tgz#0930b948914a78587de35458b86c907b6e98bbf6" @@ -97,6 +110,14 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== +"@types/rimraf@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-3.0.0.tgz#b9d03f090ece263671898d57bb7bb007023ac19f" + integrity sha512-7WhJ0MdpFgYQPXlF4Dx+DhgvlPCfz/x5mHaeDQAKhcenvQP1KCpLQ18JklAqeGMYSAT2PxLpzd0g2/HE7fj7hQ== + dependencies: + "@types/glob" "*" + "@types/node" "*" + "@types/serve-static@*": version "1.13.4" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.4.tgz#6662a93583e5a6cabca1b23592eb91e12fa80e7c" @@ -663,7 +684,7 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -chalk@2.4.2, chalk@^2.3.0, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -880,7 +901,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" -cross-spawn@6.0.5, cross-spawn@^6.0.0: +cross-spawn@6.0.5, cross-spawn@^6.0.0, 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== @@ -954,6 +975,13 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +define-properties@^1.1.2, define-properties@^1.1.3: + 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" + define-property@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" @@ -1100,6 +1128,39 @@ errno@^0.1.3, errno@~0.1.7: dependencies: prr "~1.0.1" +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.17.0-next.1, es-abstract@^1.17.5: + version "1.17.6" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" + integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.0" + is-regex "^1.1.0" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -1410,6 +1471,11 @@ fsevents@~2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== +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== + get-caller-file@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -1500,6 +1566,11 @@ has-flag@^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, has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -1531,6 +1602,13 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" +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" + hash-base@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" @@ -1564,6 +1642,11 @@ homedir-polyfill@^1.0.1: dependencies: parse-passwd "^1.0.0" +hosted-git-info@^2.1.4: + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + http-errors@1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" @@ -1695,6 +1778,11 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +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-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" @@ -1714,6 +1802,11 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-callable@^1.1.4, is-callable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" + integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -1728,6 +1821,11 @@ is-data-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + is-descriptor@^0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" @@ -1801,11 +1899,25 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" + integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw== + dependencies: + has-symbols "^1.0.1" + is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + is-windows@^1.0.1, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -1848,7 +1960,7 @@ jake@^10.6.1: filelist "^1.0.1" minimatch "^3.0.4" -json-parse-better-errors@^1.0.2: +json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: 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== @@ -1903,6 +2015,16 @@ lcid@^2.0.0: dependencies: invert-kv "^2.0.0" +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" + loader-runner@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" @@ -2021,6 +2143,11 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" +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= + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -2141,11 +2268,6 @@ mkdirp@^0.5.1, mkdirp@^0.5.3: dependencies: minimist "^1.2.5" -mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - monaco-editor@^0.20.0: version "0.20.0" resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.20.0.tgz#5d5009343a550124426cb4d965a4d27a348b4dea" @@ -2251,6 +2373,16 @@ node-pty@^0.9.0: dependencies: nan "^2.14.0" +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -2263,6 +2395,21 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +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" + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -2284,6 +2431,16 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-inspect@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" + integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" @@ -2291,6 +2448,16 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + object.pick@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" @@ -2386,6 +2553,14 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" +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" + parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" @@ -2426,11 +2601,23 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= +path-parse@^1.0.6: + 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-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +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" + pbkdf2@^3.0.3: version "3.1.1" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94" @@ -2447,6 +2634,16 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +pidtree@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a" + integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA== + +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= + pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" @@ -2645,6 +2842,15 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" +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" + "readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" @@ -2741,6 +2947,13 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= +resolve@^1.10.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== + dependencies: + path-parse "^1.0.6" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -2753,13 +2966,6 @@ rimraf@^2.5.4, rimraf@^2.6.3: dependencies: glob "^7.1.3" -rimraf@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -2815,7 +3021,7 @@ schema-utils@^2.6.5, schema-utils@^2.6.6: ajv "^6.12.2" ajv-keywords "^3.4.1" -semver@^5.5.0, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -2906,6 +3112,11 @@ shebang-regex@^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.7.2" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" + integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== + signal-exit@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -2980,6 +3191,32 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.5" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" + integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== + split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -3048,6 +3285,30 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string.prototype.padend@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz#dc08f57a8010dc5c153550318f67e13adbb72ac3" + integrity sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +string.prototype.trimend@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.trimstart@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -3069,6 +3330,11 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +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= + strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" @@ -3140,13 +3406,6 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" -tmp@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== - dependencies: - rimraf "^3.0.0" - to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" @@ -3334,6 +3593,14 @@ v8-compile-cache@2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w== +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" + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"