From a13e2b575da4136b0125ba9734b897273fabe120 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Sun, 24 Nov 2024 19:39:20 +0100 Subject: [PATCH] feat: valid if files containing exports (#606) --- package.json | 8 ++- pnpm-lock.yaml | 11 ++++ src/bin/index.ts | 10 +++ src/lint.ts | 62 +++++++++++++++++++ src/types.ts | 1 + .../cjs-pkg-esm-main-field/index.test.ts | 3 +- .../cjs-pkg-esm-main-field/package.json | 0 .../cjs-pkg-esm-main-field/src/index.js | 0 .../invalid-exports-cjs/index.test.ts | 9 +-- .../invalid-exports-cjs/package.json | 0 .../{ => lint}/invalid-exports-cjs/src/foo.js | 0 .../invalid-exports-cjs/src/index.js | 0 .../invalid-exports-esm/index.test.ts | 3 +- .../invalid-exports-esm/package.json | 0 .../{ => lint}/invalid-exports-esm/src/foo.js | 0 .../invalid-exports-esm/src/index.js | 0 .../lint/missing-files-exports/foo/index.js | 3 + .../lint/missing-files-exports/index.test.ts | 16 +++++ .../lint/missing-files-exports/package.json | 10 +++ .../lint/missing-files-exports/src/foo.js | 1 + .../lint/missing-files-exports/src/index.js | 1 + .../lint/missing-files-main/index.js | 3 + .../lint/missing-files-main/index.test.ts | 16 +++++ .../lint/missing-files-main/package.json | 7 +++ .../lint/missing-files-main/src/index.js | 1 + .../{ => lint}/single-entry/.gitignore | 0 .../{ => lint}/single-entry/index.test.ts | 4 +- .../{ => lint}/single-entry/package.json | 0 .../{ => lint}/single-entry/src/index.ts | 0 29 files changed, 156 insertions(+), 13 deletions(-) rename test/integration/{ => lint}/cjs-pkg-esm-main-field/index.test.ts (84%) rename test/integration/{ => lint}/cjs-pkg-esm-main-field/package.json (100%) rename test/integration/{ => lint}/cjs-pkg-esm-main-field/src/index.js (100%) rename test/integration/{ => lint}/invalid-exports-cjs/index.test.ts (78%) rename test/integration/{ => lint}/invalid-exports-cjs/package.json (100%) rename test/integration/{ => lint}/invalid-exports-cjs/src/foo.js (100%) rename test/integration/{ => lint}/invalid-exports-cjs/src/index.js (100%) rename test/integration/{ => lint}/invalid-exports-esm/index.test.ts (90%) rename test/integration/{ => lint}/invalid-exports-esm/package.json (100%) rename test/integration/{ => lint}/invalid-exports-esm/src/foo.js (100%) rename test/integration/{ => lint}/invalid-exports-esm/src/index.js (100%) create mode 100644 test/integration/lint/missing-files-exports/foo/index.js create mode 100644 test/integration/lint/missing-files-exports/index.test.ts create mode 100644 test/integration/lint/missing-files-exports/package.json create mode 100644 test/integration/lint/missing-files-exports/src/foo.js create mode 100644 test/integration/lint/missing-files-exports/src/index.js create mode 100644 test/integration/lint/missing-files-main/index.js create mode 100644 test/integration/lint/missing-files-main/index.test.ts create mode 100644 test/integration/lint/missing-files-main/package.json create mode 100644 test/integration/lint/missing-files-main/src/index.js rename test/integration/{ => lint}/single-entry/.gitignore (100%) rename test/integration/{ => lint}/single-entry/index.test.ts (91%) rename test/integration/{ => lint}/single-entry/package.json (100%) rename test/integration/{ => lint}/single-entry/src/index.ts (100%) diff --git a/package.json b/package.json index 709fd914..bc07e171 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,10 @@ "clean": "rm -rf ./dist", "typecheck": "tsc --noEmit && tsc -p test/tsconfig.json --noEmit", "prepublishOnly": "pnpm clean && pnpm build && chmod +x ./dist/bin/cli.js && pnpm test:ci", - "cli": "cross-env SWC_NODE_IGNORE_DYNAMIC=1 node -r @swc-node/register", - "ts-bunchee": "pnpm cli ./src/bin/index.ts", + "run-ts": "cross-env SWC_NODE_IGNORE_DYNAMIC=1 node -r @swc-node/register", + "ts-bunchee": "pnpm run-ts ./src/bin/index.ts", "build-dir": "pnpm ts-bunchee --cwd", - "build": "pnpm cli ./src/bin/index.ts --runtime node", + "build": "pnpm run-ts ./src/bin/index.ts --runtime node", "format": "prettier --write .", "prepare": "husky" }, @@ -63,6 +63,7 @@ "glob": "^11.0.0", "magic-string": "^0.30.11", "ora": "^8.0.1", + "picomatch": "^4.0.2", "pretty-bytes": "^5.6.0", "rollup": "^4.27.4", "rollup-plugin-dts": "^6.1.1", @@ -90,6 +91,7 @@ "@types/clean-css": "^4.2.11", "@types/jest": "29.0.0", "@types/node": "^22.9.3", + "@types/picomatch": "^3.0.1", "@types/yargs": "^17.0.33", "bunchee": "link:./", "cross-env": "^7.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d955efef..22d0317b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: ora: specifier: ^8.0.1 version: 8.0.1 + picomatch: + specifier: ^4.0.2 + version: 4.0.2 pretty-bytes: specifier: ^5.6.0 version: 5.6.0 @@ -87,6 +90,9 @@ importers: '@types/node': specifier: ^22.9.3 version: 22.9.3 + '@types/picomatch': + specifier: ^3.0.1 + version: 3.0.1 '@types/yargs': specifier: ^17.0.33 version: 17.0.33 @@ -775,6 +781,9 @@ packages: '@types/node@22.9.3': resolution: {integrity: sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==} + '@types/picomatch@3.0.1': + resolution: {integrity: sha512-1MRgzpzY0hOp9pW/kLRxeQhUWwil6gnrUYd3oEpeYBqp/FexhaCPv3F8LsYr47gtUU45fO2cm1dbwkSrHEo8Uw==} + '@types/prettier@2.7.0': resolution: {integrity: sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==} @@ -2689,6 +2698,8 @@ snapshots: dependencies: undici-types: 6.19.8 + '@types/picomatch@3.0.1': {} + '@types/prettier@2.7.0': {} '@types/resolve@1.20.2': {} diff --git a/src/bin/index.ts b/src/bin/index.ts index 7c8eeedd..c1c299d9 100644 --- a/src/bin/index.ts +++ b/src/bin/index.ts @@ -150,6 +150,13 @@ async function parseCliArgs(argv: string[]) { .showHelpOnFail(true) .parse() + const cmd = args._[0] + if (cmd === 'prepare' || cmd === 'lint') { + return { + cmd, + } + } + const source: string = args._[0] as string const parsedArgs: CliArgs = { source, @@ -340,6 +347,9 @@ async function main() { // if (!error) help() return exit(error as Error) } + if ('cmd' in params) { + return + } await run(params) } diff --git a/src/lint.ts b/src/lint.ts index 65eea6ef..3f617084 100644 --- a/src/lint.ts +++ b/src/lint.ts @@ -3,6 +3,7 @@ import { parseExports } from './exports' import { logger } from './logger' import { PackageMetadata } from './types' import { hasCjsExtension, isESModulePackage, isTypeFile } from './utils' +import picomatch from 'picomatch' type BadExportItem = { value: boolean @@ -19,6 +20,56 @@ function validateTypesFieldCondition(pair: [string, string]) { return false } +const isPathIncluded = (filesField: string[], filePath: string) => { + return filesField.some((pattern) => { + const normalizedPattern = path.normalize(pattern) + const matcher = picomatch(normalizedPattern) + return matcher(filePath) + }) +} + +function validateFilesField(packageJson: PackageMetadata) { + const state: { + definedField: boolean + missingFiles: string[] + } = { + definedField: false, + missingFiles: [], + } + const filesField = packageJson.files || [] + const exportsField = packageJson.exports || {} + + state.definedField = !!packageJson.files + + const resolveExportsPaths = (exports: any): string[] => { + const paths = [] + if (typeof exports === 'string') { + paths.push(exports) + } else if (typeof exports === 'object') { + for (const key in exports) { + paths.push(...resolveExportsPaths(exports[key])) + } + } + return paths + } + + const exportedPaths = resolveExportsPaths(exportsField).map((p) => + path.normalize(p), + ) + const commonFields = ['main', 'module', 'types', 'module-sync'] + for (const field of commonFields) { + if (field in packageJson) { + exportedPaths.push((packageJson as any)[field]) + } + } + + state.missingFiles = exportedPaths.filter((exportPath) => { + return !isPathIncluded(filesField, exportPath) + }) + + return state +} + export function lint(pkg: PackageMetadata) { const { name, main, exports } = pkg const isESM = isESModulePackage(pkg.type) @@ -144,6 +195,17 @@ export function lint(pkg: PackageMetadata) { } } + const fieldState = validateFilesField(pkg) + + if (!fieldState.definedField) { + logger.warn('Missing files field in package.json') + } else if (fieldState.missingFiles.length) { + logger.warn('Missing files in package.json') + fieldState.missingFiles.forEach((p) => { + logger.warn(` ${p}`) + }) + } + if (state.badMainExtension) { logger.warn( 'Cannot export `main` field with .mjs extension in CJS package, only .js extension is allowed', diff --git a/src/types.ts b/src/types.ts index 68f23a26..217011bb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -66,6 +66,7 @@ type PackageMetadata = { main?: string bin?: string | Record module?: string + files?: string[] type?: 'commonjs' | 'module' dependencies?: Record peerDependencies?: Record diff --git a/test/integration/cjs-pkg-esm-main-field/index.test.ts b/test/integration/lint/cjs-pkg-esm-main-field/index.test.ts similarity index 84% rename from test/integration/cjs-pkg-esm-main-field/index.test.ts rename to test/integration/lint/cjs-pkg-esm-main-field/index.test.ts index 0fb1b707..f2c75431 100644 --- a/test/integration/cjs-pkg-esm-main-field/index.test.ts +++ b/test/integration/lint/cjs-pkg-esm-main-field/index.test.ts @@ -1,9 +1,10 @@ -import { createIntegrationTest } from '../utils' +import { createIntegrationTest } from '../../utils' describe('integration cjs-pkg-esm-main-field', () => { it('should warn if main field with .mjs extension in CJS package', async () => { await createIntegrationTest( { + args: ['lint'], directory: __dirname, }, ({ stderr }) => { diff --git a/test/integration/cjs-pkg-esm-main-field/package.json b/test/integration/lint/cjs-pkg-esm-main-field/package.json similarity index 100% rename from test/integration/cjs-pkg-esm-main-field/package.json rename to test/integration/lint/cjs-pkg-esm-main-field/package.json diff --git a/test/integration/cjs-pkg-esm-main-field/src/index.js b/test/integration/lint/cjs-pkg-esm-main-field/src/index.js similarity index 100% rename from test/integration/cjs-pkg-esm-main-field/src/index.js rename to test/integration/lint/cjs-pkg-esm-main-field/src/index.js diff --git a/test/integration/invalid-exports-cjs/index.test.ts b/test/integration/lint/invalid-exports-cjs/index.test.ts similarity index 78% rename from test/integration/invalid-exports-cjs/index.test.ts rename to test/integration/lint/invalid-exports-cjs/index.test.ts index 4653a761..274bdb25 100644 --- a/test/integration/invalid-exports-cjs/index.test.ts +++ b/test/integration/lint/invalid-exports-cjs/index.test.ts @@ -1,16 +1,13 @@ -import { assertFilesContent, createIntegrationTest } from '../utils' +import { createIntegrationTest } from '../../utils' describe('integration invalid-exports-cjs', () => { it('should warn on invalid exports as CJS', async () => { await createIntegrationTest( { + args: ['lint'], directory: __dirname, }, - async ({ stderr, distDir }) => { - assertFilesContent(distDir, { - './index.esm.js': 'export { index }', - }) - + async ({ stderr }) => { expect(stderr).toContain('Missing package name') expect(stderr).toContain( 'Cannot export `require` field with .mjs extension in CJS package, only .cjs and .js extensions are allowed', diff --git a/test/integration/invalid-exports-cjs/package.json b/test/integration/lint/invalid-exports-cjs/package.json similarity index 100% rename from test/integration/invalid-exports-cjs/package.json rename to test/integration/lint/invalid-exports-cjs/package.json diff --git a/test/integration/invalid-exports-cjs/src/foo.js b/test/integration/lint/invalid-exports-cjs/src/foo.js similarity index 100% rename from test/integration/invalid-exports-cjs/src/foo.js rename to test/integration/lint/invalid-exports-cjs/src/foo.js diff --git a/test/integration/invalid-exports-cjs/src/index.js b/test/integration/lint/invalid-exports-cjs/src/index.js similarity index 100% rename from test/integration/invalid-exports-cjs/src/index.js rename to test/integration/lint/invalid-exports-cjs/src/index.js diff --git a/test/integration/invalid-exports-esm/index.test.ts b/test/integration/lint/invalid-exports-esm/index.test.ts similarity index 90% rename from test/integration/invalid-exports-esm/index.test.ts rename to test/integration/lint/invalid-exports-esm/index.test.ts index 91bb9bac..6721e318 100644 --- a/test/integration/invalid-exports-esm/index.test.ts +++ b/test/integration/lint/invalid-exports-esm/index.test.ts @@ -1,9 +1,10 @@ -import { createIntegrationTest } from '../utils' +import { createIntegrationTest } from '../../utils' describe('integration invalid-exports-esm', () => { it('should warn on invalid exports as ESM', async () => { await createIntegrationTest( { + args: ['lint'], directory: __dirname, }, async ({ stderr }) => { diff --git a/test/integration/invalid-exports-esm/package.json b/test/integration/lint/invalid-exports-esm/package.json similarity index 100% rename from test/integration/invalid-exports-esm/package.json rename to test/integration/lint/invalid-exports-esm/package.json diff --git a/test/integration/invalid-exports-esm/src/foo.js b/test/integration/lint/invalid-exports-esm/src/foo.js similarity index 100% rename from test/integration/invalid-exports-esm/src/foo.js rename to test/integration/lint/invalid-exports-esm/src/foo.js diff --git a/test/integration/invalid-exports-esm/src/index.js b/test/integration/lint/invalid-exports-esm/src/index.js similarity index 100% rename from test/integration/invalid-exports-esm/src/index.js rename to test/integration/lint/invalid-exports-esm/src/index.js diff --git a/test/integration/lint/missing-files-exports/foo/index.js b/test/integration/lint/missing-files-exports/foo/index.js new file mode 100644 index 00000000..3efd46d6 --- /dev/null +++ b/test/integration/lint/missing-files-exports/foo/index.js @@ -0,0 +1,3 @@ +const foo = 'foo' + +exports.foo = foo diff --git a/test/integration/lint/missing-files-exports/index.test.ts b/test/integration/lint/missing-files-exports/index.test.ts new file mode 100644 index 00000000..d202a37e --- /dev/null +++ b/test/integration/lint/missing-files-exports/index.test.ts @@ -0,0 +1,16 @@ +import { createIntegrationTest } from '../../utils' + +describe('integration - lint - missing-files-exports', () => { + it('should warn on missing files', async () => { + await createIntegrationTest( + { + args: ['lint'], + directory: __dirname, + }, + async ({ stderr }) => { + expect(stderr).toContain('Missing files in package.json') + expect(stderr).toContain('foo/index.js') + }, + ) + }) +}) diff --git a/test/integration/lint/missing-files-exports/package.json b/test/integration/lint/missing-files-exports/package.json new file mode 100644 index 00000000..9fe11e15 --- /dev/null +++ b/test/integration/lint/missing-files-exports/package.json @@ -0,0 +1,10 @@ +{ + "name": "missing-files-exports", + "exports": { + ".": "./dist/index.js", + "./foo": "./foo/index.js" + }, + "files": [ + "dist" + ] +} diff --git a/test/integration/lint/missing-files-exports/src/foo.js b/test/integration/lint/missing-files-exports/src/foo.js new file mode 100644 index 00000000..cb356468 --- /dev/null +++ b/test/integration/lint/missing-files-exports/src/foo.js @@ -0,0 +1 @@ +export const foo = 'foo' diff --git a/test/integration/lint/missing-files-exports/src/index.js b/test/integration/lint/missing-files-exports/src/index.js new file mode 100644 index 00000000..96896d71 --- /dev/null +++ b/test/integration/lint/missing-files-exports/src/index.js @@ -0,0 +1 @@ +export const index = 'index' diff --git a/test/integration/lint/missing-files-main/index.js b/test/integration/lint/missing-files-main/index.js new file mode 100644 index 00000000..afa9360c --- /dev/null +++ b/test/integration/lint/missing-files-main/index.js @@ -0,0 +1,3 @@ +const index = 'index' + +exports.index = index diff --git a/test/integration/lint/missing-files-main/index.test.ts b/test/integration/lint/missing-files-main/index.test.ts new file mode 100644 index 00000000..ae26caba --- /dev/null +++ b/test/integration/lint/missing-files-main/index.test.ts @@ -0,0 +1,16 @@ +import { createIntegrationTest } from '../../utils' + +describe('integration - lint - missing-files-main', () => { + it('should warn on missing files', async () => { + await createIntegrationTest( + { + args: ['lint'], + directory: __dirname, + }, + async ({ stderr }) => { + expect(stderr).toContain('Missing files in package.json') + expect(stderr).toContain('index.js') + }, + ) + }) +}) diff --git a/test/integration/lint/missing-files-main/package.json b/test/integration/lint/missing-files-main/package.json new file mode 100644 index 00000000..83b3ced5 --- /dev/null +++ b/test/integration/lint/missing-files-main/package.json @@ -0,0 +1,7 @@ +{ + "name": "missing-files-main", + "main": "index.js", + "files": [ + "dist" + ] +} diff --git a/test/integration/lint/missing-files-main/src/index.js b/test/integration/lint/missing-files-main/src/index.js new file mode 100644 index 00000000..96896d71 --- /dev/null +++ b/test/integration/lint/missing-files-main/src/index.js @@ -0,0 +1 @@ +export const index = 'index' diff --git a/test/integration/single-entry/.gitignore b/test/integration/lint/single-entry/.gitignore similarity index 100% rename from test/integration/single-entry/.gitignore rename to test/integration/lint/single-entry/.gitignore diff --git a/test/integration/single-entry/index.test.ts b/test/integration/lint/single-entry/index.test.ts similarity index 91% rename from test/integration/single-entry/index.test.ts rename to test/integration/lint/single-entry/index.test.ts index fd882f18..5bb00824 100644 --- a/test/integration/single-entry/index.test.ts +++ b/test/integration/lint/single-entry/index.test.ts @@ -2,8 +2,8 @@ import { assertContainFiles, assertFilesContent, stripANSIColor, -} from '../../testing-utils' -import { createIntegrationTest } from '../utils' +} from '../../../testing-utils' +import { createIntegrationTest } from '../../utils' describe('integration single-entry', () => { it('should warn on invalid exports as CJS', async () => { diff --git a/test/integration/single-entry/package.json b/test/integration/lint/single-entry/package.json similarity index 100% rename from test/integration/single-entry/package.json rename to test/integration/lint/single-entry/package.json diff --git a/test/integration/single-entry/src/index.ts b/test/integration/lint/single-entry/src/index.ts similarity index 100% rename from test/integration/single-entry/src/index.ts rename to test/integration/lint/single-entry/src/index.ts