UNPKG

3.91 kBJavaScriptView Raw
1import { lstatSync, realpathSync } from "fs";
2import path from "path";
3import { fileURLToPath } from "url";
4import { setFlagsFromString } from "v8";
5import { runInNewContext } from "vm";
6import { newLogger } from "@lbu/insight";
7import dotenv from "dotenv";
8import { isNil } from "./lodash.js";
9
10/**
11 * @returns {number}
12 */
13export function getSecondsSinceEpoch() {
14 return Math.floor(Date.now() / 1000);
15}
16
17/**
18 * @returns {undefined}
19 */
20export function noop() {
21 return undefined;
22}
23
24/**
25 * Internal gc function reference
26 * Note that this is undefined if the gc function is not called and Node is not running
27 * with --expose-gc on
28 */
29let internalGc = global.gc;
30
31/**
32 * HACKY
33 */
34export function gc() {
35 if (isNil(internalGc)) {
36 setFlagsFromString("--expose_gc");
37 internalGc = runInNewContext("gc");
38 }
39
40 internalGc();
41}
42
43/**
44 * @param {ImportMeta} meta
45 * @param {MainFnCallback} cb
46 */
47export function mainFn(meta, cb) {
48 const { isMainFn, name } = isMainFnAndReturnName(meta);
49 if (isMainFn) {
50 dotenv.config();
51 const logger = newLogger({
52 ctx: { type: name },
53 });
54
55 const unhandled = (error) => {
56 logger.error(error);
57 process.exit(1);
58 };
59
60 // Just kill the process
61 process.on("unhandledRejection", (reason, promise) =>
62 unhandled({
63 reason: {
64 name: reason.name,
65 message: reason.message,
66 stack: reason.stack,
67 },
68 promise: {
69 name: promise.name,
70 message: promise.message,
71 stack: promise.stack,
72 },
73 }),
74 );
75
76 // Node.js by default will kill the process, we just make sure to log correctly
77 process.on("uncaughtExceptionMonitor", (error, origin) =>
78 logger.error({
79 error: {
80 name: error.name,
81 message: error.message,
82 stack: error.stack,
83 },
84 origin,
85 }),
86 );
87 // Log full warnings as well, no need for exiting
88 process.on("warning", (warn) =>
89 logger.error({
90 name: warn.name,
91 message: warn.message,
92 stack: warn.stack,
93 }),
94 );
95
96 // Handle async errors from the provided callback as `unhandledRejections`
97 Promise.resolve(cb(logger)).catch(unhandled);
98 }
99}
100
101/**
102 * @param {ImportMeta} meta
103 * @returns {string}
104 */
105export function filenameForModule(meta) {
106 return fileURLToPath(meta.url);
107}
108
109/**
110 * @param {ImportMeta} meta
111 * @returns {string}
112 */
113export function dirnameForModule(meta) {
114 return path.dirname(filenameForModule(meta));
115}
116
117/**
118 * Checks if the provided meta.url is the process entrypoint and also returns the name of the entrypoint file
119 * @param {ImportMeta} meta
120 * @returns {{ isMainFn: boolean, name?: string}}
121 */
122export function isMainFnAndReturnName(meta) {
123 const modulePath = fileURLToPath(meta.url);
124
125 let scriptPath = process.argv[1];
126
127 // Support following symbolic links for node_modules/.bin items
128 const scriptStat = lstatSync(scriptPath);
129 if (scriptStat.isSymbolicLink()) {
130 scriptPath = realpathSync(scriptPath);
131 }
132 const scriptPathExt = path.extname(scriptPath);
133 if (scriptPathExt) {
134 return {
135 isMainFn: modulePath === scriptPath,
136 name: scriptPath
137 .substring(0, scriptPath.length - scriptPathExt.length)
138 .split(path.sep)
139 .pop(),
140 };
141 }
142
143 let modulePathWithoutExt = modulePath;
144 const modulePathExt = path.extname(modulePath);
145 if (modulePathExt) {
146 modulePathWithoutExt = modulePathWithoutExt.slice(0, -modulePathExt.length);
147 }
148
149 return {
150 isMainFn: modulePathWithoutExt === scriptPath,
151 name: modulePathWithoutExt.split(path.sep).pop(),
152 };
153}
154
155/**
156 * @returns {boolean}
157 */
158export function isProduction() {
159 return process.env.NODE_ENV === "production";
160}
161
162/**
163 * @returns {boolean}
164 */
165export function isStaging() {
166 return (
167 process.env.NODE_ENV !== "production" || process.env.IS_STAGING === "true"
168 );
169}