@jsenv/core
Version:
Tool to develop, test and build js projects
371 lines (361 loc) • 10.5 kB
JavaScript
import { createDetailedMessage, generateContentFrame } from "@jsenv/humanize";
import { stringifyUrlSite } from "@jsenv/urls";
import { pathToFileURL } from "node:url";
export const createResolveUrlError = ({
pluginController,
reference,
error,
}) => {
const createFailedToResolveUrlError = ({
name = "RESOLVE_URL_ERROR",
code = error.code || "RESOLVE_URL_ERROR",
reason,
...details
}) => {
const resolveError = new Error(
createDetailedMessage(
`Failed to resolve url reference
${reference.trace.message}
${reason}`,
{
...detailsFromFirstReference(reference),
...details,
...detailsFromPluginController(pluginController),
},
),
);
defineNonEnumerableProperties(resolveError, {
isJsenvCookingError: true,
name,
code,
reason,
asResponse: error.asResponse,
trace: error.trace || reference.trace,
});
return resolveError;
};
if (error.message === "NO_RESOLVE") {
return createFailedToResolveUrlError({
reason: `no plugin has handled the specifier during "resolveUrl" hook`,
});
}
if (error.code === "MODULE_NOT_FOUND") {
const bareSpecifierError = createFailedToResolveUrlError({
reason: `"${reference.specifier}" is a bare specifier but cannot be remapped to a package`,
});
return bareSpecifierError;
}
if (error.code === "DIRECTORY_REFERENCE_NOT_ALLOWED") {
error.message = createDetailedMessage(error.message, {
"reference trace": reference.trace.message,
...detailsFromFirstReference(reference),
});
return error;
}
if (error.code === "PROTOCOL_NOT_SUPPORTED") {
const notSupportedError = createFailedToResolveUrlError({
reason: error.message,
});
return notSupportedError;
}
return createFailedToResolveUrlError({
reason: `An error occured during specifier resolution`,
...detailsFromValueThrown(error),
});
};
export const createFetchUrlContentError = ({
pluginController,
urlInfo,
error,
}) => {
const createFailedToFetchUrlContentError = ({
code = error.code || "FETCH_URL_CONTENT_ERROR",
reason,
parseErrorSourceType,
...details
}) => {
const reference = urlInfo.firstReference;
const fetchError = new Error(
createDetailedMessage(
`Failed to fetch url content
${reference.trace.message}
${reason}`,
{
...detailsFromFirstReference(reference),
...details,
...detailsFromPluginController(pluginController),
},
),
);
defineNonEnumerableProperties(fetchError, {
isJsenvCookingError: true,
name: "FETCH_URL_CONTENT_ERROR",
code,
reason,
parseErrorSourceType,
url: urlInfo.url,
trace: code === "PARSE_ERROR" ? error.trace : reference.trace,
asResponse: error.asResponse,
});
return fetchError;
};
if (error.code === "EPERM") {
return createFailedToFetchUrlContentError({
code: "NOT_ALLOWED",
reason: `not allowed to read entry on filesystem`,
});
}
if (error.code === "DIRECTORY_REFERENCE_NOT_ALLOWED") {
return createFailedToFetchUrlContentError({
code: "DIRECTORY_REFERENCE_NOT_ALLOWED",
reason: `found a directory on filesystem`,
});
}
if (error.code === "ENOENT") {
const urlTried = pathToFileURL(error.path).href;
// ensure ENOENT is caused by trying to read the urlInfo.url
// any ENOENT trying to read an other file should display the error.stack
// because it means some side logic has failed
if (urlInfo.url.startsWith(urlTried)) {
return createFailedToFetchUrlContentError({
code: "NOT_FOUND",
reason: "no entry on filesystem",
});
}
}
if (error.code === "PARSE_ERROR") {
return createFailedToFetchUrlContentError({
"code": "PARSE_ERROR",
"reason": error.reasonCode,
"parseErrorSourceType": error.parseErrorSourceType,
...(error.cause ? { "parse error message": error.cause.message } : {}),
"parse error trace": error.trace?.message,
});
}
return createFailedToFetchUrlContentError({
reason: `An error occured during "fetchUrlContent"`,
...detailsFromValueThrown(error),
});
};
export const createTransformUrlContentError = ({
pluginController,
urlInfo,
error,
}) => {
if (error.code === "MODULE_NOT_FOUND") {
return error;
}
if (error.code === "PROTOCOL_NOT_SUPPORTED") {
return error;
}
if (error.code === "DIRECTORY_REFERENCE_NOT_ALLOWED") {
return error;
}
if (error.code === "PARSE_ERROR") {
if (error.isJsenvCookingError) {
return error;
}
const trace = getErrorTrace(error, urlInfo.firstReference);
const reference = urlInfo.firstReference;
const transformError = new Error(
createDetailedMessage(
`parse error on "${urlInfo.type}"
${trace.message}
${error.message}`,
{
"first reference": reference.trace.url
? `${reference.trace.url}:${reference.trace.line}:${reference.trace.column}`
: reference.trace.message,
...detailsFromFirstReference(reference),
...detailsFromPluginController(pluginController),
},
),
);
defineNonEnumerableProperties(transformError, {
isJsenvCookingError: true,
name: "TRANSFORM_URL_CONTENT_ERROR",
code: "PARSE_ERROR",
reason: error.message,
reasonCode: error.reasonCode,
parseErrorSourceType: error.parseErrorSourceType,
stack: transformError.stack,
trace,
asResponse: error.asResponse,
});
return transformError;
}
const createFailedToTransformError = ({
code = error.code || "TRANSFORM_URL_CONTENT_ERROR",
reason,
...details
}) => {
const reference = urlInfo.firstReference;
let trace = reference.trace;
const transformError = new Error(
createDetailedMessage(
`"transformUrlContent" error on "${urlInfo.type}"
${trace.message}
${reason}`,
{
...detailsFromFirstReference(reference),
...details,
...detailsFromPluginController(pluginController),
},
),
);
defineNonEnumerableProperties(transformError, {
isJsenvCookingError: true,
cause: error,
name: "TRANSFORM_URL_CONTENT_ERROR",
code,
reason,
stack: error.stack,
url: urlInfo.url,
trace,
asResponse: error.asResponse,
});
return transformError;
};
return createFailedToTransformError({
reason: `"transformUrlContent" error on "${urlInfo.type}"`,
...detailsFromValueThrown(error),
});
};
export const createFinalizeUrlContentError = ({
pluginController,
urlInfo,
error,
}) => {
const reference = urlInfo.firstReference;
const finalizeError = new Error(
createDetailedMessage(
`"finalizeUrlContent" error on "${urlInfo.type}"
${reference.trace.message}`,
{
...detailsFromFirstReference(reference),
...detailsFromValueThrown(error),
...detailsFromPluginController(pluginController),
},
),
);
defineNonEnumerableProperties(finalizeError, {
isJsenvCookingError: true,
...(error && error instanceof Error ? { cause: error } : {}),
name: "FINALIZE_URL_CONTENT_ERROR",
reason: `"finalizeUrlContent" error on "${urlInfo.type}"`,
asResponse: error.asResponse,
});
return finalizeError;
};
const getErrorTrace = (error, reference) => {
const urlInfo = reference.urlInfo;
let trace = reference.trace;
let line = error.line;
let column = error.column;
if (urlInfo.isInline) {
line = trace.line + line;
line = line - 1;
return {
...trace,
line,
column,
codeFrame: generateContentFrame({
line,
column,
content: urlInfo.inlineUrlSite.content,
}),
message: stringifyUrlSite({
url: urlInfo.inlineUrlSite.url,
line,
column,
content: urlInfo.inlineUrlSite.content,
}),
};
}
return {
url: urlInfo.url,
line,
column: error.column,
codeFrame: generateContentFrame({
line,
column: error.column,
content: urlInfo.content,
}),
message: stringifyUrlSite({
url: urlInfo.url,
line,
column: error.column,
content: urlInfo.content,
}),
};
};
const detailsFromFirstReference = (reference) => {
const referenceInProject = getFirstReferenceInProject(reference);
if (
referenceInProject === reference ||
referenceInProject.type === "http_request"
) {
return {};
}
return {
"first reference in project": `${referenceInProject.trace.url}:${referenceInProject.trace.line}:${referenceInProject.trace.column}`,
};
};
const getFirstReferenceInProject = (reference) => {
const ownerUrlInfo = reference.ownerUrlInfo;
if (ownerUrlInfo.isRoot) {
return reference;
}
if (
!ownerUrlInfo.url.includes("/node_modules/") &&
ownerUrlInfo.packageDirectoryUrl ===
ownerUrlInfo.context.packageDirectory.url
) {
return reference;
}
const { firstReference } = ownerUrlInfo;
return getFirstReferenceInProject(firstReference);
};
const detailsFromPluginController = (pluginController) => {
const currentPlugin = pluginController.getCurrentPlugin();
if (!currentPlugin) {
return null;
}
return { "plugin name": `"${currentPlugin.name}"` };
};
const detailsFromValueThrown = (valueThrownByPlugin) => {
if (valueThrownByPlugin && valueThrownByPlugin instanceof Error) {
if (
valueThrownByPlugin.code === "PARSE_ERROR" ||
valueThrownByPlugin.code === "MODULE_NOT_FOUND" ||
valueThrownByPlugin.name === "RESOLVE_URL_ERROR" ||
valueThrownByPlugin.name === "FETCH_URL_CONTENT_ERROR" ||
valueThrownByPlugin.name === "TRANSFORM_URL_CONTENT_ERROR" ||
valueThrownByPlugin.name === "FINALIZE_URL_CONTENT_ERROR"
) {
return {
"error message": valueThrownByPlugin.message,
};
}
return {
"error stack": valueThrownByPlugin.stack,
};
}
if (valueThrownByPlugin === undefined) {
return {
error: "undefined",
};
}
return {
error: JSON.stringify(valueThrownByPlugin),
};
};
export const defineNonEnumerableProperties = (object, properties) => {
for (const key of Object.keys(properties)) {
Object.defineProperty(object, key, {
configurable: true,
writable: true,
value: properties[key],
});
}
};