react-native-flux-desktop-packer 0.1.4 → 0.1.5
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/bundle.js +2 -0
- package/lib/config.js +7 -0
- package/lib/index.js +11 -0
- package/lib/sea.js +67 -22
- package/package.json +1 -1
package/lib/bundle.js
CHANGED
|
@@ -21,6 +21,8 @@ const EXTERNALS = [
|
|
|
21
21
|
'react-native-flux-desktop-linux-*',
|
|
22
22
|
'react-native-flux-desktop-android-*',
|
|
23
23
|
'react-native-flux-desktop-freebsd-*',
|
|
24
|
+
// 各原生扩展分支包的裸平台包名(ffi / webview / 未来新增):保持 external,运行期由 bootstrap 钩子重定向到自解压目录
|
|
25
|
+
'react-native-flux-desktop-*-win32-x64-msvc',
|
|
24
26
|
// @napi-rs/canvas 的平台预编译包(skia .node 由 bootstrap 经 env 指向解压副本)
|
|
25
27
|
'@napi-rs/canvas-win32-x64-msvc',
|
|
26
28
|
'@napi-rs/canvas-win32-x64-gnu',
|
package/lib/config.js
CHANGED
|
@@ -47,6 +47,12 @@ function loadConfig(projectRoot) {
|
|
|
47
47
|
const packMode = packObj.mode === 'sea' ? 'sea' : 'folder';
|
|
48
48
|
// 资源目录(相对项目根),sea 模式内嵌进 exe、运行时解压到 cwd;folder 模式整目录拷贝
|
|
49
49
|
const assetsDirRel = typeof packObj.assets === 'string' ? packObj.assets : 'assets';
|
|
50
|
+
// 目标原生库(相对项目根路径数组):随包拷进运行目录,运行期 ffi.open(相对路径) 按 cwd 命中。
|
|
51
|
+
const nativeLibs = Array.isArray(packObj.nativeLibs)
|
|
52
|
+
? packObj.nativeLibs
|
|
53
|
+
: Array.isArray(app.nativeLibs)
|
|
54
|
+
? app.nativeLibs
|
|
55
|
+
: [];
|
|
50
56
|
|
|
51
57
|
const cfg = {
|
|
52
58
|
projectRoot,
|
|
@@ -58,6 +64,7 @@ function loadConfig(projectRoot) {
|
|
|
58
64
|
outputRel: output,
|
|
59
65
|
packMode,
|
|
60
66
|
assetsDirRel,
|
|
67
|
+
nativeLibs,
|
|
61
68
|
// 归一化入口为平台分隔(Go 里用 dist\\Gallery.js)
|
|
62
69
|
mainRel: main.split('/').join(path.sep),
|
|
63
70
|
// 数据/缓存目录用的应用名(与启动器 sanitize 同规则:空格/斜杠等转 '-')
|
package/lib/index.js
CHANGED
|
@@ -79,6 +79,17 @@ async function pack(options = {}) {
|
|
|
79
79
|
fs.copyFileSync(src, path.join(stage, item));
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
+
// 目标原生库(ffi):按相对路径拷进负载根,运行期 ffi.open(相对) 按 cwd 命中。
|
|
83
|
+
for (const rel of (cfg.nativeLibs || [])) {
|
|
84
|
+
const src = path.join(projectRoot, rel);
|
|
85
|
+
if (!fs.existsSync(src)) {
|
|
86
|
+
warn(`nativeLibs 跳过(不存在):${rel}`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const dst = path.join(stage, rel);
|
|
90
|
+
ensureDir(path.dirname(dst));
|
|
91
|
+
fs.copyFileSync(src, dst);
|
|
92
|
+
}
|
|
82
93
|
// 捆绑当前 node.exe,但以应用名落盘(napi ABI 稳定,跨大版本可加载)。
|
|
83
94
|
// 运行时进程改名 → 任务管理器「详细信息」页的名称列不再显示 node.exe(node 不校验自身文件名,改名照常运行)。
|
|
84
95
|
const runtimeName = exeBase + '.exe';
|
package/lib/sea.js
CHANGED
|
@@ -61,27 +61,35 @@ function walkFiles(base, rel = '') {
|
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* 收集需内嵌为 SEA assets 的原生/数据文件。
|
|
64
|
-
*
|
|
64
|
+
* 通用发现:node_modules 下所有 react-native-flux-desktop* 包根的 *.win32-x64-msvc.node
|
|
65
|
+
* ——覆盖主包 + 各原生扩展分支包(ffi / webview / 未来新增),不再逐包硬编码。
|
|
66
|
+
* @returns {{natives:{base:string,abs:string}[],rnfdBase:string,skia:string,icu:string}}
|
|
65
67
|
*/
|
|
66
68
|
function locateNatives(projectRoot) {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
const nm = path.join(projectRoot, 'node_modules');
|
|
70
|
+
// @napi-rs/canvas 平台包:命名特殊(skia.node + icudtl.dat 同目录),单独处理
|
|
71
|
+
const canvasPkg = path.join(nm, '@napi-rs', 'canvas-win32-x64-msvc');
|
|
72
|
+
const skia = findFirst(canvasPkg, ['skia.win32-x64-msvc.node']);
|
|
73
|
+
const icu = findFirst(canvasPkg, ['icudtl.dat']);
|
|
74
|
+
// 通用扫 flux 系原生 .node(主包与所有扩展分支包一视同仁)
|
|
75
|
+
const natives = [];
|
|
76
|
+
let dirs = [];
|
|
77
|
+
try { dirs = fs.readdirSync(nm); } catch { dirs = []; }
|
|
78
|
+
for (const d of dirs) {
|
|
79
|
+
if (!d.startsWith('react-native-flux-desktop')) continue;
|
|
80
|
+
let files = [];
|
|
81
|
+
try { files = fs.readdirSync(path.join(nm, d)); } catch { continue; }
|
|
82
|
+
for (const f of files) {
|
|
83
|
+
if (/\.win32-x64-msvc\.node$/.test(f)) natives.push({ base: f, abs: path.join(nm, d, f) });
|
|
72
84
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
};
|
|
76
|
-
const rnfd = pick(['react-native-flux-desktop', 'react-native-flux-desktop.win32-x64-msvc.node']);
|
|
77
|
-
const skia = pick(['@napi-rs', 'canvas-win32-x64-msvc', 'skia.win32-x64-msvc.node']);
|
|
78
|
-
const icu = pick(['@napi-rs', 'canvas-win32-x64-msvc', 'icudtl.dat']);
|
|
85
|
+
}
|
|
86
|
+
const rnfdBase = 'react-native-flux-desktop.win32-x64-msvc.node';
|
|
79
87
|
const miss = [];
|
|
80
|
-
if (!
|
|
88
|
+
if (!natives.some((n) => n.base === rnfdBase)) miss.push(rnfdBase);
|
|
81
89
|
if (!skia) miss.push('skia.win32-x64-msvc.node');
|
|
82
90
|
if (!icu) miss.push('icudtl.dat');
|
|
83
91
|
if (miss.length) throw new Error(`未找到原生依赖:${miss.join(', ')}\n(sea 模式需在项目 node_modules 里存在这些 .node/dat)`);
|
|
84
|
-
return {
|
|
92
|
+
return { natives, rnfdBase, skia, icu };
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
// 生成 bootstrap.cjs 源码(SEA main)。manifest 以 JSON 字面量注入。
|
|
@@ -121,14 +129,21 @@ if (!fs.existsSync(appMarker) || fs.readFileSync(appMarker).toString() !== sig)
|
|
|
121
129
|
// 3) @napi-rs/canvas:绝对路径直指解压后的 skia(最高优先级)
|
|
122
130
|
process.env.NAPI_RS_NATIVE_LIBRARY_PATH = path.join(dir, MANIFEST.skiaRel);
|
|
123
131
|
|
|
124
|
-
// 4)
|
|
125
|
-
|
|
126
|
-
const
|
|
132
|
+
// 4) 自研原生(主包 + 各扩展分支包 ffi/webview/…):相对 .node 与 bare 平台包名一律重定向到 dir。
|
|
133
|
+
// 通用规则,新增扩展包无需改此处——只要它的 .node 被 locateNatives 收集进 MANIFEST.natives。
|
|
134
|
+
const NATIVE_LIST = MANIFEST.natives || [];
|
|
135
|
+
const NATIVE_SET = new Set(NATIVE_LIST);
|
|
127
136
|
const origResolve = Module._resolveFilename;
|
|
128
137
|
Module._resolveFilename = function (request) {
|
|
129
138
|
if (typeof request === 'string') {
|
|
130
|
-
|
|
131
|
-
|
|
139
|
+
for (let i = 0; i < NATIVE_LIST.length; i++) {
|
|
140
|
+
if (request.indexOf(NATIVE_LIST[i]) >= 0) return path.join(dir, NATIVE_LIST[i]);
|
|
141
|
+
}
|
|
142
|
+
const m = /^(react-native-flux-desktop(?:-[a-z0-9]+)*?)-win32-x64-msvc$/.exec(request);
|
|
143
|
+
if (m) {
|
|
144
|
+
const file = m[1] + '.win32-x64-msvc.node';
|
|
145
|
+
if (NATIVE_SET.has(file)) return path.join(dir, file);
|
|
146
|
+
}
|
|
132
147
|
}
|
|
133
148
|
return origResolve.apply(this, arguments);
|
|
134
149
|
};
|
|
@@ -139,7 +154,18 @@ if (MANIFEST.appDirEnv) process.env.FLUX_APP_DIR = MANIFEST.appDirEnv;
|
|
|
139
154
|
try { process.chdir(dir); } catch (e) {}
|
|
140
155
|
|
|
141
156
|
// 6) 用 createRequire 正常加载 app(普通 require,绕开 embedderRequire)
|
|
142
|
-
|
|
157
|
+
// GUI 子系统无控制台:启动期任何抛错(如可选原生 .node 加载失败)都会静默闪退,
|
|
158
|
+
// 故兜底把堆栈写到 LOCALAPPDATA 下 APPDIR 的 startup-error.log,便于诊断“双击打不开”。
|
|
159
|
+
try {
|
|
160
|
+
Module.createRequire(path.join(dir, 'boot.js'))(appPath);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
try {
|
|
163
|
+
const errLog = path.join(process.env.LOCALAPPDATA || os.tmpdir(), APPDIR, 'startup-error.log');
|
|
164
|
+
fs.mkdirSync(path.dirname(errLog), { recursive: true });
|
|
165
|
+
fs.writeFileSync(errLog, '[' + new Date().toISOString() + '] stamp=' + MANIFEST.stamp + '\\n' + ((err && err.stack) || err) + '\\n', 'utf8');
|
|
166
|
+
} catch (e2) {}
|
|
167
|
+
throw err;
|
|
168
|
+
}
|
|
143
169
|
`;
|
|
144
170
|
}
|
|
145
171
|
|
|
@@ -207,9 +233,24 @@ async function packSea(a) {
|
|
|
207
233
|
resources.push({ key, rel });
|
|
208
234
|
assetsMap[key] = abs;
|
|
209
235
|
};
|
|
210
|
-
|
|
236
|
+
// 通用内嵌所有 flux 原生 .node(主包 + ffi + webview + 未来扩展),按文件名落盘到 dir 根
|
|
237
|
+
for (const n of nat.natives) addRes('nat' + idx++, n.base, n.abs);
|
|
238
|
+
ok(`flux 原生绑定:${nat.natives.map((n) => n.base).join(', ')}`);
|
|
211
239
|
addRes('skia', 'skia.win32-x64-msvc.node', nat.skia);
|
|
212
240
|
addRes('icu', 'icudtl.dat', nat.icu);
|
|
241
|
+
// 目标原生库(app.json.pack.nativeLibs / app.nativeLibs):按相对路径内嵌,运行期 ffi.open(相对) 命中 cwd。
|
|
242
|
+
const nativeLibs = Array.isArray(cfg.nativeLibs) ? cfg.nativeLibs : [];
|
|
243
|
+
let nlCount = 0;
|
|
244
|
+
for (const rel of nativeLibs) {
|
|
245
|
+
const abs = path.join(projectRoot, rel);
|
|
246
|
+
if (!fs.existsSync(abs)) {
|
|
247
|
+
warn(`nativeLibs 跳过(不存在):${rel}`);
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
addRes('nl' + idx++, rel.split('/').join(path.sep), abs);
|
|
251
|
+
nlCount++;
|
|
252
|
+
}
|
|
253
|
+
if (nlCount) ok(`目标原生库随包:${nlCount} 个`);
|
|
213
254
|
|
|
214
255
|
// app.json:应用运行时从 cwd 读它取 name/description/version/icon(如 App.tsx 里
|
|
215
256
|
// readFileSync(process.cwd()/app.json))。sea 下 cwd=解压目录,故须把 app.json 也展到根,
|
|
@@ -231,7 +272,10 @@ async function packSea(a) {
|
|
|
231
272
|
|
|
232
273
|
// stamp:内容变化才重解压(含 app.json,改名字/描述也能刷新)
|
|
233
274
|
const ajSig = fs.existsSync(appJsonAbs) ? (() => { const s = fs.statSync(appJsonAbs); return s.size + ':' + Math.round(s.mtimeMs); })() : 'none';
|
|
234
|
-
const
|
|
275
|
+
const nativeFiles = nat.natives.map((n) => n.abs)
|
|
276
|
+
.concat([nat.skia, nat.icu])
|
|
277
|
+
.concat(nativeLibs.map((r) => path.join(projectRoot, r)).filter((p) => fs.existsSync(p)));
|
|
278
|
+
const sigSrc = nativeFiles.map((p) => { const s = fs.statSync(p); return s.size + ':' + Math.round(s.mtimeMs); }).join('|') + '|' + ajSig + '|' + r.size;
|
|
235
279
|
const stamp = crypto.createHash('md5').update(sigSrc).digest('hex').slice(0, 12);
|
|
236
280
|
|
|
237
281
|
const appDirEnv = cfg.safeAppName || 'Flux';
|
|
@@ -241,6 +285,7 @@ async function packSea(a) {
|
|
|
241
285
|
appDirEnv,
|
|
242
286
|
appKey: 'app',
|
|
243
287
|
skiaRel: 'skia.win32-x64-msvc.node',
|
|
288
|
+
natives: nat.natives.map((n) => n.base),
|
|
244
289
|
resources,
|
|
245
290
|
};
|
|
246
291
|
const bootstrapPath = path.join(seaDir, 'bootstrap.cjs');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-flux-desktop-packer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Package a react-native-flux-desktop app into a self-contained Windows .exe (Go launcher + go:embed payload). Install as a devDependency, add `output`/`main` to app.json, then run `flux-pack`.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "keyon",
|