From cd2e2362cf01e756c8d91a16a84c3adb6f65f6e6 Mon Sep 17 00:00:00 2001 From: dmail Date: Mon, 30 Dec 2024 10:33:07 +0100 Subject: [PATCH 1/8] prepare testing directory listing on workspace --- .../jsenv_plugin_protocol_file.js | 166 +++++++++++------- .../directory_listing_workspace.test.mjs | 44 +++++ .../fixtures/0_at_start/package.json | 6 + .../fixtures/0_at_start/packages/bar/bar.js | 0 .../fixtures/0_at_start/packages/foo/foo.js | 0 .../fixtures/0_at_start/src/main.js | 0 6 files changed, 151 insertions(+), 65 deletions(-) create mode 100644 tests/dev_server/directory_listing_workspace/directory_listing_workspace.test.mjs create mode 100644 tests/dev_server/directory_listing_workspace/fixtures/0_at_start/package.json create mode 100644 tests/dev_server/directory_listing_workspace/fixtures/0_at_start/packages/bar/bar.js create mode 100644 tests/dev_server/directory_listing_workspace/fixtures/0_at_start/packages/foo/foo.js create mode 100644 tests/dev_server/directory_listing_workspace/fixtures/0_at_start/src/main.js diff --git a/src/plugins/protocol_file/jsenv_plugin_protocol_file.js b/src/plugins/protocol_file/jsenv_plugin_protocol_file.js index 1df55024f0..435c6037a0 100644 --- a/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +++ b/src/plugins/protocol_file/jsenv_plugin_protocol_file.js @@ -11,6 +11,7 @@ import { } from "@jsenv/urls"; import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js"; import { existsSync, lstatSync, readdirSync, readFileSync } from "node:fs"; +import { lookupPackageDirectory } from "../../helpers/lookup_package_directory.js"; import { jsenvCoreDirectoryUrl } from "../../jsenv_core_directory_url.js"; import { jsenvPluginFsRedirection } from "./jsenv_plugin_fs_redirection.js"; @@ -83,6 +84,7 @@ export const jsenvPluginProtocolFile = ({ if (!urlInfo.url.startsWith("file:")) { return null; } + const { rootDirectoryUrl } = urlInfo.context; const generateContent = () => { const urlObject = new URL(urlInfo.url); const { firstReference } = urlInfo; @@ -101,11 +103,12 @@ export const jsenvPluginProtocolFile = ({ : false; if (acceptsHtml) { firstReference.expectedType = "html"; - const html = generateHtmlForDirectory( - urlObject.href, - directoryContentArray, - urlInfo.context.rootDirectoryUrl, + const directoryUrl = urlObject.href; + const directoryContentItems = generateDirectoryContentItems( + directoryUrl, + rootDirectoryUrl, ); + const html = generateHtmlForDirectory(directoryContentItems); return { type: "html", contentType: "text/html", @@ -138,32 +141,13 @@ export const jsenvPluginProtocolFile = ({ if (e.code !== "ENOENT") { throw e; } - const rootDirectoryUrl = urlInfo.context.rootDirectoryUrl; - let firstExistingAncestorDirectoryUrl = new URL("./", urlInfo.url); - while (!existsSync(firstExistingAncestorDirectoryUrl)) { - firstExistingAncestorDirectoryUrl = new URL( - "../", - firstExistingAncestorDirectoryUrl, - ); - if ( - !urlIsInsideOf( - firstExistingAncestorDirectoryUrl, - rootDirectoryUrl, - ) - ) { - firstExistingAncestorDirectoryUrl = rootDirectoryUrl; - break; - } - } - - const firstExistingAncestorDirectoryContent = readdirSync( - new URL(firstExistingAncestorDirectoryUrl), + const directoryContentItems = generateDirectoryContentItems( + urlInfo.url, + rootDirectoryUrl, ); const html = generateHtmlForENOENT( urlInfo.url, - firstExistingAncestorDirectoryContent, - firstExistingAncestorDirectoryUrl, - urlInfo.context.rootDirectoryUrl, + directoryContentItems, directoryListingUrlMocks, ); return { @@ -182,11 +166,9 @@ export const jsenvPluginProtocolFile = ({ ]; }; -const generateHtmlForDirectory = ( - directoryUrl, - directoryContentArray, - rootDirectoryUrl, -) => { +const generateHtmlForDirectory = (directoryContentItems) => { + let directoryUrl = directoryContentItems.firstExistingDirectoryUrl; + const rootDirectoryUrl = directoryContentItems.rootDirectoryUrl; directoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl); const htmlForDirectory = String(readFileSync(htmlFileUrlForDirectory)); @@ -195,23 +177,19 @@ const generateHtmlForDirectory = ( directoryUrl, directoryNav: () => generateDirectoryNav(directoryRelativeUrl, rootDirectoryUrl), - directoryContent: () => - generateDirectoryContent( - directoryContentArray, - directoryUrl, - rootDirectoryUrl, - ), + directoryContent: () => generateDirectoryContent(directoryContentItems), }; const html = replacePlaceholders(htmlForDirectory, replacers); return html; }; const generateHtmlForENOENT = ( url, - ancestorDirectoryContentArray, - ancestorDirectoryUrl, - rootDirectoryUrl, + directoryContentItems, directoryListingUrlMocks, ) => { + const ancestorDirectoryUrl = directoryContentItems.firstExistingDirectoryUrl; + const rootDirectoryUrl = directoryContentItems.rootDirectoryUrl; + const htmlFor404AndAncestorDir = String( readFileSync(html404AndAncestorDirFileUrl), ); @@ -230,11 +208,7 @@ const generateHtmlForENOENT = ( ancestorDirectoryNav: () => generateDirectoryNav(ancestorDirectoryRelativeUrl, rootDirectoryUrl), ancestorDirectoryContent: () => - generateDirectoryContent( - ancestorDirectoryContentArray, - ancestorDirectoryUrl, - rootDirectoryUrl, - ), + generateDirectoryContent(directoryContentItems), }; const html = replacePlaceholders(htmlFor404AndAncestorDir, replacers); return html; @@ -276,31 +250,93 @@ const generateDirectoryNav = (relativeUrl, rootDirectoryUrl) => { } return dirPartsHtml; }; -const generateDirectoryContent = ( - directoryContentArray, - directoryUrl, - rootDirectoryUrl, -) => { - if (directoryContentArray.length === 0) { - return `

Directory is empty

`; +const generateDirectoryContentItems = (directoryUrl, rootDirectoryUrl) => { + let firstExistingDirectoryUrl = new URL("./", directoryUrl); + while (!existsSync(firstExistingDirectoryUrl)) { + firstExistingDirectoryUrl = new URL("../", firstExistingDirectoryUrl); + if (!urlIsInsideOf(firstExistingDirectoryUrl, rootDirectoryUrl)) { + firstExistingDirectoryUrl = rootDirectoryUrl; + break; + } } - const sortedNames = []; + const directoryContentArray = readdirSync(firstExistingDirectoryUrl); + const fileUrls = []; for (const filename of directoryContentArray) { - const fileUrlObject = new URL(filename, directoryUrl); - if (lstatSync(fileUrlObject).isDirectory()) { - sortedNames.push(`${filename}/`); + const fileUrlObject = new URL(filename, firstExistingDirectoryUrl); + fileUrls.push(fileUrlObject); + } + package_workspaces: { + if (String(firstExistingDirectoryUrl) !== String(rootDirectoryUrl)) { + break package_workspaces; + } + const packageDirectoryUrl = lookupPackageDirectory(rootDirectoryUrl); + if (!packageDirectoryUrl) { + break package_workspaces; + } + if (String(packageDirectoryUrl) === String(rootDirectoryUrl)) { + break package_workspaces; + } + let packageContent; + try { + packageContent = JSON.parse( + readFileSync(new URL("package.json", packageDirectoryUrl), "utf8"), + ); + } catch { + break package_workspaces; + } + const { workspaces } = packageContent; + if (Array.isArray(workspaces)) { + for (const workspace of workspaces) { + const workspaceUrlObject = new URL(workspace, packageDirectoryUrl); + const workspaceUrl = workspaceUrlObject.href; + if (workspaceUrl.endsWith("*")) { + const directoryUrl = ensurePathnameTrailingSlash( + workspaceUrl.slice(0, -1), + ); + fileUrls.push(new URL(directoryUrl)); + } else { + const directoryUrl = ensurePathnameTrailingSlash(workspaceUrl); + fileUrls.push(new URL(directoryUrl)); + } + } + } + } + + const sortedUrls = []; + for (const fileUrl of fileUrls) { + if (lstatSync(fileUrl).isDirectory()) { + sortedUrls.push(new URL(`${fileUrl}/`)); } else { - sortedNames.push(filename); + sortedUrls.push(fileUrl); } } - sortedNames.sort(comparePathnames); - let html = `