Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for package.y[a]ml #1344

Merged
merged 4 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/early-zoos-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'lint-staged': minor
---

Add support for loading configuration from `package.yaml` and `package.yml` files, supported by `pnpm`.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ Options:

_Lint-staged_ can be configured in many ways:

- `lint-staged` object in your `package.json`
- `lint-staged` object in your `package.json`, or [`package.yaml`](https://github.com/pnpm/pnpm/pull/1799)
- `.lintstagedrc` file in JSON or YML format, or you can be explicit with the file extension:
- `.lintstagedrc.json`
- `.lintstagedrc.yaml`
Expand Down
34 changes: 27 additions & 7 deletions lib/loadConfig.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/** @typedef {import('./index').Logger} Logger */

import path from 'node:path'

import debug from 'debug'
import { lilconfig } from 'lilconfig'
import YAML from 'yaml'
Expand All @@ -9,14 +11,19 @@ import { resolveConfig } from './resolveConfig.js'

const debugLog = debug('lint-staged:loadConfig')

const PACKAGE_JSON = 'package.json'
const CONFIG_NAME = 'lint-staged'

const PACKAGE_JSON_FILE = 'package.json'

const PACKAGE_YAML_FILES = ['package.yaml', 'package.yml']

/**
* The list of files `lint-staged` will read configuration
* from, in the declared order.
*/
export const searchPlaces = [
PACKAGE_JSON,
PACKAGE_JSON_FILE,
...PACKAGE_YAML_FILES,
'.lintstagedrc',
'.lintstagedrc.json',
'.lintstagedrc.yaml',
Expand All @@ -29,20 +36,33 @@ export const searchPlaces = [
'lint-staged.config.cjs',
]

const jsonParse = (path, content) => {
const jsonParse = (filePath, content) => {
try {
return JSON.parse(content)
} catch (error) {
if (path.endsWith(PACKAGE_JSON)) {
debugLog('Ignoring invalid package file `%s` with content:\n%s', path, content)
if (path.basename(filePath) === PACKAGE_JSON_FILE) {
debugLog('Ignoring invalid package file `%s` with content:\n%s', filePath, content)
return undefined
}

throw error
}
}

const yamlParse = (path, content) => YAML.parse(content)
const yamlParse = (filePath, content) => {
const isPackageFile = PACKAGE_YAML_FILES.includes(path.basename(filePath))
try {
const yaml = YAML.parse(content)
return isPackageFile ? yaml[CONFIG_NAME] : yaml
} catch (error) {
if (isPackageFile) {
debugLog('Ignoring invalid package file `%s` with content:\n%s', filePath, content)
return undefined
}

throw error
}
}

/**
* `lilconfig` doesn't support yaml files by default,
Expand All @@ -60,7 +80,7 @@ const loaders = {
noExt: yamlParse,
}

const explorer = lilconfig('lint-staged', { searchPlaces, loaders })
const explorer = lilconfig(CONFIG_NAME, { searchPlaces, loaders })

/**
* @param {object} options
Expand Down
2 changes: 2 additions & 0 deletions test/unit/__mocks__/package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
lint-staged:
"*": mytask
84 changes: 84 additions & 0 deletions test/unit/loadConfig.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ describe('loadConfig', () => {
`)
})

it('should return null config when YAML config file is invalid', async () => {
expect.assertions(1)

const configFile = path.join(__dirname, '__mocks__', 'lint-staged.yml')

await fs.writeFile(configFile, '{')

const { config } = await loadConfig({ configPath: configFile }, logger)

expect(config).toBeUndefined()

await fs.rm(configFile)
})

it('should load CommonJS config file from absolute path', async () => {
expect.assertions(1)

Expand Down Expand Up @@ -193,6 +207,30 @@ describe('loadConfig', () => {
await fs.rm(configFile)
})

it('should read config from package.json', async () => {
expect.assertions(1)

const configFile = path.join(__dirname, '__mocks__', 'package.json')

await fs.writeFile(
configFile,
JSON.stringify({
'lint-staged': {
'*': 'mytask',
},
})
)

const { config } = await loadConfig({ configPath: configFile }, logger)

expect(config).toMatchInlineSnapshot(`
{
"*": "mytask",
}
`)
await fs.rm(configFile)
})

it('should return null config when package.json file is invalid', async () => {
expect.assertions(1)

Expand All @@ -206,4 +244,50 @@ describe('loadConfig', () => {

await fs.rm(configFile)
})

it('should read "lint-staged" key from package.yaml', async () => {
expect.assertions(1)

const configFile = path.join(__dirname, '__mocks__', 'package.yaml')

await fs.writeFile(configFile, 'lint-staged:\n "*": mytask')

const { config } = await loadConfig({ configPath: configFile }, logger)

expect(config).toMatchInlineSnapshot(`
{
"*": "mytask",
}
`)
})

it('should read "lint-staged" key from package.yml', async () => {
expect.assertions(1)

const configFile = path.join(__dirname, '__mocks__', 'package.yml')

await fs.writeFile(configFile, 'lint-staged:\n "*": mytask')

const { config } = await loadConfig({ configPath: configFile }, logger)

expect(config).toMatchInlineSnapshot(`
{
"*": "mytask",
}
`)
})

it('should return null config when package.yaml file is invalid', async () => {
expect.assertions(1)

const configFile = path.join(__dirname, '__mocks__', 'package.yaml')

await fs.writeFile(configFile, '{')

const { config } = await loadConfig({ configPath: configFile }, logger)

expect(config).toBeUndefined()

await fs.rm(configFile)
})
})