@gylautorun/dev-proxy-cookie 1.0.0-beta.1
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/LICENSE +21 -0
- package/README.md +743 -0
- package/dist/index.d.mts +264 -0
- package/dist/index.d.ts +264 -0
- package/dist/index.js +847 -0
- package/dist/index.min.js +4 -0
- package/dist/index.min.mjs +4 -0
- package/dist/index.mjs +801 -0
- package/package.json +83 -0
- package/src/index.ts +2 -0
- package/src/proxy/apply-dev-cookie-header.ts +14 -0
- package/src/proxy/core.ts +429 -0
- package/src/proxy/index.ts +5 -0
- package/src/proxy/vite-adapter.ts +98 -0
- package/src/proxy/vite-cookie-plugin.ts +94 -0
- package/src/proxy/vite-plugin.ts +66 -0
- package/src/proxy/vue-proxy-config.ts +192 -0
- package/src/utils/cookie-reader.ts +54 -0
- package/src/utils/cookie-watcher.ts +91 -0
- package/src/utils/env-detector.ts +155 -0
- package/src/utils/index.ts +3 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Plugin, ViteDevServer } from 'vite';
|
|
2
|
+
import { CookieReader, watchCookieFile, shouldEnableWatch } from '../utils';
|
|
3
|
+
|
|
4
|
+
export interface ViteDevProxyCookieOptions {
|
|
5
|
+
cookieFile: string;
|
|
6
|
+
debug?: boolean;
|
|
7
|
+
onCookieChange?: (cookie: string) => void;
|
|
8
|
+
/**
|
|
9
|
+
* 是否监听文件变化
|
|
10
|
+
* - true: 始终监听
|
|
11
|
+
* - false: 从不监听
|
|
12
|
+
* - 'auto': 根据环境自动判断(默认)
|
|
13
|
+
* @default 'auto'
|
|
14
|
+
*/
|
|
15
|
+
watch?: boolean | 'auto';
|
|
16
|
+
/**
|
|
17
|
+
* 是否为开发环境(优先级最高)
|
|
18
|
+
* 设置此参数后,将直接决定是否启用文件监听:
|
|
19
|
+
* - true: 启用监听(开发模式)
|
|
20
|
+
* - false: 禁用监听(生产模式)
|
|
21
|
+
*
|
|
22
|
+
* 使用示例: isDev: process.env.NODE_ENV === 'development'
|
|
23
|
+
*/
|
|
24
|
+
isDev?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function viteDevProxyCookie(options: ViteDevProxyCookieOptions): Plugin {
|
|
28
|
+
const { cookieFile, debug = false, onCookieChange, watch = 'auto', isDev } = options;
|
|
29
|
+
let watcher: any = null;
|
|
30
|
+
let currentCookie: string = '';
|
|
31
|
+
|
|
32
|
+
const cookieReader = new CookieReader({ cookieFile });
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
name: 'vite-dev-proxy-cookie',
|
|
36
|
+
apply: 'serve',
|
|
37
|
+
|
|
38
|
+
configureServer(server: ViteDevServer) {
|
|
39
|
+
currentCookie = cookieReader.readCookie();
|
|
40
|
+
if (debug) {
|
|
41
|
+
console.log('[vite-dev-proxy-cookie] Initial cookie loaded');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const middlewares = server.middlewares ||
|
|
45
|
+
(server as any)._middlewares ||
|
|
46
|
+
(server as any).app;
|
|
47
|
+
|
|
48
|
+
if (middlewares && typeof middlewares.use === 'function') {
|
|
49
|
+
middlewares.use((req: any, _res: any, next: any) => {
|
|
50
|
+
if (currentCookie && req.url?.startsWith('/')) {
|
|
51
|
+
req.headers = req.headers || {};
|
|
52
|
+
req.headers['cookie'] = currentCookie;
|
|
53
|
+
}
|
|
54
|
+
next();
|
|
55
|
+
});
|
|
56
|
+
} else {
|
|
57
|
+
console.warn('[vite-dev-proxy-cookie] Could not access middleware stack, cookie injection disabled');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 判断是否应该启用监听
|
|
61
|
+
// isDev 参数优先级最高
|
|
62
|
+
let shouldWatch: boolean;
|
|
63
|
+
|
|
64
|
+
if (isDev !== undefined) {
|
|
65
|
+
shouldWatch = isDev;
|
|
66
|
+
if (debug) {
|
|
67
|
+
console.log(`[vite-dev-proxy-cookie] isDev=${isDev}, ${shouldWatch ? 'enabling' : 'disabling'} watch`);
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
shouldWatch = shouldEnableWatch(watch, [], debug, '[vite-dev-proxy-cookie]');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (shouldWatch) {
|
|
74
|
+
watcher = watchCookieFile(
|
|
75
|
+
cookieFile,
|
|
76
|
+
(newCookie) => {
|
|
77
|
+
currentCookie = newCookie;
|
|
78
|
+
onCookieChange?.(newCookie);
|
|
79
|
+
console.log('[vite-dev-proxy-cookie] Cookie changed, please restart server for full effect');
|
|
80
|
+
},
|
|
81
|
+
(error) => {
|
|
82
|
+
console.error('[vite-dev-proxy-cookie] Watch error:', error.message);
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
} else if (debug) {
|
|
86
|
+
console.log('[vite-dev-proxy-cookie] File watch disabled');
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
closeBundle() {
|
|
91
|
+
watcher?.stop();
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Plugin, ViteDevServer } from 'vite';
|
|
2
|
+
import { AutoProxyCookie, createAutoProxyCookie, type AutoProxyCookieOptions } from './core';
|
|
3
|
+
|
|
4
|
+
export interface ViteAutoProxyCookiePluginOptions extends AutoProxyCookieOptions {
|
|
5
|
+
name?: string;
|
|
6
|
+
/**
|
|
7
|
+
* 是否为开发环境(优先级最高)
|
|
8
|
+
* 设置此参数后,将直接决定是否启用文件监听:
|
|
9
|
+
* - true: 启用监听(开发模式)
|
|
10
|
+
* - false: 禁用监听(生产模式)
|
|
11
|
+
*
|
|
12
|
+
* 使用示例: isDev: process.env.NODE_ENV === 'development'
|
|
13
|
+
*/
|
|
14
|
+
isDev?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getHttpServer(server: ViteDevServer): any {
|
|
18
|
+
if ('httpServer' in server && server.httpServer) {
|
|
19
|
+
return server.httpServer;
|
|
20
|
+
}
|
|
21
|
+
if ('_server' in server && server._server) {
|
|
22
|
+
return server._server;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function viteAutoProxyCookie(options: ViteAutoProxyCookiePluginOptions): Plugin {
|
|
28
|
+
const {
|
|
29
|
+
name = 'vite-auto-proxy-cookie',
|
|
30
|
+
isDev,
|
|
31
|
+
...autoProxyOptions
|
|
32
|
+
} = options;
|
|
33
|
+
|
|
34
|
+
let autoProxy: AutoProxyCookie | null = null;
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
name,
|
|
38
|
+
apply: 'serve',
|
|
39
|
+
|
|
40
|
+
async configureServer(server: ViteDevServer) {
|
|
41
|
+
autoProxy = createAutoProxyCookie({
|
|
42
|
+
...autoProxyOptions,
|
|
43
|
+
debug: options.debug ?? false,
|
|
44
|
+
autoRestart: options.autoRestart ?? true,
|
|
45
|
+
isDev,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
await autoProxy.setup(server);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('[vite-auto-proxy-cookie] Failed to setup proxy:', error);
|
|
52
|
+
|
|
53
|
+
if (options.debug) {
|
|
54
|
+
console.log('[vite-auto-proxy-cookie] Falling back to middleware mode');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
closeBundle() {
|
|
60
|
+
autoProxy?.stop();
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { AutoProxyCookie, createAutoProxyCookie };
|
|
66
|
+
export type { AutoProxyCookieOptions };
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'http';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { CookieReader, watchCookieFile, shouldEnableWatch } from '../utils';
|
|
4
|
+
import { applyDevCookieHeader } from './apply-dev-cookie-header';
|
|
5
|
+
|
|
6
|
+
/** Options for {@link createFileCookieGetter}. */
|
|
7
|
+
export interface CreateFileCookieGetterOptions {
|
|
8
|
+
/**
|
|
9
|
+
* 是否监听文件变化
|
|
10
|
+
* - true: 始终监听
|
|
11
|
+
* - false: 从不监听
|
|
12
|
+
* - 'auto': 根据环境自动判断(默认)
|
|
13
|
+
*
|
|
14
|
+
* Cookie 值每次代理请求时都会从磁盘读取,因此即使 watch 为 false,
|
|
15
|
+
* 修改 cookie 文件后也不需要重启开发服务器。
|
|
16
|
+
* @default 'auto'
|
|
17
|
+
*/
|
|
18
|
+
watch?: boolean | 'auto';
|
|
19
|
+
/** Log when the cookie file changes (only if `watch` is true). @default false */
|
|
20
|
+
debug?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* 自定义生产环境变量名称列表,用于判断是否禁用监听
|
|
23
|
+
* 例如: ['MY_APP_ENV', 'BUILD_TYPE']
|
|
24
|
+
*/
|
|
25
|
+
productionEnvs?: string[];
|
|
26
|
+
/**
|
|
27
|
+
* 是否为开发环境(优先级最高)
|
|
28
|
+
* 设置此参数后,将直接决定是否启用文件监听:
|
|
29
|
+
* - true: 启用监听(开发模式)
|
|
30
|
+
* - false: 禁用监听(生产模式)
|
|
31
|
+
*
|
|
32
|
+
* 使用示例: isDev: process.env.NODE_ENV === 'development'
|
|
33
|
+
*/
|
|
34
|
+
isDev?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface VueProxyConfigOptions {
|
|
38
|
+
getCookie?: () => string;
|
|
39
|
+
debug?: boolean;
|
|
40
|
+
headers?: Record<string, string>;
|
|
41
|
+
ws?: boolean;
|
|
42
|
+
changeOrigin?: boolean;
|
|
43
|
+
secure?: boolean;
|
|
44
|
+
onError?: (err: Error) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ProxyConfig {
|
|
48
|
+
ws: boolean;
|
|
49
|
+
target: string;
|
|
50
|
+
changeOrigin: boolean;
|
|
51
|
+
secure: boolean;
|
|
52
|
+
onProxyReq: (proxyReq: any, req: IncomingMessage) => void;
|
|
53
|
+
onProxyRes?: (proxyRes: any, req: IncomingMessage) => void;
|
|
54
|
+
onError: (err: Error) => void;
|
|
55
|
+
headers?: Record<string, string>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createVueProxyConfig(
|
|
59
|
+
target: string,
|
|
60
|
+
options: VueProxyConfigOptions = {}
|
|
61
|
+
): ProxyConfig {
|
|
62
|
+
const {
|
|
63
|
+
getCookie,
|
|
64
|
+
debug = false,
|
|
65
|
+
headers = {},
|
|
66
|
+
ws = false,
|
|
67
|
+
changeOrigin = true,
|
|
68
|
+
secure = false,
|
|
69
|
+
onError: customOnError,
|
|
70
|
+
} = options;
|
|
71
|
+
|
|
72
|
+
const config: ProxyConfig = {
|
|
73
|
+
ws,
|
|
74
|
+
target,
|
|
75
|
+
changeOrigin,
|
|
76
|
+
secure,
|
|
77
|
+
headers,
|
|
78
|
+
onProxyReq: (proxyReq: any, req: IncomingMessage) => {
|
|
79
|
+
const cookie = getCookie ? getCookie() : '';
|
|
80
|
+
if (cookie) {
|
|
81
|
+
applyDevCookieHeader(proxyReq, cookie);
|
|
82
|
+
}
|
|
83
|
+
if (debug) {
|
|
84
|
+
const reqPath = req.url || '/';
|
|
85
|
+
console.log('[Proxy Request]', reqPath, req.method, cookie ? '(with cookie)' : '(no cookie)');
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
onError: customOnError || ((err: Error) => {
|
|
89
|
+
console.error('\n[Proxy Error]', err.message);
|
|
90
|
+
}),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return config;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function createFileCookieGetter(
|
|
97
|
+
cookieFile: string,
|
|
98
|
+
options: CreateFileCookieGetterOptions = {}
|
|
99
|
+
): () => string {
|
|
100
|
+
const {
|
|
101
|
+
watch = 'auto',
|
|
102
|
+
debug = false,
|
|
103
|
+
productionEnvs = [],
|
|
104
|
+
isDev
|
|
105
|
+
} = options;
|
|
106
|
+
const reader = new CookieReader({ cookieFile: path.resolve(cookieFile) });
|
|
107
|
+
|
|
108
|
+
// 判断是否应该启用监听
|
|
109
|
+
// isDev 参数优先级最高,直接决定是否启用监听
|
|
110
|
+
let shouldWatch: boolean;
|
|
111
|
+
|
|
112
|
+
if (isDev !== undefined) {
|
|
113
|
+
// 用户显式设置了 isDev 参数
|
|
114
|
+
shouldWatch = isDev;
|
|
115
|
+
if (debug) {
|
|
116
|
+
console.log(`[CookieFile] isDev=${isDev}, ${shouldWatch ? 'enabling' : 'disabling'} watch`);
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
// 使用智能环境检测
|
|
120
|
+
shouldWatch = shouldEnableWatch(watch, productionEnvs, debug, '[CookieFile]');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (shouldWatch) {
|
|
124
|
+
watchCookieFile(
|
|
125
|
+
path.resolve(cookieFile),
|
|
126
|
+
(newCookie) => {
|
|
127
|
+
if (debug) {
|
|
128
|
+
console.log('[CookieFile] Updated:', newCookie ? '(has cookie)' : '(empty)');
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
(error) => {
|
|
132
|
+
console.error('[CookieFile] Watch error:', error.message);
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
} else if (debug) {
|
|
136
|
+
console.log('[CookieFile] File watch disabled');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return () => reader.readCookie();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface AutoProxyConfigOptions extends VueProxyConfigOptions {
|
|
143
|
+
target: string;
|
|
144
|
+
ignorePaths?: string[];
|
|
145
|
+
includePaths?: string[];
|
|
146
|
+
additionalProxies?: Record<string, string>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function createAutoProxyConfig(options: AutoProxyConfigOptions): Record<string, ProxyConfig> {
|
|
150
|
+
const { target, ignorePaths = [], includePaths = [], additionalProxies = {}, getCookie, debug, headers } = options;
|
|
151
|
+
|
|
152
|
+
const result: Record<string, ProxyConfig> = {};
|
|
153
|
+
|
|
154
|
+
if (includePaths.length > 0) {
|
|
155
|
+
for (const proxyPath of includePaths) {
|
|
156
|
+
result[proxyPath] = createVueProxyConfig(target, { getCookie, debug, headers });
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
const defaultProxy: ProxyConfig = {
|
|
160
|
+
ws: false,
|
|
161
|
+
target,
|
|
162
|
+
changeOrigin: true,
|
|
163
|
+
secure: false,
|
|
164
|
+
headers,
|
|
165
|
+
onProxyReq: (proxyReq: any, req: IncomingMessage) => {
|
|
166
|
+
const reqPath = req.url || '/';
|
|
167
|
+
|
|
168
|
+
if (ignorePaths.some(p => reqPath.startsWith(p))) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const cookie = getCookie ? getCookie() : '';
|
|
173
|
+
if (cookie) {
|
|
174
|
+
applyDevCookieHeader(proxyReq, cookie);
|
|
175
|
+
}
|
|
176
|
+
if (debug) {
|
|
177
|
+
console.log('[Proxy Request]', reqPath, req.method, cookie ? '(with cookie)' : '(no cookie)');
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
onError: (err: Error) => {
|
|
181
|
+
console.error('\n[Proxy Error]', err.message);
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
result['/'] = defaultProxy;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (const [proxyPath, proxyTarget] of Object.entries(additionalProxies)) {
|
|
188
|
+
result[proxyPath] = createVueProxyConfig(proxyTarget, { getCookie, debug, headers });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
export interface CookieReaderOptions {
|
|
5
|
+
cookieFile: string;
|
|
6
|
+
encoding?: BufferEncoding;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class CookieReader {
|
|
10
|
+
protected options: CookieReaderOptions;
|
|
11
|
+
|
|
12
|
+
constructor(options: CookieReaderOptions) {
|
|
13
|
+
this.options = {
|
|
14
|
+
encoding: 'utf-8',
|
|
15
|
+
...options,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
readCookie(): string {
|
|
20
|
+
try {
|
|
21
|
+
const filePath = path.resolve(this.options.cookieFile);
|
|
22
|
+
if (fs.existsSync(filePath)) {
|
|
23
|
+
const content = fs.readFileSync(filePath, this.options.encoding || 'utf-8');
|
|
24
|
+
// 过滤注释行(以 # 开头)和空行,然后合并成一行
|
|
25
|
+
const lines = content.split('\n');
|
|
26
|
+
const cookieLines = lines
|
|
27
|
+
.map(line => line.trim())
|
|
28
|
+
.filter(line => line && !line.startsWith('#'));
|
|
29
|
+
return cookieLines.join('; ');
|
|
30
|
+
}
|
|
31
|
+
return '';
|
|
32
|
+
} catch {
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
ensureCookieFile(): void {
|
|
38
|
+
const filePath = path.resolve(this.options.cookieFile);
|
|
39
|
+
const dir = path.dirname(filePath);
|
|
40
|
+
|
|
41
|
+
if (!fs.existsSync(dir)) {
|
|
42
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!fs.existsSync(filePath)) {
|
|
46
|
+
fs.writeFileSync(filePath, '');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function createCookieGetter(cookieFile: string): () => string {
|
|
52
|
+
const reader = new CookieReader({ cookieFile });
|
|
53
|
+
return () => reader.readCookie();
|
|
54
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import chokidar, { FSWatcher } from 'chokidar';
|
|
4
|
+
import { CookieReader } from './cookie-reader';
|
|
5
|
+
|
|
6
|
+
export interface CookieWatcherOptions {
|
|
7
|
+
cookieFile: string;
|
|
8
|
+
onCookieChange: (cookie: string) => void;
|
|
9
|
+
onError?: (error: Error) => void;
|
|
10
|
+
autoCreateFile?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class CookieWatcher {
|
|
14
|
+
private watcher: FSWatcher | null = null;
|
|
15
|
+
private options: CookieWatcherOptions;
|
|
16
|
+
private cookieReader: CookieReader;
|
|
17
|
+
private lastContent: string = '';
|
|
18
|
+
|
|
19
|
+
constructor(options: CookieWatcherOptions) {
|
|
20
|
+
this.options = {
|
|
21
|
+
autoCreateFile: true,
|
|
22
|
+
...options,
|
|
23
|
+
};
|
|
24
|
+
this.cookieReader = new CookieReader({ cookieFile: options.cookieFile });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private handleChange = (): void => {
|
|
28
|
+
const newContent = this.cookieReader.readCookie();
|
|
29
|
+
if (newContent !== this.lastContent) {
|
|
30
|
+
this.lastContent = newContent;
|
|
31
|
+
this.options.onCookieChange(newContent);
|
|
32
|
+
console.log(`[CookieWatcher] Cookie updated from file: ${this.options.cookieFile}`);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
start(): void {
|
|
37
|
+
if (this.options.autoCreateFile) {
|
|
38
|
+
this.cookieReader.ensureCookieFile();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const watchPath = path.resolve(this.options.cookieFile);
|
|
42
|
+
this.lastContent = this.cookieReader.readCookie();
|
|
43
|
+
console.log(`[CookieWatcher] Started watching: ${watchPath}`);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
this.watcher = chokidar.watch(watchPath, {
|
|
47
|
+
persistent: true,
|
|
48
|
+
ignoreInitial: true,
|
|
49
|
+
awaitWriteFinish: {
|
|
50
|
+
stabilityThreshold: 100,
|
|
51
|
+
pollInterval: 50,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// "add" covers atomic replace (unlink + rename) used by some editors; "change" covers in-place writes
|
|
56
|
+
this.watcher.on('change', this.handleChange);
|
|
57
|
+
this.watcher.on('add', this.handleChange);
|
|
58
|
+
this.watcher.on('error', (error) => {
|
|
59
|
+
this.options.onError?.(error);
|
|
60
|
+
});
|
|
61
|
+
} catch (error) {
|
|
62
|
+
this.options.onError?.(error as Error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
stop(): void {
|
|
67
|
+
if (this.watcher) {
|
|
68
|
+
this.watcher.close();
|
|
69
|
+
this.watcher = null;
|
|
70
|
+
console.log(`[CookieWatcher] Stopped watching: ${this.options.cookieFile}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getCurrentCookie(): string {
|
|
75
|
+
return this.lastContent;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function watchCookieFile(
|
|
80
|
+
cookieFile: string,
|
|
81
|
+
onCookieChange: (cookie: string) => void,
|
|
82
|
+
onError?: (error: Error) => void
|
|
83
|
+
): CookieWatcher {
|
|
84
|
+
const watcher = new CookieWatcher({
|
|
85
|
+
cookieFile,
|
|
86
|
+
onCookieChange,
|
|
87
|
+
onError,
|
|
88
|
+
});
|
|
89
|
+
watcher.start();
|
|
90
|
+
return watcher;
|
|
91
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 环境检测工具函数
|
|
3
|
+
* 用于智能判断当前是否为生产构建环境
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 判断环境变量值是否表示生产环境
|
|
8
|
+
*/
|
|
9
|
+
export function isProductionValue(value: string): boolean {
|
|
10
|
+
const productionValues = [
|
|
11
|
+
'production', // 生产环境
|
|
12
|
+
'prod', // 生产环境
|
|
13
|
+
'prd', // 生产环境
|
|
14
|
+
'release', // 发布环境
|
|
15
|
+
'staging', // 预发布环境
|
|
16
|
+
'uat', // 预发布环境
|
|
17
|
+
];
|
|
18
|
+
return productionValues.includes(value.toLowerCase().trim());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 智能检测当前是否为生产构建环境
|
|
23
|
+
* 通过多种方式综合判断,避免依赖单一环境变量
|
|
24
|
+
*
|
|
25
|
+
* @param customEnvs 用户自定义的环境变量名称列表
|
|
26
|
+
* @param debug 是否输出调试日志
|
|
27
|
+
* @param loggerPrefix 日志前缀,用于区分不同模块
|
|
28
|
+
* @returns 是否为生产环境
|
|
29
|
+
*/
|
|
30
|
+
export function detectProductionEnvironment(
|
|
31
|
+
customEnvs: string[] = [],
|
|
32
|
+
debug: boolean = false,
|
|
33
|
+
loggerPrefix: string = '[env-detector]'
|
|
34
|
+
): boolean {
|
|
35
|
+
const env = process.env;
|
|
36
|
+
|
|
37
|
+
// 1. 检查自定义环境变量
|
|
38
|
+
if (customEnvs.length > 0) {
|
|
39
|
+
for (const envName of customEnvs) {
|
|
40
|
+
const envValue = env[envName];
|
|
41
|
+
if (envValue && isProductionValue(envValue)) {
|
|
42
|
+
if (debug) {
|
|
43
|
+
console.log(`${loggerPrefix} Detected production via custom env: ${envName}=${envValue}`);
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 2. 检查常见的环境变量
|
|
51
|
+
const commonEnvNames = [
|
|
52
|
+
'NODE_ENV',
|
|
53
|
+
'BUILD_MODE',
|
|
54
|
+
'VUE_APP_ENV',
|
|
55
|
+
'VITE_NODE_ENV',
|
|
56
|
+
'WEBPACK_MODE',
|
|
57
|
+
'CI_ENV',
|
|
58
|
+
'APP_ENV',
|
|
59
|
+
'ENV',
|
|
60
|
+
'DEPLOY_ENV',
|
|
61
|
+
'RUN_MODE',
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
for (const envName of commonEnvNames) {
|
|
65
|
+
const envValue = env[envName];
|
|
66
|
+
if (envValue && isProductionValue(envValue)) {
|
|
67
|
+
if (debug) {
|
|
68
|
+
console.log(`${loggerPrefix} Detected production via env: ${envName}=${envValue}`);
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 3. 检查 CI/CD 环境标识
|
|
75
|
+
if (env.CI === 'true' || env.CI === '1' || env.CI === 'yes') {
|
|
76
|
+
if (debug) {
|
|
77
|
+
console.log(`${loggerPrefix} Detected production via CI env`);
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 4. 检查 npm 生命周期事件(构建命令)
|
|
83
|
+
if (env.npm_lifecycle_event) {
|
|
84
|
+
const lifecycleEvent = env.npm_lifecycle_event.toLowerCase();
|
|
85
|
+
if (lifecycleEvent.includes('build') ||
|
|
86
|
+
lifecycleEvent.includes('prod') ||
|
|
87
|
+
lifecycleEvent.includes('prd') ||
|
|
88
|
+
lifecycleEvent.includes('release')) {
|
|
89
|
+
if (debug) {
|
|
90
|
+
console.log(`${loggerPrefix} Detected production via lifecycle event: ${env.npm_lifecycle_event}`);
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 5. 检查进程参数(构建工具通常会传入特定参数)
|
|
97
|
+
const processArgs = process.argv.join('').toLowerCase();
|
|
98
|
+
if (processArgs.includes('build') ||
|
|
99
|
+
processArgs.includes('production') ||
|
|
100
|
+
processArgs.includes('--mode=production') ||
|
|
101
|
+
processArgs.includes('--prod') ||
|
|
102
|
+
processArgs.includes('--release')) {
|
|
103
|
+
if (debug) {
|
|
104
|
+
console.log(`${loggerPrefix} Detected production via process arguments`);
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 默认认为是开发环境
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 判断是否应该启用文件监听
|
|
115
|
+
*
|
|
116
|
+
* 注意:此函数仅在 isDev 参数未设置时调用,用于智能判断环境
|
|
117
|
+
* 如果用户已通过 isDev 参数明确指定环境,则不会调用此函数
|
|
118
|
+
*
|
|
119
|
+
* @param watch 用户设置的 watch 选项
|
|
120
|
+
* @param customEnvs 用户自定义的环境变量列表
|
|
121
|
+
* @param debug 是否输出调试日志
|
|
122
|
+
* @param loggerPrefix 日志前缀
|
|
123
|
+
* @returns 是否应该启用监听
|
|
124
|
+
*/
|
|
125
|
+
export function shouldEnableWatch(
|
|
126
|
+
watch: boolean | 'auto',
|
|
127
|
+
customEnvs: string[] = [],
|
|
128
|
+
debug: boolean = false,
|
|
129
|
+
loggerPrefix: string = '[env-detector]'
|
|
130
|
+
): boolean {
|
|
131
|
+
// 用户显式设置为 true 或 false,直接返回
|
|
132
|
+
if (typeof watch === 'boolean') {
|
|
133
|
+
if (debug && !watch) {
|
|
134
|
+
console.log(`${loggerPrefix} Watch disabled by user setting`);
|
|
135
|
+
}
|
|
136
|
+
return watch;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 'auto' 模式:智能检测环境
|
|
140
|
+
const isProduction = detectProductionEnvironment(customEnvs, debug, loggerPrefix);
|
|
141
|
+
|
|
142
|
+
if (isProduction) {
|
|
143
|
+
if (debug) {
|
|
144
|
+
console.log(`${loggerPrefix} Auto-detected production mode - disabling watch`);
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 开发环境:启用监听
|
|
150
|
+
if (debug) {
|
|
151
|
+
console.log(`${loggerPrefix} Auto-detected development mode - enabling watch`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return true;
|
|
155
|
+
}
|