Skip to content

Commit

Permalink
feat: valid if files containing exports (#606)
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi authored Nov 24, 2024
1 parent b1a2aaf commit a13e2b5
Show file tree
Hide file tree
Showing 29 changed files with 156 additions and 13 deletions.
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/bin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -340,6 +347,9 @@ async function main() {
// if (!error) help()
return exit(error as Error)
}
if ('cmd' in params) {
return
}
await run(params)
}

Expand Down
62 changes: 62 additions & 0 deletions src/lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type PackageMetadata = {
main?: string
bin?: string | Record<string, string>
module?: string
files?: string[]
type?: 'commonjs' | 'module'
dependencies?: Record<string, string>
peerDependencies?: Record<string, string>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
Original file line number Diff line number Diff line change
@@ -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 }) => {
Expand Down
3 changes: 3 additions & 0 deletions test/integration/lint/missing-files-exports/foo/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const foo = 'foo'

exports.foo = foo
16 changes: 16 additions & 0 deletions test/integration/lint/missing-files-exports/index.test.ts
Original file line number Diff line number Diff line change
@@ -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')
},
)
})
})
10 changes: 10 additions & 0 deletions test/integration/lint/missing-files-exports/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "missing-files-exports",
"exports": {
".": "./dist/index.js",
"./foo": "./foo/index.js"
},
"files": [
"dist"
]
}
1 change: 1 addition & 0 deletions test/integration/lint/missing-files-exports/src/foo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const foo = 'foo'
1 change: 1 addition & 0 deletions test/integration/lint/missing-files-exports/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const index = 'index'
3 changes: 3 additions & 0 deletions test/integration/lint/missing-files-main/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const index = 'index'

exports.index = index
16 changes: 16 additions & 0 deletions test/integration/lint/missing-files-main/index.test.ts
Original file line number Diff line number Diff line change
@@ -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')
},
)
})
})
7 changes: 7 additions & 0 deletions test/integration/lint/missing-files-main/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "missing-files-main",
"main": "index.js",
"files": [
"dist"
]
}
1 change: 1 addition & 0 deletions test/integration/lint/missing-files-main/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const index = 'index'
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
File renamed without changes.
File renamed without changes.

0 comments on commit a13e2b5

Please sign in to comment.