Skip to content

Commit 05e9b1d

Browse files
committed
Merge branch 'main' into new-terminal-formatters
# Conflicts: # package-lock.json # package.json
2 parents 487c8d1 + 4492f28 commit 05e9b1d

File tree

8 files changed

+215
-96
lines changed

8 files changed

+215
-96
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ Please see [CONTRIBUTING.md](./CONTRIBUTING.md) on how to contribute to Cucumber
99

1010
## [Unreleased]
1111

12+
## [12.4.0] - 2025-12-13
13+
### Added
14+
- Allow loading config files in TypeScript format ([#2709](https://github.com/cucumber/cucumber-js/pull/2709))
15+
16+
### Changed
17+
- Compress report content with gzip before publishing ([#2687](https://github.com/cucumber/cucumber-js/pull/2687))
18+
1219
## [12.3.0] - 2025-12-01
1320
### Added
1421
- Add support for Node.js 25.x ([#2704](https://github.com/cucumber/cucumber-js/pull/2704))
@@ -1703,7 +1710,8 @@ this.Given(), this.When(), this.Then() and this.defineStep() ([#2](https://githu
17031710

17041711
## 0.0.1
17051712

1706-
[Unreleased]: https://github.com/cucumber/cucumber-js/compare/v12.3.0...HEAD
1713+
[Unreleased]: https://github.com/cucumber/cucumber-js/compare/v12.4.0...HEAD
1714+
[12.4.0]: https://github.com/cucumber/cucumber-js/compare/v12.3.0...v12.4.0
17071715
[12.3.0]: https://github.com/cucumber/cucumber-js/compare/v12.2.0...v12.3.0
17081716
[12.2.0]: https://github.com/cucumber/cucumber-js/compare/v12.1.0...v12.2.0
17091717
[12.1.0]: https://github.com/cucumber/cucumber-js/compare/v12.0.0...v12.1.0

docs/configuration.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,18 @@ module.exports = {
7171

7272
(If you're wondering why the configuration sits within a "default" property, that's to allow for [Profiles](./profiles.md).)
7373

74-
### Type checking
74+
### TypeScript
7575

76-
If you want to type check your configuration, we export two types that can help with that:
77-
- `IProfiles` represents the dictionary of profile names to configuration objects exported for CommonJS
78-
- `IConfiguration` represents a single configuration object exported with named exports for ESM (`Partial<IConfiguration>` will be more useful in practise)
76+
You can also write your configuration file in TypeScript, with a `.ts`, `.mts` or `.cts` extension. These files are loaded with [Node.js built-in TypeScript support](https://nodejs.org/api/typescript.html), which has several caveats and limitations, mostly that your `tsconfig.json` won't be honoured and that you need to be explicit about type imports. Here's an example:
77+
78+
```typescript
79+
import type { IConfiguration } from '@cucumber/cucumber'
80+
81+
export default {
82+
parallel: 2,
83+
format: ['html:cucumber-report.html']
84+
} satisfies Partial<IConfiguration>
85+
```
7986

8087
## Options
8188

features/step_definitions/report_server_steps.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { URL } from 'node:url'
22
import assert from 'node:assert'
3+
import { gunzipSync } from 'node:zlib'
34
import { expect } from 'chai'
45
import { Given, Then, DataTable } from '../..'
56
import { World } from '../support/world'
@@ -30,7 +31,7 @@ Then(
3031
.map((row) => row[0])
3132

3233
const receivedBodies = await this.reportServer.stop()
33-
const ndjson = receivedBodies.toString('utf-8').trim()
34+
const ndjson = gunzipSync(receivedBodies).toString('utf-8').trim()
3435
if (ndjson === '') assert.fail('Server received nothing')
3536

3637
const receivedMessageTypes = ndjson

package-lock.json

Lines changed: 29 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"gherkin",
99
"tests"
1010
],
11-
"version": "12.3.0",
11+
"version": "12.4.0",
1212
"funding": "https://opencollective.com/cucumber",
1313
"homepage": "https://github.com/cucumber/cucumber-js",
1414
"author": "Julien Biezemans <[email protected]>",
@@ -227,7 +227,7 @@
227227
"@cucumber/message-streams": "4.0.1",
228228
"@cucumber/messages": "31.0.0",
229229
"@cucumber/pretty-formatter": "1.0.1",
230-
"@cucumber/query": "14.6.0",
230+
"@cucumber/query": "14.7.0",
231231
"@cucumber/tag-expressions": "8.1.0",
232232
"assertion-error-formatter": "^3.0.0",
233233
"capital-case": "^1.0.4",
@@ -264,7 +264,7 @@
264264
"@eslint/compat": "^2.0.0",
265265
"@eslint/eslintrc": "^3.3.1",
266266
"@eslint/js": "^9.29.0",
267-
"@microsoft/api-extractor": "7.55.1",
267+
"@microsoft/api-extractor": "7.55.2",
268268
"@sinonjs/fake-timers": "15.0.0",
269269
"@types/chai": "4.3.20",
270270
"@types/debug": "4.1.12",

src/configuration/from_file.ts

Lines changed: 83 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ import { IConfiguration } from './types'
88
import { mergeConfigurations } from './merge_configurations'
99
import { parseConfiguration } from './parse_configuration'
1010

11+
const SUPPORTED_EXTENSIONS = [
12+
'.json',
13+
'.yaml',
14+
'.yml',
15+
'.js',
16+
'.cjs',
17+
'.mjs',
18+
'.ts',
19+
'.cts',
20+
'.mts',
21+
]
22+
1123
export async function fromFile(
1224
logger: ILogger,
1325
cwd: string,
@@ -79,58 +91,80 @@ async function loadFile(
7991
): Promise<Record<string, any>> {
8092
const filePath: string = path.join(cwd, file)
8193
const extension = path.extname(filePath)
94+
if (!SUPPORTED_EXTENSIONS.includes(extension)) {
95+
throw new Error(`Unsupported configuration file extension "${extension}"`)
96+
}
8297
let definitions
83-
switch (extension) {
84-
case '.json':
85-
definitions = JSON.parse(
86-
await promisify(fs.readFile)(filePath, { encoding: 'utf-8' })
87-
)
88-
break
89-
case '.yaml':
90-
case '.yml':
91-
definitions = YAML.parse(
92-
await promisify(fs.readFile)(filePath, { encoding: 'utf-8' })
93-
)
94-
break
95-
case '.cjs':
96-
logger.debug(
97-
`Loading configuration file "${file}" as CommonJS based on extension`
98-
)
99-
// eslint-disable-next-line @typescript-eslint/no-require-imports
100-
definitions = require(filePath)
101-
break
102-
case '.mjs':
103-
logger.debug(
104-
`Loading configuration file "${file}" as ESM based on extension`
105-
)
106-
definitions = await import(pathToFileURL(filePath).toString())
107-
break
108-
case '.js':
109-
{
110-
const parentPackage = await readPackageJson(filePath)
111-
if (!parentPackage) {
112-
logger.debug(
113-
`Loading configuration file "${file}" as CommonJS based on absence of a parent package`
114-
)
115-
// eslint-disable-next-line @typescript-eslint/no-require-imports
116-
definitions = require(filePath)
117-
} else if (parentPackage.type === 'module') {
118-
logger.debug(
119-
`Loading configuration file "${file}" as ESM based on "${parentPackage.name}" package type`
120-
)
121-
definitions = await import(pathToFileURL(filePath).toString())
122-
} else {
123-
logger.debug(
124-
`Loading configuration file "${file}" as CommonJS based on "${parentPackage.name}" package type`
125-
)
126-
// eslint-disable-next-line @typescript-eslint/no-require-imports
127-
definitions = require(filePath)
98+
try {
99+
switch (extension) {
100+
case '.json':
101+
definitions = JSON.parse(
102+
await promisify(fs.readFile)(filePath, { encoding: 'utf-8' })
103+
)
104+
break
105+
case '.yaml':
106+
case '.yml':
107+
definitions = YAML.parse(
108+
await promisify(fs.readFile)(filePath, { encoding: 'utf-8' })
109+
)
110+
break
111+
case '.cjs':
112+
logger.debug(
113+
`Loading configuration file "${file}" as CommonJS based on extension`
114+
)
115+
// eslint-disable-next-line @typescript-eslint/no-require-imports
116+
definitions = require(filePath)
117+
break
118+
case '.cts':
119+
logger.debug(
120+
`Loading configuration file "${file}" as TypeScript based on extension`
121+
)
122+
// eslint-disable-next-line @typescript-eslint/no-require-imports
123+
definitions = require(filePath)
124+
break
125+
case '.mjs':
126+
logger.debug(
127+
`Loading configuration file "${file}" as ESM based on extension`
128+
)
129+
definitions = await import(pathToFileURL(filePath).toString())
130+
break
131+
case '.mts':
132+
case '.ts':
133+
logger.debug(
134+
`Loading configuration file "${file}" as TypeScript based on extension`
135+
)
136+
definitions = await import(pathToFileURL(filePath).toString())
137+
break
138+
case '.js':
139+
{
140+
const parentPackage = await readPackageJson(filePath)
141+
if (!parentPackage) {
142+
logger.debug(
143+
`Loading configuration file "${file}" as CommonJS based on absence of a parent package`
144+
)
145+
// eslint-disable-next-line @typescript-eslint/no-require-imports
146+
definitions = require(filePath)
147+
} else if (parentPackage.type === 'module') {
148+
logger.debug(
149+
`Loading configuration file "${file}" as ESM based on "${parentPackage.name}" package type`
150+
)
151+
definitions = await import(pathToFileURL(filePath).toString())
152+
} else {
153+
logger.debug(
154+
`Loading configuration file "${file}" as CommonJS based on "${parentPackage.name}" package type`
155+
)
156+
// eslint-disable-next-line @typescript-eslint/no-require-imports
157+
definitions = require(filePath)
158+
}
128159
}
129-
}
130-
break
131-
default:
132-
throw new Error(`Unsupported configuration file extension "${extension}"`)
160+
break
161+
}
162+
} catch (error) {
163+
throw new Error(`Configuration file "${file}" failed to load/parse`, {
164+
cause: error,
165+
})
133166
}
167+
134168
if (typeof definitions !== 'object') {
135169
throw new Error(`Configuration file ${filePath} does not export an object`)
136170
}

0 commit comments

Comments
 (0)