seabox 0.1.0-beta.3 → 0.1.0

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.
@@ -0,0 +1,411 @@
1
+ /**
2
+ * rolldown-bundler.mjs
3
+ * Automatic bundling with Rollup and native module detection.
4
+ * Replaces manual bundling step with integrated solution.
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { rolldown as rolldown } from 'rolldown';
10
+ import Module from 'module';
11
+ import { fileURLToPath } from 'url';
12
+ import * as diag from './diagnostics.mjs';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+
17
+ /**
18
+ * @typedef {Object} NativeModuleInfo
19
+ * @property {string} binaryPath - Path to the .node file
20
+ * @property {string} packageRoot - Root directory of the native module package
21
+ * @property {string} moduleName - Name of the module
22
+ * @property {string} buildPath - Path to build directory (e.g., build/Release)
23
+ */
24
+
25
+ /**
26
+ * Rollup plugin to detect and transform native module patterns.
27
+ * Based on rollup-plugin-natives approach - transforms at build time.
28
+ * Also detects path.join(__dirname, ...) asset references for auto-embedding.
29
+ */
30
+ class NativeModuleDetectorPlugin {
31
+ constructor(options = {}) {
32
+ /** @type {Map<string, NativeModuleInfo>} */
33
+ this.nativeModules = new Map();
34
+ /** @type {Set<string>} */
35
+ this.detectedAssets = new Set();
36
+ this.targetPlatform = options.targetPlatform || process.platform;
37
+ this.targetArch = options.targetArch || process.arch;
38
+ this.projectRoot = options.projectRoot || process.cwd();
39
+ }
40
+
41
+ name = 'native-module-detector';
42
+
43
+ /**
44
+ * Get module root (package.json directory) for a given file
45
+ */
46
+ getModuleRoot(id) {
47
+ let moduleRoot = path.dirname(id);
48
+ let prev = null;
49
+
50
+ while (true) {
51
+ if (moduleRoot === '.') {
52
+ moduleRoot = process.cwd();
53
+ }
54
+
55
+ if (fs.existsSync(path.join(moduleRoot, 'package.json')) ||
56
+ fs.existsSync(path.join(moduleRoot, 'node_modules'))) {
57
+ break;
58
+ }
59
+
60
+ if (prev === moduleRoot) break;
61
+
62
+ prev = moduleRoot;
63
+ moduleRoot = path.resolve(moduleRoot, '..');
64
+ }
65
+
66
+ return moduleRoot;
67
+ }
68
+
69
+ /**
70
+ * Register a native module
71
+ */
72
+ registerNativeModule(nativePath, isPrebuild = false) {
73
+ if (this.nativeModules.has(nativePath)) {
74
+ return this.nativeModules.get(nativePath);
75
+ }
76
+
77
+ const packageRoot = this.findPackageRoot(nativePath);
78
+ const moduleName = packageRoot ? path.basename(packageRoot) : path.basename(nativePath, '.node');
79
+
80
+ // Use relative path from project root to avoid conflicts and preserve structure
81
+ const relativeFromProject = path.relative(this.projectRoot, nativePath).replace(/\\/g, '/');
82
+
83
+ const info = {
84
+ binaryPath: nativePath,
85
+ packageRoot: packageRoot || path.dirname(nativePath),
86
+ moduleName: moduleName,
87
+ buildPath: path.dirname(nativePath),
88
+ assetKey: relativeFromProject,
89
+ isPrebuild: isPrebuild // Track if this is a prebuild (don't rebuild)
90
+ };
91
+
92
+ this.nativeModules.set(nativePath, info);
93
+ return info;
94
+ }
95
+
96
+ /**
97
+ * Transform hook - detect and replace native module patterns
98
+ */
99
+ transform = (code, id) => {
100
+ let hasChanges = false;
101
+ let transformedCode = code;
102
+
103
+ // Detect path.join(__dirname, 'relative/path') patterns for auto-embedding assets
104
+ this.detectDirnameAssets(code, id);
105
+
106
+ // Pattern 1: require('bindings')('module_name')
107
+ const bindingsPattern = /require\(['"]bindings['"]\)\(((['"])(.+?)\2)?\)/g;
108
+ const moduleRoot = this.getModuleRoot(id);
109
+ const self = this;
110
+
111
+ transformedCode = transformedCode.replace(bindingsPattern, function(match, args, quote, name) {
112
+ const nativeAlias = name || 'bindings.node';
113
+ const nodeName = nativeAlias.endsWith('.node') ? nativeAlias : `${nativeAlias}.node`;
114
+
115
+ // Try standard build locations
116
+ const possibilities = [
117
+ path.join(moduleRoot, 'build', nodeName),
118
+ path.join(moduleRoot, 'build', 'Debug', nodeName),
119
+ path.join(moduleRoot, 'build', 'Release', nodeName),
120
+ ];
121
+
122
+ const chosenPath = possibilities.find(p => fs.existsSync(p));
123
+ if (chosenPath) {
124
+ const info = self.registerNativeModule(chosenPath);
125
+ hasChanges = true;
126
+ return `__requireSeabox('${info.assetKey}')`;
127
+ }
128
+
129
+ return match;
130
+ });
131
+
132
+ // Pattern 2: Direct require('./path.node') or require('./path')
133
+ const directRequirePattern = /require\(['"]([^'"]+)['"]\)/g;
134
+ transformedCode = transformedCode.replace(directRequirePattern, function(match, modulePath) {
135
+ // Only process potential native module paths
136
+ if (!modulePath.includes('.node') && !modulePath.includes('/build/')) {
137
+ return match;
138
+ }
139
+
140
+ let testPath = modulePath;
141
+ if (!testPath.endsWith('.node')) {
142
+ testPath += '.node';
143
+ }
144
+
145
+ // Resolve relative to the current file
146
+ if (modulePath.startsWith('.')) {
147
+ testPath = path.resolve(path.dirname(id), testPath);
148
+ } else {
149
+ testPath = path.join(moduleRoot, testPath);
150
+ }
151
+
152
+ if (fs.existsSync(testPath)) {
153
+ const info = self.registerNativeModule(testPath);
154
+ hasChanges = true;
155
+ return `__requireSeabox('${info.assetKey}')`;
156
+ }
157
+
158
+ return match;
159
+ });
160
+
161
+ // Pattern 3: node-gyp-build pattern - needs runtime platform detection
162
+ if (code.includes('node-gyp-build')) {
163
+ const nodeGypBuildPattern = /require\(['"]node-gyp-build['"]\)\(__dirname\)/g;
164
+ transformedCode = transformedCode.replace(nodeGypBuildPattern, function(match) {
165
+ const prebuildsDir = path.join(moduleRoot, 'prebuilds');
166
+
167
+ // For node-gyp-build, we need to handle it differently since it's platform-specific
168
+ // Check if we're building for a specific platform or current platform
169
+ if (fs.existsSync(prebuildsDir)) {
170
+ const platformArchDir = path.join(prebuildsDir, `${self.targetPlatform}-${self.targetArch}`);
171
+
172
+ if (fs.existsSync(platformArchDir)) {
173
+ const files = fs.readdirSync(platformArchDir);
174
+ const nodeFiles = files.filter(f => f.endsWith('.node'));
175
+
176
+ if (nodeFiles.length > 0) {
177
+ // Pick best match (prefer napi builds)
178
+ const napiFile = nodeFiles.find(f => f.includes('napi')) || nodeFiles[0];
179
+ const nativePath = path.join(platformArchDir, napiFile);
180
+ const info = self.registerNativeModule(nativePath, true); // Mark as prebuild
181
+ hasChanges = true;
182
+ return `__requireSeabox('${info.assetKey}')`;
183
+ }
184
+ }
185
+ }
186
+
187
+ // If prebuilds don't exist, fall back to build/Release for gyp builds
188
+ const buildRelease = path.join(moduleRoot, 'build', 'Release');
189
+ if (fs.existsSync(buildRelease)) {
190
+ const files = fs.readdirSync(buildRelease);
191
+ const nodeFile = files.find(f => f.endsWith('.node'));
192
+ if (nodeFile) {
193
+ const nativePath = path.join(buildRelease, nodeFile);
194
+ const info = self.registerNativeModule(nativePath);
195
+ hasChanges = true;
196
+ return `__requireSeabox('${info.assetKey}')`;
197
+ }
198
+ }
199
+
200
+ return match;
201
+ });
202
+ }
203
+
204
+ // Pattern 4: node-pre-gyp pattern (strip it out and use direct path)
205
+ if (code.includes('node-pre-gyp') || code.includes('@mapbox/node-pre-gyp')) {
206
+ const preGypPattern = /(?:var|let|const)\s+(\w+)\s+=\s+require\(['"](?:@mapbox\/)?node-pre-gyp['"]\)/g;
207
+ const varMatch = preGypPattern.exec(code);
208
+
209
+ if (varMatch) {
210
+ const varName = varMatch[1];
211
+ const binaryPattern = new RegExp(
212
+ `(?:var|let|const)\\s+(\\w+)\\s+=\\s+${varName}\\.find\\(path\\.resolve\\(path\\.join\\(__dirname,\\s*(['"])(.+?)\\2\\)\\)\\);?\\s*(?:var|let|const)\\s+(\\w+)\\s+=\\s+require\\(\\1\\)`,
213
+ 'g'
214
+ );
215
+
216
+ transformedCode = transformedCode.replace(binaryPattern, function(match, pathVar, quote, relPath) {
217
+ // Try to find the actual binary using standard node-pre-gyp structure
218
+ const possibilities = [
219
+ path.join(moduleRoot, 'lib', 'binding'),
220
+ path.join(moduleRoot, 'build', 'Release')
221
+ ];
222
+
223
+ for (const dir of possibilities) {
224
+ if (fs.existsSync(dir)) {
225
+ const files = fs.readdirSync(dir);
226
+ const nodeFile = files.find(f => f.endsWith('.node'));
227
+ if (nodeFile) {
228
+ const nativePath = path.join(dir, nodeFile);
229
+ const info = self.registerNativeModule(nativePath);
230
+ hasChanges = true;
231
+ // Remove the pre-gyp require entirely
232
+ transformedCode = transformedCode.replace(varMatch[0], '');
233
+ const requireVarMatch = match.match(/const\s+(\w+)\s+=\s+require/);
234
+ const requireVar = requireVarMatch ? requireVarMatch[1] : 'binding';
235
+ return `const ${pathVar} = '${info.assetKey}'; const ${requireVar} = __requireSeabox('${info.assetKey}')`;
236
+ }
237
+ }
238
+ }
239
+
240
+ return match;
241
+ });
242
+ }
243
+ }
244
+
245
+ return hasChanges ? { code: transformedCode, map: null } : null;
246
+ }
247
+
248
+ /**
249
+ * Detect path.join(__dirname, ...) patterns and register them as assets
250
+ */
251
+ detectDirnameAssets(code, id) {
252
+ // Match: path.join(__dirname, 'relative/path') or path.join(__dirname, '..', 'path')
253
+ // Also: path.resolve(__dirname, ...) patterns
254
+ const pathJoinPattern = /path\.(?:join|resolve)\s*\(\s*__dirname\s*,\s*(.+?)\)/g;
255
+
256
+ let match;
257
+ while ((match = pathJoinPattern.exec(code)) !== null) {
258
+ try {
259
+ // Extract the path arguments - they could be literals or variables
260
+ const args = match[1];
261
+
262
+ // Try to extract string literals (simple case)
263
+ const literalPattern = /['"]([^'"]+)['"]/g;
264
+ const literals = [];
265
+ let literalMatch;
266
+ while ((literalMatch = literalPattern.exec(args)) !== null) {
267
+ literals.push(literalMatch[1]);
268
+ }
269
+
270
+ if (literals.length > 0) {
271
+ // Construct the relative path
272
+ const relativePath = path.join(...literals);
273
+
274
+ // Resolve from the file's directory
275
+ const fileDir = path.dirname(id);
276
+ const absolutePath = path.resolve(fileDir, relativePath);
277
+
278
+ // Make it relative to project root for the asset key
279
+ const relativeToProject = path.relative(this.projectRoot, absolutePath);
280
+
281
+ // Only register if the file exists
282
+ if (fs.existsSync(absolutePath) && !fs.statSync(absolutePath).isDirectory()) {
283
+ // Normalize path separators for asset key
284
+ const assetKey = relativeToProject.replace(/\\/g, '/');
285
+ this.detectedAssets.add(assetKey);
286
+ }
287
+ }
288
+ } catch (err) {
289
+ // Ignore parse errors - some patterns might be too complex
290
+ }
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Find the package root containing binding.gyp
296
+ */
297
+ findPackageRoot(filePath) {
298
+ let current = path.dirname(filePath);
299
+ const root = path.parse(current).root;
300
+
301
+ while (current !== root) {
302
+ const pkgPath = path.join(current, 'package.json');
303
+
304
+ if (fs.existsSync(pkgPath)) {
305
+ // Check if this package has native bindings
306
+ const hasBindingGyp = fs.existsSync(path.join(current, 'binding.gyp'));
307
+
308
+ try {
309
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
310
+ if (hasBindingGyp || pkg.gypfile === true) {
311
+ return current;
312
+ }
313
+ } catch (err) {
314
+ // Ignore JSON parse errors
315
+ }
316
+ }
317
+
318
+ current = path.dirname(current);
319
+ }
320
+
321
+ return null;
322
+ }
323
+
324
+ /**
325
+ * Get all detected native modules
326
+ */
327
+ getNativeModules() {
328
+ return this.nativeModules;
329
+ }
330
+
331
+ /**
332
+ * Get all detected assets from path.join(__dirname, ...) patterns
333
+ */
334
+ getDetectedAssets() {
335
+ return this.detectedAssets;
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Bundle application with Rollup and detect native modules
341
+ * @param {string} entryPath - Absolute path to entry file
342
+ * @param {string} outputPath - Path for bundled output
343
+ * @param {Object} config - Build configuration
344
+ * @param {string} targetPlatform - Target platform
345
+ * @param {string} targetArch - Target architecture
346
+ * @param {boolean} verbose - Enable verbose logging
347
+ * @returns {Promise<{bundledPath: string, nativeModules: Map<string, NativeModuleInfo>, detectedAssets: Set<string>}>}
348
+ */
349
+ export async function bundleWithRollup(entryPath, outputPath, config = {}, targetPlatform = process.platform, targetArch = process.arch, verbose = false) {
350
+ const projectRoot = config._projectRoot || process.cwd();
351
+
352
+ const nativeDetector = new NativeModuleDetectorPlugin({
353
+ targetPlatform,
354
+ targetArch,
355
+ projectRoot
356
+ });
357
+
358
+ diag.verbose(`Bundling entry: ${entryPath}`);
359
+ diag.verbose(`Target: ${diag.formatTarget(targetPlatform, targetArch)}`);
360
+
361
+ // Get Node.js built-in modules to mark as external
362
+ const builtinModules = Module.builtinModules || [];
363
+
364
+ const bundle = await rolldown({
365
+ input: entryPath,
366
+ platform: "node",
367
+ plugins: [
368
+ nativeDetector,
369
+ ...(config.bundler?.plugins || [])
370
+ ],
371
+ external: [
372
+ // Node built-ins are always external
373
+ ...builtinModules,
374
+ // User-specified externals (filter out functions for Rolldown compatibility)
375
+ ...(config.bundler?.external || []).filter(e => typeof e !== 'function'),
376
+ // Match .node files with regex instead of function
377
+ /\.node$/
378
+ ],
379
+ onwarn: (warning, warn) => {
380
+ // Suppress certain warnings
381
+ if (warning.code === 'CIRCULAR_DEPENDENCY') return;
382
+ if (warning.code === 'EVAL') return;
383
+
384
+ if (verbose) {
385
+ warn(warning);
386
+ }
387
+ }
388
+ });
389
+
390
+ await bundle.write({
391
+ file: outputPath,
392
+ format: 'cjs',
393
+ exports: 'auto',
394
+ banner: '/* Bundled by Seabox */\n',
395
+ sourcemap: config.bundler?.sourcemap || false
396
+ });
397
+
398
+ await bundle.close();
399
+
400
+ diag.verbose(`Bundle complete: ${outputPath}`);
401
+ diag.verbose(`Native modules detected: ${nativeDetector.nativeModules.size}`);
402
+ diag.verbose(`Assets detected: ${nativeDetector.detectedAssets.size}`);
403
+
404
+ return {
405
+ bundledPath: outputPath,
406
+ nativeModules: nativeDetector.getNativeModules(),
407
+ detectedAssets: nativeDetector.getDetectedAssets()
408
+ };
409
+ }
410
+
411
+ export { NativeModuleDetectorPlugin };
@@ -9,6 +9,18 @@ const { promisify } = require('util');
9
9
 
10
10
  const execFileAsync = promisify(execFile);
11
11
 
12
+ // Simple verbose logging flag (set by inject.mjs)
13
+ let verboseMode = false;
14
+ function setVerbose(enabled) {
15
+ verboseMode = enabled;
16
+ }
17
+
18
+ function logVerbose(message, indent = 2) {
19
+ if (verboseMode) {
20
+ console.log(' '.repeat(indent) + message);
21
+ }
22
+ }
23
+
12
24
  /**
13
25
  * Check if a signing tool is available on the system.
14
26
  * @param {string} platform - Target platform (win32, linux, darwin)
@@ -18,7 +30,6 @@ async function checkSignToolAvailability(platform) {
18
30
  const tools = {
19
31
  win32: 'signtool.exe',
20
32
  darwin: 'codesign',
21
- linux: 'osslsigncode'
22
33
  };
23
34
 
24
35
  const tool = tools[platform];
@@ -32,9 +43,7 @@ async function checkSignToolAvailability(platform) {
32
43
  await execFileAsync('where', ['signtool.exe']);
33
44
  } else if (platform === 'darwin') {
34
45
  await execFileAsync('which', ['codesign']);
35
- } else if (platform === 'linux') {
36
- await execFileAsync('which', ['osslsigncode']);
37
- }
46
+ }
38
47
  return { available: true, tool };
39
48
  } catch (error) {
40
49
  return { available: false, tool };
@@ -98,29 +107,6 @@ async function removeMacSignature(exePath) {
98
107
  }
99
108
  }
100
109
 
101
- /**
102
- * Remove signature from a Linux executable.
103
- * Note: Linux binaries are rarely signed, but osslsigncode can handle Authenticode signatures.
104
- * @param {string} exePath - Path to the executable
105
- * @returns {Promise<{success: boolean, message: string}>}
106
- */
107
- async function removeLinuxSignature(exePath) {
108
- try {
109
- // osslsigncode can remove Authenticode signatures (used for cross-platform PE files)
110
- const { stdout, stderr } = await execFileAsync('osslsigncode', ['remove-signature', exePath]);
111
-
112
- return { success: true, message: 'Signature removed successfully' };
113
- } catch (error) {
114
- const errorMsg = error.message || error.stderr || '';
115
-
116
- // Not an error if there was no signature
117
- if (errorMsg.includes('not signed') || errorMsg.includes('no signature')) {
118
- return { success: true, message: 'Binary was not signed (no signature to remove)' };
119
- }
120
-
121
- return { success: false, message: `Failed to remove signature: ${errorMsg}` };
122
- }
123
- }
124
110
 
125
111
  /**
126
112
  * Remove code signature from an executable before injection.
@@ -130,48 +116,47 @@ async function removeLinuxSignature(exePath) {
130
116
  * @returns {Promise<void>}
131
117
  */
132
118
  async function removeSignature(exePath, platform) {
133
- //console.log('Checking for code signature removal tools...');
119
+ // Linux binaries are typically not signed, so skip signature removal
120
+ if (platform === 'linux') {
121
+ logVerbose('Skipping signature removal (Linux binaries are typically unsigned)');
122
+ return;
123
+ }
134
124
 
135
125
  const { available, tool } = await checkSignToolAvailability(platform);
136
126
 
137
127
  if (!available) {
138
- console.warn(`⚠️ Warning: Signature removal tool not found for ${platform}`);
139
- console.warn(` Tool needed: ${tool || 'unknown'}`);
140
- console.warn(` The binary may have an existing signature that will be corrupted during injection.`);
128
+ logVerbose(`[!] Warning: Signature removal tool not found for ${platform}`);
129
+ logVerbose(`Tool needed: ${tool || 'unknown'}`, 2);
130
+ logVerbose(`The binary may have an existing signature that will be corrupted during injection.`, 2);
141
131
 
142
132
  if (platform === 'win32') {
143
- console.warn(` Install Windows SDK to get signtool.exe`);
144
- console.warn(` Download from: https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/`);
133
+ logVerbose(`Install Windows SDK to get signtool.exe`, 2);
134
+ logVerbose(`Download from: https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/`, 2);
145
135
  } else if (platform === 'darwin') {
146
- console.warn(` Install Xcode Command Line Tools to get codesign`);
147
- console.warn(` Run: xcode-select --install`);
148
- } else if (platform === 'linux') {
149
- console.warn(` Install osslsigncode: apt-get install osslsigncode (Debian/Ubuntu)`);
136
+ logVerbose(`Install Xcode Command Line Tools to get codesign`, 2);
137
+ logVerbose(`Run: xcode-select --install`, 2);
150
138
  }
151
139
 
152
- console.warn('');
153
140
  return;
154
141
  }
155
142
 
156
- console.log(`Found ${tool}, attempting to remove signature...`);
143
+ logVerbose(`Found ${tool}, attempting to remove signature...`);
157
144
 
158
145
  let result;
159
146
  if (platform === 'win32') {
160
147
  result = await removeWindowsSignature(exePath);
161
148
  } else if (platform === 'darwin') {
162
149
  result = await removeMacSignature(exePath);
163
- } else if (platform === 'linux') {
164
- result = await removeLinuxSignature(exePath);
165
150
  } else {
166
- console.warn(`⚠️ Warning: Unsupported platform for signature removal: ${platform}`);
151
+ logVerbose(`[!] Warning: Unsupported platform for signature removal: ${platform}`);
167
152
  return;
168
153
  }
169
154
 
170
155
  if (result.success) {
171
- console.log(`✓ ${result.message}`);
156
+ logVerbose(`[✓] ${result.message}`);
172
157
  } else {
173
- console.warn(`⚠️ Warning: ${result.message}`);
174
- console.warn(` Continuing anyway, but the executable may have signature issues.`);
158
+ logVerbose(`[!] Warning: ${result.message}`);
159
+ logVerbose(`Continuing anyway, but the executable may have signature issues.`, 2);
175
160
  }
176
161
  }
177
162
 
@@ -180,5 +165,5 @@ module.exports = {
180
165
  checkSignToolAvailability,
181
166
  removeWindowsSignature,
182
167
  removeMacSignature,
183
- removeLinuxSignature
168
+ setVerbose,
184
169
  };
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "seabox",
3
- "version": "0.1.0-beta.3",
3
+ "version": "0.1.0",
4
4
  "description": "Node.js Single Executable Application (SEA) builder tool with native and library extraction",
5
- "main": "lib/index.js",
6
- "types": "dist/index.d.ts",
5
+ "main": "lib/index.mjs",
6
+ "type": "module",
7
7
  "bin": {
8
- "seabox": "bin/seabox.js",
9
- "seabox-rebuild": "bin/seabox-rebuild.js"
8
+ "seabox": "bin/seabox.mjs"
10
9
  },
11
10
  "scripts": {
12
11
  "clean": "rimraf dist",
@@ -21,6 +20,7 @@
21
20
  "SEA",
22
21
  "native",
23
22
  "module",
23
+ "pkg",
24
24
  "dll",
25
25
  "packaging",
26
26
  "node",
@@ -34,10 +34,15 @@
34
34
  },
35
35
  "homepage": "https://github.com/MeirionHughes/seabox",
36
36
  "dependencies": {
37
+ "@rollup/plugin-commonjs": "^28.0.2",
38
+ "@rollup/plugin-json": "^6.1.0",
39
+ "@rollup/plugin-node-resolve": "^15.3.0",
37
40
  "adm-zip": "^0.5.16",
38
41
  "glob": "^10.0.0",
39
42
  "postject": "^1.0.0-alpha.6",
40
43
  "rcedit": "^4.0.1",
44
+ "rolldown": "^1.0.0-beta.50",
45
+ "rollup": "^4.28.1",
41
46
  "tar": "^6.2.1"
42
47
  },
43
48
  "devDependencies": {