unplugin-https-reverse-proxy 4.0.3 → 4.1.0-beta.10
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 +30 -6
- package/dist/{chunk-RSBXFJ3Z.js → chunk-C5A7XLKL.js} +9 -2
- package/dist/{chunk-2P7SUCQ3.js → chunk-EY3HXBJ4.js} +0 -26
- package/dist/chunk-RKHJN6KY.js +53 -0
- package/dist/{chunk-MD5CVZP7.js → chunk-U7KAIPZC.js} +982 -797
- package/dist/chunk-WOMR3ZLM.js +17 -0
- package/dist/esbuild.cjs +1086 -867
- package/dist/esbuild.js +5 -3
- package/dist/hmr-sse-bridge.cjs +12 -4
- package/dist/hmr-sse-bridge.d.cts +4 -3
- package/dist/hmr-sse-bridge.d.ts +4 -3
- package/dist/hmr-sse-bridge.js +5 -3
- package/dist/index.cjs +1086 -867
- package/dist/index.js +5 -3
- package/dist/nuxt.cjs +1057 -852
- package/dist/nuxt.js +6 -9
- package/dist/privilege-state.cjs +44 -0
- package/dist/privilege-state.d.cts +7 -0
- package/dist/privilege-state.d.ts +7 -0
- package/dist/privilege-state.js +10 -0
- package/dist/privileged-helper.cjs +824 -0
- package/dist/privileged-helper.d.cts +2 -0
- package/dist/privileged-helper.d.ts +2 -0
- package/dist/privileged-helper.js +797 -0
- package/dist/rollup.cjs +1086 -867
- package/dist/rollup.js +5 -3
- package/dist/rspack.cjs +1086 -867
- package/dist/rspack.js +5 -3
- package/dist/utils.cjs +0 -28
- package/dist/utils.d.cts +1 -3
- package/dist/utils.d.ts +1 -3
- package/dist/utils.js +1 -5
- package/dist/vite.cjs +1086 -867
- package/dist/vite.js +5 -3
- package/dist/webpack.cjs +1086 -867
- package/dist/webpack.js +5 -3
- package/package.json +1 -4
- package/src/caddy/caddyfile.ts +3 -0
- package/src/caddy/config.ts +195 -53
- package/src/caddy/constants.ts +30 -1
- package/src/caddy/index.ts +239 -206
- package/src/caddy/privileged.ts +124 -0
- package/src/caddy/utils.ts +0 -43
- package/src/hmr-sse-bridge.ts +14 -3
- package/src/index.ts +52 -25
- package/src/nuxt.ts +1 -6
- package/src/privilege-state.ts +23 -0
- package/src/privileged/bootstrap.ts +289 -0
- package/src/privileged/client.ts +288 -0
- package/src/privileged/entry.ts +73 -0
- package/src/privileged/hosts-file.ts +142 -0
- package/src/privileged/launch.ts +332 -0
- package/src/privileged/path-guard.ts +74 -0
- package/src/privileged/ports.ts +45 -0
- package/src/privileged/protocol.ts +111 -0
- package/src/privileged/server.ts +578 -0
- package/src/privileged/session.ts +169 -0
- package/src/privileged/validation.ts +38 -0
- package/src/privileged/windows-elevation.ts +80 -0
- package/src/privileged-helper.ts +123 -0
- package/src/utils.ts +0 -28
- package/src/host/index.ts +0 -52
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { PrivilegedClient } from '../privileged/client'
|
|
2
|
+
import type { HelperEvent } from '../privileged/protocol'
|
|
3
|
+
import { Buffer } from 'node:buffer'
|
|
4
|
+
import { EventEmitter } from 'node:events'
|
|
5
|
+
import { dirname, join } from 'node:path'
|
|
6
|
+
import process from 'node:process'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
import { resolveHelperEntry } from '../privileged/entry'
|
|
9
|
+
import { openHelperSession } from '../privileged/session'
|
|
10
|
+
|
|
11
|
+
export interface PrivilegedEnv {
|
|
12
|
+
/** Executable started as root, resolved from the running bundle. */
|
|
13
|
+
helperEntry: string
|
|
14
|
+
helperArgs: string[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolves what the helper needs to be started from the current bundle.
|
|
19
|
+
* `__dirname` exists in both the CJS and ESM outputs (tsup injects a shim).
|
|
20
|
+
*/
|
|
21
|
+
export function resolvePrivilegedEnv(): PrivilegedEnv {
|
|
22
|
+
const moduleDir = typeof __dirname === 'string'
|
|
23
|
+
? __dirname
|
|
24
|
+
: dirname(fileURLToPath(import.meta.url))
|
|
25
|
+
const resolved = resolveHelperEntry({ moduleDir })
|
|
26
|
+
return { helperEntry: resolved.entry, helperArgs: resolved.args }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Re-emits helper events as the `stdout`/`stderr` events the log parsing expects. */
|
|
30
|
+
export class HelperCaddyProcess extends EventEmitter {
|
|
31
|
+
readonly stdout = new EventEmitter()
|
|
32
|
+
readonly stderr = new EventEmitter()
|
|
33
|
+
private finished = false
|
|
34
|
+
private readonly unsubscribe: () => void
|
|
35
|
+
|
|
36
|
+
constructor(private readonly client: PrivilegedClient) {
|
|
37
|
+
super()
|
|
38
|
+
this.unsubscribe = client.onEvent(event => this.handleEvent(event))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private handleEvent(event: HelperEvent): void {
|
|
42
|
+
if (event.type !== 'event')
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
if (event.kind === 'caddy.stdout') {
|
|
46
|
+
this.stdout.emit('data', Buffer.from(event.data))
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (event.kind === 'caddy.stderr') {
|
|
51
|
+
this.stderr.emit('data', Buffer.from(event.data))
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (event.kind === 'caddy.exit') {
|
|
56
|
+
this.finished = true
|
|
57
|
+
this.emit('exit', event.code, event.signal)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Asks the helper to stop Caddy; the helper also stops it on disconnect. */
|
|
62
|
+
kill(): void {
|
|
63
|
+
this.unsubscribe()
|
|
64
|
+
if (this.finished)
|
|
65
|
+
return
|
|
66
|
+
this.finished = true
|
|
67
|
+
void this.client.call('caddy.stop').catch(() => {})
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface OpenedPrivilegedSession {
|
|
72
|
+
client: PrivilegedClient
|
|
73
|
+
reused: boolean
|
|
74
|
+
close: () => Promise<void>
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface PrivilegedSessionOptions {
|
|
78
|
+
log?: (message: string) => void
|
|
79
|
+
/** Runs the helper as the current user; tests use this to avoid sudo. */
|
|
80
|
+
direct?: boolean
|
|
81
|
+
/** Caddy binary the helper may run; fixed before the session opens. */
|
|
82
|
+
caddyBin: string
|
|
83
|
+
/** Caddyfile the helper may load. */
|
|
84
|
+
caddyConfigPath: string
|
|
85
|
+
/** Admin API socket; `ca.trust` talks to it instead of the TCP default. */
|
|
86
|
+
caddyAdminSocket?: string
|
|
87
|
+
/** Shown before the password prompt so the request is not a surprise. */
|
|
88
|
+
reason?: { target: string, https: boolean }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Where Caddy keeps its PKI, per platform. */
|
|
92
|
+
export function caddyStoragePath(): string {
|
|
93
|
+
return process.platform === 'win32'
|
|
94
|
+
? join(process.env.AppData ?? '', 'Caddy')
|
|
95
|
+
: join(process.env.HOME ?? '', 'Library/Application Support/Caddy')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Opens (or reuses) the privileged helper for this runtime directory.
|
|
100
|
+
* Only the helper runs as root: the plugin process, its caches and its
|
|
101
|
+
* `node_modules` keep the developer's identity.
|
|
102
|
+
*/
|
|
103
|
+
export function openPrivilegedSession(
|
|
104
|
+
runtimeDir: string,
|
|
105
|
+
options: PrivilegedSessionOptions,
|
|
106
|
+
): Promise<OpenedPrivilegedSession> {
|
|
107
|
+
const env = resolvePrivilegedEnv()
|
|
108
|
+
return openHelperSession({
|
|
109
|
+
runtimeDir,
|
|
110
|
+
helperEntry: env.helperEntry,
|
|
111
|
+
helperArgs: env.helperArgs,
|
|
112
|
+
direct: options.direct,
|
|
113
|
+
// Caddy keeps its PKI under the user's home; the helper runs it as root, so
|
|
114
|
+
// it must know which tree to hand back afterwards.
|
|
115
|
+
caddyStoragePath: caddyStoragePath(),
|
|
116
|
+
// The binary and Caddyfile are fixed here: the privileged helper must never
|
|
117
|
+
// execute a path supplied over the socket.
|
|
118
|
+
caddyBin: options.caddyBin,
|
|
119
|
+
caddyConfigPath: options.caddyConfigPath,
|
|
120
|
+
caddyAdminSocket: options.caddyAdminSocket,
|
|
121
|
+
reason: options.reason,
|
|
122
|
+
log: options.log,
|
|
123
|
+
})
|
|
124
|
+
}
|
package/src/caddy/utils.ts
CHANGED
|
@@ -1,51 +1,8 @@
|
|
|
1
|
-
import { exec } from 'node:child_process'
|
|
2
|
-
import { createServer } from 'node:net'
|
|
3
1
|
import ora from 'ora'
|
|
4
2
|
import { consola } from '../utils'
|
|
5
3
|
|
|
6
4
|
let spinner: ora.Ora
|
|
7
5
|
|
|
8
|
-
/**
|
|
9
|
-
* @returns whether the port is in use
|
|
10
|
-
*/
|
|
11
|
-
export function tryPort(port: number): Promise<boolean> {
|
|
12
|
-
return new Promise<boolean>((resolve, _reject) => {
|
|
13
|
-
try {
|
|
14
|
-
const server = createServer().listen(port)
|
|
15
|
-
server.on('listening', () => {
|
|
16
|
-
server.close()
|
|
17
|
-
resolve(false)
|
|
18
|
-
})
|
|
19
|
-
server.on('error', (err) => {
|
|
20
|
-
// @ts-expect-error err.code
|
|
21
|
-
if (err.code === 'EADDRINUSE')
|
|
22
|
-
resolve(true)
|
|
23
|
-
server.close()
|
|
24
|
-
})
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
resolve(true)
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* npx free-port :port
|
|
34
|
-
*/
|
|
35
|
-
export async function kill(port: number): Promise<void> {
|
|
36
|
-
return new Promise<void>((resolve, reject) => {
|
|
37
|
-
exec(`npx @maxbo/free-port ${port} -s`, (err, stdout, _stderr) => {
|
|
38
|
-
if (err) {
|
|
39
|
-
reject(err)
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
consola.log(stdout)
|
|
43
|
-
resolve()
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
})
|
|
47
|
-
}
|
|
48
|
-
|
|
49
6
|
function getNumber(number: number): string {
|
|
50
7
|
if (number <= 0)
|
|
51
8
|
return '0%'
|
package/src/hmr-sse-bridge.ts
CHANGED
|
@@ -134,14 +134,25 @@ function handleClientMessage(
|
|
|
134
134
|
})
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
export function shouldUseHmrSseBridge(protocol: string, userAgent: string): boolean {
|
|
138
|
+
if (protocol !== 'https:')
|
|
139
|
+
return false
|
|
140
|
+
|
|
141
|
+
const isApplePlatform = /Macintosh|iPhone|iPad|iPod/.test(userAgent)
|
|
142
|
+
const isSafari = /Version\//.test(userAgent)
|
|
143
|
+
&& /Safari\//.test(userAgent)
|
|
144
|
+
&& !/(?:Chrome|Chromium|CriOS|Edg|EdgiOS|OPR|OPiOS|Firefox|FxiOS)\//.test(userAgent)
|
|
145
|
+
|
|
146
|
+
return isApplePlatform && isSafari
|
|
147
|
+
}
|
|
148
|
+
|
|
137
149
|
/**
|
|
138
|
-
* Client-side script that patches WebSocket to use SSE+fetch on iOS Safari.
|
|
150
|
+
* Client-side script that patches WebSocket to use SSE+fetch on macOS/iOS Safari.
|
|
139
151
|
* This script is injected via Vite's transformIndexHtml hook.
|
|
140
152
|
*/
|
|
141
153
|
export const hmrSseBridgeClientScript = `
|
|
142
154
|
(function() {
|
|
143
|
-
|
|
144
|
-
if (location.protocol !== 'https:') return;
|
|
155
|
+
if (!(${shouldUseHmrSseBridge.toString()})(location.protocol, navigator.userAgent)) return;
|
|
145
156
|
|
|
146
157
|
var OriginalWebSocket = window.WebSocket;
|
|
147
158
|
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,9 @@ import c from 'picocolors'
|
|
|
7
7
|
import { createUnplugin } from 'unplugin'
|
|
8
8
|
import { CaddyInstant } from './caddy'
|
|
9
9
|
import { hmrSseBridgeClientScript, setupHmrSseBridge } from './hmr-sse-bridge'
|
|
10
|
-
import {
|
|
10
|
+
import { isPrivilegeDenied, markPrivilegeDenied } from './privilege-state'
|
|
11
|
+
import { PrivilegeDeniedError } from './privileged/launch'
|
|
12
|
+
import { consola, generateQrcode, getIpv4List, once } from './utils'
|
|
11
13
|
|
|
12
14
|
let config: ResolvedConfig
|
|
13
15
|
|
|
@@ -62,10 +64,15 @@ export const vitePrintUrls = once((
|
|
|
62
64
|
) => {
|
|
63
65
|
_printUrls?.()
|
|
64
66
|
|
|
67
|
+
// Declined earlier in this process; a hot restart must not ask again.
|
|
68
|
+
if (isPrivilegeDenied())
|
|
69
|
+
return
|
|
70
|
+
|
|
65
71
|
if (caddy)
|
|
66
72
|
return
|
|
67
|
-
|
|
68
|
-
caddy
|
|
73
|
+
const instance = new CaddyInstant()
|
|
74
|
+
caddy = instance
|
|
75
|
+
instance.run(source, target, {
|
|
69
76
|
base,
|
|
70
77
|
...options,
|
|
71
78
|
}).then(async () => {
|
|
@@ -73,6 +80,16 @@ export const vitePrintUrls = once((
|
|
|
73
80
|
|
|
74
81
|
consola.success(` ${c.green('➜')} ${c.bold('run caddy reverse proxy success')}: ${colorUrl(`${options.https ? 'https' : 'http'}://${target}${base}`)}`)
|
|
75
82
|
}).catch((e) => {
|
|
83
|
+
// Declining the password is a choice, not a crash: drop the half-started
|
|
84
|
+
// instance so a later attempt can prompt again.
|
|
85
|
+
if (caddy === instance)
|
|
86
|
+
caddy = undefined as unknown as CaddyInstant
|
|
87
|
+
if (e instanceof PrivilegeDeniedError) {
|
|
88
|
+
markPrivilegeDenied()
|
|
89
|
+
consola.warn('reverse proxy skipped: administrator permission was not granted.')
|
|
90
|
+
consola.info('Restart the dev server to enable it again.')
|
|
91
|
+
return
|
|
92
|
+
}
|
|
76
93
|
throw e
|
|
77
94
|
})
|
|
78
95
|
})
|
|
@@ -96,10 +113,6 @@ export const unpluginFactory: UnpluginFactory<Options> = options => ({
|
|
|
96
113
|
if (!enable)
|
|
97
114
|
return
|
|
98
115
|
|
|
99
|
-
if (!isAdmin()) {
|
|
100
|
-
consola.warn('please run as administrator')
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
116
|
if (!target) {
|
|
104
117
|
consola.fail('please provide target')
|
|
105
118
|
return
|
|
@@ -123,17 +136,17 @@ export const unpluginFactory: UnpluginFactory<Options> = options => ({
|
|
|
123
136
|
|
|
124
137
|
server.printUrls = () => vitePrintUrls(options, source, target, base, _printUrls)
|
|
125
138
|
|
|
126
|
-
// Setup SSE bridge for iOS Safari WSS workaround
|
|
139
|
+
// Setup SSE bridge for macOS/iOS Safari WSS workaround
|
|
127
140
|
if (options.https) {
|
|
128
141
|
setupHmrSseBridge(server)
|
|
129
142
|
hmrSseBridgeActive = true
|
|
130
|
-
consola.info('HMR SSE bridge enabled for iOS Safari WSS workaround')
|
|
143
|
+
consola.info('HMR SSE bridge enabled for macOS/iOS Safari WSS workaround')
|
|
131
144
|
}
|
|
132
145
|
},
|
|
133
146
|
transformIndexHtml() {
|
|
134
147
|
if (!hmrSseBridgeActive)
|
|
135
148
|
return []
|
|
136
|
-
// Inject client-side script to patch WebSocket on iOS Safari
|
|
149
|
+
// Inject client-side script to patch WebSocket on macOS/iOS Safari
|
|
137
150
|
return [{
|
|
138
151
|
tag: 'script',
|
|
139
152
|
children: hmrSseBridgeClientScript,
|
|
@@ -153,11 +166,6 @@ export const unpluginFactory: UnpluginFactory<Options> = options => ({
|
|
|
153
166
|
if (!enable)
|
|
154
167
|
return
|
|
155
168
|
|
|
156
|
-
if (!isAdmin()) {
|
|
157
|
-
consola.warn('please run as administrator')
|
|
158
|
-
return
|
|
159
|
-
}
|
|
160
|
-
|
|
161
169
|
if (!target) {
|
|
162
170
|
consola.fail('please provide target')
|
|
163
171
|
return
|
|
@@ -177,6 +185,9 @@ export const unpluginFactory: UnpluginFactory<Options> = options => ({
|
|
|
177
185
|
}
|
|
178
186
|
const source = `${devServer.host}:${devServer.port}`
|
|
179
187
|
|
|
188
|
+
if (isPrivilegeDenied())
|
|
189
|
+
return
|
|
190
|
+
|
|
180
191
|
if (caddy)
|
|
181
192
|
return
|
|
182
193
|
|
|
@@ -204,9 +215,10 @@ export const unpluginFactory: UnpluginFactory<Options> = options => ({
|
|
|
204
215
|
options.https = false
|
|
205
216
|
}
|
|
206
217
|
|
|
207
|
-
|
|
218
|
+
const instance = new CaddyInstant()
|
|
219
|
+
caddy = instance
|
|
208
220
|
|
|
209
|
-
|
|
221
|
+
instance.run(source, target, {
|
|
210
222
|
base,
|
|
211
223
|
...options,
|
|
212
224
|
}).then(() => {
|
|
@@ -216,6 +228,14 @@ export const unpluginFactory: UnpluginFactory<Options> = options => ({
|
|
|
216
228
|
consola.success(` ${c.green('➜')} ${c.bold('run caddy reverse proxy success')}: ${colorUrl(`${options.https ? 'https' : 'http'}://${target}${base}`)}`)
|
|
217
229
|
})
|
|
218
230
|
}).catch((e) => {
|
|
231
|
+
if (caddy === instance)
|
|
232
|
+
caddy = undefined as unknown as CaddyInstant
|
|
233
|
+
if (e instanceof PrivilegeDeniedError) {
|
|
234
|
+
markPrivilegeDenied()
|
|
235
|
+
consola.warn('reverse proxy skipped: administrator permission was not granted.')
|
|
236
|
+
consola.info('Restart the dev server to enable it again.')
|
|
237
|
+
return
|
|
238
|
+
}
|
|
219
239
|
throw e
|
|
220
240
|
})
|
|
221
241
|
},
|
|
@@ -231,11 +251,6 @@ export const unpluginFactory: UnpluginFactory<Options> = options => ({
|
|
|
231
251
|
if (!enable)
|
|
232
252
|
return
|
|
233
253
|
|
|
234
|
-
if (!isAdmin()) {
|
|
235
|
-
consola.warn('please run as administrator')
|
|
236
|
-
return
|
|
237
|
-
}
|
|
238
|
-
|
|
239
254
|
if (!target) {
|
|
240
255
|
consola.fail('please provide target')
|
|
241
256
|
return
|
|
@@ -257,10 +272,11 @@ export const unpluginFactory: UnpluginFactory<Options> = options => ({
|
|
|
257
272
|
|
|
258
273
|
const source = `${devServer.host}:${devServer.port}`
|
|
259
274
|
|
|
260
|
-
if (
|
|
275
|
+
if (isPrivilegeDenied())
|
|
261
276
|
return
|
|
262
277
|
|
|
263
|
-
|
|
278
|
+
if (caddy)
|
|
279
|
+
return
|
|
264
280
|
|
|
265
281
|
let base = '/'
|
|
266
282
|
const publicPath = compiler.options.output?.publicPath as unknown
|
|
@@ -272,7 +288,10 @@ export const unpluginFactory: UnpluginFactory<Options> = options => ({
|
|
|
272
288
|
if (base !== '/' && !base.endsWith('/'))
|
|
273
289
|
base = `${base}/`
|
|
274
290
|
|
|
275
|
-
|
|
291
|
+
const instance = new CaddyInstant()
|
|
292
|
+
caddy = instance
|
|
293
|
+
|
|
294
|
+
instance.run(source, target, {
|
|
276
295
|
base,
|
|
277
296
|
...options,
|
|
278
297
|
}).then(async () => {
|
|
@@ -280,6 +299,14 @@ export const unpluginFactory: UnpluginFactory<Options> = options => ({
|
|
|
280
299
|
|
|
281
300
|
consola.success(` ${c.green('➜')} ${c.bold('run caddy reverse proxy success')}: ${colorUrl(`${options.https ? 'https' : 'http'}://${target}${base}`)}`)
|
|
282
301
|
}).catch((e) => {
|
|
302
|
+
if (caddy === instance)
|
|
303
|
+
caddy = undefined as unknown as CaddyInstant
|
|
304
|
+
if (e instanceof PrivilegeDeniedError) {
|
|
305
|
+
markPrivilegeDenied()
|
|
306
|
+
consola.warn('reverse proxy skipped: administrator permission was not granted.')
|
|
307
|
+
consola.info('Restart the dev server to enable it again.')
|
|
308
|
+
return
|
|
309
|
+
}
|
|
283
310
|
throw e
|
|
284
311
|
})
|
|
285
312
|
},
|
package/src/nuxt.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Options } from './types'
|
|
|
2
2
|
import process from 'node:process'
|
|
3
3
|
import { defineNuxtModule } from '@nuxt/kit'
|
|
4
4
|
import { vitePrintUrls } from '.'
|
|
5
|
-
import { consola
|
|
5
|
+
import { consola } from './utils'
|
|
6
6
|
import '@nuxt/schema'
|
|
7
7
|
|
|
8
8
|
export interface ModuleOptions extends Options {
|
|
@@ -26,11 +26,6 @@ export default defineNuxtModule<ModuleOptions>({
|
|
|
26
26
|
if (!enable)
|
|
27
27
|
return
|
|
28
28
|
|
|
29
|
-
if (!isAdmin()) {
|
|
30
|
-
consola.warn('please run as administrator')
|
|
31
|
-
return
|
|
32
|
-
}
|
|
33
|
-
|
|
34
29
|
if (!target) {
|
|
35
30
|
consola.fail('please provide target')
|
|
36
31
|
return
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the user dismissed the password prompt in this process.
|
|
3
|
+
*
|
|
4
|
+
* Kept in its own module so the flag outlives a hot restart. Vite rebuilds the
|
|
5
|
+
* dev server in place (`Object.assign(server, newServer)`) rather than starting
|
|
6
|
+
* a new process, so module scope is the only place that distinguishes "saved
|
|
7
|
+
* the config again" from "started the dev server again".
|
|
8
|
+
*/
|
|
9
|
+
let denied = false
|
|
10
|
+
|
|
11
|
+
/** Records that the user declined; every entry point checks this first. */
|
|
12
|
+
export function markPrivilegeDenied(): void {
|
|
13
|
+
denied = true
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isPrivilegeDenied(): boolean {
|
|
17
|
+
return denied
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Test hook: restores the initial state between cases. */
|
|
21
|
+
export function resetPrivilegeDenied(): void {
|
|
22
|
+
denied = false
|
|
23
|
+
}
|