mirror of https://github.com/pulumi/pulumi.git
126 lines
5.6 KiB
TypeScript
126 lines
5.6 KiB
TypeScript
// Copyright 2016-2024, Pulumi Corporation.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// The very first thing we do is set up unhandled exception and rejection hooks to ensure that these
|
|
// events cause us to exit with a non-zero code. It is critically important that we do this early:
|
|
// if we do not, unhandled rejections in particular may cause us to exit with a 0 exit code, which
|
|
// will trick the engine into thinking that the program ran successfully. This can cause the engine
|
|
// to decide to delete all of a stack's resources.
|
|
//
|
|
// We track all uncaught errors here. If we have any, we will make sure we always have a non-0 exit
|
|
// code.
|
|
const uncaughtErrors = new Set<Error>();
|
|
|
|
// We also track errors we know were logged to the user using our standard `log.error` call from
|
|
// inside our uncaught-error-handler in run.ts. If all uncaught-errors above were also known to all
|
|
// be logged properly to the user, then we know the user has the information they need to proceed.
|
|
// We can then report the langhost that it should just stop running immediately and not print any
|
|
// additional superfluous information.
|
|
const loggedErrors = new Set<Error>();
|
|
|
|
let programRunning = false;
|
|
const uncaughtHandler = (err: Error) => {
|
|
uncaughtErrors.add(err);
|
|
if (!programRunning) {
|
|
console.error(err.stack || err.message || "" + err);
|
|
}
|
|
};
|
|
|
|
// Keep track if we already logged the information about an unhandled error to the user. If
|
|
// so, we end with a different exit code. The language host recognizes this and will not print
|
|
// any further messages to the user since we already took care of it.
|
|
//
|
|
// 32 was picked so as to be very unlikely to collide with any of the error codes documented by
|
|
// nodejs here:
|
|
// https://nodejs.org/api/process.html#process_exit_codes
|
|
const nodeJSProcessExitedAfterLoggingUserActionableMessage = 32;
|
|
|
|
process.on("uncaughtException", uncaughtHandler);
|
|
// @ts-ignore 'unhandledRejection' will almost always invoke uncaughtHandler with an Error. so just
|
|
// suppress the TS strictness here.
|
|
process.on("unhandledRejection", uncaughtHandler);
|
|
process.on("exit", (code: number) => {
|
|
// If there were any uncaught errors at all, we always want to exit with an error code. If we
|
|
// did not, it could be disastrous for the user. i.e. not all resources may have been created,
|
|
// but the 0 code would indicate we could proceed. That could lead to many (or all) of the
|
|
// user resources being deleted.
|
|
if (code === 0 && uncaughtErrors.size > 0) {
|
|
// Now Check if this error was already logged to the user in a visible fashion. If not
|
|
// we will exit with '1', indicating that the host should give a generic message about
|
|
// things not working.
|
|
for (const err of uncaughtErrors) {
|
|
if (!loggedErrors.has(err)) {
|
|
process.exitCode = 1;
|
|
return;
|
|
}
|
|
}
|
|
|
|
process.exitCode = nodeJSProcessExitedAfterLoggingUserActionableMessage;
|
|
}
|
|
});
|
|
|
|
// As the second thing we do, ensure that we're connected to v8's inspector API. We need to do
|
|
// this as some information is only sent out as events, without any way to query for it after the
|
|
// fact. For example, we want to keep track of ScriptId->FileNames so that we can appropriately
|
|
// report errors for Functions we cannot serialize. This can only be done (up to Node11 at least)
|
|
// by register to hear about scripts being parsed.
|
|
import * as v8Hooks from "../../runtime/closure/v8Hooks";
|
|
|
|
// This is the entrypoint for running a Node.js program with minimal scaffolding.
|
|
import minimist from "minimist";
|
|
|
|
function usage(): void {
|
|
console.error("usage: RUN <program>");
|
|
}
|
|
|
|
function printUsageAndExit(): never {
|
|
usage();
|
|
return process.exit(-1);
|
|
}
|
|
|
|
function main(args: string[]): void {
|
|
// See usage above for the intended usage of this program, including flags and required args.
|
|
const argv: minimist.ParsedArgs = minimist(args, {});
|
|
|
|
// Finally, ensure we have a program to run.
|
|
if (argv._.length < 1) {
|
|
return printUsageAndExit();
|
|
}
|
|
|
|
// Ensure that our v8 hooks have been initialized. Then actually load and run the user program.
|
|
v8Hooks.isInitializedAsync().then(() => {
|
|
const promise: Promise<void> = require("./run").run({
|
|
argv,
|
|
programStarted: () => {
|
|
programRunning = true;
|
|
},
|
|
reportLoggedError: (err: Error) => loggedErrors.add(err),
|
|
typeScript: true, // Should have no deleterious impact on JS codebases.
|
|
});
|
|
|
|
// when the user's program completes successfully, set programRunning back to false. That
|
|
// way, if the Pulumi scaffolding code ends up throwing an exception during teardown, it
|
|
// will get printed directly to the console.
|
|
//
|
|
// Note: we only do this in the 'resolved' arg of '.then' (not the 'rejected' arg). If the
|
|
// users code throws an exception, this promise will get rejected, and we don't want touch
|
|
// or otherwise intercept the exception or change the programRunning state here at all.
|
|
promise.then(() => {
|
|
programRunning = false;
|
|
});
|
|
});
|
|
}
|
|
|
|
main(process.argv.slice(2));
|