@lark-apaas/fullstack-rspack-preset 1.0.56 → 1.0.57-alpha.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/lib/index.js +6 -2
- package/lib/preset.js +55 -0
- package/lib/utils/local-dev.d.ts +42 -0
- package/lib/utils/local-dev.js +92 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -41,10 +41,14 @@ const normalize_base_path_1 = require("./utils/normalize-base-path");
|
|
|
41
41
|
* - CLIENT_DEV_PORT: 客户端开发端口 (默认: 8080)
|
|
42
42
|
*/
|
|
43
43
|
function createFullstackRspackConfig(overrides = {}) {
|
|
44
|
-
//
|
|
44
|
+
// 优先级 shell > .env.local > .env,对齐 Vite/Next.js 约定。
|
|
45
|
+
// dotenv.config 默认 override:false(先到先得),先 .env.local 让它优先,
|
|
46
|
+
// 再 .env 兜底;shell env 已在 process.env 中,两次 config 都不会覆盖。
|
|
45
47
|
try {
|
|
46
48
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
47
|
-
require('dotenv')
|
|
49
|
+
const dotenv = require('dotenv');
|
|
50
|
+
dotenv.config({ path: '.env.local' });
|
|
51
|
+
dotenv.config({ path: '.env' });
|
|
48
52
|
}
|
|
49
53
|
catch (e) {
|
|
50
54
|
// dotenv 是可选依赖,如果没有安装也不影响使用
|
package/lib/preset.js
CHANGED
|
@@ -10,6 +10,7 @@ const core_1 = __importDefault(require("@rspack/core"));
|
|
|
10
10
|
const devtool_kits_1 = require("@lark-apaas/devtool-kits");
|
|
11
11
|
const plugin_react_refresh_1 = __importDefault(require("@rspack/plugin-react-refresh"));
|
|
12
12
|
const dev_server_listener_1 = require("./utils/dev-server-listener");
|
|
13
|
+
const local_dev_1 = require("./utils/local-dev");
|
|
13
14
|
const route_parser_plugin_1 = __importDefault(require("./rspack-plugins/route-parser-plugin"));
|
|
14
15
|
const slardar_performance_monitor_plugin_1 = __importDefault(require("./rspack-plugins/slardar-performance-monitor-plugin"));
|
|
15
16
|
const view_context_injection_plugin_1 = __importDefault(require("./rspack-plugins/view-context-injection-plugin"));
|
|
@@ -75,6 +76,34 @@ function createRecommendRspackConfig(options) {
|
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
catch { /* ignore */ }
|
|
79
|
+
// 本地 dev 链路兜底:将 {clientBasePath}/__runtime__ 反代到公网沙箱
|
|
80
|
+
// (由 MIAODA_DEV_PLATFORM_BASE 或 SANDBOX_PUBLIC_URL 提供 origin),
|
|
81
|
+
// 由 SDK 而非用户应用承担 cookie / x-larkgw-suda-webuser 注入。
|
|
82
|
+
const localDev = (0, local_dev_1.isLocalDev)();
|
|
83
|
+
const sandboxBase = (0, local_dev_1.resolveSandboxOrigin)();
|
|
84
|
+
const { cookie: outboundCookie, csrfToken: outboundCsrfToken } = (0, local_dev_1.composeSandboxOutboundAuth)();
|
|
85
|
+
// parseSudaWebUserEnv 容忍 dotenv@17 对 shell-quoted JSON 不 unescape `\"` 的边界。
|
|
86
|
+
let localDevWebUserHeader = '';
|
|
87
|
+
const parsedSudaWebUser = (0, local_dev_1.parseSudaWebUserEnv)(process.env.SUDA_WEBUSER);
|
|
88
|
+
if (parsedSudaWebUser) {
|
|
89
|
+
localDevWebUserHeader = encodeURIComponent(JSON.stringify(parsedSudaWebUser));
|
|
90
|
+
}
|
|
91
|
+
// 沙箱上 /__runtime__/* 的路由本身就期望带 /app/<appId> 前缀
|
|
92
|
+
// (跟生产网关 miaoda.feishu-boe.cn 的行为一致),所以直接透传完整路径,不做 pathRewrite。
|
|
93
|
+
const localDevRuntimeProxyEntries = localDev && sandboxBase
|
|
94
|
+
? [{
|
|
95
|
+
context: [`${clientBasePath}/__runtime__`],
|
|
96
|
+
target: sandboxBase,
|
|
97
|
+
changeOrigin: true,
|
|
98
|
+
secure: true,
|
|
99
|
+
headers: {
|
|
100
|
+
...(outboundCookie ? { cookie: outboundCookie } : {}),
|
|
101
|
+
'accept-encoding': 'identity',
|
|
102
|
+
...(localDevWebUserHeader ? { 'x-larkgw-suda-webuser': localDevWebUserHeader } : {}),
|
|
103
|
+
...(outboundCsrfToken ? { 'x-suda-csrf-token': outboundCsrfToken } : {}),
|
|
104
|
+
},
|
|
105
|
+
}]
|
|
106
|
+
: [];
|
|
78
107
|
return {
|
|
79
108
|
mode: isDev ? 'development' : 'production',
|
|
80
109
|
cache: false,
|
|
@@ -384,10 +413,36 @@ function createRecommendRspackConfig(options) {
|
|
|
384
413
|
(0, dev_server_listener_1.listenHmrTiming)(server);
|
|
385
414
|
},
|
|
386
415
|
proxy: [
|
|
416
|
+
...localDevRuntimeProxyEntries,
|
|
387
417
|
{
|
|
418
|
+
// 本地 dev 没有 larkgw 经过这条 incoming 链路,server 端 NestJS 缺:
|
|
419
|
+
// 1. x-larkgw-suda-webuser header(身份)→ ViewController 拼出 window.appId=""
|
|
420
|
+
// 前端 auth-sdk 拼 URL 变 /app//api/... 全 404
|
|
421
|
+
// 2. x-suda-csrf-token header(csrf double-submit)→ csrf middleware 403
|
|
422
|
+
// 在 dev server 这层模拟 larkgw 注入这两样,让应用层 middleware 行为跟沙箱完全一致
|
|
423
|
+
// (只信 incoming header)。webuser 静态从 SUDA_WEBUSER env 取(lark-cli +env-pull
|
|
424
|
+
// 从沙箱 dump 到 .env.local);csrf token 在 onProxyReq 钩子里 per-request 从
|
|
425
|
+
// cookie 抠(浏览器 cookie 是反代 /__runtime__ 时沙箱 Set-Cookie 透传过来的)。
|
|
388
426
|
context: [`${clientBasePath}/api`],
|
|
389
427
|
target: `http://localhost:${serverPort}`,
|
|
390
428
|
changeOrigin: true,
|
|
429
|
+
...(localDev && localDevWebUserHeader
|
|
430
|
+
? { headers: { 'x-larkgw-suda-webuser': localDevWebUserHeader } }
|
|
431
|
+
: {}),
|
|
432
|
+
...(localDev
|
|
433
|
+
? {
|
|
434
|
+
onProxyReq: (proxyReq, req) => {
|
|
435
|
+
if (proxyReq.getHeader('x-suda-csrf-token'))
|
|
436
|
+
return;
|
|
437
|
+
const cookie = req.headers.cookie;
|
|
438
|
+
if (!cookie)
|
|
439
|
+
return;
|
|
440
|
+
const m = /(?:^|;\s*)suda-csrf-token=([^;]+)/.exec(cookie);
|
|
441
|
+
if (m)
|
|
442
|
+
proxyReq.setHeader('x-suda-csrf-token', m[1]);
|
|
443
|
+
},
|
|
444
|
+
}
|
|
445
|
+
: {}),
|
|
391
446
|
onError: sendBackendUnavailable502,
|
|
392
447
|
},
|
|
393
448
|
{
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本地开发总开关。由 dev-local.sh export `MIAODA_LOCAL_DEV=1`;
|
|
3
|
+
* 沙箱 dev / 生产都不设此 env。
|
|
4
|
+
*/
|
|
5
|
+
export declare function isLocalDev(): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* 反代沙箱时的认证组装。两条入口:
|
|
8
|
+
* - 新流(推荐):env `FORCE_AUTHN_PREVIEW_SESSION_ID` —— 后端下发的单值 session(实际是
|
|
9
|
+
* X-Force-Runtime-Session 的值)。SDK 自动拼 cookie,suda-csrf-token 用本地常量。
|
|
10
|
+
* - 老流(兼容):env `SANDBOX_COOKIE` —— 用户从浏览器手抠的完整 cookie 字符串(包含
|
|
11
|
+
* X-Force-Runtime-Session / suda-csrf-token / suda_web_did 等)。原样透传,suda-csrf-token
|
|
12
|
+
* 从其中正则提取作为出向 x-suda-csrf-token header。
|
|
13
|
+
*
|
|
14
|
+
* 两个都给的话 `SANDBOX_COOKIE` 优先("已显式拼好"信号更强)。两个都缺的话返回空字符串
|
|
15
|
+
* (local dev 跑不通,但不抛错——交给上层 `localDev && sandboxBase` 条件兜底)。
|
|
16
|
+
*/
|
|
17
|
+
export declare function composeSandboxOutboundAuth(): {
|
|
18
|
+
cookie: string;
|
|
19
|
+
csrfToken: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* 解析沙箱反代的 target origin。两条入口:
|
|
23
|
+
* - 新流(推荐):env `MIAODA_DEV_PLATFORM_BASE` —— 后端下发的完整 URL,**可能带路径前缀**
|
|
24
|
+
* (例如 `https://ai-tenant-<app>-<sandbox>.aiforce-boe-preview.bytedance.net/app/<app>`)。
|
|
25
|
+
* SDK 抽 `new URL(...).origin` 拿 host,扔掉路径——避免反代 target 双前缀。
|
|
26
|
+
* - 老流(兼容):env `SANDBOX_PUBLIC_URL`(用户从沙箱信息手抠 host)。
|
|
27
|
+
*
|
|
28
|
+
* 两个都给的话 `MIAODA_DEV_PLATFORM_BASE` 优先。两个都缺的话返回空字符串
|
|
29
|
+
* (local dev 跑不通,但不抛错——交给上层 `localDev && sandboxBase` 条件兜底)。
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveSandboxOrigin(): string;
|
|
32
|
+
/**
|
|
33
|
+
* 解析 `process.env.SUDA_WEBUSER`,容忍沙箱下发的 shell-quoted JSON。
|
|
34
|
+
*
|
|
35
|
+
* 沙箱 env pull 下发到 .env.local 的 SUDA_WEBUSER 是双引号包裹 + 内部 `\"` 转义;
|
|
36
|
+
* dotenv@17 剥外层引号后**不还原**内部 `\"`,导致 `process.env.SUDA_WEBUSER` 是
|
|
37
|
+
* `{\"user_id\":\"...\"}` 这种带反斜杠的字符串,直接 JSON.parse 会挂在
|
|
38
|
+
* `Expected property name or '}' in JSON at position 1`。
|
|
39
|
+
*
|
|
40
|
+
* 容错:先直接 parse;失败再 unescape `\"` 后 parse;都失败返回 null。
|
|
41
|
+
*/
|
|
42
|
+
export declare function parseSudaWebUserEnv<T = unknown>(raw: string | undefined): T | null;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isLocalDev = isLocalDev;
|
|
4
|
+
exports.composeSandboxOutboundAuth = composeSandboxOutboundAuth;
|
|
5
|
+
exports.resolveSandboxOrigin = resolveSandboxOrigin;
|
|
6
|
+
exports.parseSudaWebUserEnv = parseSudaWebUserEnv;
|
|
7
|
+
/**
|
|
8
|
+
* 本地开发总开关。由 dev-local.sh export `MIAODA_LOCAL_DEV=1`;
|
|
9
|
+
* 沙箱 dev / 生产都不设此 env。
|
|
10
|
+
*/
|
|
11
|
+
function isLocalDev() {
|
|
12
|
+
if (process.env.NODE_ENV === 'production')
|
|
13
|
+
return false;
|
|
14
|
+
const flag = process.env.MIAODA_LOCAL_DEV;
|
|
15
|
+
return flag === '1' || flag === 'true';
|
|
16
|
+
}
|
|
17
|
+
/** 本地常量 csrf token —— sandbox 是 double-submit 模式(cookie===header 即放行),本地随便定一个一致值即可。 */
|
|
18
|
+
const LOCAL_CSRF_TOKEN = 'local-dev-csrf';
|
|
19
|
+
/**
|
|
20
|
+
* 反代沙箱时的认证组装。两条入口:
|
|
21
|
+
* - 新流(推荐):env `FORCE_AUTHN_PREVIEW_SESSION_ID` —— 后端下发的单值 session(实际是
|
|
22
|
+
* X-Force-Runtime-Session 的值)。SDK 自动拼 cookie,suda-csrf-token 用本地常量。
|
|
23
|
+
* - 老流(兼容):env `SANDBOX_COOKIE` —— 用户从浏览器手抠的完整 cookie 字符串(包含
|
|
24
|
+
* X-Force-Runtime-Session / suda-csrf-token / suda_web_did 等)。原样透传,suda-csrf-token
|
|
25
|
+
* 从其中正则提取作为出向 x-suda-csrf-token header。
|
|
26
|
+
*
|
|
27
|
+
* 两个都给的话 `SANDBOX_COOKIE` 优先("已显式拼好"信号更强)。两个都缺的话返回空字符串
|
|
28
|
+
* (local dev 跑不通,但不抛错——交给上层 `localDev && sandboxBase` 条件兜底)。
|
|
29
|
+
*/
|
|
30
|
+
function composeSandboxOutboundAuth() {
|
|
31
|
+
const legacyCookie = process.env.SANDBOX_COOKIE;
|
|
32
|
+
if (legacyCookie) {
|
|
33
|
+
const m = legacyCookie.match(/(?:^|;\s*)suda-csrf-token=([^;]+)/i);
|
|
34
|
+
return { cookie: legacyCookie, csrfToken: m ? m[1] : '' };
|
|
35
|
+
}
|
|
36
|
+
const sessionId = process.env.FORCE_AUTHN_PREVIEW_SESSION_ID;
|
|
37
|
+
if (sessionId) {
|
|
38
|
+
return {
|
|
39
|
+
cookie: `X-Force-Runtime-Session=${sessionId}; suda-csrf-token=${LOCAL_CSRF_TOKEN}`,
|
|
40
|
+
csrfToken: LOCAL_CSRF_TOKEN,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { cookie: '', csrfToken: '' };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 解析沙箱反代的 target origin。两条入口:
|
|
47
|
+
* - 新流(推荐):env `MIAODA_DEV_PLATFORM_BASE` —— 后端下发的完整 URL,**可能带路径前缀**
|
|
48
|
+
* (例如 `https://ai-tenant-<app>-<sandbox>.aiforce-boe-preview.bytedance.net/app/<app>`)。
|
|
49
|
+
* SDK 抽 `new URL(...).origin` 拿 host,扔掉路径——避免反代 target 双前缀。
|
|
50
|
+
* - 老流(兼容):env `SANDBOX_PUBLIC_URL`(用户从沙箱信息手抠 host)。
|
|
51
|
+
*
|
|
52
|
+
* 两个都给的话 `MIAODA_DEV_PLATFORM_BASE` 优先。两个都缺的话返回空字符串
|
|
53
|
+
* (local dev 跑不通,但不抛错——交给上层 `localDev && sandboxBase` 条件兜底)。
|
|
54
|
+
*/
|
|
55
|
+
function resolveSandboxOrigin() {
|
|
56
|
+
const platformBase = process.env.MIAODA_DEV_PLATFORM_BASE;
|
|
57
|
+
if (platformBase) {
|
|
58
|
+
try {
|
|
59
|
+
return new URL(platformBase).origin;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// 非合法 URL → 回落到 legacy
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return process.env.SANDBOX_PUBLIC_URL || '';
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 解析 `process.env.SUDA_WEBUSER`,容忍沙箱下发的 shell-quoted JSON。
|
|
69
|
+
*
|
|
70
|
+
* 沙箱 env pull 下发到 .env.local 的 SUDA_WEBUSER 是双引号包裹 + 内部 `\"` 转义;
|
|
71
|
+
* dotenv@17 剥外层引号后**不还原**内部 `\"`,导致 `process.env.SUDA_WEBUSER` 是
|
|
72
|
+
* `{\"user_id\":\"...\"}` 这种带反斜杠的字符串,直接 JSON.parse 会挂在
|
|
73
|
+
* `Expected property name or '}' in JSON at position 1`。
|
|
74
|
+
*
|
|
75
|
+
* 容错:先直接 parse;失败再 unescape `\"` 后 parse;都失败返回 null。
|
|
76
|
+
*/
|
|
77
|
+
function parseSudaWebUserEnv(raw) {
|
|
78
|
+
if (!raw)
|
|
79
|
+
return null;
|
|
80
|
+
try {
|
|
81
|
+
return JSON.parse(raw);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// fall through to unescape attempt
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
return JSON.parse(raw.replace(/\\"/g, '"'));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|