@aiot-toolkit/aiotpack 2.0.6-beta.9 → 2.1.0-prender.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.
Files changed (42) hide show
  1. package/lib/afterCompile/ux/UxAfterCompile.d.ts +4 -0
  2. package/lib/afterCompile/ux/UxAfterCompile.js +90 -2
  3. package/lib/compiler/javascript/JavascriptCompiler.js +11 -4
  4. package/lib/compiler/javascript/TemplateCompiler.d.ts +29 -0
  5. package/lib/compiler/javascript/TemplateCompiler.js +564 -0
  6. package/lib/compiler/javascript/ViteCompiler.d.ts +13 -0
  7. package/lib/compiler/javascript/ViteCompiler.js +414 -0
  8. package/lib/compiler/javascript/interface/IJavascriptCompileOption.d.ts +26 -0
  9. package/lib/compiler/javascript/vela/VelaWebpackConfigurator.d.ts +3 -1
  10. package/lib/compiler/javascript/vela/VelaWebpackConfigurator.js +16 -1
  11. package/lib/compiler/javascript/vela/interface/IManifest.d.ts +12 -0
  12. package/lib/compiler/javascript/vela/plugin/WrapPlugin.d.ts +10 -1
  13. package/lib/compiler/javascript/vela/plugin/WrapPlugin.js +241 -57
  14. package/lib/compiler/javascript/vela/utils/UxCompileUtil.d.ts +3 -2
  15. package/lib/compiler/javascript/vela/utils/UxCompileUtil.js +12 -4
  16. package/lib/compiler/javascript/vela/utils/VruUtil.d.ts +50 -0
  17. package/lib/compiler/javascript/vela/utils/VruUtil.js +128 -0
  18. package/lib/compiler/javascript/vela/utils/ZipUtil.d.ts +9 -0
  19. package/lib/compiler/javascript/vela/utils/ZipUtil.js +112 -6
  20. package/lib/compiler/javascript/vela/utils/webpackLoader/WebpackJsLoader.js +1 -1
  21. package/lib/config/UxConfig.d.ts +12 -5
  22. package/lib/config/UxConfig.js +7 -6
  23. package/lib/loader/ux/JsLoader.d.ts +7 -0
  24. package/lib/loader/ux/JsLoader.js +38 -8
  25. package/lib/loader/ux/vela/HmlLoader.d.ts +6 -6
  26. package/lib/loader/ux/vela/HmlLoader.js +30 -13
  27. package/lib/prerender/PrerenderVM.d.ts +86 -0
  28. package/lib/prerender/PrerenderVM.js +677 -0
  29. package/lib/prerender/StyleSerializer.d.ts +18 -0
  30. package/lib/prerender/StyleSerializer.js +92 -0
  31. package/lib/prerender/TemplateSerializer.d.ts +26 -0
  32. package/lib/prerender/TemplateSerializer.js +122 -0
  33. package/lib/prerender/index.d.ts +20 -0
  34. package/lib/prerender/index.js +519 -0
  35. package/lib/prerender/interface/IPrerenderOption.d.ts +15 -0
  36. package/lib/prerender/interface/IPrerenderOption.js +1 -0
  37. package/lib/utils/BeforeCompileUtils.d.ts +1 -1
  38. package/lib/utils/BeforeCompileUtils.js +52 -9
  39. package/lib/utils/ux/ManifestSchema.js +0 -1
  40. package/lib/utils/ux/UxFileUtils.js +1 -1
  41. package/lib/utils/ux/UxLoaderUtils.js +8 -3
  42. package/package.json +9 -6
@@ -5,81 +5,265 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
  var _core = require("@rspack/core");
8
+ var _TemplateCompiler = require("../../TemplateCompiler");
8
9
  var _webpackSources = require("webpack-sources");
10
+ var _path = _interopRequireDefault(require("path"));
11
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
9
12
  class WrapPlugin {
10
13
  constructor(compilerOption) {
11
14
  this.compilerOption = compilerOption;
12
15
  }
13
16
  apply(compiler) {
14
- // 给入口文件加上包裹函数
15
17
  compiler.hooks.compilation.tap('WrapPlugin', compilation => {
16
18
  compilation.hooks.processAssets.tap({
17
19
  name: 'WrapPlugin',
18
20
  stage: _core.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE
19
- }, () => {
20
- this.wrap(compilation);
21
- });
21
+ }, () => this.wrap(compilation));
22
22
  });
23
23
  }
24
+ getComponentName(entry) {
25
+ return _path.default.parse(entry).name;
26
+ }
27
+ findMatching(code, start, open, close) {
28
+ let depth = 0,
29
+ inStr = null,
30
+ esc = false;
31
+ for (let i = start; i < code.length; i++) {
32
+ const ch = code[i];
33
+ if (esc) {
34
+ esc = false;
35
+ continue;
36
+ }
37
+ if (ch === '\\') {
38
+ esc = true;
39
+ continue;
40
+ }
41
+ if (inStr) {
42
+ if (ch === inStr) inStr = null;
43
+ continue;
44
+ }
45
+ if (ch === '"' || ch === "'" || ch === '`') {
46
+ inStr = ch;
47
+ continue;
48
+ }
49
+ if (ch === open) depth++;else if (ch === close) {
50
+ depth--;
51
+ if (depth === 0) return i;
52
+ }
53
+ }
54
+ return -1;
55
+ }
56
+ /**
57
+ * Extract keyed modules from rspack's __webpack_modules__ object
58
+ */
59
+ extractModules(code) {
60
+ const idx = code.indexOf('var __webpack_modules__');
61
+ if (idx === -1) return [{
62
+ key: '',
63
+ body: code
64
+ }];
65
+ const braceIdx = code.indexOf('{', idx + 23);
66
+ let openIdx = code.indexOf('(', idx + 23);
67
+ if (openIdx === -1 || braceIdx !== -1 && braceIdx < openIdx) openIdx = braceIdx;
68
+ if (openIdx === -1) return [{
69
+ key: '',
70
+ body: code
71
+ }];
72
+ const closeIdx = code[openIdx] === '(' ? this.findMatching(code, openIdx, '(', ')') : this.findMatching(code, openIdx, '{', '}');
73
+ if (closeIdx === -1) return [{
74
+ key: '',
75
+ body: code
76
+ }];
77
+ const inner = code.slice(openIdx + 1, closeIdx);
78
+ const results = [];
79
+ let pos = 0;
80
+ while (pos < inner.length) {
81
+ const kStart = inner.indexOf('"', pos);
82
+ if (kStart === -1) break;
83
+ const kEnd = inner.indexOf('"', kStart + 1);
84
+ if (kEnd === -1) break;
85
+ const key = inner.slice(kStart + 1, kEnd);
86
+ pos = kEnd + 1;
87
+ const fIdx = inner.indexOf('function', pos);
88
+ if (fIdx === -1) break;
89
+ const bOpen = inner.indexOf('{', fIdx);
90
+ if (bOpen === -1) break;
91
+ const bClose = this.findMatching(inner, bOpen, '{', '}');
92
+ if (bClose === -1) break;
93
+ results.push({
94
+ key,
95
+ body: inner.slice(bOpen + 1, bClose).trim()
96
+ });
97
+ pos = bClose + 1;
98
+ }
99
+ return results.length > 0 ? results : [{
100
+ key: '',
101
+ body: code
102
+ }];
103
+ }
104
+ /**
105
+ * Clean a module body into blueos-pack format component block
106
+ */
107
+ buildBlock(body, defineId, styleId) {
108
+ let s = body;
109
+ // Remove ALL _interopRequireDefault function definitions FIRST (before unwrapping calls)
110
+ while (true) {
111
+ const iIdx = s.indexOf('function _interopRequireDefault');
112
+ if (iIdx === -1) break;
113
+ const bStart = s.indexOf('{', iIdx);
114
+ if (bStart === -1) break;
115
+ const bEnd = this.findMatching(s, bStart, '{', '}');
116
+ if (bEnd === -1) break;
117
+ s = s.slice(0, iIdx) + s.slice(bEnd + 1);
118
+ }
119
+ // Then unwrap _interopRequireDefault() calls
120
+ const interopVars = [];
121
+ s = s.replace(/_interopRequireDefault\(([^)]+)\)/g, (_, inner) => inner);
122
+ // Remove .default from interop vars
123
+ for (const v of interopVars) {
124
+ s = s.replace(new RegExp(`${v}\\.default\\.`, 'g'), `${v}.`);
125
+ s = s.replace(new RegExp(`${v}\\.default([^.]|$)`, 'g'), `${v}$1`);
126
+ }
127
+ // Remove boilerplate
128
+ // Replace $app_style$ array with @info reference using unique per-component name
129
+ const styleIdx = s.indexOf('var $app_style$');
130
+ if (styleIdx !== -1) {
131
+ const bi = s.indexOf('[', styleIdx);
132
+ if (bi !== -1) {
133
+ const be = this.findMatching(s, bi, '[', ']');
134
+ if (be !== -1) {
135
+ let se = s.indexOf(';', be);
136
+ if (se === -1) se = be;
137
+ // Remove the entire var $app_style$ line (we use $app_style$<id> instead)
138
+ s = s.slice(0, styleIdx) + s.slice(se + 1);
139
+ }
140
+ }
141
+ }
142
+ // Also remove any remaining var $app_style$ = {...} (already @info format)
143
+ s = s.replace(/var \$app_style\$\s*=[^;]*;\s*/g, '');
144
+ s = s.replace(/module\.exports[^;]*;\s*/g, '');
145
+ // Preserve $app_template$ for prerender (will be stripped after template.json is generated)
146
+ let templateCode = '';
147
+ const tplIdx = body.indexOf('var $app_template$');
148
+ if (tplIdx !== -1) {
149
+ const fnStart = body.indexOf('function', tplIdx);
150
+ if (fnStart !== -1) {
151
+ const braceStart = body.indexOf('{', fnStart);
152
+ if (braceStart !== -1) {
153
+ const braceEnd = this.findMatching(body, braceStart, '{', '}');
154
+ if (braceEnd !== -1) {
155
+ templateCode = body.slice(tplIdx, braceEnd + 1) + ';';
156
+ }
157
+ }
158
+ }
159
+ }
160
+ s = s.replace(/\$app_exports\$\[[^\]]*\][^;]*;\s*/g, '');
161
+ s = s.replace(/var \$app_exports\$[^;]*;\s*/g, '');
162
+ s = s.replace(/\$app_exports\$\.default[^;]*;\s*/g, '');
163
+ s = s.replace(/"use strict";\s*/g, '');
164
+ s = s.replace(/Object\.defineProperty\(exports,\s*"__esModule"[^;]*;\s*/g, '');
165
+ s = s.replace(/exports\["default"\]\s*=\s*void 0;\s*/g, '');
166
+ s = s.replace(/const moduleOwn[\s\S]*?}\s*}\s*}\s*/g, '');
167
+ s = s.replace(/var exports\s*=\s*module\.exports;\s*/g, '');
168
+ // Remove __webpack_require__ calls (including the var declaration if present)
169
+ s = s.replace(/(?:var|const|let)\s+\w+\s*=\s*__webpack_require__\([^)]*\);?\s*/g, '');
170
+ s = s.replace(/__webpack_require__\([^)]*\);?\s*/g, '');
171
+ // Extract component object
172
+ let obj = '{}';
173
+ const m = s.match(/(?:var _default\s*=\s*)?exports\["default"\]\s*=\s*(\{[\s\S]*\}|[\w.]+);?\s*$/);
174
+ if (m) {
175
+ obj = m[1];
176
+ s = s.slice(0, s.indexOf(m[0])).trim();
177
+ }
178
+ const lines = [`let $style$${styleId} = {"@info":{"styleObjectId":${styleId}}};`, `const $app_style$${styleId} = $style$${styleId};`, ...(s.trim() ? [s.trim()] : []), ...(templateCode ? [templateCode] : []), `const $app_script$${styleId} = ${obj};`, `$app_define$("${defineId}", [], function($app_require$, $app_exports$, $app_module$) {`, ` $app_module$.exports = $app_script$${styleId}.default || $app_script$${styleId};`, ` $app_module$.exports.style = $app_style$${styleId};`, ...(templateCode ? [` $app_module$.exports.template = $app_template$;`] : []), `});`];
179
+ return lines.join('\n');
180
+ }
24
181
  wrap(compilation) {
25
- const {
26
- enableE2e
27
- } = this.compilerOption;
28
- // 获取入口文件
29
182
  const entrys = Object.keys(compilation.options.entry).map(item => `${item}.js`);
30
- // 从chunk找到所有入口文件,添加包裹函数
31
183
  entrys.forEach(entry => {
32
- if (compilation.assets[entry]) {
33
- const source = compilation.assets[entry];
34
- const isApp = entry === 'app.js';
35
- const createFuncnName = isApp ? 'createAppHandler' : 'createPageHandler';
36
- compilation.assets[entry] = new _webpackSources.ConcatSource(`export default function(global, globalThis, window, $app_exports$, $app_evaluate$){
37
- var org_app_require = $app_require$;
38
-
39
- (function(global, globalThis, window, $app_exports$, $app_evaluate$){
40
- var setTimeout = global.setTimeout;
41
- var setInterval = global.setInterval;
42
- var clearTimeout = global.clearTimeout;
43
- var clearInterval = global.clearInterval;
44
- var $app_require$ = global.$app_require$ || org_app_require
184
+ if (!compilation.assets[entry]) return;
185
+ const sourceCode = compilation.assets[entry].source().toString();
186
+ const isApp = entry === 'app.js';
187
+ const componentName = this.getComponentName(entry);
188
+ const bootstrapId = isApp ? '@app-application/app' : `@app-component/${componentName}`;
189
+ const modules = this.extractModules(sourceCode);
190
+ // Generate css.json from style arrays before buildBlock replaces them
191
+ const cssJsonData = {};
192
+ for (const mod of modules) {
193
+ const si = mod.body.indexOf('var $app_style$');
194
+ if (si === -1) continue;
195
+ const bi = mod.body.indexOf('[', si);
196
+ if (bi === -1) continue;
197
+ const be = this.findMatching(mod.body, bi, '[', ']');
198
+ if (be === -1) continue;
199
+ try {
200
+ const styleStr = mod.body.slice(bi, be + 1);
201
+ const sid = (0, _TemplateCompiler.getStyleObjectId)(mod.key || entry);
202
+ const cssObj = (0, _TemplateCompiler.parseStyleArray)(styleStr);
203
+ if (Object.keys(cssObj).length > 0) cssJsonData[String(sid)] = cssObj;
204
+ } catch {}
205
+ }
206
+ const cssJsonPath = entry.replace(/\.js$/, '.css.json');
207
+ compilation.assets[cssJsonPath] = new _webpackSources.RawSource(JSON.stringify(cssJsonData, null, 2));
208
+ // Emit component import map for template.json generation
209
+ const importMap = {};
210
+ for (const mod of modules) {
211
+ if (!mod.key.includes('?uxType=') && mod.key !== '' && mod.key.endsWith('.ux')) {
212
+ const entryM = modules.find(m => m.key.includes('?uxType=') || m.key === '');
213
+ const escaped = mod.key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
214
+ const nm = entryM?.body.match(new RegExp(`\\$app_exports\\$\\['([^']+)'\\]\\s*=\\s*__webpack_require__\\([^)]*"${escaped}"\\)`));
215
+ const name = nm ? nm[1] : _path.default.parse(mod.key.replace(/\.ux$/, '')).name.toLowerCase();
216
+ // Convert "./src/components/Card.ux" to "components/Card"
217
+ const importPath = mod.key.replace(/^\.\/src\//, '').replace(/\.ux$/, '');
218
+ importMap[name] = importPath;
219
+ }
220
+ }
221
+ if (Object.keys(importMap).length > 0) {
222
+ const mapPath = entry.replace(/\.js$/, '.imports.json');
223
+ compilation.assets[mapPath] = new _webpackSources.RawSource(JSON.stringify(importMap));
224
+ }
225
+ const blocks = [];
226
+ // Find entry module to extract component name mappings
227
+ const entryMod = modules.find(m => m.key.includes('?uxType=') || m.key === '');
228
+ for (const mod of modules) {
229
+ // Check if this entry has non-.ux JS dependencies
230
+ const hasJsDeps = modules.some(m => m.key && !m.key.endsWith('.ux') && !m.key.includes('?uxType=') && m.key !== '' && !m.key.endsWith('.json'));
45
231
 
46
- ${enableE2e ? `globalThis = undefined; \n global = typeof window === "undefined" ? global.__proto__ : window;` : ''}
47
- var ${createFuncnName} = function() {
48
- return `, source, `
49
- }
50
-
51
- return ${createFuncnName}();
52
- })(global, globalThis, window, $app_exports$, $app_evaluate$)
53
- }`);
232
+ // If entry has JS dependencies, keep the full rspack output (don't decompose)
233
+ if (hasJsDeps && modules.length > 1) {
234
+ // Just wrap the entire rspack output with $app_define$/$app_bootstrap$
235
+ const styleId = (0, _TemplateCompiler.getStyleObjectId)(entry);
236
+ const defineId = isApp ? '@app-component/app' : `@app-component/${componentName}`;
237
+ blocks.push(`let $style$${styleId} = {"@info":{"styleObjectId":${styleId}}};`);
238
+ blocks.push(`const $app_style$${styleId} = $style$${styleId};`);
239
+ blocks.push(`$app_define$("${defineId}", [], function($app_require$, $app_exports$, $app_module$) {`);
240
+ blocks.push(`var exports = $app_module$.exports;`);
241
+ blocks.push(`var module = $app_module$;`);
242
+ blocks.push(sourceCode);
243
+ blocks.push(`$app_module$.exports.style = $app_style$${styleId};`);
244
+ blocks.push(`});`);
245
+ break;
246
+ }
247
+ // Non-.ux modules (utility JS files) are handled by webpack runtime above
248
+ if (mod.key && !mod.key.endsWith('.ux') && !mod.key.includes('?uxType=') && mod.key !== '') {
249
+ continue;
250
+ }
251
+ let compName;
252
+ if (mod.key.includes('?uxType=') || mod.key === '') {
253
+ compName = componentName;
254
+ } else {
255
+ // Find component name from: $app_exports$['name'] = __webpack_require__(/*...*/ "key")
256
+ const escaped = mod.key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
257
+ const nm = entryMod?.body.match(new RegExp(`\\$app_exports\\$\\['([^']+)'\\]\\s*=\\s*__webpack_require__\\([^)]*"${escaped}"\\)`));
258
+ compName = nm ? nm[1] : _path.default.parse(mod.key.replace(/\.ux$/, '')).name.toLowerCase();
259
+ }
260
+ const defineId = isApp && mod === entryMod ? '@app-component/app' : `@app-component/${compName}`;
261
+ const styleId = (0, _TemplateCompiler.getStyleObjectId)(mod.key || entry);
262
+ blocks.push(this.buildBlock(mod.body, defineId, styleId));
54
263
  }
264
+ blocks.push(`$app_bootstrap$("${bootstrapId}");`);
265
+ compilation.assets[entry] = new _webpackSources.RawSource(blocks.join('\n') + '\n');
55
266
  });
56
267
  }
57
- translateStyleFunc() {
58
- return `
59
- var $translateStyle$ = function (value) {
60
- if (typeof value === 'string') {
61
- return Object.fromEntries(
62
- value
63
- .split(';')
64
- .filter((item) => Boolean(item && item.trim()))
65
- .map((item) => {
66
- const matchs = item.match(/([^:]+):(.*)/);
67
- if (matchs && matchs.length > 2) {
68
- return [
69
- matchs[1]
70
- .trim()
71
- .replace(/-([a-z])/g, (_, match) => match.toUpperCase()),
72
- matchs[2].trim(),
73
- ];
74
- }
75
- return [];
76
- })
77
- );
78
- }
79
- return value;
80
- };
81
- global.$translateStyle$ = $translateStyle$
82
- `;
83
- }
84
268
  }
85
269
  var _default = exports.default = WrapPlugin;
@@ -1,4 +1,5 @@
1
1
  import { Dictionary } from '@aiot-toolkit/shared-utils';
2
+ import IManifest from '../interface/IManifest';
2
3
  declare class UxCompileUtil {
3
4
  static readonly DIGEST_ZIP_DIR = "META-INF";
4
5
  static clean(dirList: string[]): void;
@@ -7,9 +8,9 @@ declare class UxCompileUtil {
7
8
  * @param config 项目配置文件的内容,应为json对象
8
9
  * @param codeDir 源码目录
9
10
  * @param projectPath 项目目录
10
- * @returns {[入口名]:源文件路径}
11
+ * @returns `{[entryName]:entryPath}`
11
12
  */
12
- static resolveEntries(config: any, codeDir: string, projectPath: string): Dictionary<string>;
13
+ static resolveEntries(config: IManifest, codeDir: string, projectPath: string): Dictionary<string>;
13
14
  /**
14
15
  * 通过无后缀的文件名路径获取存在的文件路径
15
16
  *
@@ -36,7 +36,7 @@ class UxCompileUtil {
36
36
  * @param config 项目配置文件的内容,应为json对象
37
37
  * @param codeDir 源码目录
38
38
  * @param projectPath 项目目录
39
- * @returns {[入口名]:源文件路径}
39
+ * @returns `{[entryName]:entryPath}`
40
40
  */
41
41
  static resolveEntries(config, codeDir, projectPath) {
42
42
  const {
@@ -114,11 +114,10 @@ class UxCompileUtil {
114
114
  if (Array.isArray(services)) {
115
115
  services.forEach(item => {
116
116
  const {
117
- name,
118
117
  path
119
118
  } = item;
120
- if (name && path) {
121
- result['services/' + name] = './src/' + path + `?uxType=${_EntryType.default.APP}`;
119
+ if (path) {
120
+ result[path] = './src/' + path + `?uxType=${_EntryType.default.APP}`;
122
121
  }
123
122
  });
124
123
  }
@@ -130,6 +129,15 @@ class UxCompileUtil {
130
129
  }
131
130
  }
132
131
  }
132
+ // 5. 添加 widget_provider
133
+ const {
134
+ widgetProvider
135
+ } = config;
136
+ if (widgetProvider?.length) {
137
+ for (const widgetItem of widgetProvider) {
138
+ result[widgetItem.path] = './src/' + widgetItem.path;
139
+ }
140
+ }
133
141
  return result;
134
142
  }
135
143
 
@@ -0,0 +1,50 @@
1
+ /**
2
+ * VRU (Vela Union) file format utility.
3
+ *
4
+ * Pack/unpack a multi-file binary container used to bundle app resources
5
+ * inside a release RPK. Format is byte-compatible with vivo's VRU (only
6
+ * the magic differs: we use 'vela' instead of 'vivo').
7
+ * Layout:
8
+ * 0x00-0x0F: 'vela union file\0' (16 bytes magic)
9
+ * 0x10-0x3F: 48 bytes zero padding
10
+ * 0x40-0x43: entries-table size (uint32 LE) = header_end - 0xB0
11
+ * 0x44-0x47: total VRU file size (uint32 LE)
12
+ * 0x48-0xA7: package name (96 bytes, null-padded UTF-8)
13
+ * 0xA8-0xAB: 4 bytes zero padding
14
+ * 0xAC-0xAF: entry count (uint32 LE)
15
+ * 0xB0-...: entries table, for each entry:
16
+ * 4 bytes: filename length (uint32 LE)
17
+ * N bytes: filename (UTF-8)
18
+ * 4 bytes: data offset from start of file (uint32 LE)
19
+ * 4 bytes: data size (uint32 LE)
20
+ * after entries: file data (concatenated in entry order)
21
+ */
22
+ export interface VruEntry {
23
+ name: string;
24
+ data: Buffer;
25
+ }
26
+ export declare class VruUtil {
27
+ static readonly MAGIC = "vela union file";
28
+ static readonly PACKAGE_NAME_OFFSET = 72;
29
+ static readonly PACKAGE_NAME_SIZE = 96;
30
+ static readonly ENTRY_COUNT_OFFSET = 172;
31
+ static readonly ENTRIES_TABLE_OFFSET = 176;
32
+ /**
33
+ * Pack multiple files into a VRU buffer.
34
+ * @param entries Array of {name, data} pairs
35
+ * @param packageName Application package name (max 95 chars)
36
+ */
37
+ static pack(entries: VruEntry[], packageName: string): Buffer;
38
+ /**
39
+ * Get the VRU filename from a manifest package name.
40
+ */
41
+ static getVruName(packageName: string): string;
42
+ /**
43
+ * Parse a VRU buffer back into entries + package name.
44
+ * Useful for round-trip verification and reading existing VRU files.
45
+ */
46
+ static unpack(buf: Buffer): {
47
+ entries: VruEntry[];
48
+ packageName: string;
49
+ };
50
+ }
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.VruUtil = void 0;
7
+ /**
8
+ * VRU (Vela Union) file format utility.
9
+ *
10
+ * Pack/unpack a multi-file binary container used to bundle app resources
11
+ * inside a release RPK. Format is byte-compatible with vivo's VRU (only
12
+ * the magic differs: we use 'vela' instead of 'vivo').
13
+ * Layout:
14
+ * 0x00-0x0F: 'vela union file\0' (16 bytes magic)
15
+ * 0x10-0x3F: 48 bytes zero padding
16
+ * 0x40-0x43: entries-table size (uint32 LE) = header_end - 0xB0
17
+ * 0x44-0x47: total VRU file size (uint32 LE)
18
+ * 0x48-0xA7: package name (96 bytes, null-padded UTF-8)
19
+ * 0xA8-0xAB: 4 bytes zero padding
20
+ * 0xAC-0xAF: entry count (uint32 LE)
21
+ * 0xB0-...: entries table, for each entry:
22
+ * 4 bytes: filename length (uint32 LE)
23
+ * N bytes: filename (UTF-8)
24
+ * 4 bytes: data offset from start of file (uint32 LE)
25
+ * 4 bytes: data size (uint32 LE)
26
+ * after entries: file data (concatenated in entry order)
27
+ */
28
+
29
+ class VruUtil {
30
+ static MAGIC = 'vela union file';
31
+ static PACKAGE_NAME_OFFSET = 0x48;
32
+ static PACKAGE_NAME_SIZE = 96;
33
+ static ENTRY_COUNT_OFFSET = 0xAC;
34
+ static ENTRIES_TABLE_OFFSET = 0xB0;
35
+
36
+ /**
37
+ * Pack multiple files into a VRU buffer.
38
+ * @param entries Array of {name, data} pairs
39
+ * @param packageName Application package name (max 95 chars)
40
+ */
41
+ static pack(entries, packageName) {
42
+ if (Buffer.byteLength(packageName, 'utf-8') >= VruUtil.PACKAGE_NAME_SIZE) {
43
+ throw new Error(`Package name too long: ${packageName}`);
44
+ }
45
+
46
+ // Compute table size
47
+ let tableSize = 0;
48
+ for (const e of entries) {
49
+ tableSize += 4 + Buffer.byteLength(e.name, 'utf-8') + 8;
50
+ }
51
+ const headerEnd = VruUtil.ENTRIES_TABLE_OFFSET + tableSize;
52
+
53
+ // Compute total size
54
+ let totalSize = headerEnd;
55
+ for (const e of entries) totalSize += e.data.length;
56
+ const buf = Buffer.alloc(totalSize);
57
+ // Magic
58
+ buf.write(VruUtil.MAGIC, 0, 'utf-8');
59
+ // 0x40: entries-table size (header_end - 0xB0)
60
+ buf.writeUInt32LE(tableSize, 0x40);
61
+ // 0x44: total file size
62
+ buf.writeUInt32LE(totalSize, 0x44);
63
+ // 0x48: package name (null-padded)
64
+ buf.write(packageName, VruUtil.PACKAGE_NAME_OFFSET, 'utf-8');
65
+ // 0xAC: entry count
66
+ buf.writeUInt32LE(entries.length, VruUtil.ENTRY_COUNT_OFFSET);
67
+
68
+ // Write entries table and accumulate data offsets
69
+ let entryOffset = VruUtil.ENTRIES_TABLE_OFFSET;
70
+ let dataOffset = headerEnd;
71
+ for (const e of entries) {
72
+ const nameBuf = Buffer.from(e.name, 'utf-8');
73
+ buf.writeUInt32LE(nameBuf.length, entryOffset);
74
+ entryOffset += 4;
75
+ nameBuf.copy(buf, entryOffset);
76
+ entryOffset += nameBuf.length;
77
+ buf.writeUInt32LE(dataOffset, entryOffset);
78
+ entryOffset += 4;
79
+ buf.writeUInt32LE(e.data.length, entryOffset);
80
+ entryOffset += 4;
81
+ e.data.copy(buf, dataOffset);
82
+ dataOffset += e.data.length;
83
+ }
84
+ return buf;
85
+ }
86
+
87
+ /**
88
+ * Get the VRU filename from a manifest package name.
89
+ */
90
+ static getVruName(packageName) {
91
+ return `${packageName}.vru`;
92
+ }
93
+
94
+ /**
95
+ * Parse a VRU buffer back into entries + package name.
96
+ * Useful for round-trip verification and reading existing VRU files.
97
+ */
98
+ static unpack(buf) {
99
+ const magic = buf.slice(0, VruUtil.MAGIC.length).toString('utf-8');
100
+ if (magic !== VruUtil.MAGIC) {
101
+ throw new Error(`Invalid VRU magic: expected "${VruUtil.MAGIC}", got "${magic}"`);
102
+ }
103
+ const pkgEnd = buf.indexOf(0, VruUtil.PACKAGE_NAME_OFFSET);
104
+ const packageName = buf.slice(VruUtil.PACKAGE_NAME_OFFSET, pkgEnd >= 0 ? pkgEnd : VruUtil.PACKAGE_NAME_OFFSET + VruUtil.PACKAGE_NAME_SIZE).toString('utf-8');
105
+ const count = buf.readUInt32LE(VruUtil.ENTRY_COUNT_OFFSET);
106
+ const entries = [];
107
+ let offset = VruUtil.ENTRIES_TABLE_OFFSET;
108
+ for (let i = 0; i < count; i++) {
109
+ const nameLen = buf.readUInt32LE(offset);
110
+ offset += 4;
111
+ const name = buf.slice(offset, offset + nameLen).toString('utf-8');
112
+ offset += nameLen;
113
+ const dataOffset = buf.readUInt32LE(offset);
114
+ offset += 4;
115
+ const dataSize = buf.readUInt32LE(offset);
116
+ offset += 4;
117
+ entries.push({
118
+ name,
119
+ data: Buffer.from(buf.slice(dataOffset, dataOffset + dataSize))
120
+ });
121
+ }
122
+ return {
123
+ entries,
124
+ packageName
125
+ };
126
+ }
127
+ }
128
+ exports.VruUtil = VruUtil;
@@ -32,6 +32,15 @@ declare class ZipUtil {
32
32
  * @returns 生成的 rpk 文件名
33
33
  */
34
34
  static createRpk(dist: string, param: IJavascriptCompileOption): Promise<string | undefined>;
35
+ /**
36
+ * Build the blueos-pack compatible "wrapped release" RPK.
37
+ * Outer RPK (zip) contains:
38
+ * - manifest.json (top-level metadata)
39
+ * - logo.<ext> (icon - kept as png since VUG conversion is out of scope)
40
+ * - META-INF/CERT (signature)
41
+ * - <package>.vru (multi-file VRU containing all build artifacts)
42
+ */
43
+ private static createWrappedReleaseRpk;
35
44
  private static getFileName;
36
45
  private static packageToZipBuffer;
37
46
  /**