@codemoreira/esad 1.4.6-14 → 1.4.6-16

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.
@@ -1,118 +1,112 @@
1
- const fs = require('fs-extra');
2
- const path = require('path');
3
- const AdmZip = require('adm-zip');
4
- const chalk = require('chalk');
5
- const { getWorkspaceConfig, syncHostConfig } = require('../utils/config');
6
- const { resolveModuleMetadata, listAvailableModules } = require('../utils/resolution');
7
- const { prepareNative } = require('../utils/scaffold');
8
-
9
- module.exports = async (options) => {
10
- let cwd = process.cwd();
11
- let pkgPath = path.join(cwd, 'package.json');
12
-
13
- // Enforce Workspace Root
14
- const configObj = getWorkspaceConfig();
15
- if (!configObj) {
16
- console.error(chalk.red(`❌ Error: Call this command from the project root (esad.config.json not found).`));
17
- process.exit(1);
18
- }
19
-
20
- const workspaceRoot = path.dirname(configObj.path);
21
- const { projectName } = configObj.data;
22
-
23
- syncHostConfig(configObj);
24
-
25
- let moduleId = options.id;
26
-
27
- if (moduleId) {
28
- const meta = resolveModuleMetadata(moduleId, configObj);
29
- if (!meta) {
30
- console.error(chalk.red(`\n❌ Error: Module not found: ${moduleId}`));
31
- listAvailableModules(configObj);
32
- process.exit(1);
33
- }
34
- cwd = meta.path;
35
- moduleId = meta.id; // Correct fully qualified ID
36
- pkgPath = path.join(cwd, 'package.json');
37
- console.log(`📂 Module detected for Deploy: ${path.basename(cwd)}`);
38
- } else {
39
- // Target host by default if in root
40
- const hostDir = path.join(workspaceRoot, `${projectName}-host`);
41
- if (fs.existsSync(hostDir)) {
42
- cwd = hostDir;
43
- pkgPath = path.join(cwd, 'package.json');
44
- console.log(chalk.green(`📂 Host detected for Deploy: ${path.basename(cwd)}`));
45
- }
46
- }
47
-
48
- if (!fs.existsSync(pkgPath)) {
49
- console.error(chalk.red(`❌ Error: package.json not found in ${cwd}.`));
50
- process.exit(1);
51
- }
52
-
53
- const pkg = fs.readJsonSync(pkgPath);
54
- moduleId = moduleId || pkg.name;
55
- const version = options.version || pkg.version;
56
- const entry = options.entry || 'index.bundle';
57
-
58
- // Ensure Native Folders are present (useful for consistency)
59
- await prepareNative(cwd, 'all');
60
-
61
- console.log(`\n☁️ Starting ESAD Deploy for ${moduleId} (v${version})\n`);
62
-
63
- const config = configObj ? configObj.data : null;
64
- if (!config?.deployEndpoint) {
65
- console.error(`❌ Error: 'deployEndpoint' not configured in esad.config.json.`);
66
- process.exit(1);
67
- }
68
-
69
- const deployUrl = config.deployEndpoint.replace('{{moduleId}}', moduleId);
70
- console.log(`📡 Deployment Endpoint Resolved: ${deployUrl}`);
71
-
72
- const distPath = path.join(cwd, 'build');
73
- if (!fs.existsSync(distPath)) {
74
- console.error(`❌ Error: build/ directory not found in ${cwd}. Did you run the build command?`);
75
- process.exit(1);
76
- }
77
-
78
- const zip = new AdmZip();
79
- zip.addLocalFolder(distPath);
80
-
81
- const zipPath = path.join(cwd, `bundle-${moduleId}-${version}.zip`);
82
- zip.writeZip(zipPath);
83
- console.log(`🗜️ Zipped output to ${zipPath}`);
84
-
85
- console.log(`🚀 Uploading to CDN via multipart POST...`);
86
-
87
- try {
88
- const FormData = require('form-data'); // Standard in Node versions, or use native if available
89
- const form = new FormData();
90
- form.append('version', version);
91
- form.append('bundle', fs.createReadStream(zipPath));
92
-
93
- const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
94
-
95
- // Simple CDN expects POST /api/admin/assets/:id/versions
96
- const uploadUrl = deployUrl.includes('/versions') ? deployUrl : `${deployUrl}/versions`;
97
-
98
- const response = await fetch(uploadUrl, {
99
- method: 'POST',
100
- body: form,
101
- headers: form.getHeaders(),
102
- });
103
-
104
- if (response.ok) {
105
- const result = await response.json();
106
- console.log(chalk.green(`✅ Successfully uploaded ${moduleId} v${version} to CDN!`));
107
- console.log(`📄 Active Version is now: ${result.active_version}`);
108
- } else {
109
- const errorText = await response.text();
110
- console.error(chalk.red(`❌ Failed to upload: ${response.status} ${response.statusText}`));
111
- console.error(errorText);
112
- }
113
- } catch (err) {
114
- console.error(chalk.red(`❌ Error during upload: ${err.message}`));
115
- } finally {
116
- fs.unlinkSync(zipPath);
117
- }
118
- };
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const AdmZip = require('adm-zip');
4
+ const chalk = require('chalk');
5
+ const { getWorkspaceConfig } = require('../utils/config');
6
+ const { resolveModuleMetadata, listAvailableModules } = require('../utils/resolution');
7
+
8
+ module.exports = async (options) => {
9
+ let cwd = process.cwd();
10
+ let pkgPath = path.join(cwd, 'package.json');
11
+
12
+ // Enforce Workspace Root
13
+ const configObj = getWorkspaceConfig();
14
+ if (!configObj) {
15
+ console.error(chalk.red(`❌ Error: Call this command from the project root (esad.config.json not found).`));
16
+ process.exit(1);
17
+ }
18
+
19
+ const workspaceRoot = path.dirname(configObj.path);
20
+ const { projectName } = configObj.data;
21
+
22
+ let moduleId = options.id;
23
+
24
+ if (moduleId) {
25
+ const meta = resolveModuleMetadata(moduleId, configObj);
26
+ if (!meta) {
27
+ console.error(chalk.red(`\n❌ Error: Module not found: ${moduleId}`));
28
+ listAvailableModules(configObj);
29
+ process.exit(1);
30
+ }
31
+ cwd = meta.path;
32
+ moduleId = meta.id; // Correct fully qualified ID
33
+ pkgPath = path.join(cwd, 'package.json');
34
+ console.log(`📂 Module detected for Deploy: ${path.basename(cwd)}`);
35
+ } else {
36
+ // Target host by default if in root
37
+ const hostDir = path.join(workspaceRoot, `${projectName}-host`);
38
+ if (fs.existsSync(hostDir)) {
39
+ cwd = hostDir;
40
+ pkgPath = path.join(cwd, 'package.json');
41
+ console.log(chalk.green(`📂 Host detected for Deploy: ${path.basename(cwd)}`));
42
+ }
43
+ }
44
+
45
+ if (!fs.existsSync(pkgPath)) {
46
+ console.error(chalk.red(`❌ Error: package.json not found in ${cwd}.`));
47
+ process.exit(1);
48
+ }
49
+
50
+ const pkg = fs.readJsonSync(pkgPath);
51
+ moduleId = moduleId || pkg.name;
52
+ const version = options.version || pkg.version;
53
+ const entry = options.entry || 'index.bundle';
54
+
55
+ console.log(`\n☁️ Starting ESAD Deploy for ${moduleId} (v${version})\n`);
56
+
57
+ const config = configObj ? configObj.data : null;
58
+ if (!config?.deployEndpoint) {
59
+ console.error(`❌ Error: 'deployEndpoint' not configured in esad.config.json.`);
60
+ process.exit(1);
61
+ }
62
+
63
+ const deployUrl = config.deployEndpoint.replace('{{moduleId}}', moduleId);
64
+ console.log(`📡 Deployment Endpoint Resolved: ${deployUrl}`);
65
+
66
+ const distPath = path.join(cwd, 'build');
67
+ if (!fs.existsSync(distPath)) {
68
+ console.error(`❌ Error: build/ directory not found in ${cwd}. Did you run the build command?`);
69
+ process.exit(1);
70
+ }
71
+
72
+ const zip = new AdmZip();
73
+ zip.addLocalFolder(distPath);
74
+
75
+ const zipPath = path.join(cwd, `bundle-${moduleId}-${version}.zip`);
76
+ zip.writeZip(zipPath);
77
+ console.log(`🗜️ Zipped output to ${zipPath}`);
78
+
79
+ console.log(`🚀 Uploading to CDN via multipart POST...`);
80
+
81
+ try {
82
+ const FormData = require('form-data'); // Standard in Node versions, or use native if available
83
+ const form = new FormData();
84
+ form.append('version', version);
85
+ form.append('bundle', fs.createReadStream(zipPath));
86
+
87
+ const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
88
+
89
+ // Simple CDN expects POST /api/admin/assets/:id/versions
90
+ const uploadUrl = deployUrl.includes('/versions') ? deployUrl : `${deployUrl}/versions`;
91
+
92
+ const response = await fetch(uploadUrl, {
93
+ method: 'POST',
94
+ body: form,
95
+ headers: form.getHeaders(),
96
+ });
97
+
98
+ if (response.ok) {
99
+ const result = await response.json();
100
+ console.log(chalk.green(`✅ Successfully uploaded ${moduleId} v${version} to CDN!`));
101
+ console.log(`📄 Active Version is now: ${result.active_version}`);
102
+ } else {
103
+ const errorText = await response.text();
104
+ console.error(chalk.red(`❌ Failed to upload: ${response.status} ${response.statusText}`));
105
+ console.error(errorText);
106
+ }
107
+ } catch (err) {
108
+ console.error(chalk.red(`❌ Error during upload: ${err.message}`));
109
+ } finally {
110
+ fs.unlinkSync(zipPath);
111
+ }
112
+ };
@@ -1,12 +1,10 @@
1
1
  const { spawn } = require('cross-spawn');
2
- const { getWorkspaceConfig, syncHostConfig } = require('../utils/config');
2
+ const { getWorkspaceConfig } = require('../utils/config');
3
3
  const fs = require('fs-extra');
4
4
  const path = require('path');
5
5
  const chalk = require('chalk');
6
- const { resolveModuleMetadata, listAvailableModules, resolveProjectDir } = require('../utils/resolution');
7
- const { runProcess } = require('../utils/process');
8
6
  const { prepareNative } = require('../utils/scaffold');
9
- const AdmZip = require('adm-zip');
7
+ const { resolveProjectDir, listAvailableModules } = require('../utils/resolution');
10
8
 
11
9
  module.exports = async (options) => {
12
10
  let cwd = process.cwd();
@@ -22,109 +20,70 @@ module.exports = async (options) => {
22
20
  const workspaceRoot = path.dirname(configObj.path);
23
21
  const { projectName } = configObj.data;
24
22
 
25
- // Synchronize Host Config
26
- syncHostConfig(configObj);
27
-
28
23
  if (options.id) {
29
- const metadata = resolveModuleMetadata(options.id, configObj);
30
- if (!metadata) {
24
+ const targetDir = resolveProjectDir(options.id, configObj);
25
+ if (!targetDir) {
31
26
  console.error(chalk.red(`\n❌ Error: Module not found: ${options.id}`));
32
27
  listAvailableModules(configObj);
33
28
  process.exit(1);
34
29
  }
35
- cwd = metadata.path;
36
- moduleId = metadata.id;
30
+ cwd = targetDir;
37
31
  pkgPath = path.join(cwd, 'package.json');
38
- console.log(chalk.green(`📂 Module detected: ${path.relative(workspaceRoot, cwd)} (ID: ${moduleId})`));
32
+ console.log(chalk.green(`📂 Module detected: ${path.relative(workspaceRoot, cwd)}`));
39
33
  } else {
40
34
  // Target host by default if in root
41
35
  const hostDir = path.join(workspaceRoot, `${projectName}-host`);
42
36
  if (fs.existsSync(hostDir)) {
43
37
  cwd = hostDir;
44
- moduleId = pkg.name; // Fallback to package name if in host
45
38
  pkgPath = path.join(cwd, 'package.json');
46
39
  console.log(chalk.green(`📂 Host detected: ${path.relative(workspaceRoot, cwd)}`));
47
40
  }
48
41
  }
49
42
 
50
- if (!fs.existsSync(pkgPath)) {
51
- console.error(chalk.red(`❌ Error: package.json not found in ${cwd}.`));
52
- process.exit(1);
53
- }
54
-
55
43
  const pkg = fs.readJsonSync(pkgPath);
56
- if (!moduleId) moduleId = pkg.name;
57
- const platform = options.platform || 'android';
44
+ const moduleId = options.id || pkg.name;
45
+ const port = options.port || '8081';
58
46
 
59
- console.log(chalk.cyan(`\n☁️ Starting ESAD Dev-Push for ${moduleId} (${platform})\n`));
47
+ // Determine if it's a Host or Module
48
+ const isHost = pkg.name.endsWith('-host') || pkg.dependencies?.['@callstack/repack'];
60
49
 
61
- const config = configObj ? configObj.data : null;
62
- const devUrlBase = config?.devModeEndpoint || config?.deployEndpoint?.replace('/versions', '');
63
-
64
- if (!devUrlBase) {
65
- console.error(chalk.red(`❌ Error: 'devModeEndpoint' or 'deployEndpoint' not configured in esad.config.json.`));
66
- process.exit(1);
67
- }
50
+ // 1. Initial Checks & Automated Native Preparation
51
+ await prepareNative(cwd, 'all');
68
52
 
69
- const devUploadUrl = devUrlBase.replace('{{moduleId}}', moduleId) + '/dev';
70
- console.log(`📡 Dev-Cloud Endpoint: ${devUploadUrl}`);
71
-
72
- // 0. Prepare Native
73
- await prepareNative(cwd, platform);
53
+ if (isHost && !options.id) {
54
+ console.log(`\n🚀 Starting Host App Dev Server (Re.Pack/Rspack)...\n`);
55
+ await spawn('npx', ['react-native', 'webpack-start'], { cwd, stdio: 'inherit', shell: true });
56
+ return;
57
+ }
74
58
 
75
- // 1. Build
76
- console.log(`\n🏗️ Step 1/3: Building bundle...`);
77
- const bundleOutput = path.join(cwd, 'build', platform, 'index.bundle');
78
- await fs.ensureDir(path.dirname(bundleOutput));
59
+ console.log(`\n⚡ Starting ESAD Dev Server for ${moduleId} on port ${port}...\n`);
79
60
 
80
- try {
81
- await runProcess('npx', [
82
- 'react-native',
83
- 'webpack-bundle',
84
- '--platform', platform,
85
- '--dev', 'false',
86
- '--bundle-output', bundleOutput,
87
- '--assets-dest', path.dirname(bundleOutput),
88
- '--reset-cache'
89
- ], cwd);
90
- } catch (err) {
91
- console.error(chalk.red(`❌ Build failed.`));
92
- process.exit(1);
93
- }
61
+ const config = configObj ? configObj.data : null;
62
+ let devApiUrl = config?.devModeEndpoint ? config.devModeEndpoint.replace('{{moduleId}}', moduleId) : null;
94
63
 
95
- // 2. Zip
96
- console.log(`\n🗜️ Step 2/3: Zipping assets...`);
97
- const zip = new AdmZip();
98
- const buildDir = path.join(cwd, 'build');
99
- zip.addLocalFolder(buildDir);
100
- const zipPath = path.join(cwd, `dev-bundle-${moduleId}.zip`);
101
- zip.writeZip(zipPath);
64
+ const setDevMode = async (isActive) => {
65
+ if (!devApiUrl) return;
66
+ try {
67
+ const body = {
68
+ is_dev_mode: isActive,
69
+ ...(isActive && { dev_url: `http://localhost:${port}/index.bundle` })
70
+ };
71
+ const res = await fetch(devApiUrl, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
72
+ if (res.ok) console.log(`📡 Registry Sync: Dev Override is ${isActive ? 'ON' : 'OFF'} for module ${moduleId}`);
73
+ } catch(e) { /* ignore */ }
74
+ };
102
75
 
103
- // 3. Upload
104
- console.log(`\n🚀 Step 3/3: Pushing to Dev-Cloud...`);
105
- try {
106
- const FormData = require('form-data');
107
- const form = new FormData();
108
- form.append('bundle', fs.createReadStream(zipPath));
76
+ await setDevMode(true);
109
77
 
110
- const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
111
- const response = await fetch(devUploadUrl, {
112
- method: 'POST',
113
- body: form,
114
- headers: form.getHeaders(),
115
- });
78
+ const proc = spawn('npx', ['react-native', 'webpack-start', '--port', port], { cwd, stdio: 'inherit', shell: true });
116
79
 
117
- if (response.ok) {
118
- console.log(chalk.green(`\n Successfully synced ${moduleId} to Dev-Cloud!`));
119
- console.log(`📱 Host app configured with 'devModeFor: ["${options.id || moduleId}"]' will now load this version.\n`);
120
- } else {
121
- const errorText = await response.text();
122
- console.error(chalk.red(`❌ Cloud Sync failed: ${response.status} ${response.statusText}`));
123
- console.error(errorText);
124
- }
125
- } catch (err) {
126
- console.error(chalk.red(`❌ Error during sync: ${err.message}`));
127
- } finally {
128
- if (fs.existsSync(zipPath)) fs.unlinkSync(zipPath);
129
- }
80
+ const shutdown = async () => {
81
+ console.log(`\n🛑 Stopping ESAD Dev Server and reverting registry status...`);
82
+ await setDevMode(false);
83
+ proc.kill();
84
+ process.exit(0);
85
+ };
86
+
87
+ process.on('SIGINT', shutdown);
88
+ process.on('SIGTERM', shutdown);
130
89
  };
@@ -7,7 +7,12 @@ const readline = require('readline');
7
7
  const { getWorkspaceConfig } = require('../utils/config');
8
8
  const { prepareNative } = require('../utils/scaffold');
9
9
 
10
- const askQuestion = (query, rl) => new Promise((resolve) => rl.question(query, resolve));
10
+ const rl = readline.createInterface({
11
+ input: process.stdin,
12
+ output: process.stdout
13
+ });
14
+
15
+ const askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
11
16
 
12
17
  /**
13
18
  * Check if a port is in use
@@ -23,12 +28,6 @@ const isPortInUse = (port) => new Promise((resolve) => {
23
28
  });
24
29
 
25
30
  module.exports = async (subcommand) => {
26
- const rl = readline.createInterface({
27
- input: process.stdin,
28
- output: process.stdout
29
- });
30
-
31
- if (!subcommand) subcommand = 'dev';
32
31
  let cwd = process.cwd();
33
32
  let pkgPath = path.join(cwd, 'package.json');
34
33
 
@@ -65,7 +64,7 @@ module.exports = async (subcommand) => {
65
64
  console.log(`[b] Bundler Only`);
66
65
  console.log(`[c] Cancel`);
67
66
 
68
- const choice = (await askQuestion(`\nSelect platform: `, rl)).toLowerCase();
67
+ const choice = (await askQuestion(`\nSelect platform: `)).toLowerCase();
69
68
 
70
69
  if (choice === 'c') {
71
70
  console.log(`\n❌ Cancelled.`);
@@ -87,13 +86,13 @@ module.exports = async (subcommand) => {
87
86
  if (shouldStartBundler && choice !== 'c') {
88
87
  console.log(`\n🛠️ Starting Rspack Bundler in a new window...`);
89
88
  if (process.platform === 'win32') {
90
- spawn('cmd', ['/c', 'start', '/D', cwd, 'npx', 'react-native', 'start', '--reset-cache'], {
89
+ spawn('cmd', ['/c', 'start', '/D', cwd, 'npx', 'react-native', 'webpack-start'], {
91
90
  detached: true,
92
91
  stdio: 'ignore',
93
92
  shell: true
94
93
  }).unref();
95
94
  } else {
96
- spawn('npx', ['react-native', 'start', '--reset-cache'], {
95
+ spawn('npx', ['react-native', 'webpack-start'], {
97
96
  cwd,
98
97
  detached: true,
99
98
  stdio: 'inherit',
@@ -140,16 +139,13 @@ module.exports = async (subcommand) => {
140
139
  // Other subcommands (android, ios directly)
141
140
  try {
142
141
  if (subcommand === 'android') {
143
- console.log(`🤖 Compiling and launching on Android...`);
144
142
  await runProcess('npx', ['expo', 'run:android', '--no-bundler'], cwd);
145
143
  } else if (subcommand === 'ios') {
146
- console.log(`🍎 Compiling and launching on iOS...`);
147
144
  await runProcess('npx', ['expo', 'run:ios', '--no-bundler'], cwd);
148
145
  }
149
146
  } catch (err) {
150
147
  console.error(`❌ Error running host command: ${err.message}`);
151
- } finally {
152
- rl.close();
153
148
  }
149
+ rl.close();
154
150
  }
155
151
  };
@@ -1,37 +1,11 @@
1
- const fs = require('fs-extra');
2
- const path = require('path');
3
-
4
- const getWorkspaceConfig = () => {
5
- let cwd = process.cwd();
6
- let configPath = path.join(cwd, 'esad.config.json');
7
-
8
- // Search up to 2 levels (root or project folder)
9
- if (!fs.existsSync(configPath)) configPath = path.join(cwd, '..', 'esad.config.json');
10
- if (!fs.existsSync(configPath)) configPath = path.join(cwd, '..', '..', 'esad.config.json');
11
-
12
- if (!fs.existsSync(configPath)) return null;
13
- return { path: configPath, data: fs.readJsonSync(configPath) };
14
- };
15
-
16
- const syncHostConfig = (configObj) => {
17
- if (!configObj) return;
18
- const { projectName } = configObj.data;
19
- const workspaceRoot = path.dirname(configObj.path);
20
- const hostDir = path.join(workspaceRoot, `${projectName}-host`);
21
-
22
- if (fs.existsSync(hostDir)) {
23
- const hostConfigPath = path.join(hostDir, 'esad.config.json');
24
-
25
- // Only pass client-safe fields
26
- const clientConfig = {
27
- projectName: configObj.data.projectName,
28
- registryUrl: configObj.data.registryUrl,
29
- devModeFor: configObj.data.devModeFor || []
30
- };
31
-
32
- fs.writeJsonSync(hostConfigPath, clientConfig, { spaces: 2 });
33
- console.log(`\n🔄 Sync: Config propagated to ${path.relative(workspaceRoot, hostConfigPath)}`);
34
- }
35
- };
36
-
37
- module.exports = { getWorkspaceConfig, syncHostConfig };
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ const getWorkspaceConfig = () => {
5
+ let configPath = path.join(process.cwd(), 'esad.config.json');
6
+ if (!fs.existsSync(configPath)) configPath = path.join(process.cwd(), '../esad.config.json');
7
+ if (!fs.existsSync(configPath)) return null;
8
+ return { path: configPath, data: fs.readJsonSync(configPath) };
9
+ };
10
+
11
+ module.exports = { getWorkspaceConfig };
@@ -1,21 +1,20 @@
1
- const { spawn } = require('cross-spawn');
2
-
3
- /**
4
- * Helper to spawn commands synchronously
5
- * @param {string} cmd
6
- * @param {string[]} args
7
- * @param {string} cwd
8
- * @returns {Promise<void>}
9
- */
10
- const runProcess = (cmd, args, cwd = process.cwd()) => {
11
- const executable = (process.platform === 'win32' && cmd === 'npx') ? 'npx.cmd' : cmd;
12
- return new Promise((resolve, reject) => {
13
- const child = spawn(executable, args, { stdio: 'inherit', cwd, shell: true });
14
- child.on('close', code => {
15
- if (code !== 0) reject(new Error(`Command ${cmd} ${args.join(' ')} failed`));
16
- else resolve();
17
- });
18
- });
19
- };
20
-
21
- module.exports = { runProcess };
1
+ const { spawn } = require('cross-spawn');
2
+
3
+ /**
4
+ * Helper to spawn commands synchronously
5
+ * @param {string} cmd
6
+ * @param {string[]} args
7
+ * @param {string} cwd
8
+ * @returns {Promise<void>}
9
+ */
10
+ const runProcess = (cmd, args, cwd = process.cwd()) => {
11
+ return new Promise((resolve, reject) => {
12
+ const child = spawn(cmd, args, { stdio: 'inherit', cwd, shell: true });
13
+ child.on('close', code => {
14
+ if (code !== 0) reject(new Error(`Command ${cmd} ${args.join(' ')} failed`));
15
+ else resolve();
16
+ });
17
+ });
18
+ };
19
+
20
+ module.exports = { runProcess };