@vixoniccom/module-packager 2.10.3-dev.7 → 2.11.0-dev.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.
Files changed (2) hide show
  1. package/dist/main.js +120 -104
  2. package/package.json +2 -1
package/dist/main.js CHANGED
@@ -4,7 +4,7 @@ import commandLineArgs from 'command-line-args';
4
4
  import path from 'path';
5
5
  import webpack from 'webpack';
6
6
  import WebpackDevServer from 'webpack-dev-server';
7
- import { exec } from 'child_process';
7
+ import { spawn } from 'child_process';
8
8
  import { fileURLToPath } from 'url';
9
9
  import fs from 'fs';
10
10
  import archiver from 'archiver';
@@ -12,9 +12,11 @@ import clc from 'cli-color';
12
12
  import { Configuration } from '@vixoniccom/modules';
13
13
  import tsNode from 'ts-node';
14
14
  import { createRequire } from 'module';
15
+ import which from 'which';
15
16
  const __filename = fileURLToPath(import.meta.url);
16
17
  const __dirname = path.dirname(__filename);
17
18
  const electronPath = path.join(__dirname, 'electron.js');
19
+ const npxPath = which.sync('npx');
18
20
  const optionDefinitions = [
19
21
  { name: 'externals', type: String, defaultValue: [], multiple: true },
20
22
  { name: 'target', type: String, defaultValue: 'web' },
@@ -28,9 +30,91 @@ const optionDefinitions = [
28
30
  { name: 'tsOverride', type: Boolean }
29
31
  ];
30
32
  const options = commandLineArgs(optionDefinitions);
31
- if (options.target !== 'electron-renderer' && options.externals.length > 0) {
32
- throw new Error(`Can't use external dependencies if target is not electron-renderer.`);
33
+ // Validación de entrada mejorada
34
+ function validateOptions(options) {
35
+ if (options.target !== 'electron-renderer' && options.externals.length > 0) {
36
+ throw new Error(`Can't use external dependencies if target is not electron-renderer.`);
37
+ }
38
+ // Validar puerto
39
+ if (options.port < 1 || options.port > 65535) {
40
+ throw new Error('Port must be between 1 and 65535');
41
+ }
42
+ // Validar modo
43
+ const validModes = ['build', 'watch', 'run', 'generate-configuration', 'validate-configuration'];
44
+ if (!validModes.includes(options.mode)) {
45
+ throw new Error(`Invalid mode. Must be one of: ${validModes.join(', ')}`);
46
+ }
47
+ }
48
+ // Función para sanitizar rutas
49
+ function sanitizePath(inputPath) {
50
+ // Prevenir path traversal
51
+ if (inputPath.includes('..') || inputPath.includes('~')) {
52
+ throw new Error('Invalid path: path traversal detected');
53
+ }
54
+ return path.resolve(inputPath);
55
+ }
56
+ // Función centralizada para crear argumentos de Electron
57
+ function createElectronArgs(mode) {
58
+ return [
59
+ electronPath,
60
+ '--src', sanitizePath(options.src),
61
+ '--build', sanitizePath(options.build),
62
+ '--port', String(options.port),
63
+ '--mode', mode,
64
+ '--config', options.config,
65
+ '--externals', Array.isArray(options.externals) ? options.externals.join(',') : ''
66
+ ];
67
+ }
68
+ // Función centralizada para manejar procesos Electron
69
+ function spawnElectronProcess(mode) {
70
+ const args = createElectronArgs(mode);
71
+ const nodeEnv = mode === 'run' ? 'production' : 'development';
72
+ const child = spawn(npxPath, ['electron', ...args], {
73
+ cwd: __dirname,
74
+ env: {
75
+ ...process.env,
76
+ NODE_ENV: nodeEnv,
77
+ ELECTRON_DISABLE_SECURITY_WARNINGS: 'true',
78
+ NODE_NO_WARNINGS: '1'
79
+ },
80
+ stdio: 'inherit'
81
+ });
82
+ // Manejo centralizado de logs
83
+ setupChildProcessLogging(child);
84
+ return child;
33
85
  }
86
+ // Función para manejar logging de procesos hijo
87
+ function setupChildProcessLogging(child) {
88
+ child.stdout?.on('data', (data) => {
89
+ const output = data.toString();
90
+ if (!shouldFilterLog(output)) {
91
+ process.stdout.write(output);
92
+ }
93
+ });
94
+ child.stderr?.on('data', (data) => {
95
+ const output = data.toString();
96
+ if (!shouldFilterLog(output)) {
97
+ process.stderr.write(output);
98
+ }
99
+ });
100
+ }
101
+ // Función centralizada para compilar webpack
102
+ async function compileWebpack() {
103
+ const cfgPath = path.resolve(__dirname, '..', 'dist/webpack.config.js');
104
+ const webpackConfigFactory = (await import(cfgPath)).default;
105
+ const compiler = webpack(webpackConfigFactory(options));
106
+ return new Promise((resolve, reject) => {
107
+ compiler.run((err, stats) => {
108
+ if (err)
109
+ return reject(err);
110
+ if (stats?.hasErrors()) {
111
+ return reject(new Error(stats.toString({ all: false, errors: true, colors: true })));
112
+ }
113
+ compiler.close(closeErr => (closeErr ? reject(closeErr) : resolve()));
114
+ });
115
+ });
116
+ }
117
+ validateOptions(options);
34
118
  if (options.mode === 'build') {
35
119
  pack(() => {
36
120
  if (options.target === 'electron-renderer') {
@@ -59,6 +143,7 @@ async function pack(callback) {
59
143
  console.log(clc.blue('Building module...'));
60
144
  validateConfiguration();
61
145
  const configPath = path.resolve(__dirname, options.config);
146
+ console.log("🚀 ~ pack ~ configPath:", configPath);
62
147
  try {
63
148
  const webpackConfig = (await import(configPath)).default(options);
64
149
  function handleWebpackError(err) {
@@ -106,19 +191,27 @@ async function pack(callback) {
106
191
  }
107
192
  function installExternals(callback) {
108
193
  let externals = options.externals.map((x) => {
109
- return `${x}@${JSON.parse(fs.readFileSync(path.resolve(options.src, '..', 'node_modules', x, 'package.json'), 'utf8')).version}`;
194
+ // Validar nombre del paquete
195
+ if (!/^[@a-z0-9-_./]+$/i.test(x)) {
196
+ throw new Error(`Invalid package name: ${x}`);
197
+ }
198
+ const packageJsonPath = sanitizePath(path.resolve(options.src, '..', 'node_modules', x, 'package.json'));
199
+ return `${x}@${JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')).version}`;
110
200
  });
111
201
  if (externals.length) {
112
- externals = externals.join(' ');
113
- console.log(clc.blue(`Installing externals ${externals}...`));
114
- exec(`npm install --prefix ./ --production ${externals}`, {
115
- cwd: options.build
116
- }, (err, _stdout) => {
117
- if (err)
118
- throw err;
202
+ console.log(clc.blue(`Installing externals ${externals.join(', ')}...`));
203
+ const npmPath = which.sync('npm');
204
+ const child = spawn(npmPath, ['install', '--prefix', './', '--production', ...externals], { cwd: sanitizePath(options.build), stdio: 'inherit' });
205
+ child.on('exit', (code) => {
206
+ if (code !== 0) {
207
+ return callback?.(new Error(`npm install failed with code ${code}`));
208
+ }
119
209
  console.log(clc.green('Externals installed.'));
120
210
  callback?.(null);
121
211
  });
212
+ child.on('error', (err) => {
213
+ callback?.(err);
214
+ });
122
215
  }
123
216
  else {
124
217
  callback?.(null);
@@ -127,11 +220,13 @@ function installExternals(callback) {
127
220
  function zip() {
128
221
  console.log(clc.blue('Archiving...'));
129
222
  console.log(clc.blue('Cleaning build...'));
130
- console.log("[DEBUG] Cleaning build folder", path.resolve(options.build, '../', 'build.zip'));
131
- let output = fs.createWriteStream(path.resolve(options.build, '../', 'build.zip'));
223
+ const buildPath = sanitizePath(options.build);
224
+ const zipPath = path.resolve(buildPath, '../', 'build.zip');
225
+ console.log("[DEBUG] Cleaning build folder", zipPath);
226
+ let output = fs.createWriteStream(zipPath);
132
227
  let archive = archiver('zip');
133
228
  archive.pipe(output);
134
- archive.directory(path.resolve(options.build), './');
229
+ archive.directory(buildPath, './');
135
230
  archive.finalize();
136
231
  output.on('close', function () {
137
232
  console.log(clc.green('Generated build.zip.'));
@@ -152,93 +247,12 @@ async function watch() {
152
247
  historyApiFallback: true
153
248
  }, compiler);
154
249
  await server.start();
155
- const cmd = [
156
- 'npx', 'electron', electronPath,
157
- '--src', path.resolve(options.src),
158
- '--build', path.resolve(options.build),
159
- '--port', String(options.port),
160
- '--mode', 'watch',
161
- '--config', options.config,
162
- '--externals', options.externals.join(',')
163
- ].join(' ');
164
- const child = exec(cmd, {
165
- cwd: __dirname,
166
- env: {
167
- ...process.env,
168
- NODE_ENV: 'development',
169
- ELECTRON_DISABLE_SECURITY_WARNINGS: 'true',
170
- NODE_NO_WARNINGS: '1'
171
- }
172
- });
173
- // Filtrar logs no deseados
174
- child.stdout?.on('data', (data) => {
175
- const output = data.toString();
176
- if (!shouldFilterLog(output)) {
177
- process.stdout.write(output);
178
- }
179
- });
180
- child.stderr?.on('data', (data) => {
181
- const output = data.toString();
182
- if (!shouldFilterLog(output)) {
183
- process.stderr.write(output);
184
- }
185
- });
250
+ spawnElectronProcess('watch');
186
251
  }
187
252
  async function run() {
188
- await new Promise((resolve, reject) => pack((err) => (err ? reject(err) : resolve())));
189
- const cfgPath = path.resolve(__dirname, '..', 'dist/webpack.config.js');
190
- const webpackConfigFactory = (await import(cfgPath)).default;
191
- const compiler = webpack(webpackConfigFactory(options));
192
- await new Promise((resolve, reject) => {
193
- compiler.run((err, stats) => {
194
- if (err)
195
- return reject(err);
196
- if (stats?.hasErrors()) {
197
- return reject(new Error(stats.toString({ all: false, errors: true, colors: true })));
198
- }
199
- compiler.close(closeErr => (closeErr ? reject(closeErr) : resolve()));
200
- });
201
- });
202
- const cmd = [
203
- 'npx',
204
- 'electron',
205
- electronPath,
206
- '--src',
207
- path.resolve(options.src),
208
- '--build',
209
- path.resolve(options.build),
210
- '--port',
211
- String(options.port),
212
- '--mode',
213
- 'run',
214
- '--config',
215
- options.config,
216
- '--externals',
217
- options.externals.join(',')
218
- ].join(' ');
219
- const child = exec(cmd, {
220
- cwd: __dirname,
221
- env: {
222
- ...process.env,
223
- NODE_ENV: 'production',
224
- // Suprimir warnings en producción también
225
- ELECTRON_DISABLE_SECURITY_WARNINGS: 'true',
226
- NODE_NO_WARNINGS: '1'
227
- },
228
- });
229
- // Aplicar el mismo filtrado
230
- child.stdout?.on('data', (data) => {
231
- const output = data.toString();
232
- if (!shouldFilterLog(output)) {
233
- process.stdout.write(output);
234
- }
235
- });
236
- child.stderr?.on('data', (data) => {
237
- const output = data.toString();
238
- if (!shouldFilterLog(output)) {
239
- process.stderr.write(output);
240
- }
241
- });
253
+ await new Promise((resolve) => pack((err) => (err ? console.log(err) : resolve())));
254
+ await compileWebpack();
255
+ spawnElectronProcess('run');
242
256
  }
243
257
  async function loadConfiguration() {
244
258
  const require = createRequire(import.meta.url);
@@ -250,14 +264,15 @@ async function loadConfiguration() {
250
264
  checkJs: false
251
265
  }
252
266
  });
253
- const configPath = path.resolve(options.confDefinition, 'index.ts');
267
+ const configPath = sanitizePath(path.resolve(options.confDefinition, 'index.ts'));
254
268
  return require(configPath).configuration;
255
269
  }
256
270
  async function generateConfiguration() {
257
271
  try {
258
272
  let configuration = await loadConfiguration();
259
- fs.writeFileSync(path.resolve(options.src, 'parameters.d.ts'), configuration.declarationFileString);
260
- fs.writeFileSync(path.resolve(options.src, '..', 'configuration.json'), JSON.stringify(configuration, undefined, 2));
273
+ const srcPath = sanitizePath(options.src);
274
+ fs.writeFileSync(path.resolve(srcPath, 'parameters.d.ts'), configuration.declarationFileString);
275
+ fs.writeFileSync(path.resolve(srcPath, '..', 'configuration.json'), JSON.stringify(configuration, undefined, 2));
261
276
  console.log(clc.green(`Configuration generated`));
262
277
  }
263
278
  catch (e) {
@@ -267,11 +282,12 @@ async function generateConfiguration() {
267
282
  }
268
283
  function validateConfiguration() {
269
284
  try {
270
- const jsonPath = path.resolve(options.src, '../', 'configuration.json');
285
+ const srcPath = sanitizePath(options.src);
286
+ const jsonPath = path.resolve(srcPath, '../', 'configuration.json');
271
287
  const json = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
272
288
  const conf = Configuration.fromJSON(json);
273
289
  if (conf.durationFormula) {
274
- const valuesPath = path.resolve(options.src, 'test', 'parameters.json');
290
+ const valuesPath = path.resolve(srcPath, 'test', 'parameters.json');
275
291
  const values = JSON.parse(fs.readFileSync(valuesPath, 'utf8'));
276
292
  console.log('Validate duration');
277
293
  console.log('Duration on undefined:', conf.getDuration({}), 'seconds');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vixoniccom/module-packager",
3
- "version": "2.10.3-dev.7",
3
+ "version": "2.11.0-dev.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "vixonic-module-packager": "./dist/main.js"
@@ -55,6 +55,7 @@
55
55
  "@types/command-line-args": "^5.2.3",
56
56
  "@types/node": "^20.11.25",
57
57
  "@types/webpack-env": "^1.18.8",
58
+ "@types/which": "^3.0.4",
58
59
  "concurrently": "^9.1.2",
59
60
  "css-loader": "^7.1.2",
60
61
  "npm-run-all": "^4.1.5",