mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-10 21:07:48 +00:00
[cli] Store build error in "builds.json" file in vc build (#8148)
When a build fails, store the serialized Error in the "builds.json" file under the "build" object of the Builder that failed.
Example:
```json
{
"//": "This file was generated by the `vercel build` command. It is not part of the Build Output API.",
"target": "preview",
"argv": [
"/usr/local/bin/node",
"/Users/nrajlich/Code/vercel/vercel/packages/cli/src/index.ts",
"build",
"--cwd",
"/Users/nrajlich/Downloads/vc-build-next-repro/"
],
"builds": [
{
"require": "@vercel/next",
"requirePath": "/Users/nrajlich/Code/vercel/vercel/packages/next/dist/index",
"apiVersion": 2,
"src": "package.json",
"use": "@vercel/next",
"config": {
"zeroConfig": true,
"framework": "nextjs"
},
"error": {
"name": "Error",
"message": "Command \"pnpm run build\" exited with 1",
"stack": "Error: Command \"pnpm run build\" exited with 1\n at ChildProcess.<anonymous> (/Users/nrajlich/Code/vercel/vercel/packages/build-utils/dist/index.js:20591:20)\n at ChildProcess.emit (node:events:527:28)\n at ChildProcess.emit (node:domain:475:12)\n at maybeClose (node:internal/child_process:1092:16)\n at Process.ChildProcess._handle.onexit (node:internal/child_process:302:5)",
"hideStackTrace": true,
"code": "BUILD_UTILS_SPAWN_1"
}
}
]
}
```
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
# https://prettier.io/docs/en/ignore.html
|
# https://prettier.io/docs/en/ignore.html
|
||||||
|
|
||||||
# ignore this file with an intentional syntax error
|
# ignore these files with an intentional syntax error
|
||||||
packages/cli/test/dev/fixtures/edge-function-error/api/edge-error-syntax.js
|
packages/cli/test/dev/fixtures/edge-function-error/api/edge-error-syntax.js
|
||||||
|
packages/cli/test/fixtures/unit/commands/build/node-error/api/typescript.ts
|
||||||
|
|||||||
@@ -52,6 +52,13 @@ import { sortBuilders } from '../util/build/sort-builders';
|
|||||||
|
|
||||||
type BuildResult = BuildResultV2 | BuildResultV3;
|
type BuildResult = BuildResultV2 | BuildResultV3;
|
||||||
|
|
||||||
|
interface SerializedBuilder extends Builder {
|
||||||
|
error?: Error;
|
||||||
|
require?: string;
|
||||||
|
requirePath?: string;
|
||||||
|
apiVersion: number;
|
||||||
|
}
|
||||||
|
|
||||||
const help = () => {
|
const help = () => {
|
||||||
return console.log(`
|
return console.log(`
|
||||||
${chalk.bold(`${cli.logo} ${cli.name} build`)}
|
${chalk.bold(`${cli.logo} ${cli.name} build`)}
|
||||||
@@ -297,32 +304,36 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
const ops: Promise<Error | void>[] = [];
|
const ops: Promise<Error | void>[] = [];
|
||||||
|
|
||||||
// Write the `detectedBuilders` result to output dir
|
// Write the `detectedBuilders` result to output dir
|
||||||
ops.push(
|
const buildsJsonBuilds = new Map<Builder, SerializedBuilder>(
|
||||||
fs.writeJSON(
|
builds.map(build => {
|
||||||
join(outputDir, 'builds.json'),
|
const builderWithPkg = buildersWithPkgs.get(build.use);
|
||||||
{
|
if (!builderWithPkg) {
|
||||||
'//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.',
|
throw new Error(`Failed to load Builder "${build.use}"`);
|
||||||
target,
|
|
||||||
argv: process.argv,
|
|
||||||
builds: builds.map(build => {
|
|
||||||
const builderWithPkg = buildersWithPkgs.get(build.use);
|
|
||||||
if (!builderWithPkg) {
|
|
||||||
throw new Error(`Failed to load Builder "${build.use}"`);
|
|
||||||
}
|
|
||||||
const { builder, pkg: builderPkg } = builderWithPkg;
|
|
||||||
return {
|
|
||||||
require: builderPkg.name,
|
|
||||||
requirePath: builderWithPkg.path,
|
|
||||||
apiVersion: builder.version,
|
|
||||||
...build,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
spaces: 2,
|
|
||||||
}
|
}
|
||||||
)
|
const { builder, pkg: builderPkg } = builderWithPkg;
|
||||||
|
return [
|
||||||
|
build,
|
||||||
|
{
|
||||||
|
require: builderPkg.name,
|
||||||
|
requirePath: builderWithPkg.path,
|
||||||
|
apiVersion: builder.version,
|
||||||
|
...build,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
const buildsJson = {
|
||||||
|
'//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.',
|
||||||
|
target,
|
||||||
|
argv: process.argv,
|
||||||
|
builds: Array.from(buildsJsonBuilds.values()),
|
||||||
|
};
|
||||||
|
const buildsJsonPath = join(outputDir, 'builds.json');
|
||||||
|
const writeBuildsJsonPromise = fs.writeJSON(buildsJsonPath, buildsJson, {
|
||||||
|
spaces: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
ops.push(writeBuildsJsonPromise);
|
||||||
|
|
||||||
// The `meta` config property is re-used for each Builder
|
// The `meta` config property is re-used for each Builder
|
||||||
// invocation so that Builders can share state between
|
// invocation so that Builders can share state between
|
||||||
@@ -347,51 +358,72 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
if (!builderWithPkg) {
|
if (!builderWithPkg) {
|
||||||
throw new Error(`Failed to load Builder "${build.use}"`);
|
throw new Error(`Failed to load Builder "${build.use}"`);
|
||||||
}
|
}
|
||||||
const { builder, pkg: builderPkg } = builderWithPkg;
|
|
||||||
|
|
||||||
const buildConfig: Config = {
|
try {
|
||||||
outputDirectory: project.settings.outputDirectory ?? undefined,
|
const { builder, pkg: builderPkg } = builderWithPkg;
|
||||||
...build.config,
|
|
||||||
projectSettings: project.settings,
|
|
||||||
installCommand: project.settings.installCommand ?? undefined,
|
|
||||||
devCommand: project.settings.devCommand ?? undefined,
|
|
||||||
buildCommand: project.settings.buildCommand ?? undefined,
|
|
||||||
framework: project.settings.framework,
|
|
||||||
nodeVersion: project.settings.nodeVersion,
|
|
||||||
};
|
|
||||||
const buildOptions: BuildOptions = {
|
|
||||||
files: filesMap,
|
|
||||||
entrypoint: build.src,
|
|
||||||
workPath,
|
|
||||||
repoRootPath,
|
|
||||||
config: buildConfig,
|
|
||||||
meta,
|
|
||||||
};
|
|
||||||
output.debug(
|
|
||||||
`Building entrypoint "${build.src}" with "${builderPkg.name}"`
|
|
||||||
);
|
|
||||||
const buildResult = await builder.build(buildOptions);
|
|
||||||
|
|
||||||
// Store the build result to generate the final `config.json` after
|
const buildConfig: Config = {
|
||||||
// all builds have completed
|
outputDirectory: project.settings.outputDirectory ?? undefined,
|
||||||
buildResults.set(build, buildResult);
|
...build.config,
|
||||||
|
projectSettings: project.settings,
|
||||||
|
installCommand: project.settings.installCommand ?? undefined,
|
||||||
|
devCommand: project.settings.devCommand ?? undefined,
|
||||||
|
buildCommand: project.settings.buildCommand ?? undefined,
|
||||||
|
framework: project.settings.framework,
|
||||||
|
nodeVersion: project.settings.nodeVersion,
|
||||||
|
};
|
||||||
|
const buildOptions: BuildOptions = {
|
||||||
|
files: filesMap,
|
||||||
|
entrypoint: build.src,
|
||||||
|
workPath,
|
||||||
|
repoRootPath,
|
||||||
|
config: buildConfig,
|
||||||
|
meta,
|
||||||
|
};
|
||||||
|
output.debug(
|
||||||
|
`Building entrypoint "${build.src}" with "${builderPkg.name}"`
|
||||||
|
);
|
||||||
|
const buildResult = await builder.build(buildOptions);
|
||||||
|
|
||||||
// Start flushing the file outputs to the filesystem asynchronously
|
// Store the build result to generate the final `config.json` after
|
||||||
ops.push(
|
// all builds have completed
|
||||||
writeBuildResult(
|
buildResults.set(build, buildResult);
|
||||||
outputDir,
|
|
||||||
buildResult,
|
// Start flushing the file outputs to the filesystem asynchronously
|
||||||
build,
|
ops.push(
|
||||||
builder,
|
writeBuildResult(
|
||||||
builderPkg,
|
outputDir,
|
||||||
vercelConfig?.cleanUrls
|
buildResult,
|
||||||
).then(
|
build,
|
||||||
override => {
|
builder,
|
||||||
if (override) overrides.push(override);
|
builderPkg,
|
||||||
},
|
vercelConfig?.cleanUrls
|
||||||
err => err
|
).then(
|
||||||
)
|
override => {
|
||||||
);
|
if (override) overrides.push(override);
|
||||||
|
},
|
||||||
|
err => err
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (err: any) {
|
||||||
|
await writeBuildsJsonPromise;
|
||||||
|
|
||||||
|
const buildJsonBuild = buildsJsonBuilds.get(build);
|
||||||
|
if (buildJsonBuild) {
|
||||||
|
buildJsonBuild.error = {
|
||||||
|
name: err.name,
|
||||||
|
message: err.message,
|
||||||
|
stack: err.stack,
|
||||||
|
...err,
|
||||||
|
};
|
||||||
|
|
||||||
|
await fs.writeJSON(buildsJsonPath, buildsJson, {
|
||||||
|
spaces: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (corepackShimDir) {
|
if (corepackShimDir) {
|
||||||
|
|||||||
7
packages/cli/test/fixtures/unit/commands/build/node-error/.vercel/project.json
vendored
Normal file
7
packages/cli/test/fixtures/unit/commands/build/node-error/.vercel/project.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"orgId": ".",
|
||||||
|
"projectId": ".",
|
||||||
|
"settings": {
|
||||||
|
"framework": null
|
||||||
|
}
|
||||||
|
}
|
||||||
1
packages/cli/test/fixtures/unit/commands/build/node-error/api/es6.js
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/build/node-error/api/es6.js
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default (req, res) => res.end('Vercel');
|
||||||
1
packages/cli/test/fixtures/unit/commands/build/node-error/api/index.js
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/build/node-error/api/index.js
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module.exports = (req, res) => res.end('Vercel');
|
||||||
1
packages/cli/test/fixtures/unit/commands/build/node-error/api/mjs.mjs
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/build/node-error/api/mjs.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default (req, res) => res.end('Vercel');
|
||||||
4
packages/cli/test/fixtures/unit/commands/build/node-error/api/typescript.ts
vendored
Normal file
4
packages/cli/test/fixtures/unit/commands/build/node-error/api/typescript.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { IncomingMessage, ServerResponse } from 'http';
|
||||||
|
|
||||||
|
// Intentional syntax error to make the build fail
|
||||||
|
export default (req: IncomingMessage, res: ServerResponse => res.end('Vercel');
|
||||||
@@ -589,8 +589,6 @@ describe('build', () => {
|
|||||||
const output = join(cwd, '.vercel/output');
|
const output = join(cwd, '.vercel/output');
|
||||||
try {
|
try {
|
||||||
process.chdir(cwd);
|
process.chdir(cwd);
|
||||||
client.stderr.pipe(process.stderr);
|
|
||||||
client.setArgv('build');
|
|
||||||
const exitCode = await build(client);
|
const exitCode = await build(client);
|
||||||
expect(exitCode).toEqual(0);
|
expect(exitCode).toEqual(0);
|
||||||
|
|
||||||
@@ -619,4 +617,30 @@ describe('build', () => {
|
|||||||
delete process.env.__VERCEL_BUILD_RUNNING;
|
delete process.env.__VERCEL_BUILD_RUNNING;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should store Builder error in `builds.json`', async () => {
|
||||||
|
const cwd = fixture('node-error');
|
||||||
|
const output = join(cwd, '.vercel/output');
|
||||||
|
try {
|
||||||
|
process.chdir(cwd);
|
||||||
|
const exitCode = await build(client);
|
||||||
|
expect(exitCode).toEqual(1);
|
||||||
|
|
||||||
|
// `builds.json` contains "error" build
|
||||||
|
const builds = await fs.readJSON(join(output, 'builds.json'));
|
||||||
|
expect(builds.builds).toHaveLength(4);
|
||||||
|
|
||||||
|
const errorBuilds = builds.builds.filter((b: any) => 'error' in b);
|
||||||
|
expect(errorBuilds).toHaveLength(1);
|
||||||
|
|
||||||
|
expect(errorBuilds[0].error.name).toEqual('Error');
|
||||||
|
expect(errorBuilds[0].error.message).toMatch(`TS1005`);
|
||||||
|
expect(errorBuilds[0].error.message).toMatch(`',' expected.`);
|
||||||
|
expect(errorBuilds[0].error.hideStackTrace).toEqual(true);
|
||||||
|
expect(errorBuilds[0].error.code).toEqual('NODE_TYPESCRIPT_ERROR');
|
||||||
|
} finally {
|
||||||
|
process.chdir(originalCwd);
|
||||||
|
delete process.env.__VERCEL_BUILD_RUNNING;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user