[
  {
    "path": ".gitignore",
    "content": ".DS_Store\nnode_modules/\ndist/\nnpm-debug.log\nyarn-error.log\n\n# Editor directories and files\n.vscode\n.idea\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Ruoyun Wu\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": "# Electron-Vue-Spring\n\n> An opionated desktop application with web front-end and Java backend.\n\nIn some cases, you may like to use Java backend for an Electron desktop app. The reasons could be you have some legacy Java codes that you want to reuse, or you want to have the same codes run on Cloud as well as on desktop.\n\nThis project has two sub projects:\n\n1. `vue`: a Vue 3 app in TypeScript as the front-end, based on the scaffold project created using Vite. You may also replace this project with a React or Angular project with similar design.\n2. `spring`: a Spring Boot application as the backend, based on a Maven project created by [Spring Initializer](https://start.spring.io/) with Web dependency.\n\nBoth Windows and Mac OS are supported.\n\n## Prerequisites\n\n- JDK 11, such as [Amazon Corretto 11](https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html)\n- Node 14.x\n- Maven 3.x\n\n> NOTE: This project uses your system Java to run the spring web app. If you prefer to bundle JRE into the app, configure the `extraFiles` of Electron Builder to copy it when making the installer.\n\n## Build Installer\n\nBuild the final installer, which can be found in folder `dist`. It is an `exe` file for Windows and `dmg` file for Mac.\n\n```bash\n# install dependencies\nnpm install\n\n# install dependencies for vue project\ncd vue\nnpm install\ncd ..\n\n# build installer for production\nnpm run build\n```\n\n## Development Setup\n\nDuring development, you may work on front-end and backend separately with independent tools, such as using Visual Studio Code for front-end and IntelliJ for backend. Note that the front-end `vue` project has its own `package.json` so it can be built independently.\n\n- To run backend, import the Maven project into your favorite Java IDE and launch from there. The embedded Tomcat server will be running on port `8080`.\n- To run front-end, run `npm run dev` in `vue` folder. Vite will run a server on port `9000` with hot reload. It is configured to proxy `actuator/health` and `api` URL to port `8080`.\n- To run the Electron part, run `npm run start` in root folder. The Electron app loads the home page at `http://localhost:9000`, therefore you should run both backend and front-end first.\n\n## How it works\n\nThe main idea is to use Electron as a browser, and the front-end and backend of the app work as a web app. It might not be a common design, but is helpful in some cases.\n\nThe backend is a typical Spring Boot app, serving API to the front-end. The front-end is a typical Vue app, consuming API from the backend.\n\n### Build process\n\nWhen building the final desktop app installer:\n\n1. Front-end is built first. The final artifacts, including `index.html` and JavaScript files, are copied into `spring/src/main/resources/public` folder.\n2. Backend is built second. It creates a web app with the front-end artifacts created above and an executable jar.\n3. Electron installer is built last. It includes the web app created above in the bundle and creates an executable installer.\n\nHowever, both `vue` sub project and `spring` sub project are free of Electron and can be built independently without building the Electron part. They can be deployed online, instead of packaged into Electron app, allowing you to use the same code base for online and desktop deployment.\n\n### Launch process\n\nWhen launching the Electron app:\n\n1. Electron app detects an available port and starts the backend server with Node `child_process` at the specified port. The PID of the server process is kept to potentially kill the process before quiting the app.\n2. Electron app then displays a splash window, at the same time pings the `actuator/health` URL of the backend server periodically.\n3. Once the `actuator/health` ping returns OK (the web app is up), Electron app closes the splash window and open a new window to load the home page of the web app.\n\n> The Electron app starts the backend server only in production build. During development, you will need to manually start the front-end dev server as mentioned earlier.\n\n### Shutdown process\n\nWhen shutting down the Electron app:\n\n1. Electron app handles the `will-quit` event by trying to stop the backend server and cancel the quit.\n2. The first attempt is to shutdown gracefully via the `actuator/shutdown` URL of the backend server.\n3. If that fails, the Electron app will attempt to kill the process by its PID.\n4. Either of the shutsown attempts above will clear up the `baseUrl` and call `app.quit()` again.\n5. With `baseUrl` being cleared, `will-quit` handler will not prevent the quitting this time.\n\n### Node access\n\nAlthough the Java backend is running locally, it is more secure to load the page with Node integration disabled (defualt behavior). This prevents third-party JavaScript libraries used by your web app from accessing Node directly, and mitigates the risk if your app navigates to external website.\n\nThe access to Node can be selectively re-introduced back to the web app via [preload.js](electron/preload.js), which defines a set of API on a global `window.interop` object. This object is provided by the Vue app instance in [main.ts](vue/src/main.ts) for injection into Vue components via key `KEY_INTEROP`. This allows any UI component to call the API.\n\n### Log Aggregation\n\nThe log messages from Electron, Vue and Spring apps are aggregated into the [electron logger](https://www.npmjs.com/package/electron-log) in Electron app. By default it writes logs to the following locations:\n\n- on Linux: `~/.config/{app name}/logs/{process type}.log`\n- on macOS: `~/Library/Logs/{app name}/{process type}.log`\n- on Windows: `%USERPROFILE%\\AppData\\Roaming\\{app name}\\logs\\{process type}.log`\n\nIn the Vue app, the electron logger is wrapped by the `log` property of `window.interop` object. This `log` object is provided by Vue app instance in [main.ts](vue/src/main.ts) for injection into Vue components via key `KEY_LOG`. Calling `$log.info(...)` will send the log messages (after attaching a prefix to identify it is from UI) to electron logger. Other logging level works in the same way.\n\nIn the Spring app, `logback-spring.xml` configuration sends the log to console, which is the standard output received by the Electron app. The logback message pattern put the log level (`INFO`, `DEBUG`, etc.) at the begining of the message so that Electron app checks and calls the corresponding function (`info`, `debug`, etc.) on the electron logger.\n\n## License\n\n[MIT](LICENSE)\n"
  },
  {
    "path": "build/build-installer.js",
    "content": "const shell = require('shelljs')\n\nshell.echo('##########################')\nshell.echo('#    Building electron   #')\nshell.echo('##########################')\n\nif (!shell.test('-e', 'spring/target')) {\n  shell.echo('Error: server is not built yet.')\n  shell.exit(1)\n}\n\nshell.rm('-rf', 'dist')\nif (shell.exec('electron-builder build').code !== 0) {\n  shell.echo('Error: electron build failed')\n  shell.exit(1)\n}\n"
  },
  {
    "path": "build/build-server.js",
    "content": "const shell = require('shelljs')\n\nshell.echo('##########################')\nshell.echo('#     Building vue       #')\nshell.echo('##########################')\n\nshell.cd('vue')\nconst PUBLIC = '../spring/src/main/resources/public/'\nshell.rm('-rf', PUBLIC);\nif (shell.exec('npm run build').code !== 0) {\n  shell.echo('Error: vue build failed')\n  shell.exit(1)\n}\nshell.cp('-R', 'dist/', PUBLIC)\nshell.cd('..')\n\nshell.echo('##########################')\nshell.echo('#     Building spring    #')\nshell.echo('##########################')\n\nshell.cd('spring')\nconst mvnw = process.platform === 'win32' ? 'mvnw' : './mvnw'\nif (shell.exec(mvnw + ' clean package').code !== 0) {\n  shell.echo('Error: spring build failed')\n  shell.exit(1)\n}\n"
  },
  {
    "path": "electron/index.js",
    "content": "const { app, ipcMain, BrowserWindow, dialog } = require('electron')\nconst path = require('path')\nconst url = require('url')\nvar findPort = require('find-free-port')\nconst isDev = require('electron-is-dev')\nconst logger = require('./logger')\nconst axios = require('axios')\n\nconst JAR = 'spring-1.0.0.jar' // how to avoid manual update of this?\nconst MAX_CHECK_COUNT = 10\n\n// Keep a global reference of the window object, if you don't, the window will\n// be closed automatically when the JavaScript object is garbage collected.\nlet mainWindow\n\n// The server url and process\nlet serverProcess\nlet baseUrl\n\nfunction startServer(port) {\n  logger.info(`Starting server at port ${port}`)\n\n  const server = `${path.join(app.getAppPath(), '..', '..', JAR)}`\n  logger.info(`Launching server with jar ${server} at port ${port}...`)\n\n  serverProcess = require('child_process').spawn('java', [\n    '-jar',\n    server,\n    `--server.port=${port}`,\n  ])\n\n  serverProcess.stdout.on('data', logger.server)\n\n  if (serverProcess.pid) {\n    baseUrl = `http://localhost:${port}`\n    logger.info('Server PID: ' + serverProcess.pid)\n  } else {\n    logger.error('Failed to launch server process.')\n  }\n}\n\nfunction stopServer() {\n  logger.info('Stopping server...')\n  axios\n    .post(`${baseUrl}/actuator/shutdown`, null, {\n      headers: { 'Content-Type': 'application/json' },\n    })\n    .then(() => logger.info('Server stopped'))\n    .catch((error) => {\n      logger.error('Failed to stop the server gracefully.', error)\n      if (serverProcess) {\n        logger.info(`Killing server process ${serverProcess.pid}`)\n        const kill = require('tree-kill')\n        kill(serverProcess.pid, 'SIGTERM', function (err) {\n          logger.info('Server process killed')\n          serverProcess = null\n          baseUrl = null\n          app.quit() // quit again\n        })\n      }\n    })\n    .finally(() => {\n      serverProcess = null\n      baseUrl = null\n      app.quit() // quit again\n    })\n}\n\nfunction createSplash() {\n  const splash = new BrowserWindow({ width: 400, height: 300, frame: false })\n  splash.loadURL(\n    url.format({\n      pathname: path.join(__dirname, 'splash.html'),\n      protocol: 'file:',\n      slashes: true,\n    })\n  )\n  return splash\n}\n\nfunction createWindow(callback) {\n  mainWindow = new BrowserWindow({\n    width: 800,\n    height: 600,\n    show: false, // hide until ready-to-show\n    webPreferences: {\n      preload: path.join(__dirname, 'preload.js'),\n    },\n  })\n\n  loadHomePage()\n\n  // Open the DevTools.\n  // mainWindow.webContents.openDevTools()\n\n  mainWindow.once('ready-to-show', () => {\n    mainWindow.show()\n    if (callback) callback()\n  })\n\n  // Emitted when the window is closed.\n  mainWindow.on('closed', function () {\n    // Dereference the window object, usually you would store windows\n    // in an array if your app supports multi windows, this is the time\n    // when you should delete the corresponding element.\n    mainWindow = null\n  })\n}\n\nfunction quitOnError(title, content) {\n  logger.error(content)\n  dialog.showErrorBox(title, content)\n  app.quit()\n}\n\nfunction loadHomePage() {\n  logger.info(`Loading home page at ${baseUrl}`)\n  // check server health and switch to main page\n  checkCount = 0\n  setTimeout(function cycle() {\n    axios\n      .get(`${baseUrl}/actuator/health`)\n      .then(() => mainWindow.loadURL(`${baseUrl}?_=${Date.now()}`))\n      .catch((e) => {\n        if (e.code === 'ECONNREFUSED') {\n          if (checkCount < MAX_CHECK_COUNT) {\n            checkCount++\n            setTimeout(cycle, 1000)\n          } else {\n            quitOnError(\n              'Server timeout',\n              `UI does not receive server response for ${MAX_CHECK_COUNT} seconds.`\n            )\n            app.quit()\n          }\n        } else {\n          logger.error(e)\n          quitOnError('Server error', 'UI receives an error from server.')\n        }\n      })\n  }, 200)\n}\n\n// This method will be called when Electron has finished\n// initialization and is ready to create browser windows.\n// Some APIs can only be used after this event occurs.\napp.whenReady().then(() => {\n  logger.info('###################################################')\n  logger.info('#               Application Starting              #')\n  logger.info('###################################################')\n\n  // handle messages from ipcRenderer via preload.js\n  ipcMain.on('app:badgeCount', (_, count) => app.setBadgeCount(count))\n  ipcMain.handle('dialog:openFile', () => dialog.showOpenDialogSync())\n  ipcMain.handle('dialog:saveFile', () => dialog.showSaveDialogSync())\n\n  if (isDev) {\n    // Assume the webpack dev server is up at port 9000\n    baseUrl = `http://localhost:9000`\n    createWindow()\n  } else {\n    // Create window first to show splash before starting server\n    const splash = createSplash()\n\n    // Start server at an available port (prefer 8080)\n    findPort(8080, function (err, port) {\n      if (!err) {\n        startServer(port)\n        createWindow(() => splash.close())\n      } else {\n        quitOnError('Error', 'Unable to get a server port.')\n      }\n    })\n  }\n})\n\n// Quit when all windows are closed.\napp.on('window-all-closed', function () {\n  // On OS X it is common for applications and their menu bar\n  // to stay active until the user quits explicitly with Cmd + Q\n  if (process.platform !== 'darwin') {\n    app.quit()\n  }\n})\n\napp.on('activate', function () {\n  // On OS X it's common to re-create a window in the app when the\n  // dock icon is clicked and there are no other windows open.\n  if (mainWindow === null) {\n    createWindow()\n  }\n})\n\napp.on('will-quit', (e) => {\n  if (!isDev && baseUrl != null) {\n    stopServer()\n    e.preventDefault() // will quite later after stopped the server\n  }\n})\n// In this file you can include the rest of your app's specific main process\n// code. You can also put them in separate files and require them here.\n"
  },
  {
    "path": "electron/logger.js",
    "content": "const logger = require('electron-log')\n\nlogger.server = function (data) {\n  // data is from server std.out and may includes multiple lines\n  const messages = data.toString().split('\\n')\n  messages.forEach((msg) => {\n    if (msg.length > 0) {\n      if (msg.startsWith('INFO')) logger.info(msg.substring(6))\n      else if (msg.startsWith('WARN')) logger.warn(msg.substring(6))\n      else if (msg.startsWith('ERROR')) logger.error(msg.substring(6))\n      else if (msg.startsWith('DEBUG')) logger.debug(msg.substring(6))\n      else logger.silly(msg)\n    }\n  })\n}\n\nmodule.exports = logger\n"
  },
  {
    "path": "electron/preload.js",
    "content": "const { app, contextBridge, ipcRenderer } = require('electron')\nconst logger = require('./logger')\n\nconst LOG_PREFIX = '[ui]'\n\ncontextBridge.exposeInMainWorld('interop', {\n  log: {\n    info(msg) {\n      logger.info(`${LOG_PREFIX} ${msg}`)\n    },\n    debug(msg) {\n      logger.debug(`${LOG_PREFIX} ${msg}`)\n    },\n    warn(msg) {\n      logger.warn(`${LOG_PREFIX} ${msg}`)\n    },\n    error(msg) {\n      logger.error(`${LOG_PREFIX} ${msg}`)\n    },\n    log(msg) {\n      logger.silly(`${LOG_PREFIX} ${msg}`)\n    },\n  },\n  setBadgeCount(count) {\n    return ipcRenderer.send('app:badgeCount', count)\n  },\n  showOpenDialog() {\n    return ipcRenderer.invoke('dialog:openFile')\n  },\n  showSaveDialog() {\n    return ipcRenderer.invoke('dialog:saveFile')\n  },\n})\n"
  },
  {
    "path": "electron/splash.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\">\n    <title>Electron-Vue-Spring</title>\n  </head>\n  <style>\n    body {\n      background-color:cornflowerblue;\n      padding: 20px;\n      color: white;\n    }\n  </style>\n  <body>\n    <h1>Splash Screen</h1>\n    <p>Starting server, please wait...</p>\n  </body>\n</html>"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"electron-vue-spring\",\n  \"version\": \"1.0.0\",\n  \"description\": \"A minimal starter project for using Electron, Vue and Spring.\",\n  \"author\": \"Wu, Ruoyun\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/wuruoyun/electron-vue-spring.git\"\n  },\n  \"main\": \"electron/index.js\",\n  \"scripts\": {\n    \"start\": \"electron ./electron\",\n    \"build-server\": \"node build/build-server.js\",\n    \"build-installer\": \"node build/build-installer.js\",\n    \"build\": \"npm-run-all clean build-server build-installer\",\n    \"clean\": \"rimraf dist\"\n  },\n  \"license\": \"MIT\",\n  \"build\": {\n    \"appId\": \"electron-vue-spring\",\n    \"files\": [\n      \"electron/**/*\",\n      \"node_modules/**/*\",\n      \"!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}\",\n      \"!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples}\",\n      \"!**/node_modules/*.d.ts\",\n      \"!**/node_modules/.bin\",\n      \"!**/*.{iml,o,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,xproj}\",\n      \"!.editorconfig\",\n      \"!**/._*\",\n      \"!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,.gitignore,.gitattributes}\",\n      \"!**/{__pycache__,thumbs.db,.flowconfig,.idea,.vs,.nyc_output}\",\n      \"!**/{appveyor.yml,.travis.yml,circle.yml}\",\n      \"!**/{npm-debug.log,yarn.lock,.yarn-integrity,.yarn-metadata.json}\"\n    ],\n    \"extraFiles\": [\n      {\n        \"from\": \"spring/target\",\n        \"filter\": [\n          \"*.jar\"\n        ]\n      }\n    ]\n  },\n  \"devDependencies\": {\n    \"electron\": \"^18.0.3\",\n    \"electron-builder\": \"^22.14.13\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"rimraf\": \"^3.0.2\",\n    \"shelljs\": \"^0.8.5\"\n  },\n  \"dependencies\": {\n    \"axios\": \"^0.26.1\",\n    \"electron-is-dev\": \"^2.0.0\",\n    \"electron-log\": \"^4.4.6\",\n    \"find-free-port\": \"^2.0.0\",\n    \"tree-kill\": \"^1.2.2\"\n  }\n}\n"
  },
  {
    "path": "spring/.gitignore",
    "content": "target/\n!.mvn/wrapper/maven-wrapper.jar\n\n### public folder is from external SPA ###\nsrc/main/resources/public\n\n### STS ###\n.apt_generated\n.classpath\n.factorypath\n.project\n.settings\n.springBeans\n\n### IntelliJ IDEA ###\n.idea\n*.iws\n*.iml\n*.ipr\n\n### NetBeans ###\nnbproject/private/\nbuild/\nnbbuild/\ndist/\nnbdist/\n.nb-gradle/\n\n### Eclipse ###\n.settings\n.classpath\n.project"
  },
  {
    "path": "spring/.mvn/wrapper/maven-wrapper.properties",
    "content": "distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip\n"
  },
  {
    "path": "spring/mvnw",
    "content": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n# ----------------------------------------------------------------------------\n\n# ----------------------------------------------------------------------------\n# Maven2 Start Up Batch script\n#\n# Required ENV vars:\n# ------------------\n#   JAVA_HOME - location of a JDK home dir\n#\n# Optional ENV vars\n# -----------------\n#   M2_HOME - location of maven2's installed home dir\n#   MAVEN_OPTS - parameters passed to the Java VM when running Maven\n#     e.g. to debug Maven itself, use\n#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n# ----------------------------------------------------------------------------\n\nif [ -z \"$MAVEN_SKIP_RC\" ] ; then\n\n  if [ -f /etc/mavenrc ] ; then\n    . /etc/mavenrc\n  fi\n\n  if [ -f \"$HOME/.mavenrc\" ] ; then\n    . \"$HOME/.mavenrc\"\n  fi\n\nfi\n\n# OS specific support.  $var _must_ be set to either true or false.\ncygwin=false;\ndarwin=false;\nmingw=false\ncase \"`uname`\" in\n  CYGWIN*) cygwin=true ;;\n  MINGW*) mingw=true;;\n  Darwin*) darwin=true\n    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home\n    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html\n    if [ -z \"$JAVA_HOME\" ]; then\n      if [ -x \"/usr/libexec/java_home\" ]; then\n        export JAVA_HOME=\"`/usr/libexec/java_home`\"\n      else\n        export JAVA_HOME=\"/Library/Java/Home\"\n      fi\n    fi\n    ;;\nesac\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  if [ -r /etc/gentoo-release ] ; then\n    JAVA_HOME=`java-config --jre-home`\n  fi\nfi\n\nif [ -z \"$M2_HOME\" ] ; then\n  ## resolve links - $0 may be a link to maven's home\n  PRG=\"$0\"\n\n  # need this for relative symlinks\n  while [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n      PRG=\"$link\"\n    else\n      PRG=\"`dirname \"$PRG\"`/$link\"\n    fi\n  done\n\n  saveddir=`pwd`\n\n  M2_HOME=`dirname \"$PRG\"`/..\n\n  # make it fully qualified\n  M2_HOME=`cd \"$M2_HOME\" && pwd`\n\n  cd \"$saveddir\"\n  # echo Using m2 at $M2_HOME\nfi\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched\nif $cygwin ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --unix \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --unix \"$CLASSPATH\"`\nfi\n\n# For Migwn, ensure paths are in UNIX format before anything is touched\nif $mingw ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=\"`(cd \"$M2_HOME\"; pwd)`\"\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=\"`(cd \"$JAVA_HOME\"; pwd)`\"\n  # TODO classpath?\nfi\n\nif [ -z \"$JAVA_HOME\" ]; then\n  javaExecutable=\"`which javac`\"\n  if [ -n \"$javaExecutable\" ] && ! [ \"`expr \\\"$javaExecutable\\\" : '\\([^ ]*\\)'`\" = \"no\" ]; then\n    # readlink(1) is not available as standard on Solaris 10.\n    readLink=`which readlink`\n    if [ ! `expr \"$readLink\" : '\\([^ ]*\\)'` = \"no\" ]; then\n      if $darwin ; then\n        javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n        javaExecutable=\"`cd \\\"$javaHome\\\" && pwd -P`/javac\"\n      else\n        javaExecutable=\"`readlink -f \\\"$javaExecutable\\\"`\"\n      fi\n      javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n      javaHome=`expr \"$javaHome\" : '\\(.*\\)/bin'`\n      JAVA_HOME=\"$javaHome\"\n      export JAVA_HOME\n    fi\n  fi\nfi\n\nif [ -z \"$JAVACMD\" ] ; then\n  if [ -n \"$JAVA_HOME\"  ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n      # IBM's JDK on AIX uses strange locations for the executables\n      JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n      JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n  else\n    JAVACMD=\"`which java`\"\n  fi\nfi\n\nif [ ! -x \"$JAVACMD\" ] ; then\n  echo \"Error: JAVA_HOME is not defined correctly.\" >&2\n  echo \"  We cannot execute $JAVACMD\" >&2\n  exit 1\nfi\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  echo \"Warning: JAVA_HOME environment variable is not set.\"\nfi\n\nCLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher\n\n# traverses directory structure from process work directory to filesystem root\n# first directory with .mvn subdirectory is considered project base directory\nfind_maven_basedir() {\n\n  if [ -z \"$1\" ]\n  then\n    echo \"Path not specified to find_maven_basedir\"\n    return 1\n  fi\n\n  basedir=\"$1\"\n  wdir=\"$1\"\n  while [ \"$wdir\" != '/' ] ; do\n    if [ -d \"$wdir\"/.mvn ] ; then\n      basedir=$wdir\n      break\n    fi\n    # workaround for JBEAP-8937 (on Solaris 10/Sparc)\n    if [ -d \"${wdir}\" ]; then\n      wdir=`cd \"$wdir/..\"; pwd`\n    fi\n    # end of workaround\n  done\n  echo \"${basedir}\"\n}\n\n# concatenates all lines of a file\nconcat_lines() {\n  if [ -f \"$1\" ]; then\n    echo \"$(tr -s '\\n' ' ' < \"$1\")\"\n  fi\n}\n\nBASE_DIR=`find_maven_basedir \"$(pwd)\"`\nif [ -z \"$BASE_DIR\" ]; then\n  exit 1;\nfi\n\nexport MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-\"$BASE_DIR\"}\necho $MAVEN_PROJECTBASEDIR\nMAVEN_OPTS=\"$(concat_lines \"$MAVEN_PROJECTBASEDIR/.mvn/jvm.config\") $MAVEN_OPTS\"\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --path --windows \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --path --windows \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --windows \"$CLASSPATH\"`\n  [ -n \"$MAVEN_PROJECTBASEDIR\" ] &&\n    MAVEN_PROJECTBASEDIR=`cygpath --path --windows \"$MAVEN_PROJECTBASEDIR\"`\nfi\n\nWRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nexec \"$JAVACMD\" \\\n  $MAVEN_OPTS \\\n  -classpath \"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar\" \\\n  \"-Dmaven.home=${M2_HOME}\" \"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}\" \\\n  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG \"$@\"\n"
  },
  {
    "path": "spring/mvnw.cmd",
    "content": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software Foundation (ASF) under one\n@REM or more contributor license agreements.  See the NOTICE file\n@REM distributed with this work for additional information\n@REM regarding copyright ownership.  The ASF licenses this file\n@REM to you under the Apache License, Version 2.0 (the\n@REM \"License\"); you may not use this file except in compliance\n@REM with the License.  You may obtain a copy of the License at\n@REM\n@REM    http://www.apache.org/licenses/LICENSE-2.0\n@REM\n@REM Unless required by applicable law or agreed to in writing,\n@REM software distributed under the License is distributed on an\n@REM \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n@REM KIND, either express or implied.  See the License for the\n@REM specific language governing permissions and limitations\n@REM under the License.\n@REM ----------------------------------------------------------------------------\n\n@REM ----------------------------------------------------------------------------\n@REM Maven2 Start Up Batch script\n@REM\n@REM Required ENV vars:\n@REM JAVA_HOME - location of a JDK home dir\n@REM\n@REM Optional ENV vars\n@REM M2_HOME - location of maven2's installed home dir\n@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands\n@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending\n@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven\n@REM     e.g. to debug Maven itself, use\n@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n@REM ----------------------------------------------------------------------------\n\n@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'\n@echo off\n@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'\n@if \"%MAVEN_BATCH_ECHO%\" == \"on\"  echo %MAVEN_BATCH_ECHO%\n\n@REM set %HOME% to equivalent of $HOME\nif \"%HOME%\" == \"\" (set \"HOME=%HOMEDRIVE%%HOMEPATH%\")\n\n@REM Execute a user defined script before this one\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPre\n@REM check for pre script, once with legacy .bat ending and once with .cmd ending\nif exist \"%HOME%\\mavenrc_pre.bat\" call \"%HOME%\\mavenrc_pre.bat\"\nif exist \"%HOME%\\mavenrc_pre.cmd\" call \"%HOME%\\mavenrc_pre.cmd\"\n:skipRcPre\n\n@setlocal\n\nset ERROR_CODE=0\n\n@REM To isolate internal variables from possible post scripts, we use another setlocal\n@setlocal\n\n@REM ==== START VALIDATION ====\nif not \"%JAVA_HOME%\" == \"\" goto OkJHome\n\necho.\necho Error: JAVA_HOME not found in your environment. >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n:OkJHome\nif exist \"%JAVA_HOME%\\bin\\java.exe\" goto init\n\necho.\necho Error: JAVA_HOME is set to an invalid directory. >&2\necho JAVA_HOME = \"%JAVA_HOME%\" >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n@REM ==== END VALIDATION ====\n\n:init\n\n@REM Find the project base dir, i.e. the directory that contains the folder \".mvn\".\n@REM Fallback to current working directory if not found.\n\nset MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%\nIF NOT \"%MAVEN_PROJECTBASEDIR%\"==\"\" goto endDetectBaseDir\n\nset EXEC_DIR=%CD%\nset WDIR=%EXEC_DIR%\n:findBaseDir\nIF EXIST \"%WDIR%\"\\.mvn goto baseDirFound\ncd ..\nIF \"%WDIR%\"==\"%CD%\" goto baseDirNotFound\nset WDIR=%CD%\ngoto findBaseDir\n\n:baseDirFound\nset MAVEN_PROJECTBASEDIR=%WDIR%\ncd \"%EXEC_DIR%\"\ngoto endDetectBaseDir\n\n:baseDirNotFound\nset MAVEN_PROJECTBASEDIR=%EXEC_DIR%\ncd \"%EXEC_DIR%\"\n\n:endDetectBaseDir\n\nIF NOT EXIST \"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\" goto endReadAdditionalConfig\n\n@setlocal EnableExtensions EnableDelayedExpansion\nfor /F \"usebackq delims=\" %%a in (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a\n@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%\n\n:endReadAdditionalConfig\n\nSET MAVEN_JAVA_EXE=\"%JAVA_HOME%\\bin\\java.exe\"\n\nset WRAPPER_JAR=\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.jar\"\nset WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\n%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% \"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%\" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*\nif ERRORLEVEL 1 goto error\ngoto end\n\n:error\nset ERROR_CODE=1\n\n:end\n@endlocal & set ERROR_CODE=%ERROR_CODE%\n\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPost\n@REM check for post script, once with legacy .bat ending and once with .cmd ending\nif exist \"%HOME%\\mavenrc_post.bat\" call \"%HOME%\\mavenrc_post.bat\"\nif exist \"%HOME%\\mavenrc_post.cmd\" call \"%HOME%\\mavenrc_post.cmd\"\n:skipRcPost\n\n@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'\nif \"%MAVEN_BATCH_PAUSE%\" == \"on\" pause\n\nif \"%MAVEN_TERMINATE_CMD%\" == \"on\" exit %ERROR_CODE%\n\nexit /B %ERROR_CODE%\n"
  },
  {
    "path": "spring/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\t\t xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<groupId>com.example</groupId>\n\t<artifactId>spring</artifactId>\n\t<version>1.0.0</version>\n\t<packaging>jar</packaging>\n\n\t<name>spring</name>\n\t<description>Demo project for Spring Boot</description>\n\n\t<parent>\n\t\t<groupId>org.springframework.boot</groupId>\n\t\t<artifactId>spring-boot-starter-parent</artifactId>\n\t\t<version>2.6.6</version>\n\t\t<relativePath/> <!-- lookup parent from repository -->\n\t</parent>\n\n\t<properties>\n\t\t<java.version>11</java.version>\n\t</properties>\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-actuator</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-web</artifactId>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-test</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n</project>\n"
  },
  {
    "path": "spring/src/main/java/com/example/demo/DemoApplication.java",
    "content": "package com.example.demo;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class DemoApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(DemoApplication.class, args);\n\t}\n}\n"
  },
  {
    "path": "spring/src/main/java/com/example/demo/controller/ItemController.java",
    "content": "package com.example.demo.controller;\n\nimport java.util.List;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport com.example.demo.model.Item;\nimport com.example.demo.service.ItemService;\n\n@RestController\n@RequestMapping(\"/api\")\npublic class ItemController {\n\t\n\t@Autowired\n\tprivate ItemService itemService;\n\t\n\t@RequestMapping(\"/items\") \n\tpublic List<Item> getItems() {\n\t\treturn itemService.getItems(); \n\t}\n\t\n}\n"
  },
  {
    "path": "spring/src/main/java/com/example/demo/model/Item.java",
    "content": "package com.example.demo.model;\n\npublic class Item {\n\t\n\tprivate long id;\n\n\tprivate String name;\n\n\tpublic Item() {\n\t}\n\n\tpublic Item(long id, String name) {\n\t\tthis.id = id;\n\t\tthis.name = name;\n\t}\n\n\tpublic long getId() {\n\t\treturn id;\n\t}\n\n\tpublic void setId(long id) {\n\t\tthis.id = id;\n\t}\n\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\tpublic void setName(String name) {\n\t\tthis.name = name;\n\t}\n\t\n}\n"
  },
  {
    "path": "spring/src/main/java/com/example/demo/service/ItemService.java",
    "content": "package com.example.demo.service;\n\nimport java.util.List;\n\nimport org.springframework.stereotype.Service;\n\nimport com.example.demo.model.Item;\n\npublic interface ItemService {\n\n\tList<Item> getItems();\n\t\n}\n"
  },
  {
    "path": "spring/src/main/java/com/example/demo/service/impl/ItemServiceImpl.java",
    "content": "package com.example.demo.service.impl;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.springframework.stereotype.Service;\n\nimport com.example.demo.model.Item;\nimport com.example.demo.service.ItemService;\n\n@Service(\"itemService\")\npublic class ItemServiceImpl implements ItemService {\n\n\tprivate List<Item> items = new ArrayList<>(); \n\n\tpublic ItemServiceImpl() {\n\t\titems.add(new Item(0, \"Item 1\"));\n\t\titems.add(new Item(1, \"Item 2\"));\n\t\titems.add(new Item(2, \"Item 3\"));\n\t}\n\n\t@Override\n\tpublic List<Item> getItems() {\n\t\treturn items;\n\t}\n\n}\n"
  },
  {
    "path": "spring/src/main/resources/application.properties",
    "content": "management.endpoints.web.exposure.include=*\nmanagement.endpoint.shutdown.enabled=true\nendpoints.shutdown.enabled=true"
  },
  {
    "path": "spring/src/main/resources/logback-spring.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n\n    <property name=\"LOGS\" value=\"./logs\" />\n\n    <appender name=\"Console\"\n              class=\"ch.qos.logback.core.ConsoleAppender\">\n        <layout class=\"ch.qos.logback.classic.PatternLayout\">\n            <Pattern>\n                %-5level [%thread] %logger{32} - %msg%n\n            </Pattern>\n        </layout>\n    </appender>\n\n    <!-- LOG everything at INFO level -->\n    <root level=\"info\">\n        <appender-ref ref=\"Console\" />\n    </root>\n\n</configuration>"
  },
  {
    "path": "spring/src/test/java/com/example/demo/DemoApplicationTests.java",
    "content": "package com.example.demo;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.context.SpringBootTest;\n\n@SpringBootTest\nclass DemoApplicationTests {\n\n\t@Test\n\tvoid contextLoads() {\n\t}\n\n}\n"
  },
  {
    "path": "vue/.gitignore",
    "content": ".DS_Store\nnode_modules\n/dist\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw*\n"
  },
  {
    "path": "vue/README.md",
    "content": "# front-end\n\n## Project setup\n\n```\nnpm install\n```\n\n## Compiles and hot-reloads for development\n\n```\nnpm run dev\n```\n\n## Compiles and minifies for production\n\n```\nnpm run build\n```\n"
  },
  {
    "path": "vue/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Vite App</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "vue/package.json",
    "content": "{\n  \"name\": \"vite-project\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vue-tsc --noEmit && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"@vueuse/core\": \"^8.2.5\",\n    \"vue\": \"^3.2.25\"\n  },\n  \"devDependencies\": {\n    \"@vitejs/plugin-vue\": \"^2.3.0\",\n    \"typescript\": \"^4.5.4\",\n    \"vite\": \"^2.9.0\",\n    \"vue-tsc\": \"^0.29.8\"\n  }\n}\n"
  },
  {
    "path": "vue/src/App.vue",
    "content": "<script setup lang=\"ts\">\nimport { inject, ref } from 'vue'\nimport { useFetch } from '@vueuse/core'\n\nimport { KEY_INTEROP, KEY_LOG } from './keys'\nimport type { Item } from './types/Item'\n\nconst $interop = inject(KEY_INTEROP)\nconst $log = inject(KEY_LOG)\n\nconst {\n  isFetching,\n  error,\n  data: items,\n  onFetchResponse,\n  onFetchError\n} = useFetch('/api/items', { timeout: 1000 }).get().json<Item[]>()\n\nonFetchResponse((response) => {\n  $log?.info('Received items from server.')\n})\n\nonFetchError((error) => {\n  $log?.error('Failed to fetech items from server.')\n})\n\nconst count = ref(0)\nconst selectedFile = ref<string | string[] | undefined>()\n\nfunction increase() {\n  count.value++\n  $interop?.setBadgeCount(count.value)\n}\n\nfunction decrease() {\n  if (count.value > 0) {\n    count.value--\n    $interop?.setBadgeCount(count.value)\n  }\n}\n\nfunction open() {\n  $interop?.showOpenDialog()\n    .then(result => selectedFile.value = result)\n}\n\nfunction save() {\n  $interop?.showSaveDialog()\n    .then(result => selectedFile.value = result)\n}\n</script>\n\n<template>\n  <div>\n    <h1>Items from Server</h1>\n    <p v-if=\"isFetching\">Fetching items...</p>\n    <template v-else>\n      <p v-if=\"error\">Failed to receive items. {{ error }}</p>\n      <ul v-else>\n        <li v-for=\"item in items\" :key=\"item.id\">{{ item.name }}</li>\n      </ul>\n    </template>\n\n    <h1>Set badge count (Mac Only)</h1>\n    <p>Click buttons below to set app badge count (calling Electron via preload script)</p>\n    <button @click=\"increase\">Increase</button>\n    <button @click=\"decrease\" :disabled=\"count <= 0\">Decrease</button>\n\n    <h1>File dialog</h1>\n    <button @click=\"open\">Show Open Dialog</button>\n    <button @click=\"save\">Show Save Dialog</button>\n    <p>\n      <strong>Selected File(s)</strong>\n      : {{ selectedFile }}\n    </p>\n  </div>\n</template>\n"
  },
  {
    "path": "vue/src/env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n\ndeclare module '*.vue' {\n  import type { DefineComponent } from 'vue'\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types\n  const component: DefineComponent<{}, {}, any>\n  export default component\n}\n"
  },
  {
    "path": "vue/src/interop-fallback.ts",
    "content": "import type Interop from './types/InterOp'\n\n/* eslint-disable no-console */\nfunction promptNoElectron() {\n  alert('You are not in Electron.')\n}\n\n// The following will be used if front end is run in browser during development.\nconst fallBack: Interop = {\n  log: {\n    info(msg: string) {\n      console.info(msg)\n    },\n    debug(msg: string) {\n      console.debug(msg)\n    },\n    warn(msg: string) {\n      console.warn(msg)\n    },\n    error(msg: string) {\n      console.error(msg)\n    },\n    log(msg: string) {\n      console.log(msg)\n    },\n  },\n  setBadgeCount(count: Number) {\n    promptNoElectron()\n  },\n  showOpenDialog() {\n    return new Promise((resolve) => {\n      promptNoElectron()\n      resolve(undefined)\n    })\n  },\n  showSaveDialog() {\n    return new Promise((resolve) => {\n      promptNoElectron()\n      resolve(undefined)\n    })\n  },\n}\n\nexport default fallBack\n"
  },
  {
    "path": "vue/src/keys.ts",
    "content": "import { InjectionKey } from 'vue'\nimport Interop from './types/InterOp'\n\nexport const KEY_INTEROP = Symbol() as InjectionKey<Interop>\nexport const KEY_LOG = Symbol() as InjectionKey<Interop['log']>\n"
  },
  {
    "path": "vue/src/main.ts",
    "content": "import { createApp } from 'vue'\nimport App from './App.vue'\nimport { KEY_INTEROP, KEY_LOG } from './keys'\nimport interopFallback from './interop-fallback'\n\nconst app = createApp(App)\n\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore the unknown interop on window object\nconst interop = window.interop || interopFallback\napp.provide(KEY_INTEROP, interop)\napp.provide(KEY_LOG, interop.log)\n\napp.mount('#app')\n"
  },
  {
    "path": "vue/src/types/Interop.ts",
    "content": "export default interface Interop {\n  log: {\n    info: (msg: string) => void\n    debug: (msg: string) => void\n    warn: (msg: string) => void\n    error: (msg: string) => void\n    log: (msg: string) => void\n  }\n  setBadgeCount: (count: Number) => void\n  showOpenDialog: () => Promise<string[] | undefined>\n  showSaveDialog: () => Promise<string | undefined>\n}\n"
  },
  {
    "path": "vue/src/types/Item.ts",
    "content": "export type Item = {\n  id: string\n  name: string\n}\n"
  },
  {
    "path": "vue/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"strict\": true,\n    \"jsx\": \"preserve\",\n    \"sourceMap\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"lib\": [\"esnext\", \"dom\"]\n  },\n  \"include\": [\"src/**/*.ts\", \"src/**/*.d.ts\", \"src/**/*.tsx\", \"src/**/*.vue\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "vue/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\"\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "vue/vite.config.ts",
    "content": "import { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [vue()],\n  server: {\n    port: 9000,\n    proxy: {\n      '^/api': {\n        target: 'http://localhost:8080',\n        changeOrigin: true,\n      },\n      '^/actuator/health': {\n        target: 'http://localhost:8080',\n      },\n    },\n  },\n})\n"
  }
]