Get main application page rendering

This commit is contained in:
Radon Rosborough 2020-12-25 12:06:09 -08:00
parent d54d0fb5bb
commit 14f7bec490
13 changed files with 145 additions and 125 deletions

View File

@ -97,7 +97,7 @@ build: frontend system
.PHONY: dev
dev:
make -j2 frontend-dev system-dev server-dev
make -j3 frontend-dev system-dev server-dev
### Run application code

View File

@ -1,3 +0,0 @@
import process from "process";
export const PRIVILEGED = process.env.RIJU_PRIVILEGED ? true : false;

View File

@ -1,2 +1,36 @@
// TODO
export const langs = {};
import { promises as fs } from "fs";
import path from "path";
import { log } from "./util.js";
// Map from language IDs to language configuration objects. This is
// populated at runtime and updated asynchronously.
export let langs = {};
async function readLangsFromDisk() {
const newLangs = {};
for (const filename of await fs.readdir("/opt/riju/langs")) {
if (path.parse(filename).ext !== ".json") {
continue;
}
const id = path.parse(filename).name;
const langConfig = JSON.parse(
await fs.readFile(`/opt/riju/langs/${filename}`, "utf-8")
);
if (langConfig.id !== id) {
log.error(
"Language config ${filename} has mismatched language ID ${id}, ignoring"
);
continue;
}
newLangs[id] = langConfig;
}
log.info(
`Loaded ${Object.keys(newLangs).length} language configuration(s) from disk`
);
langs = newLangs;
}
readLangsFromDisk().catch((err) => {
log.error("Failed to read languages from disk:", err);
});

View File

@ -6,7 +6,6 @@ import AsyncLock from "async-lock";
import _ from "lodash";
import parsePasswd from "parse-passwd";
import { PRIVILEGED } from "./config.js";
import { privilegedUseradd, run } from "./util.js";
// Keep in sync with system/src/riju-system-privileged.c
@ -70,27 +69,23 @@ export async function ignoreUsers(uids, log) {
}
export async function borrowUser(log) {
if (!PRIVILEGED) {
return { uid: CUR_UID, returnUID: async () => {} };
} else {
return await lock.acquire("key", async () => {
if (availIds === null || nextId === null) {
await readExistingUsers(log);
}
let uid;
if (availIds.length > 0) {
uid = availIds.pop();
} else {
uid = await createUser(log);
}
return {
uid,
returnUID: async () => {
await lock.acquire("key", () => {
availIds.push(uid);
});
},
};
});
}
return await lock.acquire("key", async () => {
if (availIds === null || nextId === null) {
await readExistingUsers(log);
}
let uid;
if (availIds.length > 0) {
uid = availIds.pop();
} else {
uid = await createUser(log);
}
return {
uid,
returnUID: async () => {
await lock.acquire("key", () => {
availIds.push(uid);
});
},
};
});
}

View File

@ -4,7 +4,6 @@ import process from "process";
import { quote } from "shell-quote";
import { PRIVILEGED } from "./config.js";
import { MIN_UID, MAX_UID } from "./users.js";
export const rijuSystemPrivileged = "system/out/riju-system-privileged";
@ -39,7 +38,7 @@ function getEnv({ uid, uuid }) {
LANG: process.env.LANG || "",
LC_ALL: process.env.LC_ALL || "",
LOGNAME: username,
PATH: PRIVILEGED ? path.join(":") : process.env.PATH || "",
PATH: path.join(":"),
PWD: cwd,
SHELL: "/usr/bin/bash",
TERM: "xterm-256color",
@ -122,3 +121,11 @@ export function bash(cmdline) {
}
return ["bash", "-c", cmdline];
}
export const log = {
trace: console.error,
debug: console.error,
info: console.error,
warn: console.error,
error: console.error,
};

View File

@ -4,7 +4,6 @@ FROM riju:composite
RUN useradd -p '!' -m -l -s /usr/bin/bash riju
WORKDIR /src
ENV RIJU_PRIVILEGED=1
COPY --chown=riju:riju --from=compile . ./
RUN chown root:riju system/out/*-privileged && chmod a=,g=rx,u=rwxs system/out/*-privileged

View File

@ -4,7 +4,6 @@ COPY docker/runtime/install.bash /tmp/
RUN /tmp/install.bash
WORKDIR /src
ENV RIJU_PRIVILEGED=1
COPY package.json yarn.lock ./
RUN yarn install

View File

@ -9,3 +9,4 @@ ENTRYPOINT ["/usr/local/sbin/my_init", "/usr/local/sbin/pid1.bash"]
CMD ["bash"]
EXPOSE 6119
EXPOSE 6120
ENV HOST=0.0.0.0

View File

@ -7,25 +7,29 @@
</head>
<body>
<h1>Riju: <i>fast</i> online playground for every programming language</h1>
<i>Pick your favorite language to get started:</i>
<div class="grid">
<% for (const [id, {name}] of Object.entries(langs).sort(
([id1, {name: name1}], [id2, {name: name2}]) => name1.toLowerCase().localeCompare(name2.toLowerCase()))) { %>
<a href=<%= "/" + encodeURIComponent(id) %> class="language">
<div class="language">
<%= name %>
</div>
</a>
<% } %>
</div>
<p>
<i>
Created by
<a href="https://github.com/raxod502">Radon Rosborough</a>.
Check out the project
<a href="https://github.com/raxod502/riju">on GitHub</a>.
</i>
</p>
<% if (Object.keys(langs).length > 0) { %>
<i>Pick your favorite language to get started:</i>
<div class="grid">
<% for (const [id, {name}] of Object.entries(langs).sort(
([id1, {name: name1}], [id2, {name: name2}]) => name1.toLowerCase().localeCompare(name2.toLowerCase()))) { %>
<a href=<%= "/" + encodeURIComponent(id) %> class="language">
<div class="language">
<%= name %>
</div>
</a>
<% } %>
</div>
<p>
<i>
Created by
<a href="https://github.com/raxod502">Radon Rosborough</a>.
Check out the project
<a href="https://github.com/raxod502/riju">on GitHub</a>.
</i>
</p>
<% } else { %>
<i>Riju is loading language configuration...</i>
<% } %>
<% if (analyticsEnabled) { %>
<script src="https://cdn.usefathom.com/script.js" site="JOBZEHJE" defer></script>
<% } %>

View File

@ -9,6 +9,7 @@
"@babel/preset-env": "^7.12.11",
"async-lock": "^1.2.6",
"babel-loader": "^8.2.2",
"buffer": "^6.0.3",
"commander": "^6.2.1",
"css-loader": "^5.0.1",
"ejs": "^3.1.5",
@ -22,6 +23,7 @@
"node-pty": "^0.9.0",
"p-queue": "^6.6.2",
"parse-passwd": "^1.0.0",
"regenerator-runtime": "^0.13.7",
"shell-quote": "^1.7.2",
"style-loader": "^2.0.0",
"uuid": "^8.3.2",

View File

@ -18,7 +18,7 @@ for src in system/src/*.c; do
out="${src/src/out}"
out="${out/.c}"
verbosely clang -Wall -Wextra -Werror -std=c11 "${src}" -o "${out}"
if [[ "${out}" == *-privileged && -n "${RIJU_PRIVILEGED:-}" ]]; then
if [[ "${out}" == *-privileged ]]; then
sudo chown root:riju "${out}"
sudo chmod a=,g=rx,u=rwxs "${out}"
fi

View File

@ -1,9 +1,10 @@
const path = require("path");
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
const webpack = require("webpack");
function isProduction(argv) {
return !argv.development;
return argv.mode !== "development";
}
module.exports = (_, argv) => ({
@ -40,7 +41,13 @@ module.exports = (_, argv) => ({
performance: {
hints: false,
},
plugins: [new MonacoWebpackPlugin()],
plugins: [
new webpack.ProvidePlugin({
Buffer: ["buffer", "Buffer"],
regeneratorRuntime: "regenerator-runtime/runtime",
}),
new MonacoWebpackPlugin(),
],
resolve: {
alias: {
vscode: require.resolve("monaco-languageclient/lib/vscode-compatibility"),

113
yarn.lock
View File

@ -814,6 +814,11 @@
lodash "^4.17.19"
to-fast-properties "^2.0.0"
"@discoveryjs/json-ext@^0.5.0":
version "0.5.1"
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.1.tgz#cf87081ac9b0f3eb3b5740415b50b7966bac8fc5"
integrity sha512-Oee4NT60Lxe90m7VTYBU4UbABNaz0N4Q3G62CPB+6mGE4KuLMsTACmH8q3PH5u9pSZCuOdE9JClJ9vBqsp6DQg==
"@types/eslint-scope@^3.7.0":
version "3.7.0"
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86"
@ -990,17 +995,17 @@
"@webassemblyjs/wast-parser" "1.9.1"
"@xtuc/long" "4.2.2"
"@webpack-cli/info@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.1.0.tgz#c596d5bc48418b39df00c5ed7341bf0f102dbff1"
integrity sha512-uNWSdaYHc+f3LdIZNwhdhkjjLDDl3jP2+XBqAq9H8DjrJUvlOKdP8TNruy1yEaDfgpAIgbSAN7pye4FEHg9tYQ==
"@webpack-cli/info@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.2.0.tgz#6051d6adf3618df664f4945a2b76355c00f83f0d"
integrity sha512-+wA8lBKopgKmN76BSGJVJby5ZXDlsrO6p/nm7fUBsHznRNWB/ozotJP7Yfcz8JPfqeG2LxwYlTH2u6D9a/0XAw==
dependencies:
envinfo "^7.7.3"
"@webpack-cli/serve@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.1.0.tgz#13ad38f89b6e53d1133bac0006a128217a6ebf92"
integrity sha512-7RfnMXCpJ/NThrhq4gYQYILB18xWyoQcBey81oIyVbmgbc6m5ZHHyFK+DyH7pLHJf0p14MxL4mTsoPAgBSTpIg==
"@webpack-cli/serve@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.2.0.tgz#8cb2c1e95426f5caed1f3bf9d7ccf3ea41d85f52"
integrity sha512-jI3P7jMp/AXDSPkM+ClwRcJZbxnlvNC8bVZBmyRr4scMMZ4p5WQcXkw3Q+Hc7RQekomJlBMN+UQGliT4hhG8Vw==
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
@ -1052,11 +1057,6 @@ ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
array-back@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.1.tgz#9b80312935a52062e1a233a9c7abeb5481b30e90"
integrity sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg==
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
@ -1099,6 +1099,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
@ -1144,6 +1149,14 @@ buffer-from@^1.0.0:
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
bytes@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
@ -1200,16 +1213,6 @@ colorette@^1.2.1:
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
command-line-usage@^6.1.0:
version "6.1.1"
resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.1.tgz#c908e28686108917758a49f45efb4f02f76bc03f"
integrity sha512-F59pEuAR9o1SF/bD0dQBDluhpT4jJQNWUHEuVBqpDmCUo6gPjCi+m9fCWnWZVR/oG6cMTUms4h+3NPl74wGXvA==
dependencies:
array-back "^4.0.1"
chalk "^2.4.2"
table-layout "^1.0.1"
typical "^5.2.0"
commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@ -1313,11 +1316,6 @@ debug@^4.1.0:
dependencies:
ms "2.1.2"
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==
define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@ -1517,6 +1515,11 @@ fast-json-stable-stringify@^2.0.0:
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
fastest-levenshtein@^1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2"
integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==
file-loader@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d"
@ -1687,6 +1690,11 @@ icss-utils@^5.0.0:
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
import-local@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6"
@ -1795,11 +1803,6 @@ json5@^2.1.2:
dependencies:
minimist "^1.2.5"
leven@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
loader-runner@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.1.0.tgz#f70bc0c29edbabdf2043e7ee73ccc3fe1c96b42d"
@ -2226,11 +2229,6 @@ rechoir@^0.7.0:
dependencies:
resolve "^1.9.0"
reduce-flatten@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27"
integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==
regenerate-unicode-properties@^8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
@ -2243,7 +2241,7 @@ regenerate@^1.4.0:
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
regenerator-runtime@^0.13.4:
regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7:
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
@ -2477,16 +2475,6 @@ supports-color@^7.0.0:
dependencies:
has-flag "^4.0.0"
table-layout@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.1.tgz#8411181ee951278ad0638aea2f779a9ce42894f9"
integrity sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q==
dependencies:
array-back "^4.0.1"
deep-extend "~0.6.0"
typical "^5.2.0"
wordwrapjs "^4.0.0"
tapable@^2.1.1, tapable@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b"
@ -2536,11 +2524,6 @@ type-is@~1.6.17, type-is@~1.6.18:
media-typer "0.3.0"
mime-types "~2.1.24"
typical@^5.0.0, typical@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066"
integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
@ -2664,20 +2647,20 @@ watchpack@^2.0.0:
graceful-fs "^4.1.2"
webpack-cli@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.2.0.tgz#10a09030ad2bd4d8b0f78322fba6ea43ec56aaaa"
integrity sha512-EIl3k88vaF4fSxWSgtAQR+VwicfLMTZ9amQtqS4o+TDPW9HGaEpbFBbAZ4A3ZOT5SOnMxNOzROsSTPiE8tBJPA==
version "4.3.0"
resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.3.0.tgz#e39303bf9f8002de122903e97029f3443d0f9174"
integrity sha512-gve+BBKrzMPTOYDjupzV8JchUznhVWMKtWM1hFIQWi6XoeLvGNoQwkrtMWVb+aJ437GgCKdta7sIn10v621pKA==
dependencies:
"@webpack-cli/info" "^1.1.0"
"@webpack-cli/serve" "^1.1.0"
"@discoveryjs/json-ext" "^0.5.0"
"@webpack-cli/info" "^1.2.0"
"@webpack-cli/serve" "^1.2.0"
colorette "^1.2.1"
command-line-usage "^6.1.0"
commander "^6.2.0"
enquirer "^2.3.6"
execa "^4.1.0"
fastest-levenshtein "^1.0.12"
import-local "^3.0.2"
interpret "^2.2.0"
leven "^3.1.0"
rechoir "^0.7.0"
v8-compile-cache "^2.2.0"
webpack-merge "^4.2.2"
@ -2734,14 +2717,6 @@ which@^2.0.1:
dependencies:
isexe "^2.0.0"
wordwrapjs@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.0.tgz#9aa9394155993476e831ba8e59fb5795ebde6800"
integrity sha512-Svqw723a3R34KvsMgpjFBYCgNOSdcW3mQFK4wIfhGQhtaFVOJmdYoXgi63ne3dTlWgatVcUc7t4HtQ/+bUVIzQ==
dependencies:
reduce-flatten "^2.0.0"
typical "^5.0.0"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"