--- /dev/null
+'use strict';
+
+/**
+ * An opinionated way to gather a source identifier to include in logged
+ * messages, using possibly too much information.
+ */
+
+const path = require('node:path');
+const fs = require('node:fs');
+
+/**
+ * Internal exception
+ */
+class FileScopeError extends Error {
+ constructor(...args) {
+ super(...args);
+ Error.captureStackTrace(this, this.constructor);
+ }
+
+ get name() {
+ return this.constructor.name;
+ }
+}
+
+
+/**
+ * Read and parse package.json from a path.
+ * @param {String} packagePath
+ * @returns {Object}
+ */
+function readPackageJSON(packagePath) {
+ try {
+ const content = fs.readFileSync(path.join(packagePath, 'package.json')); // eslint-disable-line security/detect-non-literal-fs-filename
+ return JSON.parse(content);
+ } catch (e) {
+ return {
+ name: '(unknown)',
+ version: '(unknown)',
+ };
+ }
+}
+
+
+/**
+ * Returns whether path p exists.
+ * @param {String} p
+ * @returns {Boolean}
+ */
+function pathExists(p) {
+ try {
+ fs.statSync(p); // eslint-disable-line security/detect-non-literal-fs-filename
+ return true;
+ } catch (e) {
+ if (e.code !== 'ENOENT') {
+ throw e;
+ }
+ return false;
+ }
+}
+
+
+/**
+ * Walk up the path of provided filename until directory with
+ * package.json is located. We assume this is the package root.
+ * @param {String} filename
+ * @returns {String}
+ */
+function locatePackageBase(filename) {
+ let currentPath = filename;
+ do {
+ const d = path.dirname(currentPath);
+ try {
+ fs.statSync(path.join(d, 'package.json')); // eslint-disable-line security/detect-non-literal-fs-filename
+ return d + '/';
+ } catch (e) {
+ if (e.code !== 'ENOENT') {
+ throw e;
+ }
+ }
+ currentPath = d;
+ } while (currentPath !== '/');
+ throw new FileScopeError('unable to find package root');
+}
+
+
+/**
+ * Get default options based on package directory structure.
+ * @param {String} packageBase
+ * @returns {Object}
+ */
+function defaultOptions(packageBase) {
+ const options = {
+ includePath: false,
+ includePackage: false,
+ includeVersion: false,
+ leftTrim: 0,
+ _errorEncountered: false,
+ errorPrefix: '?',
+ delimiter: ':',
+ };
+
+ if (packageBase) {
+ try {
+ options.includePath = true;
+ if (pathExists(path.join(packageBase, 'lib'))) {
+ options.leftTrim = 4;
+ options.includePackage = true;
+ options.includeVersion = true;
+ } else if (pathExists(path.join(packageBase, 'src'))) {
+ options.leftTrim = 4;
+ }
+ } catch (e) {
+ options._errorEncountered = true;
+ options.includePath = false;
+ }
+ }
+
+ return options;
+}
+
+
+/**
+ * Returns a function suitable for decorating a function name with
+ * package information and details of the source file.
+ * @param {String} filepath full path from __filename
+ * @param {Object=} options
+ * @param {Boolean=} options.includePackage
+ * @param {Boolean=} options.includeVersion
+ * @param {Boolean=} options.includePath
+ * @param {String=} options.prefix
+ * @param {Number=} options.leftTrim
+ * @param {String=} options.errorPrefix
+ * @param {String=} options.delimiter
+ * @returns {Function}
+ */
+function fileScope(filepath, options) {
+ let errorEncountered = false;
+ let packageBase = '';
+ try {
+ packageBase = locatePackageBase(filepath);
+ } catch (e) {
+ errorEncountered = true;
+ }
+ const defaults = defaultOptions(packageBase);
+ errorEncountered |= defaults._errorEncountered;
+ const {
+ includePackage,
+ includeVersion,
+ includePath,
+ prefix,
+ leftTrim,
+ errorPrefix,
+ delimiter,
+ } = {
+ ...defaults,
+ ...options,
+ };
+
+ let packageIdentifier;
+ if (includePackage || includeVersion) {
+ const { name: packageName, version: packageVersion } = readPackageJSON(packageBase);
+ // including version implies including package
+ packageIdentifier = includeVersion ? `${packageName}@${packageVersion}` : packageName;
+ }
+
+ const { base, name, ext } = path.parse(filepath);
+ const isIndex = (name === 'index') && ['.js', '.ts', '.mjs', '.cjs'].includes(ext);
+
+ // If filepath is an index, trim off filename and last slash, otherwise just trim off extension
+ const rightTrim = 0 - (isIndex ? (base.length + 1) : ext.length);
+ // We can't trim both ends at once as dirname won't work when isIndex is true
+ const rightTrimmed = filepath.slice(0, rightTrim);
+
+ // Trim leading part of path
+ const trim = (includePath && packageBase) ? (leftTrim + packageBase.length) : (path.dirname(rightTrimmed).length + 1);
+ const trimmedFilename = rightTrimmed.slice(trim);
+
+ const components = [errorEncountered ? errorPrefix : '', prefix, packageIdentifier, trimmedFilename]
+ .filter((x) => x);
+
+ const scope = components.join(delimiter);
+
+ return (name) => `${scope}${delimiter}${name}`;
+}
+
+
+module.exports = {
+ FileScopeError,
+ readPackageJSON,
+ pathExists,
+ locatePackageBase,
+ defaultOptions,
+ fileScope,
+};