aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoreug-vs <eugene@eug-vs.xyz>2022-10-22 20:06:01 +0300
committereug-vs <eugene@eug-vs.xyz>2022-10-22 20:06:01 +0300
commiteeb3bcec19b15e6b7d30f983c8f613dedb1a9b32 (patch)
treefc3b0be53cb839be0a02106b9d140f2afb0c98b6
parent82de4ac15e6a942c3b004efad24a3dc8a4ab7edf (diff)
downloadbenzin-next-eeb3bcec19b15e6b7d30f983c8f613dedb1a9b32.tar.gz
refactor: separate Nginx adapter
-rw-r--r--next.config.js2
-rw-r--r--package.json5
-rw-r--r--src/Emoji.tsx3
-rw-r--r--src/Image.tsx7
-rw-r--r--src/benzinConfig.ts9
-rw-r--r--src/deepReadDir.tsx12
-rw-r--r--src/lib/nginxAdapter.ts50
-rw-r--r--src/lib/types.ts15
-rw-r--r--src/pages/[...path].tsx39
-rw-r--r--src/pages/_app.tsx2
-rw-r--r--tsconfig.json1
-rw-r--r--yarn.lock93
12 files changed, 201 insertions, 37 deletions
diff --git a/next.config.js b/next.config.js
index e314688..494d6fe 100644
--- a/next.config.js
+++ b/next.config.js
@@ -7,7 +7,7 @@ const nextConfig = {
},
},
images: {
- domains: ['localhost', 'eug-vs.xyz'],
+ domains: ['localhost', 'eug-vs.xyz', 'git.eug-vs.xyz'],
},
redirects: async () => {
return [{
diff --git a/package.json b/package.json
index 8223b1e..95bf749 100644
--- a/package.json
+++ b/package.json
@@ -9,8 +9,11 @@
"lint": "next lint"
},
"dependencies": {
+ "axios": "^0.27.2",
+ "bluebird": "^3.7.2",
"glob": "^8.0.3",
"lodash": "^4.17.21",
+ "mem": "^9.0.2",
"next": "12.2.0",
"react": "18.2.0",
"react-dom": "18.2.0",
@@ -19,6 +22,8 @@
"unist-util-visit-parents": "^5.1.1"
},
"devDependencies": {
+ "@types/axios": "^0.14.0",
+ "@types/bluebird": "^3.5.37",
"@types/lodash": "^4.14.185",
"@types/node": "18.0.0",
"@types/react": "18.0.14",
diff --git a/src/Emoji.tsx b/src/Emoji.tsx
index b928087..df5108d 100644
--- a/src/Emoji.tsx
+++ b/src/Emoji.tsx
@@ -1,5 +1,6 @@
import { FC } from 'react';
import Image from 'next/future/image';
+import benzinConfig from './benzinConfig';
interface Props {
children: string[];
@@ -9,7 +10,7 @@ const Emoji: FC<Props> = ({ children }) => {
const src = children[0];
return (
<Image
- src={`/emoji/${src}`}
+ src={`${benzinConfig.CDN}/public/emoji/${src}`}
width={16}
alt={`${src}-emoji`}
/>
diff --git a/src/Image.tsx b/src/Image.tsx
index 770c383..37e55ba 100644
--- a/src/Image.tsx
+++ b/src/Image.tsx
@@ -1,13 +1,12 @@
import ImageBase from 'next/future/image';
import { FC } from 'react';
+import benzinConfig from './benzinConfig';
type Props = Record<'src' | 'alt', string>;
-const IMAGE_CDN = 'http://localhost:8000';
-
const localizeSrc = (src: string) => {
- if (process.env.NODE_ENV === 'production' || src.startsWith('http')) return src;
- return IMAGE_CDN + src;
+ if (src.startsWith('http')) return src;
+ return benzinConfig.CDN + src;
}
const Image: FC<Props> = ({ src, ...props }) => {
diff --git a/src/benzinConfig.ts b/src/benzinConfig.ts
new file mode 100644
index 0000000..d310bb3
--- /dev/null
+++ b/src/benzinConfig.ts
@@ -0,0 +1,9 @@
+import { BenzinConfig } from './lib/types';
+import nginxAdapter from './lib/nginxAdapter';
+
+const benzinConfig: BenzinConfig = {
+ CDN: 'http://localhost:8000',
+ adapter: nginxAdapter,
+};
+
+export default benzinConfig;
diff --git a/src/deepReadDir.tsx b/src/deepReadDir.tsx
deleted file mode 100644
index ec585ec..0000000
--- a/src/deepReadDir.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import {lstat, readdir} from 'node:fs/promises'
-import {join} from 'node:path'
-
-const deepReadDir = async (dirPath: string): Promise<string[]> => await Promise.all(
- (await readdir(dirPath)).map(async (entity) => {
- const path = join(dirPath, entity)
- return (await lstat(path)).isDirectory() ? await deepReadDir(path) : path
- }),
-).then(arr => arr.flat());
-
-
-export default deepReadDir;
diff --git a/src/lib/nginxAdapter.ts b/src/lib/nginxAdapter.ts
new file mode 100644
index 0000000..fdadd87
--- /dev/null
+++ b/src/lib/nginxAdapter.ts
@@ -0,0 +1,50 @@
+import _ from 'lodash';
+import Bluebird from 'bluebird';
+import axios from 'axios';
+import mem from 'mem';
+import { Adapter } from './types';
+
+const listNginxDirectory = async (path: string): Promise<string[]> => {
+ const basePath = _.trimEnd(path.match('http[s]?://(.*?)/')?.[0], '/');
+ const response = await axios(path);
+ const [_thisDir, ...results] = response.data
+ .match(/href='(.*)'/g)
+ .map((s: string) => s.match(/'(.*)'/)?.[1])
+ .map((s: string) => basePath + s);
+ return results;
+}
+
+const deepListNginxDirectory = async (path: string): Promise<string[]> => {
+ const objects = await listNginxDirectory(path);
+ const fileUrls = objects.filter(url => !url.endsWith('/'));
+ const dirUrls = objects.filter(url => url.endsWith('/'));
+ const deepFileUrls = await Bluebird.map(dirUrls, deepListNginxDirectory);
+ return _.flattenDeep([fileUrls, deepFileUrls]);
+}
+
+const memoizedDeepListNginxDirectory = mem(deepListNginxDirectory, { maxAge: 60000 });
+
+
+// An adapter to fetch markdown & images from Nginx server with enabled directory view
+const nginxAdapter: Adapter = {
+ async getStaticMarkdownPaths(cdn) {
+ const urls = await memoizedDeepListNginxDirectory(cdn);
+ const markdownPaths = _.compact(urls.map(globalPath => globalPath.match(`${cdn}/(.*)\.md`)?.[1]));
+
+ return markdownPaths
+ .map(path => path.split('/'))
+ .map(path => ({ params: { path } }));
+ },
+
+ async getMarkdownSource(cdn, path) {
+ const { data: markdownSource } = await axios(`${cdn}/${path?.join('/')}.md`);
+ return markdownSource;
+ },
+
+ async getEmojiFileNames(cdn) {
+ const urls = await memoizedDeepListNginxDirectory(cdn);
+ return _.compact(urls.map((s: string) => s.match(/emoji\/(.*)/)?.[1]));
+ },
+}
+
+export default nginxAdapter;
diff --git a/src/lib/types.ts b/src/lib/types.ts
new file mode 100644
index 0000000..8bbf5bb
--- /dev/null
+++ b/src/lib/types.ts
@@ -0,0 +1,15 @@
+import { GetStaticPathsResult } from 'next';
+
+/* Collection of methods to fetch data & metadata from CDN */
+export interface Adapter {
+ getStaticMarkdownPaths: (cdn: string) => Promise<GetStaticPathsResult['paths']>;
+ getMarkdownSource: (cdn: string, path: string[]) => Promise<string>;
+ getEmojiFileNames: (cdn: string) => Promise<string[]>;
+}
+
+export interface BenzinConfig {
+ CDN: string;
+ adapter: Adapter;
+}
+
+
diff --git a/src/pages/[...path].tsx b/src/pages/[...path].tsx
index ea67d17..a49286d 100644
--- a/src/pages/[...path].tsx
+++ b/src/pages/[...path].tsx
@@ -4,23 +4,30 @@ import ReactMarkdown from 'react-markdown';
import Head from 'next/head';
import Emoji from '../Emoji';
import Image from '../Image';
-import deepReadDir from '../deepReadDir';
import emojiPlugin from '../emojiPlugin';
-import fs from 'fs';
import remarkGemoji from 'remark-gemoji';
+import benzinConfig from '../benzinConfig';
-
-const MARKDOWN_DIR = '../eug-vs-xyz/src';
-const EMOJI_DIR = 'public/emoji';
-
-const transformLinkURI = (uri: string): string => {
+const transformLinkUri = (uri: string): string => {
return uri.match(/(.*)\.md/)?.[1] || uri;
}
+const transformImageUri = (uri: string): string => {
+ return uri.startsWith('http') ? uri : benzinConfig.CDN + uri;
+}
+
+export const config = {
+ unstable_runtimeJS: false,
+};
+
export const getStaticProps = async (context: GetStaticPropsContext) => {
- const path = _.isArray(context.params?.path) && context.params?.path || [context.params?.path];
- const markdownSource = fs.readFileSync(`${MARKDOWN_DIR}/${path?.join('/')}.md`).toString();
- const emojiFileNames = fs.readdirSync(EMOJI_DIR);
+ const path = _.compact(_.isArray(context.params?.path)
+ ? context.params?.path
+ : [context.params?.path]
+ );
+
+ const markdownSource = await benzinConfig.adapter.getMarkdownSource(benzinConfig.CDN, path);
+ const emojiFileNames = await benzinConfig.adapter.getEmojiFileNames(benzinConfig.CDN);
return {
props: {
@@ -32,12 +39,8 @@ export const getStaticProps = async (context: GetStaticPropsContext) => {
}
export const getStaticPaths = async () => {
- const globalPaths = await deepReadDir(MARKDOWN_DIR);
- const paths = globalPaths
- .map(globalPath => globalPath.match(`${MARKDOWN_DIR}/(.*)\.md`)?.[1] )
- .filter(p => p)
- .map(p => p?.split('/'))
- .map(path => ({ params: { path } }));
+ const paths = await benzinConfig.adapter.getStaticMarkdownPaths(benzinConfig.CDN);
+
return {
paths,
fallback: 'blocking',
@@ -54,7 +57,8 @@ const Page: NextPage = ({ markdownSource, emojiFileNames }: any) => {
</Head>
<main>
<ReactMarkdown
- transformLinkUri={transformLinkURI}
+ transformLinkUri={transformLinkUri}
+ transformImageUri={transformImageUri}
rehypePlugins={[emojiPlugin(emojiFileNames), remarkGemoji]}
components={{
emoji: Emoji,
@@ -74,4 +78,3 @@ const Page: NextPage = ({ markdownSource, emojiFileNames }: any) => {
};
export default Page;
-
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index ba9372f..18692df 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -7,7 +7,7 @@ function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<a href="/" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', textDecoration: 'none', marginBottom: '12px' }}>
- <Image src={logo} width={128} height={128} />
+ <Image src={logo} width={128} height={128} alt="logo" />
<h1>{"Eugene's Space"}</h1>
</a>
<Component {...pageProps} />
diff --git a/tsconfig.json b/tsconfig.json
index 4f7ee02..d691df6 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,6 +11,7 @@
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
+ "experimentalDecorators": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
diff --git a/yarn.lock b/yarn.lock
index a411c99..8518871 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -156,6 +156,18 @@
dependencies:
tslib "^2.4.0"
+"@types/axios@^0.14.0":
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/@types/axios/-/axios-0.14.0.tgz#ec2300fbe7d7dddd7eb9d3abf87999964cafce46"
+ integrity sha512-KqQnQbdYE54D7oa/UmYVMZKq7CO4l8DEENzOKc4aBRwxCXSlJXGz83flFx5L7AWrOQnmuN3kVsRdt+GZPPjiVQ==
+ dependencies:
+ axios "*"
+
+"@types/bluebird@^3.5.37":
+ version "3.5.37"
+ resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.37.tgz#b99e5c7fe382c2c6d5252dc99d9fba6810fedbeb"
+ integrity sha512-g2qEd+zkfkTEudA2SrMAeAvY7CrFqtbsLILm2dT2VIeKTqMqVzcdfURlvu6FU3srRgbmXN1Srm94pg34EIehww==
+
"@types/debug@^4.0.0":
version "4.1.7"
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82"
@@ -372,11 +384,24 @@ ast-types-flow@^0.0.7:
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
integrity sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==
+asynckit@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+ integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
+
axe-core@^4.4.3:
version "4.4.3"
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.3.tgz#11c74d23d5013c0fa5d183796729bc3482bd2f6f"
integrity sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==
+axios@*, axios@^0.27.2:
+ version "0.27.2"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
+ integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
+ dependencies:
+ follow-redirects "^1.14.9"
+ form-data "^4.0.0"
+
axobject-query@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
@@ -392,6 +417,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+bluebird@^3.7.2:
+ version "3.7.2"
+ resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
+ integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
+
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -457,6 +487,13 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+combined-stream@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
+ integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
+ dependencies:
+ delayed-stream "~1.0.0"
+
comma-separated-tokens@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz#d4c25abb679b7751c880be623c1179780fe1dd98"
@@ -532,6 +569,11 @@ define-properties@^1.1.3, define-properties@^1.1.4:
has-property-descriptors "^1.0.0"
object-keys "^1.1.1"
+delayed-stream@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+ integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
+
dequal@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
@@ -886,6 +928,20 @@ flatted@^3.1.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
+follow-redirects@^1.14.9:
+ version "1.15.2"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
+ integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
+
+form-data@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
+ integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.8"
+ mime-types "^2.1.12"
+
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -1292,6 +1348,13 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
+map-age-cleaner@^0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a"
+ integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==
+ dependencies:
+ p-defer "^1.0.0"
+
mdast-util-definitions@^5.0.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.1.tgz#2c1d684b28e53f84938bb06317944bee8efa79db"
@@ -1346,6 +1409,14 @@ mdurl@^1.0.0:
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==
+mem@^9.0.2:
+ version "9.0.2"
+ resolved "https://registry.yarnpkg.com/mem/-/mem-9.0.2.tgz#bbc2d40be045afe30749681e8f5d554cee0c0354"
+ integrity sha512-F2t4YIv9XQUBHt6AOJ0y7lSmP1+cY7Fm1DRh9GClTGzKST7UWLMx6ly9WZdLH/G/ppM5RL4MlQfRT71ri9t19A==
+ dependencies:
+ map-age-cleaner "^0.1.3"
+ mimic-fn "^4.0.0"
+
merge2@^1.3.0, merge2@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
@@ -1554,6 +1625,23 @@ micromatch@^4.0.4:
braces "^3.0.2"
picomatch "^2.3.1"
+mime-db@1.52.0:
+ version "1.52.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.12:
+ version "2.1.35"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
+mimic-fn@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc"
+ integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==
+
minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@@ -1708,6 +1796,11 @@ optionator@^0.9.1:
type-check "^0.4.0"
word-wrap "^1.2.3"
+p-defer@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
+ integrity sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==
+
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"