+3
-3
| { | ||
| "name": "publint", | ||
| "version": "0.3.18", | ||
| "version": "0.3.19", | ||
| "description": "Lint packaging errors", | ||
@@ -48,5 +48,5 @@ "type": "module", | ||
| "@types/prompts": "^2.4.9", | ||
| "fs-fixture": "^2.11.0", | ||
| "fs-fixture": "^2.13.0", | ||
| "prompts": "^2.4.2", | ||
| "vitest": "^4.0.18" | ||
| "vitest": "^4.1.5" | ||
| }, | ||
@@ -53,0 +53,0 @@ "scripts": { |
+3
-1
@@ -203,3 +203,5 @@ #!/usr/bin/env node | ||
| cli.parse(process.argv) | ||
| if (!process.env.PUBLINT_INTERNAL_SKIP_CLI_RUN) { | ||
| cli.parse(process.argv) | ||
| } | ||
@@ -206,0 +208,0 @@ /** |
+4
-0
@@ -135,2 +135,6 @@ export type MessageType = 'suggestion' | 'warning' | 'error' | ||
| | BaseMessage<'CJS_WITH_ESMODULE_DEFAULT_EXPORT', { filePath?: string }> | ||
| | BaseMessage< | ||
| 'NESTED_PACKAGE_JSON_FIELD_IGNORED', | ||
| { field: 'exports' | 'imports'; filePath: string } | ||
| > | ||
@@ -137,0 +141,0 @@ export interface PackFile { |
+62
-2
@@ -52,5 +52,4 @@ import { | ||
| /** | ||
| * Includes internal _include that used to filter paths that is packed. | ||
| * Includes internal _packedFiles that used to filter paths that is packed. | ||
| * Mainly for node.js local usage only. So that we lint files that are packed only. | ||
| * Currently only used if pkg has no `exports` | ||
| * @typedef {Omit<Required<import('../index.d.ts').Options>, 'pack'> & { | ||
@@ -433,2 +432,9 @@ * vfs: Vfs, | ||
| // Check if any nested package.json files have "exports" or "imports" fields, which | ||
| // Node.js ignores outside the package root (see https://github.com/nodejs/node/issues/58827). | ||
| // Some bundlers may still read them which can lead to inconsistent resolution. | ||
| promiseQueue.push(async () => { | ||
| await crawlNestedPackageJson(pkgDir) | ||
| }) | ||
| await promiseQueue.wait() | ||
@@ -1220,2 +1226,56 @@ | ||
| /** | ||
| * @param {string} dir | ||
| */ | ||
| async function crawlNestedPackageJson(dir) { | ||
| const nestedPackageJsonFields = /** @type {const} */ (['exports', 'imports']) | ||
| let items | ||
| try { | ||
| items = await vfs.readDir(dir) | ||
| } catch { | ||
| return | ||
| } | ||
| for (const item of items) { | ||
| if (item === 'node_modules') continue | ||
| const itemPath = vfs.pathJoin(dir, item) | ||
| if (await vfs.isPathDir(itemPath)) { | ||
| // If packed files are known, skip directories that don't contain | ||
| // any published files to reduce unnecessary traversal. | ||
| if (_packedFiles && !_packedFiles.some((f) => isPathNestedWithin(f, itemPath))) { | ||
| continue | ||
| } | ||
| await crawlNestedPackageJson(itemPath) | ||
| continue | ||
| } | ||
| if (item !== 'package.json' || itemPath === rootPkgPath) continue | ||
| if (_packedFiles && !_packedFiles.includes(itemPath)) continue | ||
| let nestedPkg | ||
| try { | ||
| const content = await vfs.readFile(itemPath) | ||
| nestedPkg = JSON.parse(content) | ||
| } catch { | ||
| continue | ||
| } | ||
| const filePath = './' + vfs.pathRelative(pkgDir, itemPath) | ||
| for (const field of nestedPackageJsonFields) { | ||
| if (nestedPkg[field] != null) { | ||
| messages.push({ | ||
| code: 'NESTED_PACKAGE_JSON_FIELD_IGNORED', | ||
| args: { field, filePath }, | ||
| path: ['name'], | ||
| type: 'warning', | ||
| }) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * @param {string} childPath | ||
| * @param {string} dirPath | ||
| */ | ||
| function isPathNestedWithin(childPath, dirPath) { | ||
| return childPath.startsWith(dirPath + '/') || childPath.startsWith(dirPath + '\\') | ||
| } | ||
| /** | ||
| * @param {any} binValue | ||
@@ -1222,0 +1282,0 @@ * @param {string[]} currentPath |
@@ -245,2 +245,5 @@ import picocolors from 'picocolors' | ||
| } | ||
| case 'NESTED_PACKAGE_JSON_FIELD_IGNORED': { | ||
| return `${h.bold(m.args.filePath)} has an ${h.bold(`"${m.args.field}"`)} field, but it is ignored by Node.js. The field only works in root ${h.bold('package.json')} files and not nested ones. Some bundlers may still pick them up, leading to inconsistent resolution. Consider removing it.` | ||
| } | ||
| default: | ||
@@ -247,0 +250,0 @@ return |
@@ -24,5 +24,13 @@ /** | ||
| path = path.endsWith('/') ? path : path + '/' | ||
| return files | ||
| .filter((file) => file.name.startsWith(path) && file.name !== path) | ||
| .map((file) => file.name.slice(path.length)) | ||
| /** @type {string[]} */ | ||
| const items = [] | ||
| for (const file of files) { | ||
| if (file.name.startsWith(path) && file.name !== path) { | ||
| const item = file.name.slice(path.length).replace(/\/.*$/, '') | ||
| if (!items.includes(item)) { | ||
| items.push(item) | ||
| } | ||
| } | ||
| } | ||
| return items | ||
| }, | ||
@@ -29,0 +37,0 @@ readFile: async (path) => { |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
111720
2.45%2832
2.68%9
12.5%