From a7bb2ab8affe213caf325b9282035591e2abe0e8 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Wed, 27 Nov 2024 12:46:36 -0500 Subject: [PATCH] latest upstream patches --- patches/@greenwood__cli.patch | 320 +++++++++++++++++++--------------- pnpm-lock.yaml | 18 +- 2 files changed, 191 insertions(+), 147 deletions(-) diff --git a/patches/@greenwood__cli.patch b/patches/@greenwood__cli.patch index f82a512..e3eaf65 100644 --- a/patches/@greenwood__cli.patch +++ b/patches/@greenwood__cli.patch @@ -1,63 +1,19 @@ -diff --git a/src/lib/node-modules-utils.js b/src/lib/node-modules-utils.js -index 05226f66bf42fc0a07588d373b14932cbeb9ff7f..a86924565e964815769e1fc788bf5c6cb6454083 100644 ---- a/src/lib/node-modules-utils.js -+++ b/src/lib/node-modules-utils.js -@@ -29,6 +29,28 @@ async function getNodeModulesLocationForPackage(packageName) { - return nodeModulesUrl; - } - -+function resolveBareSpecifier(specifier) { -+ let resolvedPath; -+ -+ // sometimes a package.json has no main field :/ -+ // https://unpkg.com/browse/@types/trusted-types@2.0.7/package.json -+ try { -+ resolvedPath = import.meta.resolve(specifier); -+ } catch (e) { -+ // TODO console.log(`WARNING: unable to resolve specifier \`${specifier}\``); -+ } -+ -+ return resolvedPath; -+} -+ -+function resolveRootForSpecifier(specifier) { -+ const resolved = resolveBareSpecifier(specifier); -+ -+ if (resolved) { -+ return `${resolved.split(specifier)[0]}${specifier}/`; -+ } -+} -+ - // extract the package name from a URL like /node_modules///index.js - function getPackageNameFromUrl(url) { - const packagePathPieces = url.split('node_modules/')[1].split('/'); // double split to handle node_modules within nested paths -@@ -58,5 +80,7 @@ async function getPackageJson({ userWorkspace, projectDirectory }) { - export { - getNodeModulesLocationForPackage, - getPackageJson, -- getPackageNameFromUrl -+ getPackageNameFromUrl, -+ resolveBareSpecifier, -+ resolveRootForSpecifier - }; -\ No newline at end of file diff --git a/src/lib/walker-package-ranger.js b/src/lib/walker-package-ranger.js -index a92bb3d29496a453f1714e5cd32d1800c94844be..68fd30e718f5493b426b88bc8e6aa13753c2c2c0 100644 +index a92bb3d29496a453f1714e5cd32d1800c94844be..ded1034c929edc462ab547dc9b16ca8cf26cf5a7 100644 --- a/src/lib/walker-package-ranger.js +++ b/src/lib/walker-package-ranger.js -@@ -1,217 +1,120 @@ +@@ -1,220 +1,216 @@ -/* eslint-disable max-depth,complexity */ -import * as acorn from 'acorn'; --import fs from 'fs'; + import fs from 'fs'; -import { getNodeModulesLocationForPackage } from './node-modules-utils.js'; -import path from 'path'; -import * as walk from 'acorn-walk'; -+import fs from 'fs/promises'; -+import { resolveBareSpecifier } from './node-modules-utils.js'; +/* eslint-disable max-depth,complexity */ ++// priority if from L -> R ++const SUPPORTED_EXPORT_CONDITIONS = ['import', 'module-sync', 'default']; const importMap = {}; -+const extensionFilters = ['map', 'd.ts']; -const updateImportMap = (entry, entryPath) => { +function updateImportMap(key, value) { @@ -66,34 +22,62 @@ index a92bb3d29496a453f1714e5cd32d1800c94844be..68fd30e718f5493b426b88bc8e6aa137 - if (path.extname(entryPath) === '') { - entryPath = `${entryPath}.js`; -- } -+// https://nodejs.org/api/packages.html#subpath-patterns -+async function walkExportPatterns(dependency, exp, resolvedRoot) { -+ if (exp.endsWith('*')) { -+ const dir = new URL(exp.replace('*', ''), resolvedRoot); -+ const files = await fs.readdir(dir); -+ // console.log({ dependency, exp, dir }); ++// wrapper around import.meta.resolve to provide graceful error handling / logging ++// as sometimes a package.json has no main field :/ ++// https://unpkg.com/browse/@types/trusted-types@2.0.7/package.json ++// https://github.com/nodejs/node/issues/49445#issuecomment-2484334036 ++function resolveBareSpecifier(specifier) { ++ let resolvedPath; ++ ++ try { ++ resolvedPath = import.meta.resolve(specifier); ++ } catch (e) { ++ // console.log({ e }); ++ // TODO console.log(`WARNING: unable to resolve specifier \`${specifier}\``); + } - // handle WIn v Unix-style path separators and force to / - importMap[entry.replace(/\\/g, '/')] = entryPath.replace(/\\/g, '/'); -}; -+ files -+ .filter((file) => { -+ let shouldNotFilter = true; ++ return resolvedPath; ++} -// handle ESM paths that have varying levels of nesting, e.g. export * from '../../something.js' -// https://github.com/ProjectEvergreen/greenwood/issues/820 -async function resolveRelativeSpecifier(specifier, modulePath, dependency) { - const absoluteNodeModulesLocation = await getNodeModulesLocationForPackage(dependency); -- ++/* ++ * Find root directory for a package based on result of import.meta.resolve, since dependencyName could show in multiple places ++ * until this becomes a thing - https://github.com/nodejs/node/issues/49445 ++ * { ++ * dependencyName: 'lit-html', ++ * resolved: 'file:///path/to/project/greenwood-lit-ssr/node_modules/.pnpm/lit-html@3.2.1/node_modules/lit-html/node/lit-html.js', ++ * root: 'file:///path/to/project/greenwood-lit-ssr/node_modules/.pnpm/lit-html@3.2.1/node_modules/lit-html/package.json' ++ * } ++ */ ++function derivePackageRoot(dependencyName, resolved) { ++ const root = resolved.slice(0, resolved.lastIndexOf(`/node_modules/${dependencyName}/`)); ++ const derived = `${root}/node_modules/${dependencyName}/`; ++ ++ return derived; ++} ++ ++// Helper function to convert export patterns to a regex (thanks ChatGPT :D) ++function globToRegex(pattern) { ++ // Escape special regex characters ++ pattern = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&'); ++ ++ // Replace glob `*` with regex `[^/]*` (any characters except slashes) ++ pattern = pattern.replace(/\*/g, '[^/]*'); + - // handle WIn v Unix-style path separators and force to / - return `${dependency}${path.join(path.dirname(modulePath), specifier).replace(/\\/g, '/').replace(absoluteNodeModulesLocation.replace(/\\/g, '/', ''), '')}`; --} -+ extensionFilters.forEach((extFilter) => { -+ if (file.endsWith(extFilter)) { -+ shouldNotFilter = false; -+ } -+ }); ++ // Replace glob `**` with regex `(.*)` (zero or more directories or files) ++ // pattern = pattern.replace(/\*\*/g, '(.*)'); ++ ++ // Return the final regex ++ return new RegExp('^' + pattern + '$'); + } -async function getPackageEntryPath(packageJson) { - let entry = packageJson.exports @@ -107,17 +91,24 @@ index a92bb3d29496a453f1714e5cd32d1800c94844be..68fd30e718f5493b426b88bc8e6aa137 - // use .mjs version if it exists, for packages like redux - if (!Array.isArray(entry) && fs.existsSync(`${await getNodeModulesLocationForPackage(packageJson.name)}/${entry.replace('.js', '.mjs')}`)) { - entry = entry.replace('.js', '.mjs'); -+ return shouldNotFilter; -+ }) -+ .forEach((file) => { -+ updateImportMap(`${dependency}/${exp.replace('/*', '')}/${file}`, `/node_modules/${dependency}/${exp.replace('/*', '')}/${file}`); -+ }); -+ } else { -+ // TODO -+ // "./feature/*": "./feature/*.js", ++// convert path to its lowest common root ++// e.g. ./img/path/*/index.js -> /img/path ++// https://unpkg.com/browse/@uswds/uswds@3.10.0/package.json ++function patternRoot(pattern) { ++ const segments = pattern.split('/').filter((segment) => segment !== '.'); ++ let root = ''; ++ ++ for (const segment of segments) { ++ // is there a better way to fuzzy test for a filename other than checking for a dot? ++ if (segment.indexOf('*') < 0 && segment.indexOf('.') < 0) { ++ root += `/${segment}`; ++ } else { ++ break; ++ } } -- + - return entry; ++ return root; } -async function walkModule(modulePath, dependency) { @@ -137,8 +128,40 @@ index a92bb3d29496a453f1714e5cd32d1800c94844be..68fd30e718f5493b426b88bc8e6aa137 - if (!importMap[sourceValue]) { - updateImportMap(sourceValue, `/node_modules/${sourceValue}`); - } -+async function walkPackageForExports(dependency, packageJson, resolvedRoot) { -+ const { exports, module, main } = packageJson; ++/* ++ * https://nodejs.org/api/packages.html#subpath-patterns ++ * ++ * Examples ++ * "./icons/*": "./icons/*" - https://unpkg.com/browse/@spectrum-web-components/icons-workflow@1.0.1/package.json ++ * "./components/*": "./dist/components/*.js" - https://unpkg.com/browse/@uswds/web-components@0.0.1-alpha/package.json ++ * "./src/components/*": "./src/components/* /index.js - https://unpkg.com/browse/@uswds/web-components@0.0.1-alpha/package.json ++ */ ++async function walkExportPatterns(dependency, sub, subValue, resolvedRoot) { ++ // find the "deepest" segment we can start from ++ // to avoid unnecessary file scanning / crawling ++ const rootSubValueOffset = patternRoot(subValue); ++ ++ // ideally we can use fs.glob when it comes out of experimental ++ // https://nodejs.org/docs/latest-v22.x/api/fs.html#fspromisesglobpattern-options ++ function walkDirectoryForExportPatterns(directoryUrl) { ++ const filesInDir = fs.readdirSync(directoryUrl); ++ ++ filesInDir.forEach(file => { ++ const filePathUrl = new URL(`./${file}`, directoryUrl); ++ const stat = fs.statSync(filePathUrl); ++ const pattern = `${resolvedRoot}${subValue.replace('./', '')}`; ++ const regexPattern = globToRegex(pattern); ++ ++ if (stat.isDirectory()) { ++ walkDirectoryForExportPatterns(new URL(`./${file}/`, directoryUrl)); ++ } else if (regexPattern.test(filePathUrl.href)) { ++ const rootSubOffset = patternRoot(sub); ++ const relativePath = filePathUrl.href.replace(resolvedRoot, ''); ++ ++ updateImportMap(`${dependency}${rootSubOffset}/${file}`, `/node_modules/${dependency}/${relativePath}`); ++ } ++ }); ++ } - await walkPackageJson(path.join(absoluteNodeModulesLocation, 'package.json')); - } else if (isBarePath) { @@ -148,14 +171,51 @@ index a92bb3d29496a453f1714e5cd32d1800c94844be..68fd30e718f5493b426b88bc8e6aa137 - sourceValue = !hasExtension - ? `${sourceValue}.js` - : sourceValue; -+ console.log('walkPackageForExports', { dependency, exports, module, main }); ++ walkDirectoryForExportPatterns(new URL(`.${rootSubValueOffset}/`, resolvedRoot)); ++} - if (fs.existsSync(path.join(absoluteNodeModulesLocation, sourceValue))) { - const entry = `/node_modules/${await resolveRelativeSpecifier(sourceValue, modulePath, dependency)}`; - await walkModule(path.join(absoluteNodeModulesLocation, sourceValue), dependency); -- ++function trackExportConditions(dependency, exports, sub, condition) { ++ if (typeof exports[sub] === 'object') { ++ // also check for nested conditions of conditions, default to default for now ++ // https://unpkg.com/browse/@floating-ui/dom@1.6.12/package.json ++ if (sub === '.') { ++ updateImportMap(dependency, `/node_modules/${dependency}/${exports[sub][condition].default ?? exports[sub][condition]}`); ++ } else { ++ updateImportMap(`${dependency}/${sub}`, `/node_modules/${dependency}/${exports[sub][condition].default ?? exports[sub][condition]}`); ++ } ++ } else { ++ // https://unpkg.com/browse/redux@5.0.1/package.json ++ updateImportMap(dependency, `/node_modules/${dependency}/${exports[sub][condition]}`); ++ } ++} + - updateImportMap(path.join(dependency, sourceValue), entry); -- } ++// https://nodejs.org/api/packages.html#conditional-exports ++async function walkPackageForExports(dependency, packageJson, resolvedRoot) { ++ const { exports, module, main } = packageJson; ++ ++ // favor exports over main / module ++ if (exports) { ++ for (const sub in exports) { ++ /* ++ * test for conditional subpath exports ++ * 1. import ++ * 2. module-sync ++ * 3. default ++ */ ++ if (typeof exports[sub] === 'object') { ++ let matched = false; ++ ++ for (const condition of SUPPORTED_EXPORT_CONDITIONS) { ++ if (exports[sub][condition]) { ++ matched = true; ++ trackExportConditions(dependency, exports, sub, condition); ++ break; ++ } + } - } - }, - async ExportNamedDeclaration(node) { @@ -165,29 +225,14 @@ index a92bb3d29496a453f1714e5cd32d1800c94844be..68fd30e718f5493b426b88bc8e6aa137 - // handle relative specifier - if (sourceValue.indexOf('.') === 0) { - const entry = `/node_modules/${await resolveRelativeSpecifier(sourceValue, modulePath, dependency)}`; -- + - updateImportMap(path.join(dependency, sourceValue), entry); -+ if (exports) { -+ for (const exp in exports) { -+ if (typeof exports[exp] === 'object') { -+ if (exports[exp].import) { -+ if (typeof exports[exp].import === 'object') { -+ if (exp === '.') { -+ updateImportMap(dependency, `/node_modules/${dependency}/${exports[exp].import.default ?? exports[exp].import }`); -+ } else { -+ updateImportMap(`${dependency}/${exp}`, `/node_modules/${dependency}/${exports[exp].import.default ?? exports[exp].import}`); -+ } -+ } -+ } else if (exports[exp].default) { -+ if (exp === '.') { -+ updateImportMap(dependency, `/node_modules/${dependency}/${exports[exp].default}`); -+ } else { -+ updateImportMap(`${dependency}/${exp}`, `/node_modules/${dependency}/${exports[exp].default}`); -+ } - } else { +- } else { - // handle bare specifier - updateImportMap(sourceValue, `/node_modules/${sourceValue}`); ++ if (!matched) { + // TODO what to do here? what else is there besides default? ++ // console.log(`unsupported condition \`${exports[sub]}\` for dependency => \`${dependency}\``); } - } - }, @@ -200,19 +245,22 @@ index a92bb3d29496a453f1714e5cd32d1800c94844be..68fd30e718f5493b426b88bc8e6aa137 - - updateImportMap(path.join(dependency, sourceValue), entry); + } else { -+ if (exp === '.') { -+ updateImportMap(dependency, `/node_modules/${dependency}/${exports[exp]}`); -+ } else if (exp.indexOf('*') >= 0) { -+ await walkExportPatterns(dependency, exp, resolvedRoot); ++ // handle (unconditional) subpath exports ++ if (sub === '.') { ++ updateImportMap(dependency, `/node_modules/${dependency}/${exports[sub]}`); ++ } else if (sub.indexOf('*') >= 0) { ++ await walkExportPatterns(dependency, sub, exports[sub], resolvedRoot); } else { - updateImportMap(sourceValue, `/node_modules/${sourceValue}`); -+ updateImportMap(`${dependency}/${exp}`, `/node_modules/${dependency}/${exp}`); ++ updateImportMap(`${dependency}/${sub}`, `/node_modules/${dependency}/${sub}`); } } } - }); + } else if (module || main) { + updateImportMap(dependency, `/node_modules/${dependency}/${module ?? main}`); ++ } else { ++ // TODO warn about no exports found + } } @@ -282,38 +330,15 @@ index a92bb3d29496a453f1714e5cd32d1800c94844be..68fd30e718f5493b426b88bc8e6aa137 - // use the dependency itself as an entry in the importMap - if (entry === '.') { - updateImportMap(dependency, `/node_modules/${path.join(dependency, packageExport)}`); -+ console.log({ packageJson }); -+ try { -+ for (const dependency of Object.keys(packageJson.dependencies || {})) { -+ const resolved = resolveBareSpecifier(dependency); -+ -+ console.log({ dependency, resolved }); -+ if (resolved) { -+ console.log(resolved.split(`/node_modules/${dependency}/`)[0]); -+ const resolvedRoot = new URL(`./node_modules/${dependency}/`, `${resolved.split(`/node_modules/${dependency}/`)[0]}/`); -+ console.log({ resolvedRoot }) -+ const resolvedPackageJson = (await import(new URL('./package.json', resolvedRoot), { with: { type: 'json' } })).default; -+ -+ console.log({ resolvedPackageJson }); -+ walkPackageForExports(dependency, resolvedPackageJson, resolvedRoot); -+ -+ if (resolvedPackageJson.dependencies) { -+ for (const dependency in resolvedPackageJson.dependencies) { -+ const resolved = resolveBareSpecifier(dependency); -+ -+ if (resolved) { -+ const resolvedRoot = new URL(`./node_modules/${dependency}/`, `${resolved.split(`/node_modules/${dependency}/`)[0]}/`); -+ const resolvedPackageJson = (await import(new URL('./package.json', resolvedRoot), { with: { type: 'json' } })).default; -+ -+ walkPackageForExports(dependency, resolvedPackageJson, resolvedRoot); -+ -+ await walkPackageJson(resolvedPackageJson); - } +- } - } else if (exportMapEntry.endsWith && (exportMapEntry.endsWith('.js') || exportMapEntry.endsWith('.mjs')) && exportMapEntry.indexOf('*') < 0) { - // is probably a file, so _not_ an export array, package.json, or wildcard export - packageExport = exportMapEntry; - } -- +- } ++ try { ++ for (const dependency of Object.keys(packageJson.dependencies || {})) { ++ const resolved = resolveBareSpecifier(dependency); + - if (packageExport) { - const packageExportLocation = path.resolve(absoluteNodeModulesLocation, packageExport); - @@ -330,17 +355,33 @@ index a92bb3d29496a453f1714e5cd32d1800c94844be..68fd30e718f5493b426b88bc8e6aa137 - } - } - } -- ++ if (resolved) { ++ const resolvedRoot = derivePackageRoot(dependency, resolved); ++ const resolvedPackageJson = (await import(new URL('./package.json', resolvedRoot), { with: { type: 'json' } })).default; + - await walkPackageJson(dependencyPackageJson); - } else { - const packageEntryPointPath = path.join(absoluteNodeModulesLocation, entry); -- ++ walkPackageForExports(dependency, resolvedPackageJson, resolvedRoot); + - // sometimes a main file is actually just an empty string... :/ - if (fs.existsSync(packageEntryPointPath)) { - updateImportMap(dependency, `/node_modules/${path.join(dependency, entry)}`); -- ++ if (resolvedPackageJson.dependencies) { ++ for (const dependency in resolvedPackageJson.dependencies) { ++ const resolved = resolveBareSpecifier(dependency); + - await walkModule(packageEntryPointPath, dependency); - await walkPackageJson(dependencyPackageJson); ++ if (resolved) { ++ const resolvedRoot = derivePackageRoot(dependency, resolved); ++ const resolvedPackageJson = (await import(new URL('./package.json', resolvedRoot), { with: { type: 'json' } })).default; ++ ++ walkPackageForExports(dependency, resolvedPackageJson, resolvedRoot); ++ ++ await walkPackageJson(resolvedPackageJson); ++ } ++ } } } } @@ -348,11 +389,14 @@ index a92bb3d29496a453f1714e5cd32d1800c94844be..68fd30e718f5493b426b88bc8e6aa137 + console.error('Error building up import map', e); } -+ console.log({ importMap }); return importMap; } -@@ -245,7 +148,6 @@ function mergeImportMap(html = '', map = {}, shouldShim = false) { ++// could probably go somewhere else, in a util? + function mergeImportMap(html = '', map = {}, shouldShim = false) { + const importMapType = shouldShim ? 'importmap-shim' : 'importmap'; + const hasImportMap = html.indexOf(`script type="${importMapType}"`) > 0; +@@ -245,7 +241,6 @@ function mergeImportMap(html = '', map = {}, shouldShim = false) { } export { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86f9569..1bfca32 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: patchedDependencies: '@greenwood/cli': - hash: mkksy4qd3fdejrr4ph55fkmyf4 + hash: zj6lw53t3c4qptjjlacfu6c2hu path: patches/@greenwood__cli.patch importers: @@ -19,13 +19,13 @@ importers: devDependencies: '@greenwood/cli': specifier: ~0.30.0 - version: 0.30.2(patch_hash=mkksy4qd3fdejrr4ph55fkmyf4) + version: 0.30.2(patch_hash=zj6lw53t3c4qptjjlacfu6c2hu) '@greenwood/plugin-adapter-vercel': specifier: ~0.30.0 - version: 0.30.2(@greenwood/cli@0.30.2(patch_hash=mkksy4qd3fdejrr4ph55fkmyf4)) + version: 0.30.2(@greenwood/cli@0.30.2(patch_hash=zj6lw53t3c4qptjjlacfu6c2hu)) '@greenwood/plugin-renderer-lit': specifier: ~0.30.0 - version: 0.30.2(@greenwood/cli@0.30.2(patch_hash=mkksy4qd3fdejrr4ph55fkmyf4))(lit@3.2.1) + version: 0.30.2(@greenwood/cli@0.30.2(patch_hash=zj6lw53t3c4qptjjlacfu6c2hu))(lit@3.2.1) rimraf: specifier: ^5.0.0 version: 5.0.10 @@ -1346,7 +1346,7 @@ packages: snapshots: - '@greenwood/cli@0.30.2(patch_hash=mkksy4qd3fdejrr4ph55fkmyf4)': + '@greenwood/cli@0.30.2(patch_hash=zj6lw53t3c4qptjjlacfu6c2hu)': dependencies: '@rollup/plugin-commonjs': 25.0.8(rollup@3.29.5) '@rollup/plugin-node-resolve': 15.3.0(rollup@3.29.5) @@ -1377,13 +1377,13 @@ snapshots: - supports-color - utf-8-validate - '@greenwood/plugin-adapter-vercel@0.30.2(@greenwood/cli@0.30.2(patch_hash=mkksy4qd3fdejrr4ph55fkmyf4))': + '@greenwood/plugin-adapter-vercel@0.30.2(@greenwood/cli@0.30.2(patch_hash=zj6lw53t3c4qptjjlacfu6c2hu))': dependencies: - '@greenwood/cli': 0.30.2(patch_hash=mkksy4qd3fdejrr4ph55fkmyf4) + '@greenwood/cli': 0.30.2(patch_hash=zj6lw53t3c4qptjjlacfu6c2hu) - '@greenwood/plugin-renderer-lit@0.30.2(@greenwood/cli@0.30.2(patch_hash=mkksy4qd3fdejrr4ph55fkmyf4))(lit@3.2.1)': + '@greenwood/plugin-renderer-lit@0.30.2(@greenwood/cli@0.30.2(patch_hash=zj6lw53t3c4qptjjlacfu6c2hu))(lit@3.2.1)': dependencies: - '@greenwood/cli': 0.30.2(patch_hash=mkksy4qd3fdejrr4ph55fkmyf4) + '@greenwood/cli': 0.30.2(patch_hash=zj6lw53t3c4qptjjlacfu6c2hu) '@lit-labs/ssr': 3.2.2 '@lit-labs/ssr-client': 1.1.7 lit: 3.2.1