@lark-apaas/fullstack-rspack-preset 1.0.56-alpha.8 → 1.0.56
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
CHANGED
|
@@ -41,14 +41,10 @@ const normalize_base_path_1 = require("./utils/normalize-base-path");
|
|
|
41
41
|
* - CLIENT_DEV_PORT: 客户端开发端口 (默认: 8080)
|
|
42
42
|
*/
|
|
43
43
|
function createFullstackRspackConfig(overrides = {}) {
|
|
44
|
-
//
|
|
45
|
-
// dotenv.config 默认 override:false(先到先得),先 .env.local 让它优先,
|
|
46
|
-
// 再 .env 兜底;shell env 已在 process.env 中,两次 config 都不会覆盖。
|
|
44
|
+
// 1. 自动加载 .env 文件(同步加载,确保环境变量立即可用)
|
|
47
45
|
try {
|
|
48
46
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
49
|
-
|
|
50
|
-
dotenv.config({ path: '.env.local' });
|
|
51
|
-
dotenv.config({ path: '.env' });
|
|
47
|
+
require('dotenv').config();
|
|
52
48
|
}
|
|
53
49
|
catch (e) {
|
|
54
50
|
// dotenv 是可选依赖,如果没有安装也不影响使用
|
package/lib/preset.js
CHANGED
|
@@ -10,7 +10,6 @@ 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");
|
|
14
13
|
const route_parser_plugin_1 = __importDefault(require("./rspack-plugins/route-parser-plugin"));
|
|
15
14
|
const slardar_performance_monitor_plugin_1 = __importDefault(require("./rspack-plugins/slardar-performance-monitor-plugin"));
|
|
16
15
|
const view_context_injection_plugin_1 = __importDefault(require("./rspack-plugins/view-context-injection-plugin"));
|
|
@@ -21,6 +20,7 @@ const css_legacy_plugin_1 = __importDefault(require("./rspack-plugins/css-legacy
|
|
|
21
20
|
const polyfill_plugin_1 = __importDefault(require("./rspack-plugins/polyfill-plugin"));
|
|
22
21
|
const static_assets_plugin_1 = __importDefault(require("./rspack-plugins/static-assets-plugin"));
|
|
23
22
|
const source_map_upload_plugin_1 = __importDefault(require("./rspack-plugins/source-map-upload-plugin"));
|
|
23
|
+
const capabilities_plugin_1 = require("./rspack-plugins/capabilities-plugin");
|
|
24
24
|
const dev_server_snapdom_proxy_1 = require("./utils/dev-server-snapdom-proxy");
|
|
25
25
|
function sendBackendUnavailable502(_err, _req, res) {
|
|
26
26
|
if (res.headersSent)
|
|
@@ -75,34 +75,6 @@ function createRecommendRspackConfig(options) {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
catch { /* ignore */ }
|
|
78
|
-
// 本地 dev 链路兜底:将 {clientBasePath}/__runtime__ 反代到公网沙箱
|
|
79
|
-
// (由 MIAODA_DEV_PLATFORM_BASE 或 SANDBOX_PUBLIC_URL 提供 origin),
|
|
80
|
-
// 由 SDK 而非用户应用承担 cookie / x-larkgw-suda-webuser 注入。
|
|
81
|
-
const localDev = (0, local_dev_1.isLocalDev)();
|
|
82
|
-
const sandboxBase = (0, local_dev_1.resolveSandboxOrigin)();
|
|
83
|
-
const { cookie: outboundCookie, csrfToken: outboundCsrfToken } = (0, local_dev_1.composeSandboxOutboundAuth)();
|
|
84
|
-
// parseSudaWebUserEnv 容忍 dotenv@17 对 shell-quoted JSON 不 unescape `\"` 的边界。
|
|
85
|
-
let localDevWebUserHeader = '';
|
|
86
|
-
const parsedSudaWebUser = (0, local_dev_1.parseSudaWebUserEnv)(process.env.SUDA_WEBUSER);
|
|
87
|
-
if (parsedSudaWebUser) {
|
|
88
|
-
localDevWebUserHeader = encodeURIComponent(JSON.stringify(parsedSudaWebUser));
|
|
89
|
-
}
|
|
90
|
-
// 沙箱上 /__runtime__/* 的路由本身就期望带 /app/<appId> 前缀
|
|
91
|
-
// (跟生产网关 miaoda.feishu-boe.cn 的行为一致),所以直接透传完整路径,不做 pathRewrite。
|
|
92
|
-
const localDevRuntimeProxyEntries = localDev && sandboxBase
|
|
93
|
-
? [{
|
|
94
|
-
context: [`${clientBasePath}/__runtime__`],
|
|
95
|
-
target: sandboxBase,
|
|
96
|
-
changeOrigin: true,
|
|
97
|
-
secure: true,
|
|
98
|
-
headers: {
|
|
99
|
-
...(outboundCookie ? { cookie: outboundCookie } : {}),
|
|
100
|
-
'accept-encoding': 'identity',
|
|
101
|
-
...(localDevWebUserHeader ? { 'x-larkgw-suda-webuser': localDevWebUserHeader } : {}),
|
|
102
|
-
...(outboundCsrfToken ? { 'x-suda-csrf-token': outboundCsrfToken } : {}),
|
|
103
|
-
},
|
|
104
|
-
}]
|
|
105
|
-
: [];
|
|
106
78
|
return {
|
|
107
79
|
mode: isDev ? 'development' : 'production',
|
|
108
80
|
cache: false,
|
|
@@ -221,6 +193,13 @@ function createRecommendRspackConfig(options) {
|
|
|
221
193
|
],
|
|
222
194
|
},
|
|
223
195
|
plugins: [
|
|
196
|
+
// virtual:capabilities —— client-toolkit 经 client-capability 强依赖的虚拟模块。
|
|
197
|
+
// 即便业务工程没有 capability JSON 也必须把虚拟模块注册成空 map,否则下游
|
|
198
|
+
// node_modules/@lark-apaas/client-capability/dist/index.js 的
|
|
199
|
+
// `import 'virtual:capabilities'` 在 rspack 解析阶段直接 fail。
|
|
200
|
+
// 必须放在 RuntimeInjectionPlugin 前面 —— RuntimeInjectionPlugin 注入的
|
|
201
|
+
// client-toolkit/runtime 入口会间接 import 到 client-capability。
|
|
202
|
+
new capabilities_plugin_1.CapabilitiesPlugin({ rootDir }),
|
|
224
203
|
// 运行时注入插件 - 自动将 @lark-apaas/client-toolkit/runtime 注入到所有入口之前
|
|
225
204
|
new runtime_injection_plugin_1.default(),
|
|
226
205
|
// basename 运行时注入 - 改写入口文件 process.env.CLIENT_BASE_PATH 为运行时读 window.__BASENAME__
|
|
@@ -405,7 +384,6 @@ function createRecommendRspackConfig(options) {
|
|
|
405
384
|
(0, dev_server_listener_1.listenHmrTiming)(server);
|
|
406
385
|
},
|
|
407
386
|
proxy: [
|
|
408
|
-
...localDevRuntimeProxyEntries,
|
|
409
387
|
{
|
|
410
388
|
context: [`${clientBasePath}/api`],
|
|
411
389
|
target: `http://localhost:${serverPort}`,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capabilities Plugin for Rspack
|
|
3
|
+
*
|
|
4
|
+
* 解析 `virtual:capabilities` 虚拟模块——`@lark-apaas/client-capability` SDK
|
|
5
|
+
* (经 client-toolkit 间接消费)把 `virtual:capabilities` 标 tsup external 保留
|
|
6
|
+
* 在 dist 里,下游构建工具必须自己提供解析,否则 rspack build / dev 直接 fail:
|
|
7
|
+
*
|
|
8
|
+
* Module not found: Can't resolve 'virtual:capabilities' in
|
|
9
|
+
* node_modules/@lark-apaas/client-capability/dist/index.js
|
|
10
|
+
*
|
|
11
|
+
* 实现策略(参考同目录 static-assets-plugin 的虚拟模块模式):
|
|
12
|
+
* - 扫 <rootDir>/server/capabilities/*.json + <rootDir>/shared/capabilities/*.json
|
|
13
|
+
* 生成 capability map(都不存在时空 map,SDK 在 dev 预览 / 沙箱也能正常加载)
|
|
14
|
+
* - 写虚拟模块文件到 node_modules/.cache/capabilities-virtual/index.js
|
|
15
|
+
* - 通过 `compiler.hooks.normalModuleFactory + beforeResolve` 拦截
|
|
16
|
+
* `virtual:capabilities` import,把 resolveData.request 改写为虚拟模块文件路径
|
|
17
|
+
*
|
|
18
|
+
* 跟 vite 版本的语义保持一致:DEFAULT_DIRS 顺序、capability 合并规则(后 id 覆盖前 id)、
|
|
19
|
+
* 没有 id 的 JSON 跳过 + warn、JSON 解析失败跳过 + warn。
|
|
20
|
+
*/
|
|
21
|
+
import type { Compiler } from '@rspack/core';
|
|
22
|
+
export interface CapabilitiesPluginOptions {
|
|
23
|
+
/** 覆盖扫描目录(相对项目根)。不传按 DEFAULT_DIRS 依次扫描。 */
|
|
24
|
+
dir?: string;
|
|
25
|
+
/** 项目根目录,不传走 compiler.options.context || process.cwd() */
|
|
26
|
+
rootDir?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare class CapabilitiesPlugin {
|
|
29
|
+
private options;
|
|
30
|
+
constructor(options?: CapabilitiesPluginOptions);
|
|
31
|
+
private listDirs;
|
|
32
|
+
private loadMap;
|
|
33
|
+
apply(compiler: Compiler): void;
|
|
34
|
+
}
|
|
35
|
+
export default CapabilitiesPlugin;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Capabilities Plugin for Rspack
|
|
4
|
+
*
|
|
5
|
+
* 解析 `virtual:capabilities` 虚拟模块——`@lark-apaas/client-capability` SDK
|
|
6
|
+
* (经 client-toolkit 间接消费)把 `virtual:capabilities` 标 tsup external 保留
|
|
7
|
+
* 在 dist 里,下游构建工具必须自己提供解析,否则 rspack build / dev 直接 fail:
|
|
8
|
+
*
|
|
9
|
+
* Module not found: Can't resolve 'virtual:capabilities' in
|
|
10
|
+
* node_modules/@lark-apaas/client-capability/dist/index.js
|
|
11
|
+
*
|
|
12
|
+
* 实现策略(参考同目录 static-assets-plugin 的虚拟模块模式):
|
|
13
|
+
* - 扫 <rootDir>/server/capabilities/*.json + <rootDir>/shared/capabilities/*.json
|
|
14
|
+
* 生成 capability map(都不存在时空 map,SDK 在 dev 预览 / 沙箱也能正常加载)
|
|
15
|
+
* - 写虚拟模块文件到 node_modules/.cache/capabilities-virtual/index.js
|
|
16
|
+
* - 通过 `compiler.hooks.normalModuleFactory + beforeResolve` 拦截
|
|
17
|
+
* `virtual:capabilities` import,把 resolveData.request 改写为虚拟模块文件路径
|
|
18
|
+
*
|
|
19
|
+
* 跟 vite 版本的语义保持一致:DEFAULT_DIRS 顺序、capability 合并规则(后 id 覆盖前 id)、
|
|
20
|
+
* 没有 id 的 JSON 跳过 + warn、JSON 解析失败跳过 + warn。
|
|
21
|
+
*/
|
|
22
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
25
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
26
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
27
|
+
}
|
|
28
|
+
Object.defineProperty(o, k2, desc);
|
|
29
|
+
}) : (function(o, m, k, k2) {
|
|
30
|
+
if (k2 === undefined) k2 = k;
|
|
31
|
+
o[k2] = m[k];
|
|
32
|
+
}));
|
|
33
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
34
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
35
|
+
}) : function(o, v) {
|
|
36
|
+
o["default"] = v;
|
|
37
|
+
});
|
|
38
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
39
|
+
var ownKeys = function(o) {
|
|
40
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
41
|
+
var ar = [];
|
|
42
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
43
|
+
return ar;
|
|
44
|
+
};
|
|
45
|
+
return ownKeys(o);
|
|
46
|
+
};
|
|
47
|
+
return function (mod) {
|
|
48
|
+
if (mod && mod.__esModule) return mod;
|
|
49
|
+
var result = {};
|
|
50
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
51
|
+
__setModuleDefault(result, mod);
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
})();
|
|
55
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
56
|
+
exports.CapabilitiesPlugin = void 0;
|
|
57
|
+
const fs = __importStar(require("node:fs"));
|
|
58
|
+
const path = __importStar(require("node:path"));
|
|
59
|
+
const VIRTUAL_ID = 'virtual:capabilities';
|
|
60
|
+
const PLUGIN_NAME = 'MiaodaCapabilitiesPlugin';
|
|
61
|
+
// fullstack 模板把 capability JSON 放在 `server/capabilities`。
|
|
62
|
+
// 同时兼容 jsPage 风格 `shared/capabilities`。
|
|
63
|
+
const DEFAULT_DIRS = ['server/capabilities', 'shared/capabilities'];
|
|
64
|
+
class CapabilitiesPlugin {
|
|
65
|
+
constructor(options = {}) {
|
|
66
|
+
this.options = options;
|
|
67
|
+
}
|
|
68
|
+
listDirs(rootDir) {
|
|
69
|
+
if (this.options.dir)
|
|
70
|
+
return [path.resolve(rootDir, this.options.dir)];
|
|
71
|
+
return DEFAULT_DIRS.map((d) => path.resolve(rootDir, d));
|
|
72
|
+
}
|
|
73
|
+
loadMap(rootDir) {
|
|
74
|
+
const map = {};
|
|
75
|
+
for (const abs of this.listDirs(rootDir)) {
|
|
76
|
+
if (!fs.existsSync(abs))
|
|
77
|
+
continue;
|
|
78
|
+
for (const f of fs.readdirSync(abs)) {
|
|
79
|
+
if (!f.endsWith('.json'))
|
|
80
|
+
continue;
|
|
81
|
+
try {
|
|
82
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(abs, f), 'utf-8'));
|
|
83
|
+
if (cfg?.id)
|
|
84
|
+
map[cfg.id] = cfg;
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
console.warn(`[miaoda-capabilities] skip invalid json: ${path.join(abs, f)}: ${e instanceof Error ? e.message : String(e)}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return map;
|
|
92
|
+
}
|
|
93
|
+
apply(compiler) {
|
|
94
|
+
const rootDir = this.options.rootDir || compiler.options.context || process.cwd();
|
|
95
|
+
// 临时虚拟模块目录(跟 static-assets-plugin 同款模式)
|
|
96
|
+
const virtualModulesDir = path.join(rootDir, 'node_modules', '.cache', 'capabilities-virtual');
|
|
97
|
+
if (!fs.existsSync(virtualModulesDir)) {
|
|
98
|
+
fs.mkdirSync(virtualModulesDir, { recursive: true });
|
|
99
|
+
}
|
|
100
|
+
const virtualFile = path.join(virtualModulesDir, 'index.js');
|
|
101
|
+
// 构建期写一次虚拟模块内容;dev 期 capability JSON 变更触发 watch 重写。
|
|
102
|
+
// 虚拟文件内容输出 ESM `export default ...` —— 跟 vite 版本严格对齐,避免
|
|
103
|
+
// 未来 client-capability 改用 `import * as caps` 等 NS 形态时,CJS/ESM
|
|
104
|
+
// 互操作产生的差异(CJS 会嵌一层 default)。rspack 默认把 .js 当 module 类型
|
|
105
|
+
// 解析,ESM 语法可直接消费。
|
|
106
|
+
const writeVirtualFile = () => {
|
|
107
|
+
const map = this.loadMap(rootDir);
|
|
108
|
+
const content = `export default ${JSON.stringify(map)};\n`;
|
|
109
|
+
// 仅当内容变化时写盘,避免无意义触发 watcher
|
|
110
|
+
let existing = null;
|
|
111
|
+
try {
|
|
112
|
+
existing = fs.readFileSync(virtualFile, 'utf-8');
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
existing = null;
|
|
116
|
+
}
|
|
117
|
+
if (existing !== content) {
|
|
118
|
+
fs.writeFileSync(virtualFile, content);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
writeVirtualFile();
|
|
122
|
+
// rootDir fallback 静默退化排查难,启动期检查一次实际有没有扫到 capability 目录
|
|
123
|
+
const foundDirs = this.listDirs(rootDir).filter((d) => fs.existsSync(d));
|
|
124
|
+
if (foundDirs.length === 0) {
|
|
125
|
+
console.info(`[miaoda-capabilities] no capability dirs found under ${rootDir}, registering empty map. Scanned: ${this.listDirs(rootDir).join(', ')}`);
|
|
126
|
+
}
|
|
127
|
+
// 拦截 `virtual:capabilities` import 改写到虚拟文件
|
|
128
|
+
compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, (factory) => {
|
|
129
|
+
factory.hooks.beforeResolve.tap(PLUGIN_NAME, (resolveData) => {
|
|
130
|
+
if (resolveData.request === VIRTUAL_ID) {
|
|
131
|
+
resolveData.request = virtualFile;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
// dev 期监听 capability JSON 变更:每次 compile 把 capability 目录加入
|
|
136
|
+
// contextDependencies(rspack watcher 会观察其下文件创建 / 修改 / 删除)。
|
|
137
|
+
// 不在 afterEnvironment 一次性快照 —— 那样 startup 时还没建的目录永远抓不到。
|
|
138
|
+
// 每次 compile 重新 listDirs() 让新建目录也能被加入监听。
|
|
139
|
+
compiler.hooks.afterCompile.tap(PLUGIN_NAME, (compilation) => {
|
|
140
|
+
for (const dir of this.listDirs(rootDir)) {
|
|
141
|
+
// contextDependencies 接受不存在的路径,rspack 会观察父目录的创建事件
|
|
142
|
+
compilation.contextDependencies.add(dir);
|
|
143
|
+
}
|
|
144
|
+
compilation.fileDependencies.add(virtualFile);
|
|
145
|
+
});
|
|
146
|
+
// 每次 watchRun 前重写虚拟文件(覆盖 dev 期新增 / 修改 capability)
|
|
147
|
+
compiler.hooks.watchRun.tap(PLUGIN_NAME, () => {
|
|
148
|
+
writeVirtualFile();
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
exports.CapabilitiesPlugin = CapabilitiesPlugin;
|
|
153
|
+
exports.default = CapabilitiesPlugin;
|
package/package.json
CHANGED
package/lib/utils/local-dev.d.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
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;
|
package/lib/utils/local-dev.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
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
|
-
}
|