[
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"env\": {\n    \"browser\": true,\n    \"es6\": true,\n    \"node\": true\n  },\n  \"extends\": [\"prettier\"],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"parserOptions\": {\n    \"ecmaVersion\": 2019,\n    \"project\": \"./tsconfig.json\",\n    \"sourceType\": \"module\"\n  },\n  \"plugins\": [\"@typescript-eslint\", \"prettier\"],\n  \"rules\": {\n    \"curly\": \"error\",\n    \"no-else-return\": \"error\",\n    \"prettier/prettier\": \"error\"\n  },\n  \"ignorePatterns\": [\"dist\", \"*.json\"]\n}\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\njobs:\n  build:\n    strategy:\n      matrix:\n        platform: [ubuntu-latest, macos-latest, windows-latest]\n    runs-on: ${{matrix.platform}}\n    steps:\n      - uses: actions/checkout@v1\n      - uses: actions/setup-node@v1\n        with:\n          version: 12\n      - run: |\n          yarn\n          yarn build\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v1\n      - uses: actions/setup-node@v1\n        with:\n          version: 12\n\n      - name: Cache node modules\n        uses: actions/cache@v1\n        env:\n          cache-name: cache-node-modules\n        with:\n          path: node_modules\n          key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock') }}\n\n      - run: yarn install --frozen-lockfile\n      - run: yarn lint\n\n  test-linux:\n    needs: lint\n    runs-on: ubuntu-latest\n\n    steps:\n      - run: lsb_release -a\n      - run: uname -a\n      - uses: actions/checkout@v1\n      - uses: actions/setup-node@v1\n        with:\n          version: 12\n\n      - name: Cache node modules\n        uses: actions/cache@v1\n        env:\n          cache-name: cache-node-modules\n        with:\n          path: node_modules\n          key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock') }}\n\n      - run: yarn install --frozen-lockfile\n      - run: yarn build:app\n      - run: sudo apt-get install xvfb\n      - run: |\n          export DISPLAY=:99.0\n          xvfb-run --auto-servernum yarn test:spec\n      # NOTE:\n      # Xvfb is an X server that can run on linux machines with no display hardware\n      # and no physical input devices. It emulates a dumb framebuffer  using\n      # virtual memory.\n\n  test-macos:\n    needs: lint\n    runs-on: macos-latest\n    steps:\n      - run: uname -a\n      - uses: actions/checkout@v1\n      - uses: actions/setup-node@v1\n        with:\n          version: 12\n      - name: Cache node modules\n        uses: actions/cache@v1\n        env:\n          cache-name: cache-node-modules\n        with:\n          path: node_modules\n          key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock') }}\n      - run: yarn install --frozen-lockfile\n      - run: yarn build:app\n      - run: export {no_proxy,NO_PROXY}=\"127.0.0.1,localhost\" # ChromeDriver timesout if NO_PROXY env variable is not set.\n      - run: yarn test:spec\n  # !FIXME: Spectron tests fail on Windows, probably due to a configuration issue.\n  # test-windows:\n  #   needs: lint\n  #   runs-on: windows-latest\n  #   steps:\n  #     - run: systeminfo\n  #     - run: git config --global core.autocrlf false\n  #     - run: git config --global core.eol lf\n  #     - uses: actions/checkout@v1\n  #     - uses: actions/setup-node@v1\n  #       with:\n  #         version: 12\n  #     - name: Cache node modules\n  #       uses: actions/cache@v1\n  #       env:\n  #         cache-name: cache-node-modules\n  #       with:\n  #         path: node_modules\n  #         key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock') }}\n  #     - run: yarn install --frozen-lockfile --ignore-scripts\n  #     - run: yarn build:app\n  #     - run: SET no_proxy=localhost,127.0.0.1\n  #     - run: SET NO_PROXY=localhost,127.0.0.1\n  #     - run: yarn test:spec\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\ndist\nexcalidraw.asar\nnode_modules\nlogs\n*.log"
  },
  {
    "path": ".prettierignore",
    "content": "dist"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n  \"bracketSpacing\": false,\n  \"trailingComma\": \"all\"\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Excalidraw\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "\n\n# Excalidraw Desktop (deprecated)\n\n> Read more on why we deprecated this repo: [Deprecating Excalidraw Electron in favor of the Web version](https://blog.excalidraw.com/deprecating-excalidraw-electron/)\n\n[![Excalidraw Desktop](https://pbs.twimg.com/media/EPafpoLWoAAcFhc?format=jpg&name=large)](https://excalidraw.com/?id=5698913638023168)\n\n## Develop\n\n### Install dependencies\n\n```\nyarn\n```\n\n### Commands\n\n| Command         | Description                                |\n| --------------- | ------------------------------------------ |\n| `yarn start`    | Start development application              |\n| `yarn fix`      | Reformat all files with Prettier           |\n| `yarn test`     | Run linting and tests                      |\n| `yarn download` | Fetch latest excalidraw bundle             |\n| `yarn build`    | Build artifacts for windows, mac and linux |\n\n## Contributing\n\nPull requests are welcome. For major changes, please [open an issue](https://github.com/excalidraw/excalidraw-desktop/issues/new) first to discuss what you would like to change.\n"
  },
  {
    "path": "electron-builder.json",
    "content": "{\n  \"appId\": \"com.excalidraw.Excalidraw\",\n  \"productName\": \"Excalidraw\",\n  \"directories\": {\n    \"output\": \"dist\"\n  },\n  \"files\": [\n    \"package.json\",\n    \"dist/main.js\",\n    \"dist/renderer.js\",\n    \"dist/preload.js\",\n    \"dist/pages/*.html\",\n    \"dist/client\",\n    \"build/icon.*\"\n  ],\n  \"win\": {\n    \"target\": [\n      {\n        \"target\": \"zip\",\n        \"arch\": [\"x64\", \"ia32\"]\n      }\n    ]\n  },\n  \"linux\": {\n    \"category\": \"Development\",\n    \"target\": [\n      {\n        \"target\": \"tar.gz\",\n        \"arch\": [\"x64\", \"ia32\"]\n      }\n    ]\n  },\n  \"mac\": {\n    \"hardenedRuntime\": true,\n    \"target\": \"dmg\"\n  },\n  \"fileAssociations\": [\n    {\n      \"ext\": \"excalidraw\",\n      \"name\": \"Excalidraw\",\n      \"description\": \"Excalidraw file\",\n      \"role\": \"Editor\",\n      \"mimeType\": \"application/json\"\n    }\n  ]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"author\": \"Excalidraw Team\",\n  \"description\": \"Excalidraw Desktop\",\n  \"devDependencies\": {\n    \"@types/minimist\": \"1.2.0\",\n    \"@types/node\": \"14\",\n    \"@types/node-fetch\": \"^2.5.7\",\n    \"@typescript-eslint/eslint-plugin\": \"4.0.0\",\n    \"@typescript-eslint/parser\": \"3.10.1\",\n    \"asar\": \"3.0.3\",\n    \"electron\": \"8.5.2\",\n    \"electron-builder\": \"22.8.0\",\n    \"eslint\": \"7.13.0\",\n    \"eslint-config-prettier\": \"6.11.0\",\n    \"eslint-plugin-prettier\": \"3.1.4\",\n    \"execa\": \"4.0.3\",\n    \"husky\": \"4.3.0\",\n    \"lint-staged\": \"10.5.1\",\n    \"minimist\": \"1.2.5\",\n    \"mocha\": \"8.1.3\",\n    \"mri\": \"1.1.6\",\n    \"node-fetch\": \"2.6.1\",\n    \"prettier\": \"2.1.2\",\n    \"spectron\": \"10.0.1\",\n    \"ts-loader\": \"8.0.3\",\n    \"typescript\": \"4.0.5\",\n    \"webpack\": \"4.44.2\",\n    \"webpack-cli\": \"3.3.12\"\n  },\n  \"husky\": {\n    \"hooks\": {\n      \"pre-commit\": \"lint-staged\"\n    }\n  },\n  \"lint-staged\": {\n    \"*.{js,ts,tsx}\": [\n      \"eslint --fix\"\n    ],\n    \"*.{json,md,yml}\": [\n      \"prettier --write\"\n    ]\n  },\n  \"main\": \"dist/main.js\",\n  \"name\": \"excalidraw-desktop\",\n  \"scripts\": {\n    \"build\": \"yarn download && yarn build:app && yarn build:dist\",\n    \"build:app\": \"webpack\",\n    \"build:dist\": \"node ./tasks/dist.js\",\n    \"download\": \"node ./tasks/download.js\",\n    \"fix\": \"yarn fix:other && yarn fix:code\",\n    \"fix:code\": \"yarn lint:code --fix\",\n    \"fix:other\": \"yarn prettier --write\",\n    \"postinstall\": \"yarn download\",\n    \"preinstall\": \"npx mkdirp dist\",\n    \"prettier\": \"prettier \\\"**/*.{json,md,yml}\\\"\",\n    \"start\": \"yarn build:app && electron ./dist/main.js --devtools\",\n    \"test\": \"yarn lint && yarn test:spec\",\n    \"test:spec\": \"mocha --exit\",\n    \"lint\": \"yarn lint:code && yarn lint:other\",\n    \"lint:code\": \"eslint --ignore-path .gitignore --ext .js,.ts,.tsx .\",\n    \"lint:other\": \"yarn prettier --ignore-path .gitignore --list-different\",\n    \"watch\": \"webpack -w\"\n  },\n  \"version\": \"0.0.1\",\n  \"dependencies\": {\n    \"electron-store\": \"^5.2.0\",\n    \"html-webpack-plugin\": \"^4.3.0\"\n  }\n}\n"
  },
  {
    "path": "src/constants.ts",
    "content": "export const APP_NAME = \"Excalidraw\";\nexport const EXCALIDRAW_API = \"https://excalidraw.com/version.json\";\nexport const EXCALIDRAW_ASAR_SOURCE = \"https://excalidraw.com/excalidraw.asar\";\nexport const EXCALIDRAW_GITHUB_PACKAGE_JSON_URL =\n  \"https://raw.githubusercontent.com/excalidraw/excalidraw-desktop/master/package.json\";\n"
  },
  {
    "path": "src/main.ts",
    "content": "import {app, BrowserWindow, shell, globalShortcut} from \"electron\";\nimport * as minimist from \"minimist\";\nimport * as path from \"path\";\nimport * as url from \"url\";\nimport {setupMenu} from \"./menu\";\nimport checkVersion from \"./util/checkVersion\";\nimport {setMetadata, setAppName} from \"./util/metadata\";\nimport {APP_NAME} from \"./constants\";\n\nlet mainWindow: Electron.BrowserWindow;\nconst argv = minimist(process.argv.slice(1));\nconst EXCALIDRAW_BUNDLE = path.join(__dirname, \"client\", \"index.html\");\nconst APP_ICON_PATH = path.join(__dirname, \"client\", \"logo-180x180.png\");\n\nfunction createWindow() {\n  mainWindow = new BrowserWindow({\n    show: false,\n    height: 600,\n    width: 800,\n    webPreferences: {\n      contextIsolation: true, // protect against prototype pollution\n      preload: `${__dirname}/preload.js`,\n    },\n  });\n\n  if (argv.devtools) {\n    mainWindow.webContents.openDevTools({mode: \"detach\"});\n  }\n\n  mainWindow.webContents.on(\"will-navigate\", openExternalURLs);\n  mainWindow.webContents.on(\"new-window\", openExternalURLs);\n\n  mainWindow.loadURL(\n    url.format({\n      pathname: EXCALIDRAW_BUNDLE,\n      protocol: \"file\",\n      slashes: true,\n    }),\n  );\n\n  mainWindow.on(\"closed\", () => {\n    mainWindow = null;\n  });\n\n  // Enable Cmd+Q on mac to quit the application\n  if (process.platform === \"darwin\") {\n    globalShortcut.register(\"Command+Q\", () => {\n      app.quit();\n    });\n  }\n\n  // calling.show after this event, ensure there's no visual flash\n  mainWindow.once(\"ready-to-show\", async () => {\n    const versions = await checkVersion();\n\n    console.info(\"Current version\", versions.local);\n    console.info(\"Needs update\", versions.needsUpdate);\n\n    setAppName(APP_NAME);\n    setMetadata(\"versions\", versions);\n    setMetadata(\"appIconPath\", APP_ICON_PATH);\n    setupMenu(mainWindow);\n\n    mainWindow.show();\n  });\n}\n\n// Open external links in user's default browser instead of webview\nfunction openExternalURLs(event: Electron.Event, url: string) {\n  if (url.startsWith(\"http\")) {\n    event.preventDefault();\n    shell.openExternal(url);\n  }\n}\n\napp.on(\"ready\", createWindow);\n\napp.on(\"window-all-closed\", () => {\n  if (process.platform !== \"darwin\") {\n    app.quit();\n  }\n});\n\napp.on(\"activate\", () => {\n  if (mainWindow === null) {\n    createWindow();\n  }\n});\n"
  },
  {
    "path": "src/menu.ts",
    "content": "import {BrowserWindow, Menu, MenuItem} from \"electron\";\nimport * as path from \"path\";\nimport * as url from \"url\";\nimport {APP_NAME} from \"./constants\";\nimport {getAppVersions, getAppName, getMetadata} from \"./util/metadata\";\n\nconst ABOUT_PAGE_PATH = path.resolve(__dirname, \"pages\", \"about.html\");\n\nconst openAboutWindow = () => {\n  let aboutWindow = new BrowserWindow({\n    height: 320,\n    width: 320,\n    modal: true,\n    backgroundColor: \"white\",\n    show: false,\n    webPreferences: {\n      contextIsolation: true,\n      preload: `${__dirname}/preload.js`,\n    },\n  });\n\n  aboutWindow.loadURL(\n    url.format({\n      pathname: ABOUT_PAGE_PATH,\n      protocol: \"file\",\n      slashes: true,\n    }),\n  );\n\n  aboutWindow.setMenuBarVisibility(false);\n  aboutWindow.center();\n  aboutWindow.on(\"ready-to-show\", () => aboutWindow.show());\n  aboutWindow.on(\"show\", () => {\n    const aboutContent = {\n      appName: getAppName(),\n      iconPath: getMetadata(\"appIconPath\"),\n      versions: getAppVersions(),\n    };\n\n    aboutWindow.webContents.send(\"show-about-contents\", aboutContent);\n  });\n};\n\nexport const setupMenu = (activeWindow: BrowserWindow, options = {}) => {\n  const isDarwin = process.platform === \"darwin\";\n  const defaultMenuItems: MenuItem[] = Menu.getApplicationMenu().items;\n  const menuTemplate = [];\n  if (isDarwin) {\n    defaultMenuItems.shift();\n    menuTemplate.push({\n      label: APP_NAME,\n      submenu: [\n        {\n          label: `About ${APP_NAME}`,\n          enabled: true,\n          click: () => openAboutWindow(),\n        },\n      ],\n    });\n    menuTemplate.push(...defaultMenuItems);\n  } else {\n    defaultMenuItems.pop();\n    menuTemplate.push(...defaultMenuItems);\n    menuTemplate.push({\n      label: \"Help\",\n      submenu: [\n        {\n          label: `About ${APP_NAME}`,\n          enabled: true,\n          click: () => openAboutWindow(),\n        },\n      ],\n    });\n  }\n\n  // TODO: Remove default menu items that aren't relevant\n  const appMenu = Menu.buildFromTemplate(menuTemplate);\n  Menu.setApplicationMenu(appMenu);\n};\n"
  },
  {
    "path": "src/pages/about.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes\"\n    />\n    <meta\n      http-equiv=\"Content-Security-Policy\"\n      content=\"block-all-mixed-content; child-src 'none'; connect-src https: wss: filesystem:; default-src 'self'; font-src 'self' data: https: filesystem:; img-src 'self' data: https:; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https:;\"\n    />\n    <title>About This App</title>\n    <style>\n      body,\n      html {\n        width: 100%;\n        height: 100%;\n        -webkit-user-select: none;\n        user-select: none;\n        -webkit-app-region: drag;\n      }\n\n      body {\n        margin: 0;\n        display: flex;\n        flex-direction: column;\n        justify-content: center;\n        align-items: center;\n        color: #1d1d1d;\n        background-color: #fafafa;\n        font-size: 12px;\n        font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto,\n          Oxygen, Ubuntu, Cantarell, \"Open Sans\", \"Helvetica Neue\", sans-serif;\n      }\n\n      #app-icon {\n        -webkit-user-select: none;\n        user-select: none;\n        display: inline-block;\n        background: #ffffff;\n        border-radius: 50px;\n        box-shadow: 20px 20px 60px #d9d9d9, -20px -20px 60px #ffffff;\n      }\n\n      .versions {\n        margin: 0.3em;\n      }\n\n      .title {\n        margin-top: 0.6em;\n        margin-bottom: 0.6em;\n      }\n\n      .clickable {\n        cursor: pointer;\n      }\n\n      .description {\n        margin-bottom: 1em;\n        text-align: center;\n      }\n\n      .copyright {\n        color: #999;\n        margin-top: 1.5em;\n      }\n\n      .link {\n        cursor: pointer;\n        color: #80a0c2;\n      }\n\n    </style>\n  </head>\n  <body>\n    <img id=\"app-icon\" alt=\"App icon\" height=\"60\" width=\"60\" />\n    <h2 class=\"title\"></h2>\n    <p id=\"app-version\" class=\"versions\"></p>\n    <p id=\"web-version\" class=\"versions\"></p>\n    <p class=\"copyright\"></p>\n\n    <!-- https://github.com/electron/electron/issues/2863 -->\n    <script>\n      var exports = exports || {};\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "src/preload.ts",
    "content": "import {ipcRenderer, contextBridge, remote} from \"electron\";\nimport {IpcListener} from \"./types\";\n\ncontextBridge.exposeInMainWorld(\"ipcRenderer\", {\n  send: (channel: string, data: any[]) => {\n    ipcRenderer.send(channel, data);\n  },\n  receive: (channel: string, func: IpcListener) => {\n    ipcRenderer.on(channel, (event, ...args: any[]) => func(event, ...args));\n  },\n});\n\ncontextBridge.exposeInMainWorld(\"remote\", {\n  getVersion: remote.app.getVersion,\n});\n"
  },
  {
    "path": "src/renderer.ts",
    "content": "const rendererWindow: RendererWindow = window;\n\nrendererWindow.ipcRenderer.receive(\"show-about-contents\", (_, options: any) => {\n  const {appName, versions, iconPath} = options;\n  document.title = `About ${appName}`;\n\n  const $titleElement = document.querySelector(\".title\") as HTMLHeadingElement;\n  $titleElement.textContent = appName;\n\n  const $iconElement = document.getElementById(\"app-icon\") as HTMLImageElement;\n  $iconElement.src = iconPath;\n\n  const $appVersionElement = document.getElementById(\n    \"app-version\",\n  ) as HTMLParagraphElement;\n\n  $appVersionElement.textContent = `Version ${versions.app.remote}`;\n\n  const $webVersionElement = document.getElementById(\n    \"web-version\",\n  ) as HTMLParagraphElement;\n\n  $webVersionElement.textContent = `${appName} for Web Version ${versions.web.remote}`;\n\n  const $copyRightElement = document.querySelector(\n    \".copyright\",\n  ) as HTMLParagraphElement;\n\n  $copyRightElement.textContent = `Copyright (c) ${new Date().getFullYear()} ${appName}`;\n});\n"
  },
  {
    "path": "src/types.ts",
    "content": "import {IpcRendererEvent, IpcRenderer} from \"electron\";\n\nexport type IpcListener = (event: IpcRendererEvent, ...args: any[]) => void;\nexport type CustomIpcSender = (channel: string, ...args: any[]) => null;\n\nexport interface CustomIpcRenderer extends IpcRenderer {\n  send: (channel: string, ...args: any[]) => null;\n  receive: (channel: string, listener: IpcListener) => null;\n}\n\ndeclare global {\n  interface RendererWindow extends Window {\n    ipcRenderer?: CustomIpcRenderer;\n    remote?: {\n      getVersion: () => string;\n    };\n  }\n}\n"
  },
  {
    "path": "src/util/checkVersion.ts",
    "content": "import * as fs from \"fs\";\nimport * as path from \"path\";\nimport fetch from \"node-fetch\";\nimport {EXCALIDRAW_API, EXCALIDRAW_GITHUB_PACKAGE_JSON_URL} from \"../constants\";\n\nconst LOCAL_VERSION_PATH = path.resolve(__dirname, \"client\", \"version.json\");\n\ninterface CheckResponse {\n  local: string;\n  remote: string;\n  appVersion: string;\n  needsUpdate: boolean;\n}\n\nconst _getLocalVersion = (): string => {\n  const raw = fs.readFileSync(LOCAL_VERSION_PATH).toString();\n  const contents = JSON.parse(raw);\n  return contents.version;\n};\n\nconst _getRemoteVersion = async (): Promise<string> => {\n  const raw = await fetch(EXCALIDRAW_API);\n  const contents = await raw.json();\n  return contents.version;\n};\n\nconst _getRemoteDesktopAppVersion = async (): Promise<string> => {\n  const raw = await fetch(EXCALIDRAW_GITHUB_PACKAGE_JSON_URL);\n  const contents = await raw.json();\n  return contents.version;\n};\n\nexport default async function checkVersion(): Promise<CheckResponse> {\n  const localVersion = _getLocalVersion();\n  const remoteVersion = await _getRemoteVersion();\n  const appVersion = await _getRemoteDesktopAppVersion();\n\n  return {\n    local: localVersion,\n    remote: remoteVersion,\n    appVersion,\n    needsUpdate: localVersion < remoteVersion,\n  };\n}\n"
  },
  {
    "path": "src/util/metadata.ts",
    "content": "import * as Store from \"electron-store\";\n\nlet metadataStore: Store;\n\nif (!metadataStore) {\n  metadataStore = new Store({});\n}\n\nexport const getMetadata = (key: string): Store => {\n  return metadataStore.get(`metadata.${key}`);\n};\n\nexport const setMetadata = (key: string, value: any) => {\n  return metadataStore.set(`metadata.${key}`, value);\n};\n\nexport const setAppName = (value: string) => {\n  return metadataStore.set(`metadata.appName`, value);\n};\n\nexport const getAppName = () => {\n  return metadataStore.get(`metadata.appName`);\n};\n\nexport const getAppVersions = () => {\n  const versions = metadataStore.get(`metadata.versions`);\n  const {\n    local: localVersion,\n    remote: remoteVersion,\n    needsUpdate,\n    appVersion,\n  } = versions;\n\n  return {\n    needsUpdate,\n    app: {\n      local: \"\",\n      remote: appVersion,\n    },\n    web: {\n      local: localVersion,\n      remote: remoteVersion,\n    },\n  };\n};\n"
  },
  {
    "path": "tasks/dist.js",
    "content": "#!/usr/bin/env node\nconst argv = require(\"mri\")(process.argv);\n\nconst exec = require(\"execa\").sync;\n\nconst pkg = require(\"../package\");\n\nconst {publish, config} = argv;\n\nconst artifactOptions = [\n  \"-c.artifactName=${name}-${version}-${os}-${arch}.${ext}\",\n  \"-c.dmg.artifactName=${name}-${version}-${os}.${ext}\",\n  \"-c.nsis.artifactName=${name}-${version}-${os}-setup.${ext}\",\n  \"-c.nsisWeb.artifactName=${name}-${version}-${os}-web-setup.${ext}\",\n  argv.compress === false && \"-c.compression=store\",\n].filter((f) => f);\n\n// interpret shorthand target options\n// --win, --linux, --mac\nconst platforms = [\n  argv.win ? \"win\" : null,\n  argv.linux ? \"linux\" : null,\n  argv.mac ? \"mac\" : null,\n].filter((f) => f);\n\nconst platformOptions = platforms.map((p) => `--${p}`);\n\nconst publishOptions =\n  typeof publish !== undefined\n    ? [`--publish=${publish ? \"always\" : \"never\"}`].filter((f) => f)\n    : [];\n\nconst signingOptions = [`-c.forceCodeSigning=${false}`];\n\nif (publish && (argv.ia32 || argv.x64)) {\n  console.error(\"Do not override arch; is manually pinned\");\n  process.exit(1);\n}\n\nconst archOptions = [\"x64\", \"ia32\"].filter((a) => argv[a]).map((a) => `--${a}`);\n\nconst args = [\n  ...[config && `-c=${config}`].filter((f) => f),\n  ...archOptions,\n  ...signingOptions,\n  ...platformOptions,\n  ...publishOptions,\n  ...artifactOptions,\n];\n\nconsole.log(`\nBuilding ${pkg.name} distro\n\n---\n\n  version: ${pkg.version}\n  platforms: [${(platforms.length && platforms) || \"current\"}]\n  publish: ${publish || false}\n\n---\n\nelectron-builder ${args.join(\" \")}\n`);\n\nexec(\"electron-builder\", args, {\n  stdio: \"inherit\",\n});\n"
  },
  {
    "path": "tasks/download.js",
    "content": "#!/usr/bin/env node\n\nconst https = require(\"https\");\nconst fs = require(\"fs\");\nconst asar = require(\"asar\");\n\nconst DEST = \"dist/excalidraw.asar\";\nconst SOURCE = \"https://excalidraw.com/excalidraw.asar\";\nconst UNPACK = \"dist/client\";\n\nconst file = fs.createWriteStream(DEST);\nconst request = https\n  .get(SOURCE, (response) => {\n    response.pipe(file);\n    file.on(\"finish\", async () => {\n      console.info(`${DEST} is downloaded`);\n\n      // unpack\n      await asar.extractAll(DEST, UNPACK);\n\n      file.close();\n    });\n  })\n  .on(\"error\", (error) => {\n    fs.unlink(DEST, () => {});\n    console.error(error);\n  });\n"
  },
  {
    "path": "test/main.spec.js",
    "content": "const Application = require(\"spectron\").Application;\nconst assert = require(\"assert\");\nconst path = require(\"path\");\n\nconst rootDir = path.resolve(__dirname, \"..\");\nconst isWindows = process.platform === \"win32\";\nconst electronBinary = isWindows ? \"electron.cmd\" : \"electron\";\nconst electronPath = path.join(rootDir, \"node_modules\", \".bin\", electronBinary);\n\ndescribe(\"Application launch\", function () {\n  this.timeout(20000);\n\n  beforeEach(function () {\n    this.app = new Application({\n      path: electronPath,\n      args: [rootDir],\n      startTimeout: 10000,\n      waitTimeout: 10e3,\n      chromeDriverLogPath: \"./chromedriver.log\",\n    });\n\n    return this.app.start();\n  });\n\n  afterEach(function () {\n    if (this.app && this.app.isRunning()) {\n      return this.app.stop();\n    }\n  });\n\n  it(\"shows an initial window\", function () {\n    return this.app.client.getWindowCount().then(function (count) {\n      assert.equal(count, 1);\n    });\n  });\n});\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"noImplicitAny\": true,\n    \"sourceMap\": true,\n    \"outDir\": \"dist\",\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"*\": [\"node_modules/*\"]\n    }\n  },\n  \"include\": [\"src/**/*\", \"tasks\", \"test/**/*\", \"webpack.config.js\"]\n}\n"
  },
  {
    "path": "webpack.config.js",
    "content": "const path = require(\"path\");\nconst HtmlWebpackPlugin = require(\"html-webpack-plugin\");\n\nconst commonConfiguration = {\n  mode: \"development\",\n  devtool: \"sourcemap\",\n  module: {\n    rules: [\n      {\n        test: /\\.ts$/,\n        include: /src/,\n        use: [{loader: \"ts-loader\"}],\n      },\n    ],\n  },\n  resolve: {\n    extensions: [\".ts\", \".js\", \".json\"],\n  },\n};\n\nmodule.exports = [\n  {\n    ...commonConfiguration,\n    target: \"electron-main\",\n    entry: \"./src/main.ts\",\n    output: {\n      path: path.resolve(__dirname, \"dist\"),\n      filename: \"main.js\",\n    },\n    node: {\n      __dirname: false,\n    },\n  },\n  {\n    ...commonConfiguration,\n    target: \"electron-renderer\",\n    entry: \"./src/preload.ts\",\n    node: {__dirname: false, global: true},\n    output: {\n      path: path.resolve(__dirname, \"dist\"),\n      filename: \"preload.js\",\n    },\n  },\n  {\n    ...commonConfiguration,\n    target: \"electron-renderer\",\n    entry: \"./src/renderer.ts\",\n    output: {\n      path: path.resolve(__dirname, \"dist\"),\n      filename: \"renderer.js\",\n    },\n\n    plugins: [\n      new HtmlWebpackPlugin({\n        filename: \"pages/about.html\",\n        template: \"./src/pages/about.html\",\n      }),\n    ],\n  },\n];\n"
  }
]