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

cli: esm support #1589

Merged
merged 34 commits into from
Apr 17, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
327c8fe
add mjs wrapper
davidjgoss Feb 26, 2021
38e7282
use import instead of require for support code
davidjgoss Feb 26, 2021
964118f
support es2018 compile target
davidjgoss Feb 26, 2021
c8943bc
separate cli for esm, override importer fn
davidjgoss Feb 27, 2021
9dc49f6
include mjs wrapper in package
davidjgoss Feb 27, 2021
50da4dc
add feature for testing with esm
davidjgoss Feb 28, 2021
01de7f2
for now, bail if on windows
davidjgoss Feb 28, 2021
9e6b29d
all same exports as cjs entry point
davidjgoss Feb 28, 2021
0f30b12
pivot to single binary with flag and hack to import import
davidjgoss Mar 10, 2021
6743af8
fix feature file wording
davidjgoss Mar 10, 2021
9d10ce1
fix lint and test
davidjgoss Mar 10, 2021
b37fb13
skip esm scenario if not node 12 or higher
davidjgoss Mar 11, 2021
68096e6
make it fail with imports in cucumber.js file
davidjgoss Mar 11, 2021
0eaaa0a
make it work for cucumber.js file
davidjgoss Mar 11, 2021
0e36903
make it fail for a custom formatter
davidjgoss Mar 11, 2021
c2c7ad5
avoid collision
davidjgoss Mar 11, 2021
23af315
Merge branch 'master' into esm-maybe
davidjgoss Mar 11, 2021
eaf2b69
Merge branch 'master' into esm-maybe
davidjgoss Apr 7, 2021
7d34fe3
make importing formatters work
davidjgoss Apr 7, 2021
4a5c7ca
make custom snippets work
davidjgoss Apr 7, 2021
a78b63a
Include .mjs files by default if using ESM
davidjgoss Apr 7, 2021
a9a8cc3
test with and without parallel
davidjgoss Apr 7, 2021
d0ed9cc
unignore windows in esm tests
davidjgoss Apr 7, 2021
52595c0
add cli doc
davidjgoss Apr 7, 2021
ca140ce
add changelog entry
davidjgoss Apr 7, 2021
5808b39
rename this
davidjgoss Apr 7, 2021
32a8158
sometimes use `pathToFileURL` as appropriate
davidjgoss Apr 7, 2021
a1b6ba6
readd semi
davidjgoss Apr 13, 2021
c2f6bd3
rework config builder tests
davidjgoss Apr 13, 2021
5e1da47
further resimplify test
davidjgoss Apr 13, 2021
d11087b
improve doco
davidjgoss Apr 14, 2021
ad28700
include importer.js in src, copy at build time
davidjgoss Apr 17, 2021
3f2e640
link to docs from changelog entry
davidjgoss Apr 17, 2021
38febb5
put wrapper.mjs in src as well
davidjgoss Apr 17, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions bin/cucumber-es.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env node
davidjgoss marked this conversation as resolved.
Show resolved Hide resolved

(async () => {
const run = await import('../lib/cli/run.js')
await run.default.default((path) => import(path))
})()
37 changes: 37 additions & 0 deletions features/esm.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
Feature: ES modules support

cucumber-js works with native ES modules, via a separate executable
called `cucumber-es`.

@esm
Scenario: native module syntax works when using cucumber-es
Given a file named "features/a.feature" with:
"""
Feature:
Scenario:
Given a step passes
"""
And a file named "features/step_definitions/cucumber_steps.js" with:
"""
import {Given} from '@cucumber/cucumber'

Given(/^a step passes$/, function() {});
"""
When I run cucumber-es
Then it passes

Scenario: native module syntax doesn't work when using cucumber-js
Given a file named "features/a.feature" with:
"""
Feature:
Scenario:
Given a step passes
"""
And a file named "features/step_definitions/cucumber_steps.js" with:
"""
import {Given} from '@cucumber/cucumber'

Given(/^a step passes$/, function() {});
"""
When I run cucumber-js
Then it fails
10 changes: 10 additions & 0 deletions features/step_definitions/cli_steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ When('my env includes {string}', function (this: World, envString: string) {
this.sharedEnv = this.parseEnvString(envString)
})

When(
/^I run cucumber-es(?: with `(|.+)`)?$/,
{ timeout: 10000 },
async function (this: World, args: string) {
const renderedArgs = Mustache.render(valueOrDefault(args, ''), this)
const stringArgs = stringArgv(renderedArgs)
return await this.run(this.localEsmExecutablePath, stringArgs)
}
)

When(
/^I run cucumber-js(?: with `(|.+)`)?$/,
{ timeout: 10000 },
Expand Down
14 changes: 13 additions & 1 deletion features/support/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Before('@debug', function (this: World) {
this.debug = true
})

Before('@spawn', function (this: World) {
Before('@spawn or @esm', function (this: World) {
this.spawn = true
})

Expand Down Expand Up @@ -41,6 +41,18 @@ Before(function (
)
fsExtra.ensureSymlinkSync(projectPath, tmpDirCucumberPath)
this.localExecutablePath = path.join(projectPath, 'bin', 'cucumber-js')
this.localEsmExecutablePath = path.join(projectPath, 'bin', 'cucumber-es.mjs')
})

Before('@esm', function (this: World) {
if (process.platform === 'win32') {
return 'skipped'
}
fsExtra.writeJSONSync(path.join(this.tmpDir, 'package.json'), {
name: 'feature-test-pickle',
type: 'module',
})
return undefined
})

Before('@global-install', function (this: World) {
Expand Down
1 change: 1 addition & 0 deletions features/support/world.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class World {
public lastRun: ILastRun
public verifiedLastRunError: boolean
public localExecutablePath: string
public localEsmExecutablePath: string
public globalExecutablePath: string
public reportServer: FakeReportServer

Expand Down
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@
"lib": "./lib"
},
"main": "./lib/index.js",
"exports": {
"import": "./wrapper.mjs",
"require": "./lib/index.js"
},
"types": "./lib/index.d.ts",
"engines": {
"node": ">=10"
Expand Down Expand Up @@ -282,11 +286,13 @@
}
},
"bin": {
"cucumber-js": "./bin/cucumber-js"
"cucumber-js": "./bin/cucumber-js",
"cucumber-es": "./bin/cucumber-es.mjs"
},
"license": "MIT",
"files": [
"bin/",
"lib/"
"lib/",
"wrapper.mjs"
]
}
22 changes: 17 additions & 5 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,30 @@ interface IGetSupportCodeLibraryRequest {
supportCodePaths: string[]
}

export type ISupportCodeImporter = (path: string) => Promise<any>

export default class Cli {
private readonly argv: string[]
private readonly cwd: string
private readonly importer: ISupportCodeImporter
private readonly stdout: IFormatterStream

constructor({
argv,
cwd,
importer,
stdout,
}: {
argv: string[]
cwd: string
importer?: ISupportCodeImporter
stdout: IFormatterStream
}) {
this.argv = argv
this.cwd = cwd
this.importer =
// eslint-disable-next-line @typescript-eslint/no-var-requires
importer ?? (async (path) => await Promise.resolve(require(path)))
this.stdout = stdout
}

Expand Down Expand Up @@ -139,14 +147,18 @@ export default class Cli {
}
}

getSupportCodeLibrary({
async getSupportCodeLibrary({
newId,
supportCodeRequiredModules,
supportCodePaths,
}: IGetSupportCodeLibraryRequest): ISupportCodeLibrary {
supportCodeRequiredModules.map((module) => require(module))
}: IGetSupportCodeLibraryRequest): Promise<ISupportCodeLibrary> {
for (const requiredModule of supportCodeRequiredModules) {
await this.importer(requiredModule)
}
supportCodeLibraryBuilder.reset(this.cwd, newId)
supportCodePaths.forEach((codePath) => require(codePath))
for (const codePath of supportCodePaths) {
await this.importer(codePath)
}
return supportCodeLibraryBuilder.finalize()
}

Expand All @@ -165,7 +177,7 @@ export default class Cli {
configuration.predictableIds && configuration.parallel <= 1
? incrementing()
: uuid()
const supportCodeLibrary = this.getSupportCodeLibrary({
const supportCodeLibrary = await this.getSupportCodeLibrary({
newId,
supportCodePaths: configuration.supportCodePaths,
supportCodeRequiredModules: configuration.supportCodeRequiredModules,
Expand Down
7 changes: 5 additions & 2 deletions src/cli/run.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Cli, { ICliRunResult } from './'
import Cli, { ICliRunResult, ISupportCodeImporter } from './'
import VError from 'verror'
import publishBanner from './publish_banner'

Expand All @@ -11,11 +11,14 @@ function displayPublishAdvertisementBanner(): void {
console.error(publishBanner)
}

export default async function run(): Promise<void> {
export default async function run(
importer?: ISupportCodeImporter
): Promise<void> {
const cwd = process.cwd()
const cli = new Cli({
argv: process.argv,
cwd,
importer,
stdout: process.stdout,
})

Expand Down
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"compilerOptions": {
"esModuleInterop": true,
"lib": ["es2017"],
"lib": ["es2018"],
"module": "commonjs",
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"resolveJsonModule": true,
"sourceMap": true,
"target": "es2017",
"target": "es2018",
"typeRoots": [
"./node_modules/@types",
"./src/types"
Expand Down
38 changes: 38 additions & 0 deletions wrapper.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import cucumber from './lib/index.js'

export const Cli = cucumber.Cli
export const parseGherkinMessageStream = cucumber.parseGherkinMessageStream
export const PickleFilter = cucumber.PickleFilter
export const Runtime = cucumber.Runtime
export const supportCodeLibraryBuilder = cucumber.supportCodeLibraryBuilder
export const Status = cucumber.Status
export const DataTable = cucumber.DataTable

export const Formatter = cucumber.Formatter
export const FormatterBuilder = cucumber.FormatterBuilder
export const JsonFormatter = cucumber.JsonFormatter
export const ProgressFormatter = cucumber.ProgressFormatter
export const RerunFormatter = cucumber.RerunFormatter
export const SnippetsFormatter = cucumber.SnippetsFormatter
export const SummaryFormatter = cucumber.SummaryFormatter
export const UsageFormatter = cucumber.UsageFormatter
export const UsageJsonFormatter = cucumber.UsageJsonFormatter
export const formatterHelpers = cucumber.formatterHelpers

export const After = cucumber.After
export const AfterAll = cucumber.AfterAll
export const AfterStep = cucumber.AfterStep
export const Before = cucumber.Before
export const BeforeAll = cucumber.BeforeAll
export const BeforeStep = cucumber.BeforeStep
export const defineParameterType = cucumber.defineParameterType
export const defineStep = cucumber.defineStep
export const Given = cucumber.Given
export const setDefaultTimeout = cucumber.setDefaultTimeout
export const setDefinitionFunctionWrapper = cucumber.setDefinitionFunctionWrapper
export const setWorldConstructor = cucumber.setWorldConstructor
export const Then = cucumber.Then
export const When = cucumber.When

export const World = cucumber.World