Index: node_modules/simple-update-notifier/src/borderedText.ts
===================================================================
--- node_modules/simple-update-notifier/src/borderedText.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
+++ node_modules/simple-update-notifier/src/borderedText.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
@@ -0,0 +1,12 @@
+const borderedText = (text: string) => {
+  const lines = text.split('\n');
+  const width = Math.max(...lines.map((l) => l.length));
+  const res = [`┌${'─'.repeat(width + 2)}┐`];
+  for (const line of lines) {
+    res.push(`│ ${line.padEnd(width)} │`);
+  }
+  res.push(`└${'─'.repeat(width + 2)}┘`);
+  return res.join('\n');
+};
+
+export default borderedText;
Index: node_modules/simple-update-notifier/src/cache.spec.ts
===================================================================
--- node_modules/simple-update-notifier/src/cache.spec.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
+++ node_modules/simple-update-notifier/src/cache.spec.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
@@ -0,0 +1,17 @@
+import { createConfigDir, getLastUpdate, saveLastUpdate } from './cache';
+
+createConfigDir();
+
+jest.useFakeTimers().setSystemTime(new Date('2022-01-01'));
+
+const fakeTime = new Date('2022-01-01').getTime();
+
+test('can save update then get the update details', () => {
+  saveLastUpdate('test');
+  expect(getLastUpdate('test')).toBe(fakeTime);
+});
+
+test('prefixed module can save update then get the update details', () => {
+  saveLastUpdate('@alexbrazier/test');
+  expect(getLastUpdate('@alexbrazier/test')).toBe(fakeTime);
+});
Index: node_modules/simple-update-notifier/src/cache.ts
===================================================================
--- node_modules/simple-update-notifier/src/cache.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
+++ node_modules/simple-update-notifier/src/cache.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
@@ -0,0 +1,44 @@
+import os from 'os';
+import path from 'path';
+import fs from 'fs';
+
+const homeDirectory = os.homedir();
+const configDir =
+  process.env.XDG_CONFIG_HOME ||
+  path.join(homeDirectory, '.config', 'simple-update-notifier');
+
+const getConfigFile = (packageName: string) => {
+  return path.join(
+    configDir,
+    `${packageName.replace('@', '').replace('/', '__')}.json`
+  );
+};
+
+export const createConfigDir = () => {
+  if (!fs.existsSync(configDir)) {
+    fs.mkdirSync(configDir, { recursive: true });
+  }
+};
+
+export const getLastUpdate = (packageName: string) => {
+  const configFile = getConfigFile(packageName);
+
+  try {
+    if (!fs.existsSync(configFile)) {
+      return undefined;
+    }
+    const file = JSON.parse(fs.readFileSync(configFile, 'utf8'));
+    return file.lastUpdateCheck as number;
+  } catch {
+    return undefined;
+  }
+};
+
+export const saveLastUpdate = (packageName: string) => {
+  const configFile = getConfigFile(packageName);
+
+  fs.writeFileSync(
+    configFile,
+    JSON.stringify({ lastUpdateCheck: new Date().getTime() })
+  );
+};
Index: node_modules/simple-update-notifier/src/getDistVersion.spec.ts
===================================================================
--- node_modules/simple-update-notifier/src/getDistVersion.spec.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
+++ node_modules/simple-update-notifier/src/getDistVersion.spec.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
@@ -0,0 +1,35 @@
+import Stream from 'stream';
+import https from 'https';
+import getDistVersion from './getDistVersion';
+
+jest.mock('https', () => ({
+  get: jest.fn(),
+}));
+
+test('Valid response returns version', async () => {
+  const st = new Stream();
+  (https.get as jest.Mock).mockImplementation((url, cb) => {
+    cb(st);
+
+    st.emit('data', '{"latest":"1.0.0"}');
+    st.emit('end');
+  });
+
+  const version = await getDistVersion('test', 'latest');
+
+  expect(version).toEqual('1.0.0');
+});
+
+test('Invalid response throws error', async () => {
+  const st = new Stream();
+  (https.get as jest.Mock).mockImplementation((url, cb) => {
+    cb(st);
+
+    st.emit('data', 'some invalid json');
+    st.emit('end');
+  });
+
+  expect(getDistVersion('test', 'latest')).rejects.toThrow(
+    'Could not parse version response'
+  );
+});
Index: node_modules/simple-update-notifier/src/getDistVersion.ts
===================================================================
--- node_modules/simple-update-notifier/src/getDistVersion.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
+++ node_modules/simple-update-notifier/src/getDistVersion.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
@@ -0,0 +1,29 @@
+import https from 'https';
+
+const getDistVersion = async (packageName: string, distTag: string) => {
+  const url = `https://registry.npmjs.org/-/package/${packageName}/dist-tags`;
+
+  return new Promise<string>((resolve, reject) => {
+    https
+      .get(url, (res) => {
+        let body = '';
+
+        res.on('data', (chunk) => (body += chunk));
+        res.on('end', () => {
+          try {
+            const json = JSON.parse(body);
+            const version = json[distTag];
+            if (!version) {
+              reject(new Error('Error getting version'));
+            }
+            resolve(version);
+          } catch {
+            reject(new Error('Could not parse version response'));
+          }
+        });
+      })
+      .on('error', (err) => reject(err));
+  });
+};
+
+export default getDistVersion;
Index: node_modules/simple-update-notifier/src/hasNewVersion.spec.ts
===================================================================
--- node_modules/simple-update-notifier/src/hasNewVersion.spec.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
+++ node_modules/simple-update-notifier/src/hasNewVersion.spec.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
@@ -0,0 +1,82 @@
+import hasNewVersion from './hasNewVersion';
+import { getLastUpdate } from './cache';
+import getDistVersion from './getDistVersion';
+
+jest.mock('./getDistVersion', () => jest.fn().mockReturnValue('1.0.0'));
+jest.mock('./cache', () => ({
+  getLastUpdate: jest.fn().mockReturnValue(undefined),
+  createConfigDir: jest.fn(),
+  saveLastUpdate: jest.fn(),
+}));
+
+const pkg = { name: 'test', version: '1.0.0' };
+
+afterEach(() => jest.clearAllMocks());
+
+const defaultArgs = {
+  pkg,
+  shouldNotifyInNpmScript: true,
+  alwaysRun: true,
+};
+
+test('it should not trigger update for same version', async () => {
+  const newVersion = await hasNewVersion(defaultArgs);
+
+  expect(newVersion).toBe(false);
+});
+
+test('it should trigger update for patch version bump', async () => {
+  (getDistVersion as jest.Mock).mockReturnValue('1.0.1');
+
+  const newVersion = await hasNewVersion(defaultArgs);
+
+  expect(newVersion).toBe('1.0.1');
+});
+
+test('it should trigger update for minor version bump', async () => {
+  (getDistVersion as jest.Mock).mockReturnValue('1.1.0');
+
+  const newVersion = await hasNewVersion(defaultArgs);
+
+  expect(newVersion).toBe('1.1.0');
+});
+
+test('it should trigger update for major version bump', async () => {
+  (getDistVersion as jest.Mock).mockReturnValue('2.0.0');
+
+  const newVersion = await hasNewVersion(defaultArgs);
+
+  expect(newVersion).toBe('2.0.0');
+});
+
+test('it should not trigger update if version is lower', async () => {
+  (getDistVersion as jest.Mock).mockReturnValue('0.0.9');
+
+  const newVersion = await hasNewVersion(defaultArgs);
+
+  expect(newVersion).toBe(false);
+});
+
+it('should trigger update check if last update older than config', async () => {
+  const TWO_WEEKS = new Date().getTime() - 1000 * 60 * 60 * 24 * 14;
+  (getLastUpdate as jest.Mock).mockReturnValue(TWO_WEEKS);
+  const newVersion = await hasNewVersion({
+    pkg,
+    shouldNotifyInNpmScript: true,
+  });
+
+  expect(newVersion).toBe(false);
+  expect(getDistVersion).toHaveBeenCalled();
+});
+
+it('should not trigger update check if last update is too recent', async () => {
+  const TWELVE_HOURS = new Date().getTime() - 1000 * 60 * 60 * 12;
+  (getLastUpdate as jest.Mock).mockReturnValue(TWELVE_HOURS);
+  const newVersion = await hasNewVersion({
+    pkg,
+    shouldNotifyInNpmScript: true,
+  });
+
+  expect(newVersion).toBe(false);
+  expect(getDistVersion).not.toHaveBeenCalled();
+});
Index: node_modules/simple-update-notifier/src/hasNewVersion.ts
===================================================================
--- node_modules/simple-update-notifier/src/hasNewVersion.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
+++ node_modules/simple-update-notifier/src/hasNewVersion.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
@@ -0,0 +1,40 @@
+import semver from 'semver';
+import { createConfigDir, getLastUpdate, saveLastUpdate } from './cache';
+import getDistVersion from './getDistVersion';
+import { IUpdate } from './types';
+
+const hasNewVersion = async ({
+  pkg,
+  updateCheckInterval = 1000 * 60 * 60 * 24,
+  distTag = 'latest',
+  alwaysRun,
+  debug,
+}: IUpdate) => {
+  createConfigDir();
+  const lastUpdateCheck = getLastUpdate(pkg.name);
+  if (
+    alwaysRun ||
+    !lastUpdateCheck ||
+    lastUpdateCheck < new Date().getTime() - updateCheckInterval
+  ) {
+    const latestVersion = await getDistVersion(pkg.name, distTag);
+    saveLastUpdate(pkg.name);
+    if (semver.gt(latestVersion, pkg.version)) {
+      return latestVersion;
+    } else if (debug) {
+      console.error(
+        `Latest version (${latestVersion}) not newer than current version (${pkg.version})`
+      );
+    }
+  } else if (debug) {
+    console.error(
+      `Too recent to check for a new update. simpleUpdateNotifier() interval set to ${updateCheckInterval}ms but only ${
+        new Date().getTime() - lastUpdateCheck
+      }ms since last check.`
+    );
+  }
+
+  return false;
+};
+
+export default hasNewVersion;
Index: node_modules/simple-update-notifier/src/index.spec.ts
===================================================================
--- node_modules/simple-update-notifier/src/index.spec.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
+++ node_modules/simple-update-notifier/src/index.spec.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
@@ -0,0 +1,27 @@
+import simpleUpdateNotifier from '.';
+import hasNewVersion from './hasNewVersion';
+
+const consoleSpy = jest.spyOn(console, 'error');
+
+jest.mock('./hasNewVersion', () => jest.fn().mockResolvedValue('2.0.0'));
+
+beforeEach(jest.clearAllMocks);
+
+test('it logs message if update is available', async () => {
+  await simpleUpdateNotifier({
+    pkg: { name: 'test', version: '1.0.0' },
+    alwaysRun: true,
+  });
+
+  expect(consoleSpy).toHaveBeenCalledTimes(1);
+});
+
+test('it does not log message if update is not available', async () => {
+  (hasNewVersion as jest.Mock).mockResolvedValue(false);
+  await simpleUpdateNotifier({
+    pkg: { name: 'test', version: '2.0.0' },
+    alwaysRun: true,
+  });
+
+  expect(consoleSpy).toHaveBeenCalledTimes(0);
+});
Index: node_modules/simple-update-notifier/src/index.ts
===================================================================
--- node_modules/simple-update-notifier/src/index.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
+++ node_modules/simple-update-notifier/src/index.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
@@ -0,0 +1,34 @@
+import isNpmOrYarn from './isNpmOrYarn';
+import hasNewVersion from './hasNewVersion';
+import { IUpdate } from './types';
+import borderedText from './borderedText';
+
+const simpleUpdateNotifier = async (args: IUpdate) => {
+  if (
+    !args.alwaysRun &&
+    (!process.stdout.isTTY || (isNpmOrYarn && !args.shouldNotifyInNpmScript))
+  ) {
+    if (args.debug) {
+      console.error('Opting out of running simpleUpdateNotifier()');
+    }
+    return;
+  }
+
+  try {
+    const latestVersion = await hasNewVersion(args);
+    if (latestVersion) {
+      console.error(
+        borderedText(`New version of ${args.pkg.name} available!
+Current Version: ${args.pkg.version}
+Latest Version: ${latestVersion}`)
+      );
+    }
+  } catch (err) {
+    // Catch any network errors or cache writing errors so module doesn't cause a crash
+    if (args.debug && err instanceof Error) {
+      console.error('Unexpected error in simpleUpdateNotifier():', err);
+    }
+  }
+};
+
+export default simpleUpdateNotifier;
Index: node_modules/simple-update-notifier/src/isNpmOrYarn.ts
===================================================================
--- node_modules/simple-update-notifier/src/isNpmOrYarn.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
+++ node_modules/simple-update-notifier/src/isNpmOrYarn.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
@@ -0,0 +1,12 @@
+import process from 'process';
+
+const packageJson = process.env.npm_package_json;
+const userAgent = process.env.npm_config_user_agent;
+const isNpm6 = Boolean(userAgent && userAgent.startsWith('npm'));
+const isNpm7 = Boolean(packageJson && packageJson.endsWith('package.json'));
+
+const isNpm = isNpm6 || isNpm7;
+const isYarn = Boolean(userAgent && userAgent.startsWith('yarn'));
+const isNpmOrYarn = isNpm || isYarn;
+
+export default isNpmOrYarn;
Index: node_modules/simple-update-notifier/src/types.ts
===================================================================
--- node_modules/simple-update-notifier/src/types.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
+++ node_modules/simple-update-notifier/src/types.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
@@ -0,0 +1,8 @@
+export interface IUpdate {
+  pkg: { name: string; version: string };
+  updateCheckInterval?: number;
+  shouldNotifyInNpmScript?: boolean;
+  distTag?: string;
+  alwaysRun?: boolean;
+  debug?: boolean;
+}
