Repository: wuruoyun/electron-vue-spring
Branch: master
Commit: 42eccb72578b
Files: 38
Total size: 39.5 KB
Directory structure:
gitextract_hvz2qq8e/
├── .gitignore
├── LICENSE
├── README.md
├── build/
│ ├── build-installer.js
│ └── build-server.js
├── electron/
│ ├── index.js
│ ├── logger.js
│ ├── preload.js
│ └── splash.html
├── package.json
├── spring/
│ ├── .gitignore
│ ├── .mvn/
│ │ └── wrapper/
│ │ ├── maven-wrapper.jar
│ │ └── maven-wrapper.properties
│ ├── mvnw
│ ├── mvnw.cmd
│ ├── pom.xml
│ └── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── demo/
│ │ │ ├── DemoApplication.java
│ │ │ ├── controller/
│ │ │ │ └── ItemController.java
│ │ │ ├── model/
│ │ │ │ └── Item.java
│ │ │ └── service/
│ │ │ ├── ItemService.java
│ │ │ └── impl/
│ │ │ └── ItemServiceImpl.java
│ │ └── resources/
│ │ ├── application.properties
│ │ └── logback-spring.xml
│ └── test/
│ └── java/
│ └── com/
│ └── example/
│ └── demo/
│ └── DemoApplicationTests.java
└── vue/
├── .gitignore
├── README.md
├── index.html
├── package.json
├── src/
│ ├── App.vue
│ ├── env.d.ts
│ ├── interop-fallback.ts
│ ├── keys.ts
│ ├── main.ts
│ └── types/
│ ├── Interop.ts
│ └── Item.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules/
dist/
npm-debug.log
yarn-error.log
# Editor directories and files
.vscode
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 Ruoyun Wu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Electron-Vue-Spring
> An opionated desktop application with web front-end and Java backend.
In 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.
This project has two sub projects:
1. `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.
2. `spring`: a Spring Boot application as the backend, based on a Maven project created by [Spring Initializer](https://start.spring.io/) with Web dependency.
Both Windows and Mac OS are supported.
## Prerequisites
- JDK 11, such as [Amazon Corretto 11](https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html)
- Node 14.x
- Maven 3.x
> 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.
## Build Installer
Build the final installer, which can be found in folder `dist`. It is an `exe` file for Windows and `dmg` file for Mac.
```bash
# install dependencies
npm install
# install dependencies for vue project
cd vue
npm install
cd ..
# build installer for production
npm run build
```
## Development Setup
During 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.
- 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`.
- 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`.
- 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.
## How it works
The 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.
The 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.
### Build process
When building the final desktop app installer:
1. Front-end is built first. The final artifacts, including `index.html` and JavaScript files, are copied into `spring/src/main/resources/public` folder.
2. Backend is built second. It creates a web app with the front-end artifacts created above and an executable jar.
3. Electron installer is built last. It includes the web app created above in the bundle and creates an executable installer.
However, 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.
### Launch process
When launching the Electron app:
1. 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.
2. Electron app then displays a splash window, at the same time pings the `actuator/health` URL of the backend server periodically.
3. 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.
> 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.
### Shutdown process
When shutting down the Electron app:
1. Electron app handles the `will-quit` event by trying to stop the backend server and cancel the quit.
2. The first attempt is to shutdown gracefully via the `actuator/shutdown` URL of the backend server.
3. If that fails, the Electron app will attempt to kill the process by its PID.
4. Either of the shutsown attempts above will clear up the `baseUrl` and call `app.quit()` again.
5. With `baseUrl` being cleared, `will-quit` handler will not prevent the quitting this time.
### Node access
Although 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.
The 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.
### Log Aggregation
The 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:
- on Linux: `~/.config/{app name}/logs/{process type}.log`
- on macOS: `~/Library/Logs/{app name}/{process type}.log`
- on Windows: `%USERPROFILE%\AppData\Roaming\{app name}\logs\{process type}.log`
In 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.
In 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.
## License
[MIT](LICENSE)
================================================
FILE: build/build-installer.js
================================================
const shell = require('shelljs')
shell.echo('##########################')
shell.echo('# Building electron #')
shell.echo('##########################')
if (!shell.test('-e', 'spring/target')) {
shell.echo('Error: server is not built yet.')
shell.exit(1)
}
shell.rm('-rf', 'dist')
if (shell.exec('electron-builder build').code !== 0) {
shell.echo('Error: electron build failed')
shell.exit(1)
}
================================================
FILE: build/build-server.js
================================================
const shell = require('shelljs')
shell.echo('##########################')
shell.echo('# Building vue #')
shell.echo('##########################')
shell.cd('vue')
const PUBLIC = '../spring/src/main/resources/public/'
shell.rm('-rf', PUBLIC);
if (shell.exec('npm run build').code !== 0) {
shell.echo('Error: vue build failed')
shell.exit(1)
}
shell.cp('-R', 'dist/', PUBLIC)
shell.cd('..')
shell.echo('##########################')
shell.echo('# Building spring #')
shell.echo('##########################')
shell.cd('spring')
const mvnw = process.platform === 'win32' ? 'mvnw' : './mvnw'
if (shell.exec(mvnw + ' clean package').code !== 0) {
shell.echo('Error: spring build failed')
shell.exit(1)
}
================================================
FILE: electron/index.js
================================================
const { app, ipcMain, BrowserWindow, dialog } = require('electron')
const path = require('path')
const url = require('url')
var findPort = require('find-free-port')
const isDev = require('electron-is-dev')
const logger = require('./logger')
const axios = require('axios')
const JAR = 'spring-1.0.0.jar' // how to avoid manual update of this?
const MAX_CHECK_COUNT = 10
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
// The server url and process
let serverProcess
let baseUrl
function startServer(port) {
logger.info(`Starting server at port ${port}`)
const server = `${path.join(app.getAppPath(), '..', '..', JAR)}`
logger.info(`Launching server with jar ${server} at port ${port}...`)
serverProcess = require('child_process').spawn('java', [
'-jar',
server,
`--server.port=${port}`,
])
serverProcess.stdout.on('data', logger.server)
if (serverProcess.pid) {
baseUrl = `http://localhost:${port}`
logger.info('Server PID: ' + serverProcess.pid)
} else {
logger.error('Failed to launch server process.')
}
}
function stopServer() {
logger.info('Stopping server...')
axios
.post(`${baseUrl}/actuator/shutdown`, null, {
headers: { 'Content-Type': 'application/json' },
})
.then(() => logger.info('Server stopped'))
.catch((error) => {
logger.error('Failed to stop the server gracefully.', error)
if (serverProcess) {
logger.info(`Killing server process ${serverProcess.pid}`)
const kill = require('tree-kill')
kill(serverProcess.pid, 'SIGTERM', function (err) {
logger.info('Server process killed')
serverProcess = null
baseUrl = null
app.quit() // quit again
})
}
})
.finally(() => {
serverProcess = null
baseUrl = null
app.quit() // quit again
})
}
function createSplash() {
const splash = new BrowserWindow({ width: 400, height: 300, frame: false })
splash.loadURL(
url.format({
pathname: path.join(__dirname, 'splash.html'),
protocol: 'file:',
slashes: true,
})
)
return splash
}
function createWindow(callback) {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
show: false, // hide until ready-to-show
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
})
loadHomePage()
// Open the DevTools.
// mainWindow.webContents.openDevTools()
mainWindow.once('ready-to-show', () => {
mainWindow.show()
if (callback) callback()
})
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
}
function quitOnError(title, content) {
logger.error(content)
dialog.showErrorBox(title, content)
app.quit()
}
function loadHomePage() {
logger.info(`Loading home page at ${baseUrl}`)
// check server health and switch to main page
checkCount = 0
setTimeout(function cycle() {
axios
.get(`${baseUrl}/actuator/health`)
.then(() => mainWindow.loadURL(`${baseUrl}?_=${Date.now()}`))
.catch((e) => {
if (e.code === 'ECONNREFUSED') {
if (checkCount < MAX_CHECK_COUNT) {
checkCount++
setTimeout(cycle, 1000)
} else {
quitOnError(
'Server timeout',
`UI does not receive server response for ${MAX_CHECK_COUNT} seconds.`
)
app.quit()
}
} else {
logger.error(e)
quitOnError('Server error', 'UI receives an error from server.')
}
})
}, 200)
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
logger.info('###################################################')
logger.info('# Application Starting #')
logger.info('###################################################')
// handle messages from ipcRenderer via preload.js
ipcMain.on('app:badgeCount', (_, count) => app.setBadgeCount(count))
ipcMain.handle('dialog:openFile', () => dialog.showOpenDialogSync())
ipcMain.handle('dialog:saveFile', () => dialog.showSaveDialogSync())
if (isDev) {
// Assume the webpack dev server is up at port 9000
baseUrl = `http://localhost:9000`
createWindow()
} else {
// Create window first to show splash before starting server
const splash = createSplash()
// Start server at an available port (prefer 8080)
findPort(8080, function (err, port) {
if (!err) {
startServer(port)
createWindow(() => splash.close())
} else {
quitOnError('Error', 'Unable to get a server port.')
}
})
}
})
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
}
})
app.on('will-quit', (e) => {
if (!isDev && baseUrl != null) {
stopServer()
e.preventDefault() // will quite later after stopped the server
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
================================================
FILE: electron/logger.js
================================================
const logger = require('electron-log')
logger.server = function (data) {
// data is from server std.out and may includes multiple lines
const messages = data.toString().split('\n')
messages.forEach((msg) => {
if (msg.length > 0) {
if (msg.startsWith('INFO')) logger.info(msg.substring(6))
else if (msg.startsWith('WARN')) logger.warn(msg.substring(6))
else if (msg.startsWith('ERROR')) logger.error(msg.substring(6))
else if (msg.startsWith('DEBUG')) logger.debug(msg.substring(6))
else logger.silly(msg)
}
})
}
module.exports = logger
================================================
FILE: electron/preload.js
================================================
const { app, contextBridge, ipcRenderer } = require('electron')
const logger = require('./logger')
const LOG_PREFIX = '[ui]'
contextBridge.exposeInMainWorld('interop', {
log: {
info(msg) {
logger.info(`${LOG_PREFIX} ${msg}`)
},
debug(msg) {
logger.debug(`${LOG_PREFIX} ${msg}`)
},
warn(msg) {
logger.warn(`${LOG_PREFIX} ${msg}`)
},
error(msg) {
logger.error(`${LOG_PREFIX} ${msg}`)
},
log(msg) {
logger.silly(`${LOG_PREFIX} ${msg}`)
},
},
setBadgeCount(count) {
return ipcRenderer.send('app:badgeCount', count)
},
showOpenDialog() {
return ipcRenderer.invoke('dialog:openFile')
},
showSaveDialog() {
return ipcRenderer.invoke('dialog:saveFile')
},
})
================================================
FILE: electron/splash.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Electron-Vue-Spring</title>
</head>
<style>
body {
background-color:cornflowerblue;
padding: 20px;
color: white;
}
</style>
<body>
<h1>Splash Screen</h1>
<p>Starting server, please wait...</p>
</body>
</html>
================================================
FILE: package.json
================================================
{
"name": "electron-vue-spring",
"version": "1.0.0",
"description": "A minimal starter project for using Electron, Vue and Spring.",
"author": "Wu, Ruoyun",
"repository": {
"type": "git",
"url": "https://github.com/wuruoyun/electron-vue-spring.git"
},
"main": "electron/index.js",
"scripts": {
"start": "electron ./electron",
"build-server": "node build/build-server.js",
"build-installer": "node build/build-installer.js",
"build": "npm-run-all clean build-server build-installer",
"clean": "rimraf dist"
},
"license": "MIT",
"build": {
"appId": "electron-vue-spring",
"files": [
"electron/**/*",
"node_modules/**/*",
"!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}",
"!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples}",
"!**/node_modules/*.d.ts",
"!**/node_modules/.bin",
"!**/*.{iml,o,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,xproj}",
"!.editorconfig",
"!**/._*",
"!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,.gitignore,.gitattributes}",
"!**/{__pycache__,thumbs.db,.flowconfig,.idea,.vs,.nyc_output}",
"!**/{appveyor.yml,.travis.yml,circle.yml}",
"!**/{npm-debug.log,yarn.lock,.yarn-integrity,.yarn-metadata.json}"
],
"extraFiles": [
{
"from": "spring/target",
"filter": [
"*.jar"
]
}
]
},
"devDependencies": {
"electron": "^18.0.3",
"electron-builder": "^22.14.13",
"npm-run-all": "^4.1.5",
"rimraf": "^3.0.2",
"shelljs": "^0.8.5"
},
"dependencies": {
"axios": "^0.26.1",
"electron-is-dev": "^2.0.0",
"electron-log": "^4.4.6",
"find-free-port": "^2.0.0",
"tree-kill": "^1.2.2"
}
}
================================================
FILE: spring/.gitignore
================================================
target/
!.mvn/wrapper/maven-wrapper.jar
### public folder is from external SPA ###
src/main/resources/public
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
nbproject/private/
build/
nbbuild/
dist/
nbdist/
.nb-gradle/
### Eclipse ###
.settings
.classpath
.project
================================================
FILE: spring/.mvn/wrapper/maven-wrapper.properties
================================================
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip
================================================
FILE: spring/mvnw
================================================
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven2 Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Migwn, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
# TODO classpath?
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
echo $MAVEN_PROJECTBASEDIR
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
================================================
FILE: spring/mvnw.cmd
================================================
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven2 Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%
================================================
FILE: spring/pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>spring</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>spring</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
================================================
FILE: spring/src/main/java/com/example/demo/DemoApplication.java
================================================
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
================================================
FILE: spring/src/main/java/com/example/demo/controller/ItemController.java
================================================
package com.example.demo.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.model.Item;
import com.example.demo.service.ItemService;
@RestController
@RequestMapping("/api")
public class ItemController {
@Autowired
private ItemService itemService;
@RequestMapping("/items")
public List<Item> getItems() {
return itemService.getItems();
}
}
================================================
FILE: spring/src/main/java/com/example/demo/model/Item.java
================================================
package com.example.demo.model;
public class Item {
private long id;
private String name;
public Item() {
}
public Item(long id, String name) {
this.id = id;
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
================================================
FILE: spring/src/main/java/com/example/demo/service/ItemService.java
================================================
package com.example.demo.service;
import java.util.List;
import org.springframework.stereotype.Service;
import com.example.demo.model.Item;
public interface ItemService {
List<Item> getItems();
}
================================================
FILE: spring/src/main/java/com/example/demo/service/impl/ItemServiceImpl.java
================================================
package com.example.demo.service.impl;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.example.demo.model.Item;
import com.example.demo.service.ItemService;
@Service("itemService")
public class ItemServiceImpl implements ItemService {
private List<Item> items = new ArrayList<>();
public ItemServiceImpl() {
items.add(new Item(0, "Item 1"));
items.add(new Item(1, "Item 2"));
items.add(new Item(2, "Item 3"));
}
@Override
public List<Item> getItems() {
return items;
}
}
================================================
FILE: spring/src/main/resources/application.properties
================================================
management.endpoints.web.exposure.include=*
management.endpoint.shutdown.enabled=true
endpoints.shutdown.enabled=true
================================================
FILE: spring/src/main/resources/logback-spring.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOGS" value="./logs" />
<appender name="Console"
class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%-5level [%thread] %logger{32} - %msg%n
</Pattern>
</layout>
</appender>
<!-- LOG everything at INFO level -->
<root level="info">
<appender-ref ref="Console" />
</root>
</configuration>
================================================
FILE: spring/src/test/java/com/example/demo/DemoApplicationTests.java
================================================
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoApplicationTests {
@Test
void contextLoads() {
}
}
================================================
FILE: vue/.gitignore
================================================
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
================================================
FILE: vue/README.md
================================================
# front-end
## Project setup
```
npm install
```
## Compiles and hot-reloads for development
```
npm run dev
```
## Compiles and minifies for production
```
npm run build
```
================================================
FILE: vue/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
================================================
FILE: vue/package.json
================================================
{
"name": "vite-project",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview"
},
"dependencies": {
"@vueuse/core": "^8.2.5",
"vue": "^3.2.25"
},
"devDependencies": {
"@vitejs/plugin-vue": "^2.3.0",
"typescript": "^4.5.4",
"vite": "^2.9.0",
"vue-tsc": "^0.29.8"
}
}
================================================
FILE: vue/src/App.vue
================================================
<script setup lang="ts">
import { inject, ref } from 'vue'
import { useFetch } from '@vueuse/core'
import { KEY_INTEROP, KEY_LOG } from './keys'
import type { Item } from './types/Item'
const $interop = inject(KEY_INTEROP)
const $log = inject(KEY_LOG)
const {
isFetching,
error,
data: items,
onFetchResponse,
onFetchError
} = useFetch('/api/items', { timeout: 1000 }).get().json<Item[]>()
onFetchResponse((response) => {
$log?.info('Received items from server.')
})
onFetchError((error) => {
$log?.error('Failed to fetech items from server.')
})
const count = ref(0)
const selectedFile = ref<string | string[] | undefined>()
function increase() {
count.value++
$interop?.setBadgeCount(count.value)
}
function decrease() {
if (count.value > 0) {
count.value--
$interop?.setBadgeCount(count.value)
}
}
function open() {
$interop?.showOpenDialog()
.then(result => selectedFile.value = result)
}
function save() {
$interop?.showSaveDialog()
.then(result => selectedFile.value = result)
}
</script>
<template>
<div>
<h1>Items from Server</h1>
<p v-if="isFetching">Fetching items...</p>
<template v-else>
<p v-if="error">Failed to receive items. {{ error }}</p>
<ul v-else>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</ul>
</template>
<h1>Set badge count (Mac Only)</h1>
<p>Click buttons below to set app badge count (calling Electron via preload script)</p>
<button @click="increase">Increase</button>
<button @click="decrease" :disabled="count <= 0">Decrease</button>
<h1>File dialog</h1>
<button @click="open">Show Open Dialog</button>
<button @click="save">Show Save Dialog</button>
<p>
<strong>Selected File(s)</strong>
: {{ selectedFile }}
</p>
</div>
</template>
================================================
FILE: vue/src/env.d.ts
================================================
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}
================================================
FILE: vue/src/interop-fallback.ts
================================================
import type Interop from './types/InterOp'
/* eslint-disable no-console */
function promptNoElectron() {
alert('You are not in Electron.')
}
// The following will be used if front end is run in browser during development.
const fallBack: Interop = {
log: {
info(msg: string) {
console.info(msg)
},
debug(msg: string) {
console.debug(msg)
},
warn(msg: string) {
console.warn(msg)
},
error(msg: string) {
console.error(msg)
},
log(msg: string) {
console.log(msg)
},
},
setBadgeCount(count: Number) {
promptNoElectron()
},
showOpenDialog() {
return new Promise((resolve) => {
promptNoElectron()
resolve(undefined)
})
},
showSaveDialog() {
return new Promise((resolve) => {
promptNoElectron()
resolve(undefined)
})
},
}
export default fallBack
================================================
FILE: vue/src/keys.ts
================================================
import { InjectionKey } from 'vue'
import Interop from './types/InterOp'
export const KEY_INTEROP = Symbol() as InjectionKey<Interop>
export const KEY_LOG = Symbol() as InjectionKey<Interop['log']>
================================================
FILE: vue/src/main.ts
================================================
import { createApp } from 'vue'
import App from './App.vue'
import { KEY_INTEROP, KEY_LOG } from './keys'
import interopFallback from './interop-fallback'
const app = createApp(App)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore the unknown interop on window object
const interop = window.interop || interopFallback
app.provide(KEY_INTEROP, interop)
app.provide(KEY_LOG, interop.log)
app.mount('#app')
================================================
FILE: vue/src/types/Interop.ts
================================================
export default interface Interop {
log: {
info: (msg: string) => void
debug: (msg: string) => void
warn: (msg: string) => void
error: (msg: string) => void
log: (msg: string) => void
}
setBadgeCount: (count: Number) => void
showOpenDialog: () => Promise<string[] | undefined>
showSaveDialog: () => Promise<string | undefined>
}
================================================
FILE: vue/src/types/Item.ts
================================================
export type Item = {
id: string
name: string
}
================================================
FILE: vue/tsconfig.json
================================================
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"skipLibCheck": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
================================================
FILE: vue/tsconfig.node.json
================================================
{
"compilerOptions": {
"composite": true,
"module": "esnext",
"moduleResolution": "node"
},
"include": ["vite.config.ts"]
}
================================================
FILE: vue/vite.config.ts
================================================
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
port: 9000,
proxy: {
'^/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
'^/actuator/health': {
target: 'http://localhost:8080',
},
},
},
})
gitextract_hvz2qq8e/
├── .gitignore
├── LICENSE
├── README.md
├── build/
│ ├── build-installer.js
│ └── build-server.js
├── electron/
│ ├── index.js
│ ├── logger.js
│ ├── preload.js
│ └── splash.html
├── package.json
├── spring/
│ ├── .gitignore
│ ├── .mvn/
│ │ └── wrapper/
│ │ ├── maven-wrapper.jar
│ │ └── maven-wrapper.properties
│ ├── mvnw
│ ├── mvnw.cmd
│ ├── pom.xml
│ └── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── demo/
│ │ │ ├── DemoApplication.java
│ │ │ ├── controller/
│ │ │ │ └── ItemController.java
│ │ │ ├── model/
│ │ │ │ └── Item.java
│ │ │ └── service/
│ │ │ ├── ItemService.java
│ │ │ └── impl/
│ │ │ └── ItemServiceImpl.java
│ │ └── resources/
│ │ ├── application.properties
│ │ └── logback-spring.xml
│ └── test/
│ └── java/
│ └── com/
│ └── example/
│ └── demo/
│ └── DemoApplicationTests.java
└── vue/
├── .gitignore
├── README.md
├── index.html
├── package.json
├── src/
│ ├── App.vue
│ ├── env.d.ts
│ ├── interop-fallback.ts
│ ├── keys.ts
│ ├── main.ts
│ └── types/
│ ├── Interop.ts
│ └── Item.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
SYMBOL INDEX (49 symbols across 13 files)
FILE: build/build-server.js
constant PUBLIC (line 8) | const PUBLIC = '../spring/src/main/resources/public/'
FILE: electron/index.js
constant JAR (line 9) | const JAR = 'spring-1.0.0.jar' // how to avoid manual update of this?
constant MAX_CHECK_COUNT (line 10) | const MAX_CHECK_COUNT = 10
function startServer (line 20) | function startServer(port) {
function stopServer (line 42) | function stopServer() {
function createSplash (line 69) | function createSplash() {
function createWindow (line 81) | function createWindow(callback) {
function quitOnError (line 110) | function quitOnError(title, content) {
function loadHomePage (line 116) | function loadHomePage() {
FILE: electron/preload.js
constant LOG_PREFIX (line 4) | const LOG_PREFIX = '[ui]'
method info (line 8) | info(msg) {
method debug (line 11) | debug(msg) {
method warn (line 14) | warn(msg) {
method error (line 17) | error(msg) {
method log (line 20) | log(msg) {
method setBadgeCount (line 24) | setBadgeCount(count) {
method showOpenDialog (line 27) | showOpenDialog() {
method showSaveDialog (line 30) | showSaveDialog() {
FILE: spring/src/main/java/com/example/demo/DemoApplication.java
class DemoApplication (line 6) | @SpringBootApplication
method main (line 9) | public static void main(String[] args) {
FILE: spring/src/main/java/com/example/demo/controller/ItemController.java
class ItemController (line 12) | @RestController
method getItems (line 19) | @RequestMapping("/items")
FILE: spring/src/main/java/com/example/demo/model/Item.java
class Item (line 3) | public class Item {
method Item (line 9) | public Item() {
method Item (line 12) | public Item(long id, String name) {
method getId (line 17) | public long getId() {
method setId (line 21) | public void setId(long id) {
method getName (line 25) | public String getName() {
method setName (line 29) | public void setName(String name) {
FILE: spring/src/main/java/com/example/demo/service/ItemService.java
type ItemService (line 9) | public interface ItemService {
method getItems (line 11) | List<Item> getItems();
FILE: spring/src/main/java/com/example/demo/service/impl/ItemServiceImpl.java
class ItemServiceImpl (line 11) | @Service("itemService")
method ItemServiceImpl (line 16) | public ItemServiceImpl() {
method getItems (line 22) | @Override
FILE: spring/src/test/java/com/example/demo/DemoApplicationTests.java
class DemoApplicationTests (line 6) | @SpringBootTest
method contextLoads (line 9) | @Test
FILE: vue/src/interop-fallback.ts
function promptNoElectron (line 4) | function promptNoElectron() {
method info (line 11) | info(msg: string) {
method debug (line 14) | debug(msg: string) {
method warn (line 17) | warn(msg: string) {
method error (line 20) | error(msg: string) {
method log (line 23) | log(msg: string) {
method setBadgeCount (line 27) | setBadgeCount(count: Number) {
method showOpenDialog (line 30) | showOpenDialog() {
method showSaveDialog (line 36) | showSaveDialog() {
FILE: vue/src/keys.ts
constant KEY_INTEROP (line 4) | const KEY_INTEROP = Symbol() as InjectionKey<Interop>
constant KEY_LOG (line 5) | const KEY_LOG = Symbol() as InjectionKey<Interop['log']>
FILE: vue/src/types/Interop.ts
type Interop (line 1) | interface Interop {
FILE: vue/src/types/Item.ts
type Item (line 1) | type Item = {
Condensed preview — 38 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (45K chars).
[
{
"path": ".gitignore",
"chars": 135,
"preview": ".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*"
},
{
"path": "LICENSE",
"chars": 1066,
"preview": "MIT License\n\nCopyright (c) 2017 Ruoyun Wu\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n"
},
{
"path": "README.md",
"chars": 6464,
"preview": "# Electron-Vue-Spring\n\n> An opionated desktop application with web front-end and Java backend.\n\nIn some cases, you may l"
},
{
"path": "build/build-installer.js",
"chars": 409,
"preview": "const shell = require('shelljs')\n\nshell.echo('##########################')\nshell.echo('# Building electron #')\nshel"
},
{
"path": "build/build-server.js",
"chars": 725,
"preview": "const shell = require('shelljs')\n\nshell.echo('##########################')\nshell.echo('# Building vue #')\nshel"
},
{
"path": "electron/index.js",
"chars": 5891,
"preview": "const { app, ipcMain, BrowserWindow, dialog } = require('electron')\nconst path = require('path')\nconst url = require('ur"
},
{
"path": "electron/logger.js",
"chars": 584,
"preview": "const logger = require('electron-log')\n\nlogger.server = function (data) {\n // data is from server std.out and may inclu"
},
{
"path": "electron/preload.js",
"chars": 751,
"preview": "const { app, contextBridge, ipcRenderer } = require('electron')\nconst logger = require('./logger')\n\nconst LOG_PREFIX = '"
},
{
"path": "electron/splash.html",
"chars": 322,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\">\n <title>Electron-Vue-Spring</title>\n </head>\n <style>\n "
},
{
"path": "package.json",
"chars": 1768,
"preview": "{\n \"name\": \"electron-vue-spring\",\n \"version\": \"1.0.0\",\n \"description\": \"A minimal starter project for using Electron,"
},
{
"path": "spring/.gitignore",
"chars": 366,
"preview": "target/\n!.mvn/wrapper/maven-wrapper.jar\n\n### public folder is from external SPA ###\nsrc/main/resources/public\n\n### STS #"
},
{
"path": "spring/.mvn/wrapper/maven-wrapper.properties",
"chars": 110,
"preview": "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",
"chars": 6468,
"preview": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Softwa"
},
{
"path": "spring/mvnw.cmd",
"chars": 4994,
"preview": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software F"
},
{
"path": "spring/pom.xml",
"chars": 1390,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
},
{
"path": "spring/src/main/java/com/example/demo/DemoApplication.java",
"chars": 304,
"preview": "package com.example.demo;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfig"
},
{
"path": "spring/src/main/java/com/example/demo/controller/ItemController.java",
"chars": 555,
"preview": "package com.example.demo.controller;\n\nimport java.util.List;\n\nimport org.springframework.beans.factory.annotation.Autowi"
},
{
"path": "spring/src/main/java/com/example/demo/model/Item.java",
"chars": 394,
"preview": "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\tp"
},
{
"path": "spring/src/main/java/com/example/demo/service/ItemService.java",
"chars": 204,
"preview": "package com.example.demo.service;\n\nimport java.util.List;\n\nimport org.springframework.stereotype.Service;\n\nimport com.ex"
},
{
"path": "spring/src/main/java/com/example/demo/service/impl/ItemServiceImpl.java",
"chars": 555,
"preview": "package com.example.demo.service.impl;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.springframework.s"
},
{
"path": "spring/src/main/resources/application.properties",
"chars": 117,
"preview": "management.endpoints.web.exposure.include=*\nmanagement.endpoint.shutdown.enabled=true\nendpoints.shutdown.enabled=true"
},
{
"path": "spring/src/main/resources/logback-spring.xml",
"chars": 521,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n\n <property name=\"LOGS\" value=\"./logs\" />\n\n <appender name="
},
{
"path": "spring/src/test/java/com/example/demo/DemoApplicationTests.java",
"chars": 206,
"preview": "package com.example.demo;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.context.SpringBootTes"
},
{
"path": "vue/.gitignore",
"chars": 214,
"preview": ".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"
},
{
"path": "vue/README.md",
"chars": 181,
"preview": "# 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##"
},
{
"path": "vue/index.html",
"chars": 337,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <link rel=\"icon\" href=\"/favicon.ico\" />\n <"
},
{
"path": "vue/package.json",
"chars": 401,
"preview": "{\n \"name\": \"vite-project\",\n \"private\": true,\n \"version\": \"0.0.0\",\n \"scripts\": {\n \"dev\": \"vite\",\n \"build\": \"vue"
},
{
"path": "vue/src/App.vue",
"chars": 1832,
"preview": "<script setup lang=\"ts\">\nimport { inject, ref } from 'vue'\nimport { useFetch } from '@vueuse/core'\n\nimport { KEY_INTEROP"
},
{
"path": "vue/src/env.d.ts",
"chars": 281,
"preview": "/// <reference types=\"vite/client\" />\n\ndeclare module '*.vue' {\n import type { DefineComponent } from 'vue'\n // eslint"
},
{
"path": "vue/src/interop-fallback.ts",
"chars": 875,
"preview": "import type Interop from './types/InterOp'\n\n/* eslint-disable no-console */\nfunction promptNoElectron() {\n alert('You a"
},
{
"path": "vue/src/keys.ts",
"chars": 199,
"preview": "import { InjectionKey } from 'vue'\nimport Interop from './types/InterOp'\n\nexport const KEY_INTEROP = Symbol() as Injecti"
},
{
"path": "vue/src/main.ts",
"chars": 434,
"preview": "import { createApp } from 'vue'\nimport App from './App.vue'\nimport { KEY_INTEROP, KEY_LOG } from './keys'\nimport interop"
},
{
"path": "vue/src/types/Interop.ts",
"chars": 358,
"preview": "export default interface Interop {\n log: {\n info: (msg: string) => void\n debug: (msg: string) => void\n warn: ("
},
{
"path": "vue/src/types/Item.ts",
"chars": 51,
"preview": "export type Item = {\n id: string\n name: string\n}\n"
},
{
"path": "vue/tsconfig.json",
"chars": 491,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"esnext\",\n \"useDefineForClassFields\": true,\n \"module\": \"esnext\",\n \"modul"
},
{
"path": "vue/tsconfig.node.json",
"chars": 142,
"preview": "{\n \"compilerOptions\": {\n \"composite\": true,\n \"module\": \"esnext\",\n \"moduleResolution\": \"node\"\n },\n \"include\":"
},
{
"path": "vue/vite.config.ts",
"chars": 384,
"preview": "import { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\n\n// https://vitejs.dev/config/\nexport default d"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the wuruoyun/electron-vue-spring GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 38 files (39.5 KB), approximately 11.8k tokens, and a symbol index with 49 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.