mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-09 21:07:46 +00:00
[cli] Add node_modules/.bin to PATH instead of running npx/yarn run (#8890)
`runDevCommand()` assumes the dev command is an npm package and thus uses `npx` or `yarn run` to execute it. In the case of a Hugo-based app, there is no npm package, so we want spawn to find Hugo in the `PATH`. Then for Node-based apps, instead of `npx`, spawn should find the command since `node_modules/.bin` has been added to the `PATH`. ### Related Issues > https://github.com/vercel/customer-issues/issues/871 Note: This PR is a recreation of https://github.com/vercel/vercel/pull/8864 because prettier changed a bunch of Hugo files, which was bloating the original PR. ### 📋 Checklist <!-- Please keep your PR as a Draft until the checklist is complete --> #### Tests - [x] The code changed/added as part of this PR has been covered with tests - [x] All tests pass locally with `yarn test-unit` #### Code Review - [ ] This PR has a concise title and thorough description useful to a reviewer - [ ] Issue from task tracker has a link to this PR
This commit is contained in:
@@ -172,7 +172,6 @@
|
||||
"typescript": "4.7.4",
|
||||
"universal-analytics": "0.4.20",
|
||||
"utility-types": "2.1.0",
|
||||
"which": "2.0.2",
|
||||
"write-json-file": "2.2.0",
|
||||
"xdg-app-paths": "5.1.0",
|
||||
"yauzl-promise": "2.1.3"
|
||||
|
||||
@@ -18,7 +18,6 @@ import directoryTemplate from 'serve-handler/src/directory';
|
||||
import getPort from 'get-port';
|
||||
import isPortReachable from 'is-port-reachable';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import which from 'which';
|
||||
import npa from 'npm-package-arg';
|
||||
import type { ChildProcess } from 'child_process';
|
||||
|
||||
@@ -33,6 +32,7 @@ import {
|
||||
Builder,
|
||||
cloneEnv,
|
||||
Env,
|
||||
getNodeBinPath,
|
||||
StartDevServerResult,
|
||||
FileFsRef,
|
||||
PackageJson,
|
||||
@@ -2238,6 +2238,10 @@ export default class DevServer {
|
||||
}
|
||||
);
|
||||
|
||||
// add the node_modules/.bin directory to the PATH
|
||||
const nodeBinPath = await getNodeBinPath({ cwd });
|
||||
env.PATH = `${nodeBinPath}${path.delimiter}${env.PATH}`;
|
||||
|
||||
// This is necesary so that the dev command in the Project
|
||||
// will work cross-platform (especially Windows).
|
||||
let command = devCommand
|
||||
@@ -2252,22 +2256,6 @@ export default class DevServer {
|
||||
})}`
|
||||
);
|
||||
|
||||
const isNpxAvailable = await which('npx')
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
if (isNpxAvailable) {
|
||||
command = `npx --no-install ${command}`;
|
||||
} else {
|
||||
const isYarnAvailable = await which('yarn')
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
if (isYarnAvailable) {
|
||||
command = `yarn run --silent ${command}`;
|
||||
}
|
||||
}
|
||||
|
||||
this.output.debug(`Spawning dev command: ${command}`);
|
||||
|
||||
const proxyPort = new RegExp(port.toString(), 'g');
|
||||
|
||||
@@ -104,13 +104,38 @@ test(
|
||||
|
||||
test('[vercel dev] 08-hugo', async () => {
|
||||
if (process.platform === 'darwin') {
|
||||
// Update PATH to find the Hugo executable installed via GH Actions
|
||||
// 1. run the test without Hugo in the PATH
|
||||
let tester = await testFixtureStdio(
|
||||
'08-hugo',
|
||||
async () => {
|
||||
throw new Error('Expected dev server to fail to be ready');
|
||||
},
|
||||
{
|
||||
readyTimeout: 2000,
|
||||
|
||||
// Important: for the first test, we MUST deploy this app so that the
|
||||
// framework (e.g. Hugo) will be detected by the server and associated
|
||||
// with the project since `vc dev` doesn't do framework detection
|
||||
skipDeploy: false,
|
||||
}
|
||||
);
|
||||
await expect(tester()).rejects.toThrow(
|
||||
new Error('Dev server timed out while waiting to be ready')
|
||||
);
|
||||
|
||||
// 2. Update PATH to find the Hugo executable installed via GH Actions
|
||||
process.env.PATH = `${resolve(fixture('08-hugo'))}${delimiter}${
|
||||
process.env.PATH
|
||||
}`;
|
||||
const tester = testFixtureStdio('08-hugo', async (testPath: any) => {
|
||||
|
||||
// 3. Rerun the test now that Hugo is in the PATH
|
||||
tester = testFixtureStdio(
|
||||
'08-hugo',
|
||||
async (testPath: any) => {
|
||||
await testPath(200, '/', /Hugo/m);
|
||||
});
|
||||
},
|
||||
{ skipDeploy: true }
|
||||
);
|
||||
await tester();
|
||||
} else {
|
||||
console.log(`Skipping 08-hugo on platform ${process.platform}`);
|
||||
|
||||
@@ -61,8 +61,13 @@ function fetchWithRetry(url, opts = {}) {
|
||||
|
||||
function createResolver() {
|
||||
let resolver;
|
||||
const p = new Promise(res => (resolver = res));
|
||||
let rejector;
|
||||
const p = new Promise((resolve, reject) => {
|
||||
resolver = resolve;
|
||||
rejector = reject;
|
||||
});
|
||||
p.resolve = resolver;
|
||||
p.reject = rejector;
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -274,7 +279,13 @@ async function testFixture(directory, opts = {}, args = []) {
|
||||
function testFixtureStdio(
|
||||
directory,
|
||||
fn,
|
||||
{ expectedCode = 0, skipDeploy, isExample, projectSettings } = {}
|
||||
{
|
||||
expectedCode = 0,
|
||||
skipDeploy,
|
||||
isExample,
|
||||
projectSettings,
|
||||
readyTimeout = 0,
|
||||
} = {}
|
||||
) {
|
||||
return async () => {
|
||||
const nodeMajor = Number(process.versions.node.split('.')[0]);
|
||||
@@ -385,6 +396,18 @@ function testFixtureStdio(
|
||||
const readyResolver = createResolver();
|
||||
const exitResolver = createResolver();
|
||||
|
||||
// By default, tests will wait 6 minutes for the dev server to be ready and
|
||||
// perform the tests, however a `readyTimeout` can be used to reduce the
|
||||
// wait time if the dev server is expected to fail to start or hang
|
||||
let readyTimer = null;
|
||||
if (readyTimeout > 0) {
|
||||
readyTimer = setTimeout(() => {
|
||||
readyResolver.reject(
|
||||
new Error('Dev server timed out while waiting to be ready')
|
||||
);
|
||||
}, readyTimeout);
|
||||
}
|
||||
|
||||
try {
|
||||
let printedOutput = false;
|
||||
|
||||
@@ -424,6 +447,7 @@ function testFixtureStdio(
|
||||
stderr += data;
|
||||
|
||||
if (stripAnsi(data).includes('Ready! Available at')) {
|
||||
clearTimeout(readyTimer);
|
||||
readyResolver.resolve();
|
||||
}
|
||||
|
||||
@@ -507,5 +531,6 @@ module.exports = {
|
||||
shouldSkip,
|
||||
fixture,
|
||||
fetch,
|
||||
fetchWithRetry,
|
||||
validateResponseHeaders,
|
||||
};
|
||||
|
||||
14
yarn.lock
14
yarn.lock
@@ -13677,13 +13677,6 @@ which-module@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
|
||||
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
|
||||
|
||||
which@2.0.2, which@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
||||
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
which@^1.2.9, which@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||
@@ -13691,6 +13684,13 @@ which@^1.2.9, which@^1.3.1:
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
which@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
||||
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
wide-align@^1.1.0:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
|
||||
|
||||
Reference in New Issue
Block a user