@witchcraft/nuxt-electron 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -73
- package/dist/module.json +1 -1
- package/dist/module.mjs +38 -17
- package/dist/runtime/components/WindowControls/PinButton.d.vue.ts +1 -1
- package/dist/runtime/components/WindowControls/PinButton.vue.d.ts +1 -1
- package/dist/runtime/electron/createPriviledgedProtocolScheme.d.ts +25 -0
- package/dist/runtime/electron/createPriviledgedProtocolScheme.js +16 -0
- package/dist/runtime/electron/createPrivilegedProtocolScheme.d.ts +25 -0
- package/dist/runtime/electron/createPrivilegedProtocolScheme.js +16 -0
- package/dist/runtime/electron/createProxiedProtocolHandler.d.ts +31 -0
- package/dist/runtime/electron/createProxiedProtocolHandler.js +158 -0
- package/dist/runtime/electron/getPaths.d.ts +7 -15
- package/dist/runtime/electron/getPaths.js +18 -12
- package/dist/runtime/electron/index.d.ts +2 -1
- package/dist/runtime/electron/index.js +2 -1
- package/genDevDesktop.js +50 -3
- package/package.json +22 -20
- package/src/module.ts +45 -18
- package/src/runtime/electron/createPriviledgedProtocolScheme.ts +40 -0
- package/src/runtime/electron/createPrivilegedProtocolScheme.ts +40 -0
- package/src/runtime/electron/createProxiedProtocolHandler.ts +229 -0
- package/src/runtime/electron/getPaths.ts +32 -27
- package/src/runtime/electron/index.ts +2 -1
- package/dist/runtime/electron/createNuxtFileProtocolHandler.d.ts +0 -14
- package/dist/runtime/electron/createNuxtFileProtocolHandler.js +0 -20
- package/src/runtime/electron/createNuxtFileProtocolHandler.ts +0 -42
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { keys } from "@alanscodelog/utils/keys";
|
|
2
|
+
import { pick } from "@alanscodelog/utils/pick";
|
|
3
|
+
import { net } from "electron";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
const cache = /* @__PURE__ */ new Map();
|
|
8
|
+
async function getPathToServe(originalPath, pathWithIndexHtml, key, logger) {
|
|
9
|
+
if (cache.has(key)) {
|
|
10
|
+
const result = cache.get(key);
|
|
11
|
+
if (logger) {
|
|
12
|
+
logger.trace({
|
|
13
|
+
ns: "main:createProxiedProtocolHandler:cacheHit",
|
|
14
|
+
requestUrl: key,
|
|
15
|
+
result: cache.get(key)
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
const stats = await fs.stat(originalPath).catch(() => void 0);
|
|
21
|
+
if (logger) {
|
|
22
|
+
logger.trace({
|
|
23
|
+
ns: "main:createProxiedProtocolHandler:stats",
|
|
24
|
+
key,
|
|
25
|
+
stats,
|
|
26
|
+
isFile: stats?.isFile(),
|
|
27
|
+
isDirectory: stats?.isDirectory()
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const pathToServe = stats !== void 0 ? stats.isFile() ? originalPath : stats.isDirectory() ? (await fs.stat(pathWithIndexHtml).catch(() => void 0))?.isFile() ? pathWithIndexHtml : void 0 : void 0 : void 0;
|
|
31
|
+
cache.set(key, pathToServe);
|
|
32
|
+
return pathToServe;
|
|
33
|
+
}
|
|
34
|
+
export function createProxiedProtocolHandler(protocol, protocolName = "app", basePath, routeProxies, {
|
|
35
|
+
logger,
|
|
36
|
+
errorPage = "404.html"
|
|
37
|
+
} = {}) {
|
|
38
|
+
const routeProxyKeys = keys(routeProxies);
|
|
39
|
+
if (protocol.isProtocolHandled(protocolName)) {
|
|
40
|
+
throw new Error(`Protocol ${protocolName} is already handled.`);
|
|
41
|
+
}
|
|
42
|
+
if (routeProxyKeys.length > 0) {
|
|
43
|
+
if (!process.env.PUBLIC_SERVER_URL && !process.env.VITE_DEV_URL && !process.env.PUBLIC_SERVER_URL) {
|
|
44
|
+
throw new Error("You defined proxy routes but didn't set PUBLIC_SERVER_URL or VITE_DEV_URL set. This is required for the /api routes to work.");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
let errorPage404;
|
|
48
|
+
protocol.handle(protocolName, async (request) => {
|
|
49
|
+
errorPage404 ??= await getPathToServe(path.join(basePath, errorPage), "404.html", "404.html", logger);
|
|
50
|
+
if (!errorPage404) {
|
|
51
|
+
throw new Error(`Error page ${path.join(basePath, errorPage)} does not exist. Did you override the routeRules for it?`);
|
|
52
|
+
}
|
|
53
|
+
const parsedUrl = new URL(request.url);
|
|
54
|
+
const requestPath = decodeURIComponent(parsedUrl.pathname);
|
|
55
|
+
const proxyKey = routeProxyKeys.find((key) => requestPath.startsWith(key));
|
|
56
|
+
if (proxyKey) {
|
|
57
|
+
const proxyUrl = routeProxies[proxyKey];
|
|
58
|
+
const finalPath2 = proxyUrl + requestPath;
|
|
59
|
+
if (logger) {
|
|
60
|
+
logger.trace({
|
|
61
|
+
ns: "main:createProxiedProtocolHandler:fetchingViaProxy",
|
|
62
|
+
requestUrl: request.url,
|
|
63
|
+
proxyKey,
|
|
64
|
+
route: proxyUrl,
|
|
65
|
+
requestPath,
|
|
66
|
+
finalPath: finalPath2
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return net.fetch(finalPath2, {
|
|
70
|
+
...pick(request, ["headers", "destination", "referrer", "referrerPolicy", "mode", "credentials", "cache", "redirect", "integrity", "keepalive"])
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const originalPath = path.join(basePath, requestPath);
|
|
74
|
+
const pathWithIndexHtml = path.join(originalPath, "index.html");
|
|
75
|
+
const relativePath = path.relative(basePath, originalPath);
|
|
76
|
+
const isSafe = !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
|
|
77
|
+
if (!isSafe) {
|
|
78
|
+
if (logger) {
|
|
79
|
+
logger.error({
|
|
80
|
+
ns: "main:createProxiedProtocolHandler:badRequest",
|
|
81
|
+
request: pick(request, [
|
|
82
|
+
"url",
|
|
83
|
+
"headers",
|
|
84
|
+
"destination",
|
|
85
|
+
"referrer",
|
|
86
|
+
"referrerPolicy",
|
|
87
|
+
"mode",
|
|
88
|
+
"credentials",
|
|
89
|
+
"cache",
|
|
90
|
+
"redirect",
|
|
91
|
+
"integrity",
|
|
92
|
+
"keepalive",
|
|
93
|
+
// these also seem to exist
|
|
94
|
+
...["isReloadNavigation", "isHistoryNavigation"]
|
|
95
|
+
]),
|
|
96
|
+
requestPath,
|
|
97
|
+
pathWithIndexHtml,
|
|
98
|
+
relativePath,
|
|
99
|
+
originalPath,
|
|
100
|
+
isSafe
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return new Response(JSON.stringify({
|
|
104
|
+
error: "Bad Request - Unsafe Path"
|
|
105
|
+
}), {
|
|
106
|
+
headers: { "content-type": "application/json" },
|
|
107
|
+
status: 400
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const pathToServe = await getPathToServe(originalPath, pathWithIndexHtml, request.url, logger);
|
|
111
|
+
if (!pathToServe) {
|
|
112
|
+
if (logger) {
|
|
113
|
+
logger.error({
|
|
114
|
+
ns: "main:createProxiedProtocolHandler:noFileFound",
|
|
115
|
+
requestUrl: request.url,
|
|
116
|
+
originalPath,
|
|
117
|
+
requestPath,
|
|
118
|
+
pathWithIndexHtml,
|
|
119
|
+
pathToServe,
|
|
120
|
+
errorPath: pathToFileURL(errorPage404).toString()
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
const res = await net.fetch(pathToFileURL(errorPage404).toString(), {
|
|
124
|
+
// see below
|
|
125
|
+
bypassCustomProtocolHandlers: true
|
|
126
|
+
}).catch((err) => err);
|
|
127
|
+
return res;
|
|
128
|
+
}
|
|
129
|
+
const finalPath = pathToFileURL(pathToServe).toString();
|
|
130
|
+
if (logger) {
|
|
131
|
+
logger.trace({
|
|
132
|
+
ns: "main:createProxiedProtocolHandler:fetchingFile",
|
|
133
|
+
requestUrl: request.url,
|
|
134
|
+
finalPath,
|
|
135
|
+
requestPath,
|
|
136
|
+
originalPath
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
const response = await net.fetch(finalPath, {
|
|
140
|
+
// avoid infinite loop if protocolName protocol is "file"
|
|
141
|
+
// or file is otherwise handled
|
|
142
|
+
bypassCustomProtocolHandlers: true
|
|
143
|
+
}).catch((err) => {
|
|
144
|
+
if (logger) {
|
|
145
|
+
logger.error({
|
|
146
|
+
ns: "main:createProxiedProtocolHandler:fetchError",
|
|
147
|
+
requestUrl: request.url,
|
|
148
|
+
finalPath,
|
|
149
|
+
requestPath,
|
|
150
|
+
originalPath,
|
|
151
|
+
err
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return err;
|
|
155
|
+
});
|
|
156
|
+
return response;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
@@ -1,25 +1,17 @@
|
|
|
1
|
-
export declare function forceRelativePath(filepath: string): string;
|
|
2
1
|
/**
|
|
3
2
|
* Calculates the correct paths for the app to run in electron.
|
|
4
3
|
*
|
|
5
|
-
* Note: For serverUrl to not be overridable in production, you should set `electron.additionalElectronVariables.publicServerUrl` to a quoted string.
|
|
6
4
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* additionalElectronVariables: {
|
|
14
|
-
* publicServerUrl: process.env.NODE_ENV === "production"
|
|
15
|
-
* ? `"mysite.com"`
|
|
16
|
-
* : `undefined`
|
|
17
|
-
* }
|
|
18
|
-
* }
|
|
5
|
+
* Paths are not overridable in production unless you pass the variables that can override them.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* const paths = getPaths("app", {
|
|
9
|
+
* windowUrl: process.env.OVERRIDE_WINDOW_URL,
|
|
10
|
+
* publicServerUrl: process.env.OVERRIDE_PUBLIC_SERVER_URL
|
|
19
11
|
* })
|
|
20
12
|
* ```
|
|
21
13
|
*/
|
|
22
|
-
export declare function getPaths(): {
|
|
14
|
+
export declare function getPaths(protocolName?: string, overridingEnvs?: Record<string, string | undefined>): {
|
|
23
15
|
windowUrl: string;
|
|
24
16
|
publicServerUrl: string;
|
|
25
17
|
nuxtPublicDir: string;
|
|
@@ -1,32 +1,38 @@
|
|
|
1
1
|
import { unreachable } from "@alanscodelog/utils/unreachable";
|
|
2
|
-
import { app } from "electron";
|
|
3
2
|
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { STATIC } from "./static.js";
|
|
5
|
-
export function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
const nuxtPublicDir = path.
|
|
11
|
-
const preloadPath = path.
|
|
5
|
+
export function getPaths(protocolName = "app", overridingEnvs = {
|
|
6
|
+
windowUrl: void 0,
|
|
7
|
+
publicServerUrl: void 0
|
|
8
|
+
}) {
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const nuxtPublicDir = path.join(__dirname, STATIC.ELECTRON_NUXT_PUBLIC_DIR);
|
|
11
|
+
const preloadPath = path.join(__dirname, STATIC.ELECTRON_BUILD_DIR, "./preload.cjs");
|
|
12
12
|
const base = {
|
|
13
13
|
nuxtPublicDir,
|
|
14
14
|
preloadPath,
|
|
15
|
-
publicServerUrl:
|
|
15
|
+
publicServerUrl: overridingEnvs.publicServerUrl ?? process.env.PUBLIC_SERVER_URL ?? process.env.VITE_DEV_SERVER_URL
|
|
16
16
|
};
|
|
17
17
|
if (!base.publicServerUrl) {
|
|
18
18
|
throw new Error("publicServerUrl could not be determined.");
|
|
19
19
|
}
|
|
20
|
-
if (
|
|
20
|
+
if (overridingEnvs.windowUrl) {
|
|
21
|
+
return {
|
|
22
|
+
...base,
|
|
23
|
+
windowUrl: `${overridingEnvs.windowUrl}${STATIC.ELECTRON_ROUTE}`
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (process.env.NODE_ENV === "production" && process.env.VITE_DEV_SERVER_URL) {
|
|
21
27
|
return {
|
|
22
28
|
...base,
|
|
23
29
|
windowUrl: `${process.env.VITE_DEV_SERVER_URL}${STATIC.ELECTRON_ROUTE}`
|
|
24
30
|
};
|
|
25
|
-
} else if (STATIC.ELECTRON_PROD_URL && STATIC.ELECTRON_BUILD_DIR) {
|
|
31
|
+
} else if (STATIC.ELECTRON_PROD_URL !== void 0 && STATIC.ELECTRON_BUILD_DIR !== void 0) {
|
|
26
32
|
return {
|
|
27
33
|
...base,
|
|
28
34
|
// careful, do not use path.join, it will remove extra slashes
|
|
29
|
-
windowUrl:
|
|
35
|
+
windowUrl: `${protocolName}://bundle/${STATIC.ELECTRON_PROD_URL}`
|
|
30
36
|
};
|
|
31
37
|
}
|
|
32
38
|
unreachable();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./types";
|
|
2
2
|
export { getPaths } from "./getPaths";
|
|
3
|
-
export {
|
|
3
|
+
export { createProxiedProtocolHandler } from "./createProxiedProtocolHandler";
|
|
4
|
+
export { createPrivilegedProtocolScheme } from "./createPrivilegedProtocolScheme";
|
|
4
5
|
export { createWindowControlsApi } from "./createWindowControlsApi";
|
|
5
6
|
export { createWindowControlsApiHandler } from "./createWindowControlsApiHandler";
|
|
6
7
|
export { useNuxtRuntimeConfig } from "./useNuxtRuntimeConfig";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./types.js";
|
|
2
2
|
export { getPaths } from "./getPaths.js";
|
|
3
|
-
export {
|
|
3
|
+
export { createProxiedProtocolHandler } from "./createProxiedProtocolHandler.js";
|
|
4
|
+
export { createPrivilegedProtocolScheme } from "./createPrivilegedProtocolScheme.js";
|
|
4
5
|
export { createWindowControlsApi } from "./createWindowControlsApi.js";
|
|
5
6
|
export { createWindowControlsApiHandler } from "./createWindowControlsApiHandler.js";
|
|
6
7
|
export { useNuxtRuntimeConfig } from "./useNuxtRuntimeConfig.js";
|
package/genDevDesktop.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { crop } from "@alanscodelog/utils/crop"
|
|
1
2
|
import { run } from "@alanscodelog/utils/run"
|
|
2
3
|
import JSON5 from "json5"
|
|
4
|
+
import fsSync from "node:fs"
|
|
3
5
|
import fs from "node:fs/promises"
|
|
4
6
|
import path from "node:path"
|
|
5
7
|
|
|
6
8
|
if (process.env.CI) {
|
|
9
|
+
// eslint-disable-next-line no-console
|
|
7
10
|
console.log("Skipping desktop file generation in CI.")
|
|
8
11
|
process.exit(0)
|
|
9
12
|
}
|
|
@@ -15,10 +18,54 @@ if (["darwin", "linux"].includes(process.platform)) {
|
|
|
15
18
|
process.exit(0)
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
const
|
|
21
|
+
function getConfigPath() {
|
|
22
|
+
const electronBuilderConfigJsPath = path.join(process.cwd(), "electron-builder-config.js")
|
|
23
|
+
const electronBuilderConfigJson5Path = path.join(process.cwd(), "electron-builder.json5")
|
|
24
|
+
const electronBuilderConfigJsonPath = path.join(process.cwd(), "electron-builder.json")
|
|
25
|
+
const existingPath = fsSync.existsSync(electronBuilderConfigJsPath)
|
|
26
|
+
? electronBuilderConfigJsPath
|
|
27
|
+
: fsSync.existsSync(electronBuilderConfigJsonPath)
|
|
28
|
+
? electronBuilderConfigJsonPath
|
|
29
|
+
: fsSync.existsSync(electronBuilderConfigJson5Path)
|
|
30
|
+
? electronBuilderConfigJson5Path
|
|
31
|
+
: undefined
|
|
32
|
+
if (!existingPath) {
|
|
33
|
+
throw new Error(crop`Electron builder config file could not be found at the default locations. If you are using a different file path, please pass it as the second argument.
|
|
34
|
+
|
|
35
|
+
Default search locations:
|
|
36
|
+
- electron-builder-config.js (this cannot be named electron-builder.js, see, https://github.com/electron-userland/electron-builder/issues/6227)
|
|
37
|
+
- electron-builder.json5
|
|
38
|
+
- electron-builder.json
|
|
39
|
+
`
|
|
40
|
+
)
|
|
41
|
+
} else {
|
|
42
|
+
// eslint-disable-next-line no-console
|
|
43
|
+
console.log(`Using config path ${existingPath}.`)
|
|
44
|
+
}
|
|
45
|
+
return existingPath
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function getConfig(pathOverride) {
|
|
49
|
+
const configPath = pathOverride ?? getConfigPath()
|
|
50
|
+
/** @type {import("electron-builder").Configuration} */
|
|
51
|
+
let config
|
|
52
|
+
if (configPath.endsWith(".js")) {
|
|
53
|
+
config = (await import(configPath)).default
|
|
54
|
+
} else {
|
|
55
|
+
config = JSON5.parse(await fs.readFile(configPath, "utf-8"))
|
|
56
|
+
}
|
|
57
|
+
if (process.env.DEBUG) {
|
|
58
|
+
// eslint-disable-next-line no-console
|
|
59
|
+
console.log(config)
|
|
60
|
+
}
|
|
61
|
+
return config
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const configPathOverride = process.argv[4]
|
|
65
|
+
|
|
66
|
+
const config = await getConfig(configPathOverride)
|
|
20
67
|
|
|
21
|
-
const desktopFile =
|
|
68
|
+
const desktopFile = config.linux?.desktop
|
|
22
69
|
|
|
23
70
|
desktopFile.Path = process.cwd()
|
|
24
71
|
desktopFile.Exec = process.argv[3] ?? `npm run launch:electron "%u"`
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@witchcraft/nuxt-electron",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Nuxt module for working with electron.",
|
|
5
|
-
"repository": "witchcraftjs/nuxt-electron",
|
|
5
|
+
"repository": "https://github.com/witchcraftjs/nuxt-electron",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"sideEffects": false,
|
|
@@ -26,17 +26,18 @@
|
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@alanscodelog/utils": "^6.0.2",
|
|
29
|
-
"@nuxt/kit": "^4.
|
|
29
|
+
"@nuxt/kit": "^4.3.1",
|
|
30
30
|
"@witchcraft/nuxt-utils": "^0.3.6",
|
|
31
|
-
"@witchcraft/ui": "^0.3.
|
|
31
|
+
"@witchcraft/ui": "^0.3.24",
|
|
32
32
|
"defu": "^6.1.4",
|
|
33
|
-
"
|
|
33
|
+
"electron": "^37.10.3",
|
|
34
|
+
"es-toolkit": "^1.44.0",
|
|
34
35
|
"vite-plugin-electron": "^0.29.0",
|
|
35
36
|
"vite-plugin-externalize-deps": "^0.9.0"
|
|
36
37
|
},
|
|
37
38
|
"peerDependencies": {
|
|
38
|
-
"@witchcraft/ui": "^0.3.
|
|
39
|
-
"unplugin-icons": "^22.
|
|
39
|
+
"@witchcraft/ui": "^0.3.24",
|
|
40
|
+
"unplugin-icons": "^22.5.0"
|
|
40
41
|
},
|
|
41
42
|
"peerDependenciesMeta": {
|
|
42
43
|
"unplugin-icons": {
|
|
@@ -48,23 +49,22 @@
|
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@alanscodelog/eslint-config": "^6.3.1",
|
|
51
|
-
"@alanscodelog/semantic-release-config": "^6.0.
|
|
52
|
+
"@alanscodelog/semantic-release-config": "^6.0.2",
|
|
52
53
|
"@alanscodelog/tsconfigs": "^6.2.0",
|
|
53
|
-
"@nuxt/eslint-config": "^1.
|
|
54
|
+
"@nuxt/eslint-config": "^1.15.1",
|
|
54
55
|
"@nuxt/module-builder": "^1.0.2",
|
|
55
|
-
"@nuxt/schema": "^4.
|
|
56
|
-
"@nuxt/test-utils": "^3.
|
|
56
|
+
"@nuxt/schema": "^4.3.1",
|
|
57
|
+
"@nuxt/test-utils": "^3.23.0",
|
|
57
58
|
"@types/node": "latest",
|
|
58
59
|
"changelogen": "^0.6.2",
|
|
59
|
-
"
|
|
60
|
-
"eslint": "^9.34.0",
|
|
60
|
+
"eslint": "^9.39.2",
|
|
61
61
|
"husky": "^9.1.7",
|
|
62
62
|
"json5": "^2.2.3",
|
|
63
|
-
"nuxt": "^4.
|
|
64
|
-
"typescript": "^5.
|
|
65
|
-
"unplugin-icons": "^22.
|
|
63
|
+
"nuxt": "^4.3.1",
|
|
64
|
+
"typescript": "^5.9.3",
|
|
65
|
+
"unplugin-icons": "^22.5.0",
|
|
66
66
|
"vitest": "^3.2.4",
|
|
67
|
-
"vue-tsc": "^3.
|
|
67
|
+
"vue-tsc": "^3.2.4"
|
|
68
68
|
},
|
|
69
69
|
"release": {
|
|
70
70
|
"extends": [
|
|
@@ -80,11 +80,13 @@
|
|
|
80
80
|
"access": "public"
|
|
81
81
|
},
|
|
82
82
|
"scripts": {
|
|
83
|
-
"build": "nuxt-module-build prepare && nuxt-module-build build &&
|
|
83
|
+
"build": "nuxt-module-build prepare && nuxt-module-build build && pnpm build:playground",
|
|
84
|
+
"build:playground": "cd playground && pnpm nuxt prepare && pnpm build:electron",
|
|
84
85
|
"build:only": "nuxt-module-build build",
|
|
85
|
-
"dev": "
|
|
86
|
+
"dev": "cd playground && pnpm dev:electron",
|
|
86
87
|
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
|
|
87
|
-
"lint": "eslint
|
|
88
|
+
"lint": "pnpm lint:eslint && pnpm lint:types",
|
|
89
|
+
"lint:eslint": "eslint \"{src,test,playground/app}/**/*.{ts,vue}\" \"*.{js,cjs,mjs,ts}\"",
|
|
88
90
|
"lint:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit",
|
|
89
91
|
"test": "vitest run",
|
|
90
92
|
"test:watch": "vitest watch"
|
package/src/module.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
extendRouteRules,
|
|
10
10
|
useLogger
|
|
11
11
|
} from "@nuxt/kit"
|
|
12
|
-
import { createConstantCaseVariables,
|
|
12
|
+
import { createConstantCaseVariables, nuxtRemoveUneededPages, nuxtRerouteOutputTo } from "@witchcraft/nuxt-utils/utils"
|
|
13
13
|
import { defu } from "defu"
|
|
14
14
|
import fs from "node:fs/promises"
|
|
15
15
|
import path from "node:path"
|
|
@@ -20,7 +20,6 @@ import { build, type ElectronOptions, startup } from "vite-plugin-electron"
|
|
|
20
20
|
import { notBundle } from "vite-plugin-electron/plugin"
|
|
21
21
|
import { externalizeDeps } from "vite-plugin-externalize-deps"
|
|
22
22
|
|
|
23
|
-
import pkg from "../package.json" with { type: "json" }
|
|
24
23
|
|
|
25
24
|
// https://github.com/electron-vite/vite-plugin-electron/issues/251#issuecomment-2360153184
|
|
26
25
|
startup.exit = async () => {
|
|
@@ -206,13 +205,14 @@ export default defineNuxtModule<ModuleOptions>({
|
|
|
206
205
|
const srcDir = await resolvePath(options.srcDir, nuxt.options.alias)
|
|
207
206
|
const nonElectronNuxtBuildDir = await resolvePath(options.nonElectronNuxtBuildDir, nuxt.options.alias)
|
|
208
207
|
|
|
208
|
+
// at this point they are made relative to the nuxt dir
|
|
209
|
+
// for passing to electron we make them relative to main
|
|
209
210
|
const electronRootBuildDir = await resolvePath(options.electronBuildDir, nuxt.options.alias)
|
|
210
211
|
const relativeElectronDir = path.relative(nuxt.options.rootDir, electronRootBuildDir)
|
|
211
212
|
// must be relative
|
|
212
213
|
const electronNuxtDir = path.join(relativeElectronDir, ".output")
|
|
213
214
|
// must be relative
|
|
214
215
|
const electronBuildDir = path.join(relativeElectronDir, "build")
|
|
215
|
-
// relative to nuxt dir
|
|
216
216
|
const electronProdUrl = `${options.electronRoute}/index.html`
|
|
217
217
|
const electronRoute = options.electronRoute
|
|
218
218
|
|
|
@@ -230,7 +230,7 @@ export default defineNuxtModule<ModuleOptions>({
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
const isElectronBuild = process.env.BUILD_ELECTRON === "true" && hasScripts
|
|
233
|
-
const skipElectronPack = process.env.SKIP_ELECTRON_PACK === "true"
|
|
233
|
+
const skipElectronPack = !isElectronBuild || process.env.SKIP_ELECTRON_PACK === "true"
|
|
234
234
|
|
|
235
235
|
const autoOpen = !!(options.autoOpen && hasScripts && isDev)
|
|
236
236
|
const useWatch = nuxt.options.dev
|
|
@@ -295,6 +295,7 @@ export default defineNuxtModule<ModuleOptions>({
|
|
|
295
295
|
let started = false
|
|
296
296
|
|
|
297
297
|
nuxt.hook("vite:extendConfig", config => {
|
|
298
|
+
// @ts-expect-error it's both readonly and possible undefined???
|
|
298
299
|
config.define ??= {}
|
|
299
300
|
config.define["import.meta.electron"] = "false"
|
|
300
301
|
config.define["process.electron"] = "false"
|
|
@@ -313,11 +314,11 @@ export default defineNuxtModule<ModuleOptions>({
|
|
|
313
314
|
)
|
|
314
315
|
const additionalElectronVariables = defu(
|
|
315
316
|
options.additionalElectronVariables,
|
|
316
|
-
nuxt.options.electron.additionalElectronVariables
|
|
317
|
+
(nuxt.options.electron === false ? {} : nuxt.options.electron.additionalElectronVariables)
|
|
317
318
|
)
|
|
318
319
|
const additionalViteDefinesToCopy = [
|
|
319
320
|
...options.additionalViteDefinesToCopy,
|
|
320
|
-
...(nuxt.options.electron.additionalViteDefinesToCopy ?? [])
|
|
321
|
+
...(nuxt.options.electron === false ? [] : (nuxt.options.electron.additionalViteDefinesToCopy ?? []))
|
|
321
322
|
]
|
|
322
323
|
|
|
323
324
|
const copyFromVite = [
|
|
@@ -337,11 +338,13 @@ export default defineNuxtModule<ModuleOptions>({
|
|
|
337
338
|
copyFromVite.map(v => [v, viteConfig.define![v]])
|
|
338
339
|
),
|
|
339
340
|
...createConstantCaseVariables({
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
electronBuildDir,
|
|
341
|
+
// on windows the backslashes in the paths must be double escaped
|
|
342
|
+
electronRoute: electronRoute.replaceAll("\\", "\\\\"),
|
|
343
|
+
electronProdUrl: electronProdUrl.replaceAll("\\", "\\\\"),
|
|
344
|
+
// all paths muxt be made relative to the build dir where electron will launch from in production
|
|
345
|
+
electronNuxtDir: path.relative(electronBuildDir, relativeElectronDir).replaceAll("\\", "\\\\"),
|
|
346
|
+
electronNuxtPublicDir: path.relative(electronBuildDir, electronNuxtPublicDir).replaceAll("\\", "\\\\"),
|
|
347
|
+
electronBuildDir: path.relative(electronBuildDir, electronBuildDir).replaceAll("\\", "\\\\"),
|
|
345
348
|
// ...options.additionalElectronVariables,
|
|
346
349
|
// nuxt's runtimeConfig cannot be used in electron's main since it's built seperately
|
|
347
350
|
// also we must stringify ourselves since escaped double quotes are not preserved in the final output :/
|
|
@@ -514,12 +517,28 @@ export default defineNuxtModule<ModuleOptions>({
|
|
|
514
517
|
|
|
515
518
|
// the "/" is not technically needed but only if we properly split the chunks by pages and that was causing issues
|
|
516
519
|
nuxtRemoveUneededPages(nuxt, ["/", electronRoute, ...options.additionalRoutes!])
|
|
517
|
-
extendRouteRules(electronRoute, { ssr: false, prerender: true }, { override: true })
|
|
518
520
|
|
|
519
|
-
nuxt.options.router
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
521
|
+
nuxt.options.router.options ??= {}
|
|
522
|
+
nuxt.options.router.options.hashMode = false
|
|
523
|
+
|
|
524
|
+
extendRouteRules(electronRoute + "/**", {
|
|
525
|
+
prerender: true
|
|
526
|
+
}, { override: true })
|
|
527
|
+
|
|
528
|
+
for (const route of options.additionalRoutes) {
|
|
529
|
+
extendRouteRules(route, {
|
|
530
|
+
prerender: true
|
|
531
|
+
}, { override: true })
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
/* Nuxt in build mode won't generate this. And we need build mode so api calls aren't baked in. */
|
|
536
|
+
nuxt.hook("prerender:routes", ({ routes }) => {
|
|
537
|
+
for (const route of ["/404.html"]) {
|
|
538
|
+
routes.add(route)
|
|
539
|
+
}
|
|
540
|
+
})
|
|
541
|
+
|
|
523
542
|
|
|
524
543
|
nuxtRerouteOutputTo(nuxt, electronNuxtDir)
|
|
525
544
|
|
|
@@ -550,8 +569,16 @@ export default defineNuxtModule<ModuleOptions>({
|
|
|
550
569
|
} else {
|
|
551
570
|
logger.info(`Skipping Electron Build`)
|
|
552
571
|
}
|
|
553
|
-
|
|
554
|
-
|
|
572
|
+
const nuxtOutputDir = nuxt.options.nitro?.output?.dir
|
|
573
|
+
if (nuxtOutputDir === undefined || nuxtOutputDir === ".output") {
|
|
574
|
+
logger.warn(crop`Nitro output dir is not set or set to the default, it's suggested you set it to the following when using nuxt-electron:
|
|
575
|
+
nitro: {
|
|
576
|
+
output: ".dist/web/.output",
|
|
577
|
+
serverDir: ".dist/web/.output/server"
|
|
578
|
+
publicDir: ".dist/web/.output/public"
|
|
579
|
+
}
|
|
580
|
+
.`)
|
|
581
|
+
}
|
|
555
582
|
}
|
|
556
583
|
|
|
557
584
|
addImportsDir(resolve("runtime/utils"))
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a protocol scheme to register as privileged with the following permissions (you can override them with the second parameter).
|
|
3
|
+
*
|
|
4
|
+
* Note this must be called before the app's ready event:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* protocol.registerSchemesAsPrivileged([
|
|
8
|
+
* createPrivilegedProtocolScheme("app")
|
|
9
|
+
* })
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* ## Privileges
|
|
13
|
+
*
|
|
14
|
+
* Basically all except `bypassCSP` and `enableCORS`.
|
|
15
|
+
*
|
|
16
|
+
* - `standard`
|
|
17
|
+
* - `allowServiceWorkers`
|
|
18
|
+
* - `bypassCSP`
|
|
19
|
+
* - `corsEnabled`
|
|
20
|
+
* - `secure`
|
|
21
|
+
* - `supportFetchAPI`
|
|
22
|
+
* - `codeCache`
|
|
23
|
+
* - `stream`
|
|
24
|
+
*/
|
|
25
|
+
export function createPrivilegedProtocolScheme(protocolName: string, privileges: Partial<Electron.CustomScheme["privileges"]> = {}): Electron.CustomScheme {
|
|
26
|
+
return {
|
|
27
|
+
scheme: protocolName,
|
|
28
|
+
privileges: {
|
|
29
|
+
bypassCSP: false,
|
|
30
|
+
corsEnabled: false,
|
|
31
|
+
stream: false,
|
|
32
|
+
standard: true,
|
|
33
|
+
secure: true,
|
|
34
|
+
supportFetchAPI: true,
|
|
35
|
+
allowServiceWorkers: true,
|
|
36
|
+
codeCache: true,
|
|
37
|
+
...privileges
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a protocol scheme to register as privileged with the following permissions (you can override them with the second parameter).
|
|
3
|
+
*
|
|
4
|
+
* Note this must be called before the app's ready event:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* protocol.registerSchemesAsPrivileged([
|
|
8
|
+
* createPrivilegedProtocolScheme("app")
|
|
9
|
+
* })
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* ## Privileges
|
|
13
|
+
*
|
|
14
|
+
* Basically all except `bypassCSP` and `enableCORS`.
|
|
15
|
+
*
|
|
16
|
+
* - `standard`
|
|
17
|
+
* - `allowServiceWorkers`
|
|
18
|
+
* - `bypassCSP`
|
|
19
|
+
* - `corsEnabled`
|
|
20
|
+
* - `secure`
|
|
21
|
+
* - `supportFetchAPI`
|
|
22
|
+
* - `codeCache`
|
|
23
|
+
* - `stream`
|
|
24
|
+
*/
|
|
25
|
+
export function createPrivilegedProtocolScheme(protocolName: string, privileges: Partial<Electron.CustomScheme["privileges"]> = {}): Electron.CustomScheme {
|
|
26
|
+
return {
|
|
27
|
+
scheme: protocolName,
|
|
28
|
+
privileges: {
|
|
29
|
+
bypassCSP: false,
|
|
30
|
+
corsEnabled: false,
|
|
31
|
+
stream: false,
|
|
32
|
+
standard: true,
|
|
33
|
+
secure: true,
|
|
34
|
+
supportFetchAPI: true,
|
|
35
|
+
allowServiceWorkers: true,
|
|
36
|
+
codeCache: true,
|
|
37
|
+
...privileges
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|