@mintlify/previewing 4.0.1551 → 4.0.1553

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.
@@ -18,6 +18,17 @@ describe('configurePreviewPath', () => {
18
18
  expect(constants.CLIENT_PATH).toBe(path.join(constants.MINT_PATH, 'apps', 'client'));
19
19
  expect(constants.TAR_PATH).toBe(path.join(constants.PREVIEW_PATH, 'mint.tar.gz'));
20
20
  });
21
+ it('keeps the beta vite client and its content apart from the next preview', () => {
22
+ constants.configurePreviewPath(3000, 'vite');
23
+ expect(constants.CLIENT_FRAMEWORK).toBe('vite');
24
+ expect(constants.PREVIEW_PATH).toBe(path.join(constants.DOT_MINTLIFY, 'previews', '3000-beta'));
25
+ expect(constants.CLIENT_PATH).toBe(path.join(constants.MINT_PATH, 'apps', 'client-vite'));
26
+ expect(constants.NEXT_PUBLIC_PATH).toBe(path.join(constants.CLIENT_PATH, '.mint-cli', 'public'));
27
+ expect(constants.NEXT_PROPS_PATH).toBe(path.join(constants.CLIENT_PATH, '.mint-cli', 'src', '_props'));
28
+ constants.configurePreviewPath(3000);
29
+ expect(constants.CLIENT_FRAMEWORK).toBe('next');
30
+ expect(constants.NEXT_PUBLIC_PATH).toBe(path.join(constants.CLIENT_PATH, 'public'));
31
+ });
21
32
  it('returns to the same files for a reused port', () => {
22
33
  constants.configurePreviewPath(4000);
23
34
  const firstPath = constants.PREVIEW_PATH;
@@ -16,6 +16,7 @@ vi.mock('../constants.js', async (importOriginal) => {
16
16
  ...actual,
17
17
  CMD_EXEC_PATH: join(root, 'docs'),
18
18
  CLIENT_PATH: join(root, 'client'),
19
+ CONTENT_PATH: join(root, 'client'),
19
20
  NEXT_PROPS_PATH: join(root, 'client', 'src', '_props'),
20
21
  NEXT_PUBLIC_PATH: join(root, 'client', 'public'),
21
22
  };
@@ -18,6 +18,7 @@ vi.mock('../constants.js', async (importOriginal) => {
18
18
  ...actual,
19
19
  CMD_EXEC_PATH: join(root, 'docs'),
20
20
  CLIENT_PATH: join(root, 'client'),
21
+ CONTENT_PATH: join(root, 'client'),
21
22
  NEXT_PROPS_PATH: join(root, 'client', 'src', '_props'),
22
23
  NEXT_PUBLIC_PATH: join(root, 'client', 'public'),
23
24
  };
@@ -6,13 +6,16 @@ export declare let PREVIEW_LAST_PATH: string;
6
6
  export declare let MINT_PATH: string;
7
7
  export declare let VERSION_PATH: string;
8
8
  export declare let CLIENT_PATH: string;
9
+ export declare let CONTENT_PATH: string;
9
10
  export declare let NEXT_SIDE_EFFECT_PATH: string;
10
11
  export declare let NEXT_ROUTER_SERVER_PATH: string;
11
12
  export declare let NEXT_CONFIG_PATH: string;
12
13
  export declare let NEXT_PUBLIC_PATH: string;
13
14
  export declare let NEXT_PROPS_PATH: string;
14
15
  export declare let TAR_PATH: string;
15
- export declare const configurePreviewPath: (port?: string | number) => void;
16
+ export type ClientFramework = 'next' | 'vite';
17
+ export declare let CLIENT_FRAMEWORK: ClientFramework;
18
+ export declare const configurePreviewPath: (port?: string | number, framework?: ClientFramework) => void;
16
19
  export declare const configureSharedPreviewPath: () => void;
17
20
  export declare const MINT_CLIENT_RELEASES_CLOUDFRONT_URL = "https://releases.mintlify.com";
18
21
  export declare const TARGET_MINT_VERSION_URL = "https://releases.mintlify.com/mint-version.txt";
package/dist/constants.js CHANGED
@@ -10,34 +10,38 @@ export let PREVIEW_LAST_PATH;
10
10
  export let MINT_PATH;
11
11
  export let VERSION_PATH;
12
12
  export let CLIENT_PATH;
13
+ export let CONTENT_PATH;
13
14
  export let NEXT_SIDE_EFFECT_PATH;
14
15
  export let NEXT_ROUTER_SERVER_PATH;
15
16
  export let NEXT_CONFIG_PATH;
16
17
  export let NEXT_PUBLIC_PATH;
17
18
  export let NEXT_PROPS_PATH;
18
19
  export let TAR_PATH;
19
- const setPreviewPath = (name) => {
20
- PREVIEW_PATH = path.join(DOT_MINTLIFY, 'previews', name);
20
+ export let CLIENT_FRAMEWORK = 'next';
21
+ const setPreviewPath = (name, framework = 'next') => {
22
+ CLIENT_FRAMEWORK = framework;
23
+ PREVIEW_PATH = path.join(DOT_MINTLIFY, 'previews', framework === 'vite' ? `${name}-beta` : name);
21
24
  PREVIEW_LAST_PATH = `${PREVIEW_PATH}-last`;
22
25
  MINT_PATH = path.join(PREVIEW_PATH, 'mint');
23
26
  VERSION_PATH = path.join(MINT_PATH, 'mint-version.txt');
24
- CLIENT_PATH = path.join(MINT_PATH, 'apps', 'client');
27
+ CLIENT_PATH = path.join(MINT_PATH, 'apps', framework === 'vite' ? 'client-vite' : 'client');
28
+ CONTENT_PATH = framework === 'vite' ? path.join(CLIENT_PATH, '.mint-cli') : CLIENT_PATH;
25
29
  const nextDistServerPath = path.join(MINT_PATH, 'node_modules', 'next', 'dist', 'server');
26
30
  NEXT_SIDE_EFFECT_PATH = path.join(nextDistServerPath, 'next.js');
27
31
  NEXT_ROUTER_SERVER_PATH = path.join(nextDistServerPath, 'lib', 'router-server.js');
28
32
  NEXT_CONFIG_PATH = path.join(CLIENT_PATH, '.next', 'required-server-files.json');
29
- NEXT_PUBLIC_PATH = path.join(CLIENT_PATH, 'public');
30
- NEXT_PROPS_PATH = path.join(CLIENT_PATH, 'src', '_props');
33
+ NEXT_PUBLIC_PATH = path.join(CONTENT_PATH, 'public');
34
+ NEXT_PROPS_PATH = path.join(CONTENT_PATH, 'src', '_props');
31
35
  TAR_PATH = path.join(PREVIEW_PATH, 'mint.tar.gz');
32
36
  };
33
37
  // A port can only belong to one local server at a time, so it gives each running
34
38
  // preview an isolated workspace while still allowing warm starts to reuse files.
35
- export const configurePreviewPath = (port = 3000) => {
39
+ export const configurePreviewPath = (port = 3000, framework = 'next') => {
36
40
  const numericPort = Number(port);
37
41
  if (!Number.isInteger(numericPort) || numericPort < 1 || numericPort > 65535) {
38
42
  throw new Error(`invalid preview port: ${port}`);
39
43
  }
40
- setPreviewPath(String(numericPort));
44
+ setPreviewPath(String(numericPort), framework);
41
45
  };
42
46
  export const configureSharedPreviewPath = () => {
43
47
  setPreviewPath('shared');
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { getFileListSync, prebuild } from '@mintlify/prebuild';
3
3
  import fse, { pathExists } from 'fs-extra';
4
4
  import pathUtil from 'path';
5
- import { CLIENT_PATH, CMD_EXEC_PATH, VERSION_PATH, NEXT_PUBLIC_PATH, NEXT_PROPS_PATH, PREVIEW_PATH, configurePreviewPath, } from '../constants.js';
5
+ import { CLIENT_FRAMEWORK, CMD_EXEC_PATH, CONTENT_PATH, VERSION_PATH, NEXT_PUBLIC_PATH, NEXT_PROPS_PATH, PREVIEW_PATH, configurePreviewPath, } from '../constants.js';
6
6
  import { addLog, clearLogs } from '../logging-state.js';
7
7
  import { ErrorLog, SpinnerLog } from '../logs.js';
8
8
  import { seedPreviewFromShared } from '../util.js';
@@ -41,6 +41,12 @@ const startDev = async (argv) => {
41
41
  const cliVersion = argv.cliVersion;
42
42
  const disableOpenApi = argv.disableOpenapi;
43
43
  const disablePrefetch = argv.disablePrefetch;
44
+ if (CLIENT_FRAMEWORK === 'vite' && !localClientVersion) {
45
+ clearLogs();
46
+ addLog(_jsx(ErrorLog, { message: `${packageName} dev --beta needs --local-client-version until beta clients are released.` }));
47
+ await new Promise((resolve) => setTimeout(resolve, 50));
48
+ process.exit(1);
49
+ }
44
50
  await timeDev('ensure preview directory', () => fse.ensureDir(PREVIEW_PATH));
45
51
  if (!localClientVersion && !clientVersion) {
46
52
  timeDevSync('seed preview from shared workspace', () => seedPreviewFromShared());
@@ -88,8 +94,9 @@ const startDev = async (argv) => {
88
94
  prunePublicOrphans();
89
95
  }
90
96
  });
91
- timeDevSync('switch to local client directory', () => {
92
- process.chdir(CLIENT_PATH);
97
+ await timeDev('switch to local client directory', async () => {
98
+ await fse.ensureDir(CONTENT_PATH);
99
+ process.chdir(CONTENT_PATH);
93
100
  });
94
101
  let fileImportsMap;
95
102
  try {
@@ -116,7 +123,7 @@ const startDev = async (argv) => {
116
123
  };
117
124
  const dev = async (argv) => {
118
125
  const port = Number(argv.port ?? 3000);
119
- configurePreviewPath(port);
126
+ configurePreviewPath(port, argv.beta === true ? 'vite' : 'next');
120
127
  const releasePreviewLock = acquirePreviewLock(port);
121
128
  try {
122
129
  await startDev(argv);
@@ -2,8 +2,8 @@ import { generateOpenApiAnchorsOrTabs, categorizeFilePaths, generateOpenApiDivis
2
2
  import { upgradeToDocsConfig } from '@mintlify/validation';
3
3
  import fse from 'fs-extra';
4
4
  import { join } from 'path';
5
- import { CMD_EXEC_PATH, CLIENT_PATH } from '../../constants.js';
6
- export const getDocsState = async (onError, { contentDirectory = CMD_EXEC_PATH, clientDirectory = CLIENT_PATH, } = {}) => {
5
+ import { CMD_EXEC_PATH, CONTENT_PATH } from '../../constants.js';
6
+ export const getDocsState = async (onError, { contentDirectory = CMD_EXEC_PATH, clientDirectory = CONTENT_PATH, } = {}) => {
7
7
  const mintIgnore = await getMintIgnore(contentDirectory);
8
8
  const docsJsonConfigPath = join(contentDirectory, 'docs.json');
9
9
  const explicitOverlays = await readExplicitOverlayConfig((await fse.pathExists(docsJsonConfigPath))
@@ -9,7 +9,7 @@ import fse from 'fs-extra';
9
9
  import fs from 'fs/promises';
10
10
  import yaml from 'js-yaml';
11
11
  import pathUtil from 'path';
12
- import { CMD_EXEC_PATH, NEXT_PROPS_PATH, NEXT_PUBLIC_PATH, CLIENT_PATH } from '../../constants.js';
12
+ import { CMD_EXEC_PATH, CONTENT_PATH, NEXT_PROPS_PATH, NEXT_PUBLIC_PATH } from '../../constants.js';
13
13
  import { addChangeLog, addErrorLog, clearErrorLogs, getCurrentErrorLogs, } from '../../logging-state.js';
14
14
  import { AddedLog, DeletedLog, EditedLog, WarningLog, InfoLog, ErrorLog } from '../../logs.js';
15
15
  import { reportPageCompileError, reportPageCompileSuccess } from '../on-demand-page.js';
@@ -69,11 +69,11 @@ const writeDocsConfigWithCurrentNavigation = async (docsConfig) => {
69
69
  const propsDocsJsonPath = pathUtil.join(NEXT_PROPS_PATH, 'docs.json');
70
70
  try {
71
71
  const currentDocsConfig = await readJsonFile(propsDocsJsonPath);
72
- await DocsConfigUpdater.writeConfigFile({ ...docsConfig, navigation: currentDocsConfig.navigation }, CLIENT_PATH);
72
+ await DocsConfigUpdater.writeConfigFile({ ...docsConfig, navigation: currentDocsConfig.navigation }, CONTENT_PATH);
73
73
  return;
74
74
  }
75
75
  catch { }
76
- await DocsConfigUpdater.writeConfigFile(docsConfig, CLIENT_PATH);
76
+ await DocsConfigUpdater.writeConfigFile(docsConfig, CONTENT_PATH);
77
77
  };
78
78
  const getCurrentRawDocsConfig = async () => {
79
79
  const docsJsonPath = pathUtil.join(CMD_EXEC_PATH, 'docs.json');
@@ -202,9 +202,9 @@ const onUnlinkEvent = async (filename, triggerRefresh, options) => {
202
202
  const { mintConfig, openApiFiles, docsConfig } = await getDocsState(handleParseError);
203
203
  await refreshTrackedReferencedFiles();
204
204
  if (mintConfig) {
205
- await MintConfigUpdater.writeConfigFile(mintConfig, CLIENT_PATH);
205
+ await MintConfigUpdater.writeConfigFile(mintConfig, CONTENT_PATH);
206
206
  }
207
- await DocsConfigUpdater.writeConfigFile(docsConfig, CLIENT_PATH);
207
+ await DocsConfigUpdater.writeConfigFile(docsConfig, CONTENT_PATH);
208
208
  await updateOpenApiFiles(openApiFiles, suppressParseError);
209
209
  await updateCustomLanguages(docsConfig, suppressParseError);
210
210
  await updateGeneratedNav(suppressParseError);
@@ -256,9 +256,9 @@ const onUnlinkEvent = async (filename, triggerRefresh, options) => {
256
256
  try {
257
257
  const { mintConfig, openApiFiles, docsConfig } = await getDocsState(handleParseError);
258
258
  if (mintConfig) {
259
- await MintConfigUpdater.writeConfigFile(mintConfig, CLIENT_PATH);
259
+ await MintConfigUpdater.writeConfigFile(mintConfig, CONTENT_PATH);
260
260
  }
261
- await DocsConfigUpdater.writeConfigFile(docsConfig, CLIENT_PATH);
261
+ await DocsConfigUpdater.writeConfigFile(docsConfig, CONTENT_PATH);
262
262
  await updateOpenApiFiles(openApiFiles, suppressParseError);
263
263
  await updateGeneratedNav(suppressParseError);
264
264
  triggerRefresh();
@@ -406,9 +406,9 @@ const onUpdateEvent = async (filename, triggerRefresh, options = {}) => {
406
406
  const { mintConfig, openApiFiles, docsConfig, pagesAcc } = await getDocsState(handleParseError);
407
407
  await refreshTrackedReferencedFiles();
408
408
  if (mintConfig) {
409
- await MintConfigUpdater.writeConfigFile(mintConfig, CLIENT_PATH);
409
+ await MintConfigUpdater.writeConfigFile(mintConfig, CONTENT_PATH);
410
410
  }
411
- await DocsConfigUpdater.writeConfigFile(docsConfig, CLIENT_PATH);
411
+ await DocsConfigUpdater.writeConfigFile(docsConfig, CONTENT_PATH);
412
412
  await updateOpenApiFiles(openApiFiles, suppressParseError);
413
413
  await updateCustomLanguages(docsConfig, suppressParseError);
414
414
  await updateGeneratedNav(suppressParseError, { pagesAcc, docsConfig });
@@ -524,13 +524,13 @@ const onUpdateEvent = async (filename, triggerRefresh, options = {}) => {
524
524
  // returns the API-expanded config, which we then persist.
525
525
  await updateCustomLanguages(docsConfig, suppressParseError);
526
526
  const generatedDocsConfig = await updateGeneratedNavFromPageMetadataCache(docsConfig, suppressParseError, { reuseGeneratedMetadata: true });
527
- await DocsConfigUpdater.writeConfigFile(generatedDocsConfig, CLIENT_PATH);
527
+ await DocsConfigUpdater.writeConfigFile(generatedDocsConfig, CONTENT_PATH);
528
528
  }
529
529
  else if (shouldRegenerateGeneratedData) {
530
530
  // Preserve the old full-rebuild route for top-level api, API-derived
531
531
  // navigation, and top-level api removals.
532
532
  const docsState = await getDocsState(handleParseError);
533
- await DocsConfigUpdater.writeConfigFile(docsState.docsConfig, CLIENT_PATH);
533
+ await DocsConfigUpdater.writeConfigFile(docsState.docsConfig, CONTENT_PATH);
534
534
  await updateOpenApiFiles(docsState.openApiFiles, suppressParseError);
535
535
  await updateCustomLanguages(docsState.docsConfig, suppressParseError);
536
536
  await updateGeneratedNav(suppressParseError, {
@@ -570,9 +570,9 @@ const onUpdateEvent = async (filename, triggerRefresh, options = {}) => {
570
570
  await refreshTrackedReferencedFiles();
571
571
  const { mintConfig, openApiFiles, docsConfig } = await getDocsState(handleParseError);
572
572
  if (mintConfig) {
573
- await MintConfigUpdater.writeConfigFile(mintConfig, CLIENT_PATH);
573
+ await MintConfigUpdater.writeConfigFile(mintConfig, CONTENT_PATH);
574
574
  }
575
- await DocsConfigUpdater.writeConfigFile(docsConfig, CLIENT_PATH);
575
+ await DocsConfigUpdater.writeConfigFile(docsConfig, CONTENT_PATH);
576
576
  await updateOpenApiFiles(openApiFiles, suppressParseError);
577
577
  await updateCustomLanguages(docsConfig, suppressParseError);
578
578
  }
@@ -690,7 +690,7 @@ const onUpdateEvent = async (filename, triggerRefresh, options = {}) => {
690
690
  const docsConfig = rawDocsConfig ?? (await getCurrentDocsConfig());
691
691
  if (hasDocsConfigApiRoutes(docsConfig)) {
692
692
  const generatedDocsConfig = await updateGeneratedNav(suppressParseError);
693
- await DocsConfigUpdater.writeConfigFile(generatedDocsConfig, CLIENT_PATH);
693
+ await DocsConfigUpdater.writeConfigFile(generatedDocsConfig, CONTENT_PATH);
694
694
  }
695
695
  else {
696
696
  await updateGeneratedNavFromPageMetadataCache(docsConfig, suppressParseError);
@@ -699,7 +699,7 @@ const onUpdateEvent = async (filename, triggerRefresh, options = {}) => {
699
699
  else if (regenerateNav) {
700
700
  // TODO: Instead of re-generating the entire nav, optimize by just updating the specific page that changed.
701
701
  const docsConfig = await updateGeneratedNav(suppressParseError);
702
- await DocsConfigUpdater.writeConfigFile(docsConfig, CLIENT_PATH);
702
+ await DocsConfigUpdater.writeConfigFile(docsConfig, CONTENT_PATH);
703
703
  }
704
704
  triggerRefresh();
705
705
  return category;
@@ -3,7 +3,7 @@ import open from 'better-opn';
3
3
  import express from 'express';
4
4
  import { createServer } from 'http';
5
5
  import { Server as SocketServer } from 'socket.io';
6
- import { CMD_EXEC_PATH, NEXT_PUBLIC_PATH } from '../constants.js';
6
+ import { CLIENT_FRAMEWORK, CMD_EXEC_PATH, NEXT_PUBLIC_PATH } from '../constants.js';
7
7
  import { addLog, removeSpinnerLogs } from '../logging-state.js';
8
8
  import { LaunchLog, SearchDisabledLog, UpdateLog } from '../logs.js';
9
9
  import { maybeFixMissingWindowsEnvVar } from '../util.js';
@@ -14,6 +14,7 @@ import { getLocalNetworkIp } from './network.js';
14
14
  import { ensurePageCompiled, getFailedCompileEvents, markOnDemandPagesReady, setCompileListener, } from './on-demand-page.js';
15
15
  import { packageRoute, setPackagesRebuiltListener } from './packages/index.js';
16
16
  import { setupNext } from './setupNext.js';
17
+ import { setupVite } from './setupVite.js';
17
18
  const PROXY_RESPONSE_HEADER_DENYLIST = new Set([
18
19
  'connection',
19
20
  'keep-alive',
@@ -52,8 +53,6 @@ export const run = async (argv) => {
52
53
  const server = createServer(app);
53
54
  const io = new SocketServer(server);
54
55
  process.env.LOCAL_OG_PREVIEW = 'true';
55
- const requestHandler = await timeDev('setup Next handler', () => setupNext());
56
- const localIp = getLocalNetworkIp();
57
56
  const apiUrl = argv.apiUrl ?? process.env.MINTLIFY_API_URL ?? 'http://localhost:5000';
58
57
  const accessToken = argv.accessToken;
59
58
  const subdomain = argv.subdomain;
@@ -61,6 +60,8 @@ export const run = async (argv) => {
61
60
  process.env.MINTLIFY_API_URL = apiUrl;
62
61
  if (subdomain)
63
62
  process.env.MINTLIFY_CLI_SUBDOMAIN = subdomain;
63
+ const requestHandler = await timeDev('setup client handler', () => CLIENT_FRAMEWORK === 'vite' ? setupVite() : setupNext());
64
+ const localIp = getLocalNetworkIp();
64
65
  app.use(packageRoute.prefix, packageRoute.handler);
65
66
  // next-server is bugged, public files added after starting aren't served
66
67
  app.use('/', express.static(NEXT_PUBLIC_PATH));
@@ -0,0 +1,4 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ type RequestListener = (req: IncomingMessage, res: ServerResponse) => Promise<void>;
3
+ export declare const setupVite: () => Promise<RequestListener>;
4
+ export {};
@@ -0,0 +1,14 @@
1
+ import fse from 'fs-extra';
2
+ import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { CLIENT_PATH } from '../constants.js';
5
+ export const setupVite = async () => {
6
+ const entryPath = path.join(CLIENT_PATH, 'dist-cli', 'index.js');
7
+ if (!(await fse.pathExists(entryPath))) {
8
+ throw new Error(`Client not built. Run: cd <path>/apps/client-vite && pnpm build:cli`);
9
+ }
10
+ const entry = await import(pathToFileURL(entryPath).href);
11
+ if (!entry.createCliHandler)
12
+ throw new Error(`${entryPath} does not export createCliHandler`);
13
+ return entry.createCliHandler();
14
+ };
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { spawn } from 'child_process';
3
3
  import fse from 'fs-extra';
4
4
  import path from 'path';
5
- import { LOCAL_LINKED_CLI_VERSION, MINT_PATH, PREVIEW_PATH, CLIENT_PATH } from '../constants.js';
5
+ import { CLIENT_FRAMEWORK, LOCAL_LINKED_CLI_VERSION, MINT_PATH, PREVIEW_PATH, CLIENT_PATH, } from '../constants.js';
6
6
  import { clearLogs, addLog } from '../logging-state.js';
7
7
  import { SpinnerLog } from '../logs.js';
8
8
  import { getLatestClientVersion, tryDownloadTargetMint, getCompatibleClientVersion, } from './client.js';
@@ -17,15 +17,16 @@ const buildClient = async () => {
17
17
  return new Promise((resolve) => {
18
18
  clearLogs();
19
19
  addLog(_jsx(SpinnerLog, { message: "building local client (this may take a few minutes)..." }));
20
- const buildProcess = spawn('pnpm', ['build'], {
20
+ const vite = CLIENT_FRAMEWORK === 'vite';
21
+ const buildProcess = spawn('pnpm', [vite ? 'build:cli' : 'build'], {
21
22
  cwd: CLIENT_PATH,
22
23
  env: {
23
24
  ...process.env,
24
- STANDALONE_BUILD: 'true',
25
+ ...(!vite && { STANDALONE_BUILD: 'true' }),
25
26
  NEXT_PUBLIC_ENV: 'cli',
26
27
  NEXT_PUBLIC_IS_LOCAL_CLIENT: 'true',
27
28
  },
28
- stdio: 'pipe',
29
+ stdio: ['ignore', 'ignore', 'pipe'],
29
30
  shell: true,
30
31
  });
31
32
  let stderr = '';