[
  {
    "path": ".build/setPackageVersion.js",
    "content": "const nbgv = require(\"nerdbank-gitversioning\");\n\nconst setPackageVersionAndBuildNumber = (versionInfo) => {\n  // Set a build output value representing the NPM package version\n  console.log(\n    \"::set-output name=package_version::\" + versionInfo.npmPackageVersion,\n  );\n\n  nbgv.setPackageVersion(\"cucumber-tsflow\");\n  nbgv.setPackageVersion(\"cucumber-tsflow-specs\");\n};\n\nconst handleError = (err) =>\n  console.error(\n    \"Failed to update the package version number. nerdbank-gitversion failed: \" +\n      err,\n  );\n\nnbgv.getVersion().then(setPackageVersionAndBuildNumber).catch(handleError);\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\non:\n  push:\n    branches: [master, release/**]\n  pull_request:\n    branches: [master, release/**]\njobs:\n  # Build and Test the 'cucumber-tsflow' package\n  build:\n    name: Build and Test\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        cucumberVersion: [\"^10\", \"^11\", \"^12\"]\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n      - name: Install npm packages\n        run: |-\n          npm ci\n          npm install @cucumber/cucumber@${{ matrix.cucumberVersion }}\n      - name: Build\n        run: npm run build\n      - name: Run specification tests\n        run: npm test\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "#\n# This workflow creates a release from a specified branch. The Package version is managed\n# by Nerdbank Gitversioning based on configuration held in 'version.json' file.\n#\nname: Release\non:\n  workflow_dispatch:\n\njobs:\n  # Build, Test and Pack the 'cucumber-tsflow' package\n  build:\n    name: Build and Test\n    runs-on: ubuntu-latest\n    outputs:\n      version: ${{ steps.set_package_version.outputs.NpmPackageVersion }}\n      releaseTag: ${{ steps.tagInfo.outputs.releaseTag }}\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          # avoid shallow clone (required by Nerbank GitVersioning)\n          fetch-depth: 0\n      - uses: actions/setup-node@v3\n        with:\n          node-version: 22\n      - name: Install npm packages\n        run: npm ci\n      - name: Update package version\n        id: set_package_version\n        uses: dotnet/nbgv@master\n        with:\n          stamp: cucumber-tsflow/package.json\n      - name: Build\n        run: npm run build\n      - name: Create npm package\n        run: npm pack ./cucumber-tsflow\n      - name: Read tag info\n        id: tagInfo\n        run: |-\n          echo \"releaseTag=$(jq '.releaseTag // \"latest\"' version.json)\" | tee -a $GITHUB_OUTPUT\n      - uses: actions/upload-artifact@v4\n        with:\n          name: npm-package\n          path: |\n            cucumber-tsflow-${{ steps.set_package_version.outputs.NpmPackageVersion }}.tgz\n\n  # Publish the 'cucumber-tsflow' package to npm\n  publish:\n    name: Publish to npm\n    runs-on: ubuntu-latest\n    needs: build\n    permissions:\n      contents: write\n    steps:\n      - uses: actions/setup-node@v3\n        with:\n          node-version: 22\n          registry-url: \"https://registry.npmjs.org\"\n      - uses: actions/download-artifact@v4\n        name: Download npm package\n        with:\n          name: npm-package\n      - name: Publish npm package\n        run: |-\n          npm publish \\\n            cucumber-tsflow-${{ needs.build.outputs.version }}.tgz \\\n            --tag ${{ needs.build.outputs.releaseTag }}\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n      - name: Publish GitHub release\n        uses: ncipollo/release-action@v1\n        with:\n          tag: ${{ needs.build.outputs.version }}\n          commit: ${{ github.sha }}\n          artifacts: cucumber-tsflow-${{ needs.build.outputs.version }}.tgz\n          generateReleaseNotes: true\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: \"Stale issue handler\"\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: \"0 0 * * *\"\n\npermissions:\n  contents: write # only for delete-branch option\n  issues: write\n  pull-requests: write\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/stale@v6\n        id: stale\n        with:\n          days-before-stale: 60\n          days-before-close: 7\n\n          stale-issue-message: \"This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.\"\n          close-issue-message: \"There hasn't been any activity on this issue for 67 days. Closing it as Spoiled.\"\n          stale-issue-label: stale\n          close-issue-label: spoiled\n          exempt-issue-labels: \"blocked,discussion,good first issue\"\n\n          stale-pr-message: \"This PR is stale because it has been 60 days with no activity. Remove stale lable or comment or this will be closed in 7 days.\"\n          close-pr-message: \"There hasn't been any activity on this PR for 67 days. Closing it as Spoiled.\"\n          stale-pr-label: stale\n          close-pr-label: spoiled\n          exempt-pr-labels: \"blocked,discussion\"\n\n      - name: Print outputs\n        run: echo ${{ join(steps.stale.outputs.*, ',') }}\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\ndist\ntmp/\n.idea/\ntsconfig.tsbuildinfo\n"
  },
  {
    "path": ".npm-upgrade.json",
    "content": "{\n  \"ignore\": {\n    \"@cucumber/cucumber\": {\n      \"versions\": \"^8\",\n      \"reason\": \"Mantain compatibility with cucumber 7 and 8\"\n    }\n  }\n}\n"
  },
  {
    "path": ".run/All Tests.run.xml",
    "content": "<component name=\"ProjectRunConfigurationManager\">\n  <configuration default=\"false\" name=\"All Tests\" type=\"cucumber.js\" factoryName=\"Cucumber.js\">\n    <option name=\"myFilePath\" value=\"$PROJECT_DIR$/cucumber-tsflow-specs/features\" />\n    <option name=\"myNameFilter\" value=\"\" />\n    <option name=\"cucumberJsArguments\" value=\"\" />\n    <option name=\"workingDirectory\" value=\"$PROJECT_DIR$\" />\n    <method v=\"2\">\n      <option name=\"NpmBeforeRunTask\" enabled=\"true\">\n        <package-json value=\"$PROJECT_DIR$/package.json\" />\n        <command value=\"run\" />\n        <scripts>\n          <script value=\"build\" />\n        </scripts>\n        <node-interpreter value=\"project\" />\n        <envs />\n      </option>\n    </method>\n  </configuration>\n</component>"
  },
  {
    "path": ".run/Template Cucumber.js.run.xml",
    "content": "<component name=\"ProjectRunConfigurationManager\">\n  <configuration default=\"true\" type=\"cucumber.js\" factoryName=\"Cucumber.js\">\n    <option name=\"myFilePath\" value=\"\" />\n    <option name=\"myNameFilter\" value=\"\" />\n    <option name=\"cucumberJsArguments\" value=\"\" />\n    <option name=\"workingDirectory\" value=\"$PROJECT_DIR$\" />\n    <method v=\"2\">\n      <option name=\"NpmBeforeRunTask\" enabled=\"true\">\n        <package-json value=\"$PROJECT_DIR$/package.json\" />\n        <command value=\"run\" />\n        <scripts>\n          <script value=\"build\" />\n        </scripts>\n        <node-interpreter value=\"project\" />\n        <envs />\n      </option>\n    </method>\n  </configuration>\n</component>"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"name\": \"Specs\",\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"program\": \"${workspaceRoot}/node_modules/cucumber/bin/cucumber-js\",\n      \"stopOnEntry\": true,\n      \"args\": [\"--require\", \"./cucumber-tsflow-specs/dist\"],\n      \"cwd\": \"${workspaceRoot}\",\n      \"runtimeExecutable\": null,\n      \"runtimeArgs\": [\"--nolazy\"],\n      \"env\": {\n        \"NODE_ENV\": \"development\"\n      },\n      \"externalConsole\": false,\n      \"sourceMaps\": true,\n      \"outDir\": null\n    },\n    {\n      \"name\": \"Attach\",\n      \"type\": \"node\",\n      \"request\": \"attach\",\n      \"port\": 5858,\n      \"sourceMaps\": false,\n      \"outDir\": null,\n      \"localRoot\": \"${workspaceRoot}\",\n      \"remoteRoot\": null\n    }\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"typescript.tsdk\": \"node_modules\\\\typescript\\\\lib\"\n}\n"
  },
  {
    "path": "CONTRIBUTE.md",
    "content": "The project should set-up all of its inner links and bindings when you first install it.\n\nRun the tests locally to ensure everything is properly configured.\n\n```terminal\n> git clone https://github.com/timjroberts/cucumber-js-tsflow.git\n> cd cucumber-js-tsflow\n> npm install\n> npm test\n```\n\n## Setting up Run/Debug in IDE\n\nFor IntelliJ, a run configuration is stored in `.run/cucumber-js.run.xml` to run/debug the tests.\n\nFor other IDE, using the following runtime config for node:\n\n- working dir: `cucumber-tsflow-spec`\n- node-parameters: `--require ts-node/register `\n- js script to run: `node_modules/@cucumber/cucumber/bin/cucumber-js`\n- application parameters: `features/**/*.feature --require \"src/step_definitions/**/*.ts\" `\n\nAn example command line runner:\n\n```shell script\n\"C:\\Program Files\\nodejs\\node.exe\" --require ts-node/register C:\\Users\\wudon\\repo\\cucumber-js-tsflow\\node_modules\\@cucumber\\cucumber\\bin\\cucumber-js features/**/*.feature --require src/step_definitions/**/*.ts\n```\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018-2020 Tim Roberts and contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# cucumber-tsflow\n\n![CI](https://github.com/timjroberts/cucumber-js-tsflow/workflows/CI/badge.svg)\n\nProvides 'SpecFlow' like bindings for CucumberJS in TypeScript 1.7+.\n\n## Table of content\n\nSee that menu icon to the left of \"README.md\"?\n\nDid you know that every markdown file in GitHub with more than two headings\nhave that icon as a Table of Content linking to every heading?\n\n## Quick Start\n\ncucumber-tsflow uses TypeScript Decorators to create SpecFlow like bindings for\nTypeScript classes and methods that allow those classes and methods to be used\nin your CucumberJS support files. As such, cucumber-tsflow has a peer dependency\non CucumberJS, and you still run your specifications using the cucumber\ncommand line tool.\n\n### Install cucumber and cucumber-tsflow\n\n```bash\nnpm install @cucumber/cucumber cucumber-tsflow\n```\n\n### Create .feature files to describe your specifications\n\nBy default, CucumberJS looks for .feature files in a folder called 'features',\nso create that folder and then create a new file called `my_feature.feature`:\n\n```gherkin\n# features/my_feature.feature\n\nFeature: Example Feature\n    This is an example feature\n\n    Scenario: Adding two numbers\n        Given I enter '2' and '8'\n        Then I receive the result '10'\n```\n\n### Create the Support Files to support the Feature\n\nCucumberJS requires Support Files defining what each step in the Feature files mean.\n\nBy default, CucumberJS looks for Support Files beneath the 'features' folder.\nWe need to write step definitions to support the two steps that we created above.\n\nCreate a new `ArithmeticSteps.ts` file:\n\n```ts\n// features/ArithmeticSteps.ts\n\nimport { binding, given, then } from \"cucumber-tsflow\";\n\n@binding()\nclass ArithmeticSteps {\n  private computedResult: number;\n\n  @given(/I enter '(\\d*)' and '(\\d*)'/)\n  public givenTwoNumbers(num1: string, num2: string): void {\n    this.computedResult = parseInt(num1) + parseInt(num2);\n  }\n\n  @then(/I receive the result '(\\d*)'/)\n  public thenResultReceived(expectedResult: string): void {\n    if (parseInt(expectedResult) !== this.computedResult) {\n      throw new Error(\"Arithmetic Error\");\n    }\n  }\n}\n\nexport = ArithmeticSteps;\n```\n\nNote how the cucumber-tsflow Decorators are being used to bind the methods in\nthe class. During runtime, these Decorators simply call the Cucumber code on\nyour behalf in order to register callbacks with Given(), When(), Then(), etc.\n\nThe callbacks that are being registered with Cucumber are wrappers around your\nbound class. This allows you to maintain a state between each step on the same\nclass by using instance properties.\n\nIn this quick example, the entire test state is encapsulated directly in the class.\nAs your test suite grows larger and step definitions get shared between\nmultiple classes, you can use 'Context Injection' to share state between\nrunning step definitions (see below).\n\n### Compiling your TypeScript Support Code\n\nTo use `cucumber-tsflow` with TypeScript, you'll also need a `tsconfig.json` file\nwith these options:\n\n```json\n{\n  \"compilerOptions\": {\n    \"moduleResolution\": \"node\",\n    \"experimentalDecorators\": true\n  }\n}\n```\n\n> Hint: You can add that to `features/tsconfig.json` to have it applied only for\n> your integration tests.\n\nWith the TS config in place, CucumberJS should automatically compile your code\nbefore running it.\n\n## Reference\n\n### Bindings\n\nBindings provide the automation that connects a specification step in a Gherkin\nfeature file to some code that executes for that step.\nWhen using Cucumber with TypeScript you can define this automation using the\n`binding` decorator on top of a class:\n\n```ts\nimport { binding } from \"cucumber-tsflow\";\n\n@binding()\nclass MySteps {\n  // ...\n}\n\nexport = MySteps;\n```\n\nThrough this reference, classes decorated with the `binding` decorator are\nreferred \"binding classes\".\n\n_Note_: You must use the `export = <class>;` due to how Cucumber interprets\nthe exported items of a Support File.\n\n### Step Definitions\n\nStep definitions can be bound to automation code in a binding class by decorating\na public function with a 'given', 'when' or 'then' binding decorator:\n\n```ts\nimport { binding, given, when, then } from \"cucumber-tsflow\";\n\n@binding()\nclass MySteps {\n  @given(/I perform a search using the value \"([^\"]*)\"/)\n  public givenAValueBasedSearch(searchValue: string): void {\n    // ...\n  }\n}\n\nexport = MySteps;\n```\n\nThe methods have the same requirements and guarantees of functions you would normally\nsupply to Cucumber, which means that the methods may be:\n\n- Synchronous by returning `void`\n- Asynchronous by receiving and using a callback as the last parameter\\\n  The callback has signature `() => void`\n- Asynchronous by returning a `Promise<void>`\n\nThe step definition functions must always receive a pattern as the first argument,\nwhich can be either a string or a regular expression.\n\nAdditionally, a step definition may receive additional options in the format:\n\n```ts\n@binding()\nclass MySteps {\n  @given(\"pattern\", {\n    tag: \"not @expensive\",\n    timeout: 1000,\n    wrapperOptions: {},\n  })\n  public givenAValueBasedSearch(searchValue: string): void {\n    // ...\n  }\n}\n```\n\nFor backward compatibility, the `tag` and `timeout` options can also be passed\nas direct arguments:\n\n```ts\n@binding()\nclass MySteps {\n  @given(\"pattern\", \"not @expensive\", 1000)\n  public givenAValueBasedSearch(searchValue: string): void {\n    // ...\n  }\n}\n```\n\n### Hooks\n\nHooks can be used to add logic that happens before or after each scenario execution.\nThey are configured in the same way as the [Step Definitions](#step-definitions).\n\n```typescript\nimport { binding, before, beforeAll, after, afterAll } from \"cucumber-tsflow\";\n\n@binding()\nclass MySteps {\n  @beforeAll()\n  public static beforeAllScenarios(): void {\n    // ...\n  }\n\n  @afterAll()\n  public static beforeAllScenarios(): void {\n    // ...\n  }\n\n  @before()\n  public beforeAllScenarios(): void {\n    // ...\n  }\n\n  @after()\n  public afterAllScenarios(): void {\n    // ...\n  }\n}\n\nexport = MySteps;\n```\n\nContrary to the Step Definitions, Hooks don't need a pattern since they don't\nrun for some particular step, but once for each scenario.\n\nHooks can receive aditional options just like the Step Definitions:\n\n```ts\n@binding()\nclass MySteps {\n  // Runs before each scenarios with tag `@requireTempDir` with 2 seconds of timeout\n  @before({ tag: \"@requireTempDir\", timeout: 2000 })\n  public async beforeAllScenariosRequiringTempDirectory(): Promise<void> {\n    let tempDirInfo = await this.createTemporaryDirectory();\n    // ...\n  }\n\n  // Runs after each scenarios with tag `@requireTempDir` with 2 seconds of timeout\n  @after({ tag: \"@requireTempDir\", timeout: 2000 })\n  public async afterAllScenariosRequiringTempDirectory(): void {\n    await this.deleteTemporaryDirectory();\n    // ...\n  }\n}\n```\n\nFor backward compatibility, the `tag` option can also be passes as a direct argument:\n\n```ts\n@binding()\nclass MySteps {\n  @before('@local')\n  public async runForLocalOnly(): Promise<void> {\n  ...\n  }\n}\n```\n\n### Step and hook options\n\n#### Tag filters\n\nBoth Step Definitions and Hooks can receive a `tag` option. This option defines\na filter such that the binding will only be considered for scenarios matching\nthe filter.\n\nThe syntax of the tag filter is\na [\"Tag expression\"](https://cucumber.io/docs/cucumber/api/?lang=javascript#tag-expressions)\nspecified by Cucumber.\n\n**Note**: The tag might be set for the `Feature` or for the `Scenario`, and there\nis no distinction between them. This is\ncalled [\"Tag Inheritance\"](https://cucumber.io/docs/cucumber/api/?lang=javascript#tag-inheritance).\n\nFor backward compatibility, setting a tag to a single word is treated the same\nas a filter for that word as a tag:\n\n```ts\n// This backward compatible format\n@given({ tag: 'foo' })\n\n// Is transformed into this\n@given({ tag: '@foo' })\n```\n\n#### Timeout\n\nBoth Step Definition and Hooks can receive a `timeout` option. This option defines\nthe maximum runtime allowed for the binding before it is flagged as failed.\n\n`cucumber-tsflow` currently doesn't have a way to define a global default step timeout,\nbut it can be easily done through CucumberJS' `setDefaultTimeout` function.\n\n#### Passing WrapOptions\n\nIn step definition, we can passing additional wrapper options to CucumberJS.\n\nFor example:\n\n```typescript\n@given(/I perform a search using the value \"([^\"]*)\"/, { wrapperOptions: { retry: 2 } })\npublic\ngivenAValueBasedSearch(searchValue\n:\nstring\n):\nvoid {\n  ...\n}\n```\n\nThe type of `wrapperOptions` is defined by the function given to `setDefinitionFunctionWrapper`.\n\n**Note**: `wrapperOptions` and `setDefinitionFunctionWrapper` were deprecated in\n[CucumberJS 7.3.1](https://github.com/cucumber/cucumber-js/blob/8900158748a3f36c4b2fa5d172fe27013b39ab17/CHANGELOG.md#731---2021-07-20)\nand are kept here for backward compatibility only while this library supports\nCucumberJS 7.\n\n### Sharing Data between Bindings\n\n#### Context Injection\n\nLike 'SpecFlow', `cucumber-tsflow` supports a simple dependency injection\nframework that will instantitate and inject class instances into binding classes\nfor each executing scenario.\n\nTo use context injection:\n\n- Create simple classes representing the shared data and/or behavior.\\\n  These classes **must** have public constructors with no arguments (default constructors).\n  Defining a class with no constructor at all also works.\n- Define a constructor on the binding classes that receives an instance of\n  the class defined above as an parameter.\n- Update the `@binding()` decorator to indicate the types of context objects\n  that are required by the binding class\n\n```ts\n// Workspace.ts\n\nexport class Workspace {\n  public folder: string = \"default folder\";\n\n  public updateFolder(folder: string) {\n    this.folder = folder;\n  }\n}\n\n// my-steps.ts\nimport { binding, before, after } from \"cucumber-tsflow\";\nimport { Workspace } from \"./Workspace\";\n\n@binding([Workspace])\nclass MySteps {\n  public constructor(protected workspace: Workspace) {}\n\n  @before(\"requireTempDir\")\n  public async beforeAllScenariosRequiringTempDirectory(): Promise<void> {\n    let tempDirInfo = await this.createTemporaryDirectory();\n\n    this.workspace.updateFolder(tempDirInfo);\n  }\n}\n\nexport = MySteps;\n```\n\n#### Provided Context Types\n\nThis library provides 3 Context Types to interact with CucumberJS' World object.\n\n- `WorldParameters`, which expose value passed to the `worldParameters` configuration\n  or the `--world-parameters` CLI option.\n- `CucumberLog`, which exposes the `log` method of the `World` object.\n- `CucumberAttachments`, which exposes the `attach` method of the `World` object.\n- `ScenarioInfo`, which exposes information about the running scenario and allows\n  changing the behavior of steps and hooks based on tags easier.\n"
  },
  {
    "path": "cucumber-tsflow/.npmignore",
    "content": "*.ts\ntsconfig.json\ntypings.json\ntypings\n.npmignore\n*.tsbuildinfo\n!dist/**/*.d.ts\n"
  },
  {
    "path": "cucumber-tsflow/package.json",
    "content": "{\n  \"name\": \"cucumber-tsflow\",\n  \"description\": \"Provides 'specflow' like bindings for CucumberJS 7.0.0+ in TypeScript 1.7+.\",\n  \"version\": \"5.0.0\",\n  \"author\": \"Tim Roberts <tim@timjroberts.com>\",\n  \"maintainers\": [\n    {\n      \"name\": \"Luiz Ferraz\",\n      \"email\": \"luiz@lferraz.com\",\n      \"url\": \"https://github.com/Fryuni\"\n    }\n  ],\n  \"license\": \"MIT\",\n  \"main\": \"./dist\",\n  \"keywords\": [\n    \"testing\",\n    \"bdd\",\n    \"cucumber\",\n    \"gherkin\",\n    \"tests\",\n    \"typescript\",\n    \"specflow\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/timjroberts/cucumber-js-tsflow.git\"\n  },\n  \"dependencies\": {\n    \"log4js\": \"^6.9.1\",\n    \"source-map-support\": \"^0.5.21\",\n    \"underscore\": \"^1.13.8\"\n  },\n  \"peerDependencies\": {\n    \"@cucumber/cucumber\": \"^10 || ^11 || ^12\"\n  }\n}\n"
  },
  {
    "path": "cucumber-tsflow/src/binding-decorator.ts",
    "content": "import {\n  After,\n  AfterAll,\n  AfterStep,\n  Before,\n  BeforeAll,\n  BeforeStep,\n  Given,\n  Then,\n  When,\n  World,\n} from \"@cucumber/cucumber\";\nimport {\n  IDefineStepOptions,\n  IDefineTestStepHookOptions,\n} from \"@cucumber/cucumber/lib/support_code_library_builder/types\";\nimport { PickleTag } from \"@cucumber/messages\";\nimport * as _ from \"underscore\";\nimport { BindingRegistry, DEFAULT_TAG } from \"./binding-registry\";\nimport logger from \"./logger\";\nimport {\n  ManagedScenarioContext,\n  ScenarioContext,\n  ScenarioInfo,\n} from \"./managed-scenario-context\";\nimport {\n  CucumberAttachments,\n  CucumberLog,\n  WorldParameters,\n} from \"./provided-context\";\nimport { StepBinding, StepBindingFlags } from \"./step-binding\";\nimport { ContextType, StepPattern, TypeDecorator } from \"./types\";\n\ninterface WritableWorld extends World {\n  [key: string]: any;\n}\n\n/**\n * The property name of the current scenario context that will be attached to the Cucumber\n * world object.\n */\nconst SCENARIO_CONTEXT_SLOTNAME: string = \"__SCENARIO_CONTEXT\";\n\n/**\n * A set of step patterns that have been registered with Cucumber.\n *\n * In order to support scoped (or tagged) step definitions, we must ensure that any step binding is\n * only registered with Cucumber once. The binding function for that step pattern then becomes\n * responsible for looking up and execuing the step binding based on the context that is in scope at\n * the point of invocation.\n */\nconst stepPatternRegistrations = new Map<StepPattern, StepBindingFlags>();\n\n// tslint:disable:no-bitwise\n\nfunction ensureNoCyclicDependencies(target: any, currentPath: any[] = []) {\n  const dependencies = BindingRegistry.instance.getContextTypesForTarget(\n    target.prototype,\n  );\n\n  if (dependencies.length === 0) {\n    return;\n  }\n\n  for (const dependency of dependencies) {\n    if (dependency === undefined) {\n      throw new Error(\n        `Undefined dependency detected in ${target.name}. You possibly have an import cycle.\\n` +\n          \"See https://nodejs.org/api/modules.html#modules_cycles\",\n      );\n    }\n\n    if (currentPath.includes(dependency)) {\n      throw new Error(\n        `Cyclic dependency detected: ${dependency.name} -> ${target.name} -> ${currentPath.map((t) => t.name).join(\" -> \")}`,\n      );\n    }\n\n    ensureNoCyclicDependencies(dependency, [...currentPath, target]);\n  }\n}\n\n/**\n * A class decorator that marks the associated class as a CucumberJS binding.\n *\n * @param requiredContextTypes An optional array of Types that will be created and passed into the created\n * object for each scenario.\n *\n * An instance of the decorated class will be created for each scenario.\n */\nexport function binding(requiredContextTypes?: ContextType[]): TypeDecorator {\n  return <T>(target: new (...args: any[]) => T) => {\n    ensureSystemBindings();\n    const bindingRegistry = BindingRegistry.instance;\n    bindingRegistry.registerContextTypesForTarget(\n      target.prototype,\n      requiredContextTypes,\n    );\n\n    ensureNoCyclicDependencies(target);\n\n    const allBindings: StepBinding[] = [\n      ...bindingRegistry.getStepBindingsForTarget(target),\n      ...bindingRegistry.getStepBindingsForTarget(target.prototype),\n    ];\n\n    for (const stepBinding of allBindings) {\n      if (stepBinding.bindingType & StepBindingFlags.StepDefinitions) {\n        let stepBindingFlags = stepPatternRegistrations.get(\n          stepBinding.stepPattern.toString(),\n        );\n\n        if (stepBindingFlags === undefined) {\n          stepBindingFlags = StepBindingFlags.none;\n        }\n\n        if (stepBindingFlags & stepBinding.bindingType) {\n          return;\n        }\n\n        const bound = bindStepDefinition(stepBinding);\n\n        if (bound) {\n          stepPatternRegistrations.set(\n            stepBinding.stepPattern.toString(),\n            stepBindingFlags | stepBinding.bindingType,\n          );\n        }\n      } else if (stepBinding.bindingType & StepBindingFlags.Hooks) {\n        bindHook(stepBinding);\n      } else {\n        logger.trace(\"Ignored binding\", stepBinding);\n      }\n    }\n  };\n}\n\nfunction getContextFromWorld(world: World): ScenarioContext {\n  const context: unknown = (world as Record<string, any>)[\n    SCENARIO_CONTEXT_SLOTNAME\n  ];\n\n  if (context instanceof ManagedScenarioContext) {\n    return context;\n  }\n\n  throw new Error(\n    \"Scenario context have not been initialized in the provided World object.\",\n  );\n}\n\nexport function getBindingFromWorld<T extends ContextType>(\n  world: World,\n  contextType: T,\n): InstanceType<T> {\n  const context = getContextFromWorld(world);\n\n  return context.getContextInstance(contextType);\n}\n\nexport function ensureWorldIsInitialized() {\n  ensureSystemBindings();\n}\n\n/**\n * Ensures that the 'cucumber-tsflow' hooks are bound to Cucumber.\n *\n * @param cucumber The cucumber object.\n *\n * The hooks will only be registered with Cucumber once regardless of which binding invokes the\n * function.\n */\nconst ensureSystemBindings = _.once(() => {\n  Before(function (this: WritableWorld, scenario) {\n    logger.trace(\n      \"Setting up scenario context for scenario:\",\n      JSON.stringify(scenario),\n    );\n\n    const scenarioInfo = new ScenarioInfo(\n      scenario.pickle.name!,\n      _.map(scenario.pickle.tags!, (tag: PickleTag) => tag.name!),\n    );\n\n    const scenarioContext = new ManagedScenarioContext(scenarioInfo);\n\n    this[SCENARIO_CONTEXT_SLOTNAME] = scenarioContext;\n\n    scenarioContext.addExternalObject(scenarioInfo);\n    scenarioContext.addExternalObject(new WorldParameters(this.parameters));\n    scenarioContext.addExternalObject(new CucumberLog(this.log.bind(this)));\n    scenarioContext.addExternalObject(\n      new CucumberAttachments(this.attach.bind(this)),\n    );\n  });\n\n  After(function (this: WritableWorld) {\n    const scenarioContext = this[\n      SCENARIO_CONTEXT_SLOTNAME\n    ] as ManagedScenarioContext;\n\n    if (scenarioContext) {\n      scenarioContext.dispose();\n    }\n  });\n\n  try {\n    const stackFilter = require(\"@cucumber/cucumber/lib/filter_stack_trace\");\n    const path = require(\"path\");\n\n    const originalFileNameFilter = stackFilter.isFileNameInCucumber;\n\n    if (originalFileNameFilter !== undefined) {\n      const projectRootPath = path.join(__dirname, \"..\") + \"/\";\n\n      Object.defineProperty(stackFilter, \"isFileNameInCucumber\", {\n        value: (fileName: string) =>\n          originalFileNameFilter(fileName) ||\n          fileName.startsWith(projectRootPath) ||\n          fileName.includes(\"node_modules\"),\n        configurable: true,\n        enumerable: true,\n      });\n    }\n  } catch {\n    // Ignore errors, proper stack filtering is not officially supported\n    // so we override on a best effor basis only\n  }\n\n  // Decorate the Cucumber step definition snippet builder so that it uses our syntax\n\n  // let currentSnippetBuilder = cucumberSys.SupportCode.StepDefinitionSnippetBuilder;\n\n  // cucumberSys.SupportCode.StepDefinitionSnippetBuilder = function (step, syntax) {\n  //     return currentSnippetBuilder(step, {\n  //         build: function (functionName: string, pattern, parameters, comment) {\n  //             let callbackName = parameters[parameters.length - 1];\n\n  //             return `@${functionName.toLowerCase()}(${pattern})\\n` +\n  //                    `public ${functionName}XXX (${parameters.join(\", \")}): void {\\n` +\n  //                    `  // ${comment}\\n` +\n  //                    `  ${callbackName}.pending();\\n` +\n  //                    `}\\n`;\n  //         }\n  //     });\n  // }\n});\n\n/**\n * Binds a step definition to Cucumber.\n *\n * @param stepBinding The [[StepBinding]] that represents a 'given', 'when', or 'then' step definition.\n */\nfunction bindStepDefinition(stepBinding: StepBinding): boolean {\n  const bindingFunc = function (this: WritableWorld): any {\n    const bindingRegistry = BindingRegistry.instance;\n\n    const scenarioContext = this[\n      SCENARIO_CONTEXT_SLOTNAME\n    ] as ManagedScenarioContext;\n\n    const matchingStepBindings = bindingRegistry.getStepBindings(\n      stepBinding.stepPattern.toString(),\n    );\n\n    const contextTypes = bindingRegistry.getContextTypesForTarget(\n      matchingStepBindings[0].targetPrototype,\n    );\n    const bindingObject = scenarioContext.getOrActivateBindingClass(\n      matchingStepBindings[0].targetPrototype,\n      contextTypes,\n    );\n\n    return (\n      bindingObject[matchingStepBindings[0].targetPropertyKey] as () => void\n    ).apply(bindingObject, arguments as any);\n  };\n\n  Object.defineProperty(bindingFunc, \"length\", {\n    value: stepBinding.argsLength,\n  });\n\n  logger.trace(\"Binding step:\", stepBinding);\n\n  const bindingOptions: IDefineStepOptions & IDefineTestStepHookOptions = {\n    timeout: stepBinding.timeout,\n    wrapperOptions: stepBinding.wrapperOption,\n    tags: stepBinding.tag === DEFAULT_TAG ? undefined : stepBinding.tag,\n  };\n\n  if (stepBinding.bindingType & StepBindingFlags.given) {\n    Given(stepBinding.stepPattern, bindingOptions, bindingFunc);\n  } else if (stepBinding.bindingType & StepBindingFlags.when) {\n    When(stepBinding.stepPattern, bindingOptions, bindingFunc);\n  } else if (stepBinding.bindingType & StepBindingFlags.then) {\n    Then(stepBinding.stepPattern, bindingOptions, bindingFunc);\n  } else {\n    return false;\n  }\n\n  return true;\n}\n\n/**\n * Binds a hook to Cucumber.\n *\n * @param cucumber The cucumber object.\n * @param stepBinding The [[StepBinding]] that represents a 'before', or 'after', step definition.\n */\nfunction bindHook(stepBinding: StepBinding): void {\n  const bindingFunc = function (this: any): any {\n    const scenarioContext = this[\n      SCENARIO_CONTEXT_SLOTNAME\n    ] as ManagedScenarioContext;\n    const contextTypes = BindingRegistry.instance.getContextTypesForTarget(\n      stepBinding.targetPrototype,\n    );\n    const bindingObject = scenarioContext.getOrActivateBindingClass(\n      stepBinding.targetPrototype,\n      contextTypes,\n    );\n\n    return (bindingObject[stepBinding.targetPropertyKey] as () => void).apply(\n      bindingObject,\n      arguments as any,\n    );\n  };\n\n  const globalBindFunc = () => {\n    const targetPrototype = stepBinding.targetPrototype;\n    const targetPrototypeKey = stepBinding.targetPropertyKey;\n\n    return targetPrototype[targetPrototypeKey].apply(targetPrototype);\n  };\n\n  Object.defineProperty(bindingFunc, \"length\", {\n    value: stepBinding.argsLength,\n  });\n\n  const bindingOptions: IDefineTestStepHookOptions = {\n    timeout: stepBinding.timeout,\n    tags: stepBinding.tag === DEFAULT_TAG ? undefined : stepBinding.tag,\n    ...(stepBinding.hookOptions ?? {}),\n  };\n\n  logger.trace(\"Binding hook:\", stepBinding);\n\n  switch (stepBinding.bindingType) {\n    case StepBindingFlags.before:\n      Before(bindingOptions, bindingFunc);\n      break;\n    case StepBindingFlags.after:\n      After(bindingOptions, bindingFunc);\n      break;\n    case StepBindingFlags.beforeAll:\n      BeforeAll(globalBindFunc);\n      break;\n    case StepBindingFlags.beforeStep:\n      BeforeStep(bindingFunc);\n      break;\n    case StepBindingFlags.afterStep:\n      AfterStep(bindingFunc);\n      break;\n    case StepBindingFlags.afterAll:\n      AfterAll(globalBindFunc);\n      break;\n  }\n}\n"
  },
  {
    "path": "cucumber-tsflow/src/binding-registry.ts",
    "content": "import logger from \"./logger\";\n\nimport { StepBinding } from \"./step-binding\";\nimport { ContextType, StepPattern } from \"./types\";\n\n/**\n * Describes the binding metadata that is associated with a binding class.\n */\ninterface TargetBinding {\n  /**\n   * A reference to the step bindings that are associated with the binding class.\n   */\n  stepBindings: StepBinding[];\n\n  /**\n   * The context types that are to be injected into the binding class during execution.\n   */\n  contextTypes: ContextType[];\n}\n\n/**\n * Represents the default step pattern.\n */\nexport const DEFAULT_STEP_PATTERN: string = \"/.*/\";\n\n/**\n * Represents the default tag.\n */\nexport const DEFAULT_TAG: string = \"*\";\n\n/**\n * A metadata registry that captures information about bindings and their bound step bindings.\n */\nexport class BindingRegistry {\n  private _bindings = new Map<StepPattern, StepBinding[]>();\n\n  private _targetBindings = new Map<any, TargetBinding>();\n\n  /**\n   * Gets the binding registry singleton.\n   *\n   * @returns A [[BindingRegistry]].\n   */\n  public static get instance(): BindingRegistry {\n    const BINDING_REGISTRY_SLOTNAME: string =\n      \"__CUCUMBER_TSFLOW_BINDINGREGISTRY\";\n\n    const registry = (global as any)[BINDING_REGISTRY_SLOTNAME];\n\n    if (!registry) {\n      (global as any)[BINDING_REGISTRY_SLOTNAME] = new BindingRegistry();\n    }\n\n    return registry || (global as any)[BINDING_REGISTRY_SLOTNAME];\n  }\n\n  /**\n   * Updates the binding registry with information about the context types required by a\n   * binding class.\n   *\n   * @param targetPrototype The class representing the binding (constructor function).\n   * @param contextTypes An array of [[ContextType]] that define the types of objects that\n   * should be injected into the binding class during a scenario execution.\n   */\n  public registerContextTypesForTarget(\n    targetPrototype: any,\n    contextTypes?: ContextType[],\n  ): void {\n    if (!contextTypes) {\n      return;\n    }\n\n    let targetDecorations = this._targetBindings.get(targetPrototype);\n\n    if (!targetDecorations) {\n      targetDecorations = {\n        stepBindings: [],\n        contextTypes: [],\n      };\n\n      this._targetBindings.set(targetPrototype, targetDecorations);\n    }\n\n    targetDecorations.contextTypes = contextTypes;\n  }\n\n  /**\n   * Retrieves the context types that have been registered for a given binding class.\n   *\n   * @param targetPrototype The class representing the binding (constructor function).\n   *\n   * @returns An array of [[ContextType]] that have been registered for the specified\n   * binding class.\n   */\n  public getContextTypesForTarget(targetPrototype: any): ContextType[] {\n    const targetBinding = this._targetBindings.get(targetPrototype);\n\n    if (!targetBinding) {\n      return [];\n    }\n\n    return targetBinding.contextTypes;\n  }\n\n  /**\n   * Updates the binding registry indexes with a step binding.\n   *\n   * @param stepBinding The step binding that is to be registered with the binding registry.\n   */\n  public registerStepBinding(stepBinding: StepBinding): void {\n    if (!stepBinding.tag) {\n      stepBinding.tag = DEFAULT_TAG;\n    }\n\n    const stepPattern: StepPattern = stepBinding.stepPattern\n      ? stepBinding.stepPattern.toString()\n      : DEFAULT_STEP_PATTERN;\n\n    let stepBindings = this._bindings.get(stepPattern);\n\n    if (!stepBindings) {\n      stepBindings = [];\n\n      this._bindings.set(stepPattern, stepBindings);\n    }\n\n    logger.trace(\"Attempting to register step binding\", stepBinding);\n\n    if (!stepBindings.some((b) => isSameStepBinding(stepBinding, b))) {\n      logger.trace(\"Saving new step binding.\");\n      stepBindings.push(stepBinding);\n    }\n\n    // Index the step binding for the target\n\n    let targetBinding = this._targetBindings.get(stepBinding.targetPrototype);\n\n    if (!targetBinding) {\n      targetBinding = {\n        stepBindings: [],\n        contextTypes: [],\n      };\n\n      this._targetBindings.set(stepBinding.targetPrototype, targetBinding);\n    }\n\n    if (\n      !targetBinding.stepBindings.some((b) => isSameStepBinding(stepBinding, b))\n    ) {\n      logger.trace(\"Saving new step binding to target.\");\n      targetBinding.stepBindings.push(stepBinding);\n    }\n\n    logger.trace(\n      \"All target step bindings\",\n      targetBinding.stepBindings.map(\n        (binding) => `${binding.stepPattern} ${binding.tag}`,\n      ),\n    );\n\n    function isSameStepBinding(a: StepBinding, b: StepBinding) {\n      return (\n        a.callsite.filename === b.callsite.filename &&\n        a.callsite.lineNumber === b.callsite.lineNumber &&\n        String(a.stepPattern) === String(b.stepPattern) &&\n        a.targetPropertyKey === b.targetPropertyKey\n      );\n    }\n  }\n\n  /**\n   * Retrieves the step bindings that have been registered for a given binding class.\n   *\n   * @param targetPrototype The class representing the binding (constructor function).\n   *\n   * @returns An array of [[StepBinding]] objects that have been registered for the specified\n   * binding class.\n   */\n  public getStepBindingsForTarget(targetPrototype: any): StepBinding[] {\n    const targetBinding = this._targetBindings.get(targetPrototype);\n\n    if (!targetBinding) {\n      return [];\n    }\n\n    return targetBinding.stepBindings;\n  }\n\n  /**\n   * Retrieves the step bindings for a given step pattern and collection of tag names.\n   *\n   * @param stepPattern The step pattern to search.\n   *\n   * @returns An array of [[StepBinding]] that map to the given step pattern and set of tag names.\n   */\n  public getStepBindings(stepPattern: StepPattern): StepBinding[] {\n    return this._bindings.get(stepPattern) ?? [];\n  }\n}\n"
  },
  {
    "path": "cucumber-tsflow/src/hook-decorators.ts",
    "content": "import {\n  IDefineTestCaseHookOptions,\n  IDefineTestRunHookOptions,\n  IDefineTestStepHookOptions,\n} from \"@cucumber/cucumber/lib/support_code_library_builder/types\";\nimport { BindingRegistry } from \"./binding-registry\";\nimport { Callsite } from \"./our-callsite\";\nimport { StepBinding, StepBindingFlags } from \"./step-binding\";\nimport { normalizeTag } from \"./tag-normalization\";\n\n// Replace `tags` with `tag` for backwards compatibility\ntype HookOptions = Omit<IDefineTestCaseHookOptions, \"tags\"> & {\n  tag?: string;\n};\n\nfunction overloadedOption(tag?: string | HookOptions): HookOptions {\n  if (tag === undefined || typeof tag === \"string\") {\n    return { tag };\n  }\n\n  return tag;\n}\n\nfunction createHookDecorator(\n  flag: StepBindingFlags,\n  tagOrOption?: string | HookOptions,\n): MethodDecorator {\n  const callsite = Callsite.capture(2);\n\n  const { tag, timeout, ...hookOptions } = overloadedOption(tagOrOption);\n\n  return <T>(\n    target: any,\n    propertyKey: string | symbol,\n    descriptor: TypedPropertyDescriptor<T>,\n  ) => {\n    const stepBinding: StepBinding = {\n      stepPattern: \"\",\n      bindingType: flag,\n      targetPrototype: target,\n      targetPropertyKey: propertyKey,\n      argsLength: target[propertyKey].length,\n      tag: normalizeTag(tag),\n      callsite: callsite,\n      timeout: timeout,\n      hookOptions: hookOptions,\n    };\n\n    BindingRegistry.instance.registerStepBinding(stepBinding);\n\n    return descriptor;\n  };\n}\n\n/**\n * A method decorator that marks the associated function as a 'Before Scenario' step. The function is\n * executed before each scenario.\n *\n * @param tagOrOption An optional tag or hook options object.\n */\nexport function before(tagOrOption?: string | HookOptions): MethodDecorator {\n  return createHookDecorator(StepBindingFlags.before, tagOrOption);\n}\n\n/**\n * A method decorator that marks the associated function as an 'After Scenario' step. The function is\n * executed after each scenario.\n *\n * @param tagOrOption An optional tag or hook options object.\n */\nexport function after(tagOrOption?: string | HookOptions): MethodDecorator {\n  return createHookDecorator(StepBindingFlags.after, tagOrOption);\n}\n\n/**\n * A method decorator that marks the associated function as a 'Before Scenario' step. The function is\n * executed before each scenario.\n *\n * @param options Optional hook options object.\n */\nexport function beforeAll(\n  options?: IDefineTestRunHookOptions,\n): MethodDecorator {\n  return createHookDecorator(StepBindingFlags.beforeAll, options);\n}\n\n/**\n * A method decorator that marks the associated function as an 'After Scenario' step. The function is\n * executed after each scenario.\n *\n * @param options Optional hook options object.\n */\nexport function afterAll(options?: IDefineTestRunHookOptions): MethodDecorator {\n  return createHookDecorator(StepBindingFlags.afterAll, options);\n}\n\n/**\n * A method decorator that marks the associated function as a 'Before Step' step. The function is\n * executed before each step.\n *\n * @param options Optional hook options object.\n */\nexport function beforeStep(\n  options?: IDefineTestStepHookOptions,\n): MethodDecorator {\n  return createHookDecorator(StepBindingFlags.beforeStep, options);\n}\n\n/**\n * A method decorator that marks the associated function as an 'After Step' step. The function is\n * executed after each step.\n *\n * @param options Optional hook options object.\n */\nexport function afterStep(\n  options?: IDefineTestStepHookOptions,\n): MethodDecorator {\n  return createHookDecorator(StepBindingFlags.afterStep, options);\n}\n"
  },
  {
    "path": "cucumber-tsflow/src/index.ts",
    "content": "export * from \"./binding-decorator\";\nexport * from \"./hook-decorators\";\nexport * from \"./step-definition-decorators\";\nexport { ScenarioContext, ScenarioInfo } from \"./scenario-context\";\nexport * from \"./provided-context\";\n"
  },
  {
    "path": "cucumber-tsflow/src/logger.ts",
    "content": "import * as log4js from \"log4js\";\n\nconst logger = log4js.getLogger(\"cucumber-js.tsflow\");\n\nexport default logger;\n"
  },
  {
    "path": "cucumber-tsflow/src/managed-scenario-context.ts",
    "content": "import * as _ from \"underscore\";\nimport { BindingRegistry } from \"./binding-registry\";\nimport { ScenarioContext } from \"./scenario-context\";\nimport { ScenarioInfo } from \"./scenario-info\";\nimport { ContextType, isProvidedContextType } from \"./types\";\n\n/**\n * Represents a [[ScenarioContext]] implementation that manages a collection of context objects that\n * are created and used by binding classes during a running Cucumber scenario.\n */\nexport class ManagedScenarioContext implements ScenarioContext {\n  private _activeObjects = new Map<any, any>();\n\n  constructor(private readonly _scenarioInfo: ScenarioInfo) {}\n\n  /**\n   * Gets information about the scenario.\n   */\n  public get scenarioInfo(): ScenarioInfo {\n    return this._scenarioInfo;\n  }\n\n  public getOrActivateBindingClass(\n    targetPrototype: any,\n    contextTypes: ContextType[],\n  ): any {\n    return this.getOrActivateObject(targetPrototype, () => {\n      return this.activateBindingClass(targetPrototype, contextTypes);\n    });\n  }\n\n  public dispose(): void {\n    this._activeObjects.forEach((value: any) => {\n      if (typeof value.dispose === \"function\") {\n        value.dispose();\n      }\n    });\n  }\n\n  /**\n   * @internal\n   */\n  public getContextInstance(contextType: ContextType) {\n    return this.getOrActivateObject(contextType.prototype, () => {\n      if (isProvidedContextType(contextType)) {\n        throw new Error(\n          `The requested type \"${contextType.name}\" should be provided by cucumber-tsflow, but was not registered. Please report a bug.`,\n        );\n      }\n\n      return new contextType();\n    });\n  }\n\n  /**\n   * @internal\n   */\n  public addExternalObject(value: unknown) {\n    if (value == null) {\n      return;\n    }\n\n    const proto = value.constructor.prototype;\n\n    const existingObject = this._activeObjects.get(proto);\n\n    if (existingObject !== undefined) {\n      throw new Error(\n        `Conflicting objects of type \"${proto.name}\" registered.`,\n      );\n    }\n\n    this._activeObjects.set(proto, value);\n  }\n\n  private activateBindingClass(\n    targetPrototype: any,\n    contextTypes: ContextType[],\n  ): any {\n    const invokeBindingConstructor = (args: any[]): any => {\n      return new (targetPrototype.constructor as any)(...args);\n    };\n\n    const contextObjects = _.map(contextTypes, (contextType) => {\n      return this.getOrActivateBindingClass(\n        contextType.prototype,\n        BindingRegistry.instance.getContextTypesForTarget(\n          contextType.prototype,\n        ),\n      );\n    });\n\n    return invokeBindingConstructor(contextObjects);\n  }\n\n  private getOrActivateObject(\n    targetPrototype: any,\n    activatorFunc: () => any,\n  ): any {\n    let activeObject = this._activeObjects.get(targetPrototype);\n\n    if (activeObject) {\n      return activeObject;\n    }\n\n    activeObject = activatorFunc();\n\n    this._activeObjects.set(targetPrototype, activeObject);\n\n    return activeObject;\n  }\n}\n\nexport * from \"./scenario-context\";\n"
  },
  {
    "path": "cucumber-tsflow/src/our-callsite.ts",
    "content": "// @ts-ignore\nimport * as sourceMapSupport from \"source-map-support\";\n\n/**\n * Represents a callsite of where a step binding is being applied.\n */\nexport class Callsite {\n  /**\n   * Initializes a new [[Callsite]].\n   *\n   * @param filename The filename of the callsite.\n   * @param lineNumber The line number of the callsite.\n   */\n  constructor(\n    public filename: string,\n    public lineNumber: number,\n  ) {}\n\n  /**\n   * Captures the current [[Callsite]] object.\n   */\n  public static capture(up = 1): Callsite {\n    const stack = callsites()[up + 1];\n    const tsStack = sourceMapSupport.wrapCallSite(stack);\n    return new Callsite(\n      tsStack.getFileName() || \"\",\n      tsStack.getLineNumber() || -1,\n    );\n  }\n\n  /**\n   * Returns a string representation of the callsite.\n   *\n   * @returns A string representing the callsite formatted with the filename and line\n   * number.\n   */\n  public toString(): string {\n    return `${this.filename}:${this.lineNumber}`;\n  }\n}\n\nfunction callsites() {\n  const _prepareStackTrace = Error.prepareStackTrace;\n  try {\n    let result: NodeJS.CallSite[] = [];\n    Error.prepareStackTrace = (_, callSites) => {\n      const callSitesWithoutCurrent = callSites.slice(1);\n      result = callSitesWithoutCurrent;\n      return callSitesWithoutCurrent;\n    };\n\n    new Error().stack; // eslint-disable-line unicorn/error-message, no-unused-expressions\n    return result;\n  } finally {\n    Error.prepareStackTrace = _prepareStackTrace;\n  }\n}\n"
  },
  {
    "path": "cucumber-tsflow/src/provided-context.ts",
    "content": "/* tslint:disable:max-classes-per-file */\nimport {\n  ICreateAttachment,\n  ICreateLog,\n} from \"@cucumber/cucumber/lib/runtime/attachment_manager\";\nimport { Readable } from \"stream\";\n\nexport class WorldParameters<T = any> {\n  public constructor(public readonly value: T) {}\n}\n\nexport class CucumberLog {\n  public constructor(private readonly target: ICreateLog) {}\n\n  public log(text: string): void | Promise<void> {\n    return this.target(text);\n  }\n}\n\nexport class CucumberAttachments {\n  public constructor(private readonly target: ICreateAttachment) {}\n\n  public attach(data: string, mediaType?: string): void;\n  public attach(data: Buffer, mediaType: string): void;\n  public attach(data: Readable, mediaType: string): Promise<void>;\n  public attach(data: Readable, mediaType: string, callback: () => void): void;\n  public attach(...args: any): void | Promise<void> {\n    return this.target.apply(this, args);\n  }\n}\n"
  },
  {
    "path": "cucumber-tsflow/src/scenario-context.ts",
    "content": "import { ScenarioInfo } from \"./scenario-info\";\n\n/**\n * Provides context for the currently running Cucumber scenario.\n */\nexport interface ScenarioContext {\n  /**\n   * Gets information about the scenario.\n   *\n   */\n  scenarioInfo: ScenarioInfo;\n\n  /**\n   * Gets or sets an arbitary object within the running scenario.\n   */\n  [key: string]: any;\n}\n\nexport * from \"./scenario-info\";\n"
  },
  {
    "path": "cucumber-tsflow/src/scenario-info.ts",
    "content": "import logger from \"./logger\";\nimport { TagName } from \"./types\";\n\n/**\n * Provides information about a running Cucumber scenario.\n */\nexport class ScenarioInfo {\n  private _attributeTags?: Map<string, unknown>;\n\n  private _optionTags?: Map<string, string[]>;\n\n  private _flagTags?: Set<string>;\n\n  /**\n   * Initializes the [[ScenarioInfo]] object.\n   *\n   * @param scenarioTitle The string title of the currently running Cucumber scenario.\n   * @param tags An array of [[TagName]] representing the tags that are in scope for the currently\n   * running Cucumber scenario.\n   */\n  constructor(\n    public scenarioTitle: string,\n    public tags: TagName[],\n  ) {}\n\n  private static parseAttributeTags(tags: TagName[]): Map<string, unknown> {\n    const RGX = /^@?(?<attributeName>[\\w-]+)\\((?<value>.+?)\\)$/s;\n\n    const result = new Map<string, unknown>();\n\n    for (const tag of tags) {\n      const match = tag.match(RGX)?.groups;\n\n      if (match !== undefined) {\n        const { attributeName, value } = match;\n        result.set(attributeName, JSON.parse(value));\n      }\n    }\n\n    logger.trace(\"Parsed attribute tags\", { fromTags: tags, options: result });\n\n    return result;\n  }\n\n  private static parseOptionTags(tags: TagName[]): Map<string, string[]> {\n    const RGX = /^@?(?<option>[\\w-]+)\\((?<value>.+?)\\)$/s;\n\n    const result = new Map<string, string[]>();\n\n    for (const tag of tags) {\n      const match = tag.match(RGX)?.groups;\n\n      if (match !== undefined) {\n        const { option, value } = match;\n\n        const list = result.get(option);\n        if (list === undefined) {\n          result.set(option, [value]);\n        } else {\n          list.push(value);\n        }\n      }\n    }\n\n    logger.trace(\"Parsed options\", { fromTags: tags, options: result });\n\n    return result;\n  }\n\n  private static parseFlagTags(tags: TagName[]): Set<string> {\n    const RGX = /^@?(?<flag>[\\w-]+)$/s;\n\n    const result = new Set<string>();\n\n    for (const tag of tags) {\n      const flag = tag.match(RGX)?.groups?.flag;\n\n      if (flag !== undefined) {\n        result.add(flag);\n      }\n    }\n\n    logger.trace(\"Parsed flags\", { fromTags: tags, flags: result });\n\n    return result;\n  }\n\n  public getAttributeTag(name: string): unknown | undefined {\n    if (this._attributeTags === undefined) {\n      this._attributeTags = ScenarioInfo.parseAttributeTags(this.tags);\n    }\n\n    return this._attributeTags.get(name);\n  }\n\n  public getOptionTag(name: string): string | undefined {\n    if (this._optionTags === undefined) {\n      this._optionTags = ScenarioInfo.parseOptionTags(this.tags);\n    }\n\n    return this._optionTags.get(name)?.at(-1);\n  }\n\n  public getMultiOptionTag(name: string): string[] | undefined {\n    if (this._optionTags === undefined) {\n      this._optionTags = ScenarioInfo.parseOptionTags(this.tags);\n    }\n\n    return this._optionTags.get(name) ?? [];\n  }\n\n  public getFlag(name: string): boolean {\n    if (this._flagTags === undefined) {\n      this._flagTags = ScenarioInfo.parseFlagTags(this.tags);\n    }\n\n    return this._flagTags.has(name);\n  }\n}\n"
  },
  {
    "path": "cucumber-tsflow/src/step-binding-flags.ts",
    "content": "// tslint:disable:no-bitwise\n/**\n * The CucumberJS step binding types.\n */\nexport enum StepBindingFlags {\n  /**\n   * No bindings.\n   */\n  none = 0,\n\n  /**\n   * A 'Given' step definition binding.\n   */\n  given = 1 << 0,\n\n  /**\n   * A 'When' step definition binding.\n   */\n  when = 1 << 1,\n\n  /**\n   * A 'Then' step definition binding.\n   */\n  then = 1 << 2,\n\n  /**\n   * A 'Before' hook binding.\n   */\n  before = 1 << 3,\n\n  /**\n   * An 'After' hook binding.\n   */\n  after = 1 << 4,\n\n  /**\n   * A 'Before All' hook binding.\n   */\n  beforeAll = 1 << 5,\n\n  /**\n   * An 'After All' hook binding.\n   */\n  afterAll = 1 << 6,\n\n  /**\n   * A 'Before Step' hook binding.\n   */\n  beforeStep = 1 << 7,\n\n  /**\n   * An 'After Step' hook binding.\n   */\n  afterStep = 1 << 8,\n\n  /**\n   * All step definition bindings.\n   */\n  StepDefinitions = StepBindingFlags.given |\n    StepBindingFlags.when |\n    StepBindingFlags.then,\n\n  /**\n   * All hook bindings.\n   */\n  Hooks = StepBindingFlags.before |\n    StepBindingFlags.after |\n    StepBindingFlags.beforeAll |\n    StepBindingFlags.afterAll |\n    StepBindingFlags.beforeStep |\n    StepBindingFlags.afterStep,\n}\n"
  },
  {
    "path": "cucumber-tsflow/src/step-binding.ts",
    "content": "import type { IDefineTestStepHookOptions } from \"@cucumber/cucumber/lib/support_code_library_builder/types\";\nimport { Callsite } from \"./our-callsite\";\nimport { StepBindingFlags } from \"./step-binding-flags\";\n\n/**\n * Encapsulates data about a step binding.\n */\nexport interface StepBinding {\n  /**\n   * The step pattern.\n   */\n  stepPattern: RegExp | string;\n\n  /**\n   * The step binding type.\n   */\n  bindingType: StepBindingFlags;\n\n  /**\n   * The type that is associated with the current step binding.\n   */\n  targetPrototype: any;\n\n  /**\n   * The function name that is associated with the current step binding.\n   */\n  targetPropertyKey: string | symbol;\n\n  /**\n   * The count of arguments that have been specified on the [[StepBindingDescriptor.targetPropertyKey]].\n   */\n  argsLength: number;\n\n  /**\n   * The optional tag that is associated with the current step binding.\n   */\n  tag?: string;\n\n  /**\n   * The optiomal timeout that is associated with the current step binding.\n   */\n  timeout?: number;\n\n  /**\n   * The wrapper Option passing to cucumber\n   */\n  wrapperOption?: any;\n\n  hookOptions?: Omit<IDefineTestStepHookOptions, \"tags\" | \"timeout\">;\n\n  /**\n   * The callsite of the step binding.\n   */\n  callsite: Callsite;\n}\n\nexport * from \"./step-binding-flags\";\n"
  },
  {
    "path": "cucumber-tsflow/src/step-definition-decorators.ts",
    "content": "import { BindingRegistry } from \"./binding-registry\";\nimport logger from \"./logger\";\nimport { Callsite } from \"./our-callsite\";\nimport { StepBinding, StepBindingFlags } from \"./step-binding\";\nimport { normalizeTag } from \"./tag-normalization\";\n\ninterface StepOptions {\n  tag?: string;\n\n  timeout?: number;\n\n  wrapperOption?: any;\n}\n\nfunction overloadedOptions(\n  tag?: string | StepOptions,\n  timeout?: number,\n): StepOptions {\n  if (tag === undefined || typeof tag === \"string\") {\n    return { tag, timeout };\n  }\n\n  if (timeout !== undefined) {\n    throw new Error(\n      \"Cannot specify a separate timeout argument when an options object is given.\",\n    );\n  }\n\n  return tag;\n}\n\n/**\n * A method decorator that marks the associated function as a 'Given' step.\n *\n * @param stepPattern The regular expression that will be used to match steps.\n * @param tag An optional tag or an options object.\n * @param timeout An optional timeout.\n */\nexport function given(\n  stepPattern: RegExp | string,\n  tagOrOption?: string | StepOptions,\n  timeout?: number,\n): MethodDecorator {\n  const callsite = Callsite.capture();\n\n  const options = overloadedOptions(tagOrOption, timeout);\n\n  return <T>(\n    target: any,\n    propertyKey: string | symbol,\n    descriptor: TypedPropertyDescriptor<T>,\n  ) => {\n    const stepBinding: StepBinding = {\n      stepPattern: stepPattern,\n      bindingType: StepBindingFlags.given,\n      targetPrototype: target,\n      targetPropertyKey: propertyKey,\n      argsLength: target[propertyKey].length,\n      callsite: callsite,\n      tag: normalizeTag(options.tag),\n      timeout: options.timeout,\n      wrapperOption: options.wrapperOption,\n    };\n\n    logger.trace(\"Registering step definition:\", stepBinding);\n\n    BindingRegistry.instance.registerStepBinding(stepBinding);\n\n    return descriptor;\n  };\n}\n\n/**\n * A method decorator that marks the associated function as a 'When' step.\n *\n * @param stepPattern The regular expression that will be used to match steps.\n * @param tag An optional tag.\n * @param timeout An optional timeout.\n */\nexport function when(\n  stepPattern: RegExp | string,\n  tagOrOption?: string | StepOptions,\n  timeout?: number,\n): MethodDecorator {\n  const callsite = Callsite.capture();\n\n  const options = overloadedOptions(tagOrOption, timeout);\n\n  return <T>(\n    target: any,\n    propertyKey: string | symbol,\n    descriptor: TypedPropertyDescriptor<T>,\n  ) => {\n    const stepBinding: StepBinding = {\n      stepPattern: stepPattern,\n      bindingType: StepBindingFlags.when,\n      targetPrototype: target,\n      targetPropertyKey: propertyKey,\n      argsLength: target[propertyKey].length,\n      callsite: callsite,\n      tag: normalizeTag(options.tag),\n      timeout: options.timeout,\n      wrapperOption: options.wrapperOption,\n    };\n\n    BindingRegistry.instance.registerStepBinding(stepBinding);\n\n    return descriptor;\n  };\n}\n\n/**\n * A method decorator that marks the associated function as a 'Then' step.\n *\n * @param stepPattern The regular expression that will be used to match steps.\n * @param tag An optional tag.\n * @param timeout An optional timeout.\n */\nexport function then(\n  stepPattern: RegExp | string,\n  tagOrOption?: string | StepOptions,\n  timeout?: number,\n): MethodDecorator {\n  const callsite = Callsite.capture();\n\n  const options = overloadedOptions(tagOrOption, timeout);\n\n  return <T>(\n    target: any,\n    propertyKey: string | symbol,\n    descriptor: TypedPropertyDescriptor<T>,\n  ) => {\n    const stepBinding: StepBinding = {\n      stepPattern: stepPattern,\n      bindingType: StepBindingFlags.then,\n      targetPrototype: target,\n      targetPropertyKey: propertyKey,\n      argsLength: target[propertyKey].length,\n      callsite: callsite,\n      tag: normalizeTag(options.tag),\n      timeout: options.timeout,\n      wrapperOption: options.wrapperOption,\n    };\n\n    BindingRegistry.instance.registerStepBinding(stepBinding);\n\n    return descriptor;\n  };\n}\n"
  },
  {
    "path": "cucumber-tsflow/src/tag-normalization.ts",
    "content": "export function normalizeTag(tag?: string): string | undefined {\n  // Tag is not provided or already includes a @\n  if (tag === undefined || tag.includes(\"@\")) {\n    return tag;\n  }\n\n  // If a tag doesn't include any @, for compatibility, prefix it with a @\n  return `@${tag}`;\n}\n"
  },
  {
    "path": "cucumber-tsflow/src/types.ts",
    "content": "import {\n  CucumberAttachments,\n  CucumberLog,\n  WorldParameters,\n} from \"./provided-context\";\nimport { ScenarioInfo } from \"./scenario-info\";\n\n/**\n * A string representation of a [[RegExp]] that defines a Cucumber step pattern.\n */\nexport type StepPattern = string;\n\n/**\n * A Cucumber tag name.\n */\nexport type TagName = string;\n\n/**\n * Represents a class that will be injected into a binding class to provide context\n * during the execution of a Cucumber scenario.\n */\nexport type CustomContextType = new (...args: any[]) => any;\n\nexport type ProvidedContextType =\n  | typeof ScenarioInfo\n  | typeof WorldParameters\n  | typeof CucumberLog\n  | typeof CucumberAttachments;\n\nexport type ContextType = ProvidedContextType | CustomContextType;\n\nconst providedPrototypes: ProvidedContextType[] = [\n  WorldParameters,\n  CucumberLog,\n  CucumberAttachments,\n  ScenarioInfo,\n];\n\nexport function isProvidedContextType(\n  typ: ContextType,\n): typ is ProvidedContextType {\n  return providedPrototypes.some((proto) => Object.is(typ, proto));\n}\n\nexport type TypeDecorator = <T>(target: new (...args: any[]) => T) => void;\n"
  },
  {
    "path": "cucumber-tsflow/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"module\": \"umd\",\n    \"moduleResolution\": \"node\",\n    \"target\": \"es2022\",\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"removeComments\": false,\n    \"experimentalDecorators\": true,\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\"\n  },\n  \"include\": [\"./src/**/*.ts\"]\n}\n"
  },
  {
    "path": "cucumber-tsflow-specs/features/basic-test.feature",
    "content": "Feature: Binding steps\n\n    Scenario Outline: Bind steps with <Bind Mode>\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: some feature\n              Scenario: scenario a\n                Given step one\n                When step two\n                Then step three\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, when, then} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                @given(<Step 1>)\n                public given() {\n                    console.log(\"Step one executed\");\n                }\n\n                @when(<Step 2>)\n                public when() {\n                    console.log(\"Step two executed\");\n                }\n\n                @then(<Step 3>)\n                public then() {\n                    console.log(\"Step three executed\");\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output contains \"Step one executed\"\n        And the output contains \"Step two executed\"\n        And the output contains \"Step three executed\"\n\n        Examples:\n            | Bind Mode | Step 1       | Step 2       | Step 3         |\n            | names     | \"step one\"   | \"step two\"   | \"step three\"   |\n            | regex     | /^step one$/ | /^step two$/ | /^step three$/ |\n\n    Scenario: Failing test\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Some feature\n              Scenario: example\n                Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given} from 'cucumber-tsflow';\n\n            @binding()\n            class Step {\n                @given(\"a step\")\n                public step() {\n                    throw new Error(\"Inner error message.\");\n                }\n            }\n\n            export = Step;\n            \"\"\"\n        When I run cucumber-js\n        Then it fails\n        And the output contains \"Error: Inner error message.\"\n\n    Scenario: Missing step definition\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: some feature\n              Scenario: scenario a\n                Given missing step\n            \"\"\"\n        When I run cucumber-js\n        Then it fails\n        # TODO: https://github.com/timjroberts/cucumber-js-tsflow/issues/97\n        And the output contains \"Implement with the following snippet:\"\n"
  },
  {
    "path": "cucumber-tsflow-specs/features/cucumber-context-objects.feature",
    "content": "Feature: Cucumber context objects\n\n    Scenario: Using the cucumber logger\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n              Scenario: example\n                Given a step\n                And another step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, CucumberLog} from 'cucumber-tsflow';\n\n            @binding([CucumberLog])\n            class Steps {\n                public constructor(private readonly logger: CucumberLog) {}\n\n                @given(\"a step\")\n                public one() {\n                    this.logger.log(\"logged value\");\n                }\n\n                @given(\"another step\")\n                public noop() {}\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And scenario \"example\" step \"Given a step\" has the logs:\n            | logged value |\n        And scenario \"example\" step \"And another step\" has no attachments\n\n    Scenario: Using the cucumber attachments\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n              Scenario: example\n                Given a step\n                And another step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, CucumberAttachments} from 'cucumber-tsflow';\n\n            @binding([CucumberAttachments])\n            class Steps {\n                public constructor(private readonly att: CucumberAttachments) {}\n\n                @given(\"a step\")\n                public one() {\n                    this.att.attach(\"my string\", \"text/plain+custom\");\n                }\n\n                @given(\"another step\")\n                public noop() {}\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And scenario \"example\" step \"Given a step\" has the attachments:\n            | DATA      | MEDIA TYPE        | MEDIA ENCODING |\n            | my string | text/plain+custom | IDENTITY       |\n        And scenario \"example\" step \"And another step\" has no attachments\n\n    Scenario: Using the cucumber attachments in hooks\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n              Scenario: example\n                Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, before, after, given, CucumberAttachments} from 'cucumber-tsflow';\n\n            @binding([CucumberAttachments])\n            class Steps {\n                public constructor(private readonly att: CucumberAttachments) {}\n\n                @before()\n                public before() {\n                    this.att.attach(\"my first string\", \"text/plain+custom\");\n                }\n\n                @given(\"a step\")\n                public one() {}\n\n                @after()\n                public after() {\n                    this.att.attach(\"my second string\", \"text/plain+custom\");\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And scenario \"example\" \"Before\" hook has the attachments:\n            | DATA            | MEDIA TYPE        | MEDIA ENCODING |\n            | my first string | text/plain+custom | IDENTITY       |\n        And scenario \"example\" \"After\" hook has the attachments:\n            | DATA             | MEDIA TYPE        | MEDIA ENCODING |\n            | my second string | text/plain+custom | IDENTITY       |\n        And scenario \"example\" step \"Given a step\" has no attachments\n\n    Scenario: Using world parameters\n        Given a file named \"cucumber.js\" with:\n            \"\"\"js\n            const cucumberPkg = require(\"@cucumber/cucumber/package.json\");\n\n            module.exports = cucumberPkg.version.startsWith(\"7.\")\n              ? {\n                default: [\n                  \"--world-parameters '{\\\"name\\\":\\\"Earth\\\"}'\"\n                ].join(\" \")\n              }\n              : {\n                default: {\n                  worldParameters: {\n                    name: 'Earth'\n                  }\n                }\n              };\n            \"\"\"\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n              Scenario: example\n                Then the world name is \"Earth\"\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, then, WorldParameters} from 'cucumber-tsflow';\n            import * as assert from 'node:assert';\n\n            @binding([WorldParameters])\n            class Steps {\n                public constructor(private readonly world: WorldParameters) {}\n\n                @then(\"the world name is {string}\")\n                public checkWorldName(name: string) {\n                    assert.deepStrictEqual(this.world.value, {name})\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n\n        When I run cucumber-js\n        Then it passes\n\n    Scenario: Reading the scenario information\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            @foo\n            Feature: Feature\n              @bar\n              Scenario: example\n                Then the scenario title is \"example\"\n                And  the tags are [ \"@foo\", \"@bar\" ]\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, then, ScenarioInfo} from 'cucumber-tsflow';\n            import * as assert from 'node:assert';\n\n            @binding([ScenarioInfo])\n            class Steps {\n                public constructor(private readonly scenario: ScenarioInfo) {}\n\n                @then(\"the scenario title is {string}\")\n                public checkScenarioName(name: string) {\n                    assert.strictEqual(this.scenario.scenarioTitle, name);\n                }\n\n                @then(\"the tags are {}\")\n                public checkTags(tags: string) {\n                    assert.deepStrictEqual(this.scenario.tags, JSON.parse(tags));\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n\n        When I run cucumber-js\n        Then it passes\n"
  },
  {
    "path": "cucumber-tsflow-specs/features/custom-context-objects.feature",
    "content": "Feature: Custom context objects\n\n    Scenario: Using custom context objects to share state\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: some feature\n              Scenario: scenario a\n                Given the state is \"initial value\"\n                When I set the state to \"step value\"\n                Then the state is \"step value\"\n            \"\"\"\n        And a file named \"support/state.ts\" with:\n            \"\"\"ts\n            export class State {\n                public value: string = \"initial value\";\n            }\n            \"\"\"\n        And a file named \"step_definitions/one.ts\" with:\n            \"\"\"ts\n            import {State} from '../support/state';\n            import {binding, when} from 'cucumber-tsflow';\n\n            @binding([State])\n            class Steps {\n                public constructor(private readonly state: State) {}\n\n                @when(\"I set the state to {string}\")\n                public setState(newValue: string) {\n                    this.state.value = newValue;\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        And a file named \"step_definitions/two.ts\" with:\n            \"\"\"ts\n            import {State} from '../support/state';\n            import {binding, then} from 'cucumber-tsflow';\n            import * as assert from 'node:assert';\n\n            @binding([State])\n            class Steps {\n                public constructor(private readonly state: State) {}\n\n                @then(\"the state is {string}\")\n                public checkValue(value: string) {\n                    console.log(`The state is '${this.state.value}'`);\n                    assert.equal(this.state.value, value, \"State value does not match\");\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output contains \"The state is 'initial value'\"\n        And the output contains \"The state is 'step value'\"\n\n    Scenario: Custom context objects can depend on other custom context objects two levels deep\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: some feature\n              Scenario: scenario a\n                Given the state is \"initial value\"\n                When I set the state to \"step value\"\n                Then the state is \"step value\"\n            \"\"\"\n        And a file named \"support/level-one-state.ts\" with:\n            \"\"\"ts\n            import {binding} from 'cucumber-tsflow';\n            import {LevelTwoState} from './level-two-state';\n\n            @binding([LevelTwoState])\n            export class LevelOneState {\n                constructor(public levelTwoState: LevelTwoState) {\n                }\n            }\n            \"\"\"\n        And a file named \"support/level-two-state.ts\" with:\n            \"\"\"ts\n            export class LevelTwoState {\n                public value: string = \"initial value\";\n            }\n            \"\"\"\n        And a file named \"step_definitions/one.ts\" with:\n            \"\"\"ts\n            import {LevelTwoState} from '../support/level-two-state';\n            import {binding, when} from 'cucumber-tsflow';\n\n            @binding([LevelTwoState])\n            class Steps {\n                public constructor(private readonly levelTwoState: LevelTwoState) {\n                }\n\n                @when(\"I set the state to {string}\")\n                public setState(newValue: string) {\n                    this.levelTwoState.value = newValue;\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        And a file named \"step_definitions/two.ts\" with:\n            \"\"\"ts\n            import {LevelOneState} from '../support/level-one-state';\n            import {binding, then} from 'cucumber-tsflow';\n            import * as assert from 'node:assert';\n\n            @binding([LevelOneState])\n            class Steps {\n                public constructor(private readonly levelOneState: LevelOneState) {}\n\n                @then(\"the state is {string}\")\n                public checkValue(value: string) {\n                    console.log(`The state is '${this.levelOneState.levelTwoState.value}'`);\n                    assert.equal(this.levelOneState.levelTwoState.value, value, \"State value does not match\");\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output contains \"The state is 'initial value'\"\n        And the output contains \"The state is 'step value'\"\n\n    Scenario: Custom context objects can depend on other custom context objects three levels deep\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: some feature\n              Scenario: scenario a\n                Given the state is \"initial value\"\n                When I set the state to \"step value\"\n                Then the state is \"step value\"\n            \"\"\"\n        And a file named \"support/level-one-state.ts\" with:\n            \"\"\"ts\n            import {binding} from 'cucumber-tsflow';\n            import {LevelTwoState} from './level-two-state';\n\n            @binding([LevelTwoState])\n            export class LevelOneState {\n                constructor(public levelTwoState: LevelTwoState) {\n                }\n            }\n            \"\"\"\n        And a file named \"support/level-two-state.ts\" with:\n            \"\"\"ts\n            import {binding} from 'cucumber-tsflow';\n            import {LevelThreeState} from './level-three-state';\n\n            @binding([LevelThreeState])\n            export class LevelTwoState {\n                constructor(public levelThreeState: LevelThreeState) {\n                }\n            }\n            \"\"\"\n        And a file named \"support/level-three-state.ts\" with:\n            \"\"\"ts\n            export class LevelThreeState {\n                public value: string = \"initial value\";\n            }\n            \"\"\"\n        And a file named \"step_definitions/one.ts\" with:\n            \"\"\"ts\n            import {LevelThreeState} from '../support/level-three-state';\n            import {binding, when} from 'cucumber-tsflow';\n\n            @binding([LevelThreeState])\n            class Steps {\n                public constructor(private readonly levelThreeState: LevelThreeState) {\n                }\n\n                @when(\"I set the state to {string}\")\n                public setState(newValue: string) {\n                    this.levelThreeState.value = newValue;\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        And a file named \"step_definitions/two.ts\" with:\n            \"\"\"ts\n            import {LevelOneState} from '../support/level-one-state';\n            import {binding, then} from 'cucumber-tsflow';\n            import * as assert from 'node:assert';\n\n            @binding([LevelOneState])\n            class Steps {\n                public constructor(private readonly levelOneState: LevelOneState) {}\n\n                @then(\"the state is {string}\")\n                public checkValue(value: string) {\n                    console.log(`The state is '${this.levelOneState.levelTwoState.levelThreeState.value}'`);\n                    assert.equal(this.levelOneState.levelTwoState.levelThreeState.value, value, \"State value does not match\");\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output contains \"The state is 'initial value'\"\n        And the output contains \"The state is 'step value'\"\n\n    Scenario: Cyclic imports are detected and communicated to the developer\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: some feature\n              Scenario: scenario a\n                Given the state is \"initial value\"\n                When I set the state to \"step value\"\n                Then the state is \"step value\"\n            \"\"\"\n        And a file named \"support/state-one.ts\" with:\n            \"\"\"ts\n            import {binding} from 'cucumber-tsflow';\n            import {StateTwo} from './state-two';\n\n            @binding([StateTwo])\n            export class StateOne {\n                constructor(public stateTwo: StateTwo) {\n                }\n            }\n            \"\"\"\n        And a file named \"support/state-two.ts\" with:\n            \"\"\"ts\n            import {StateOne} from './state-one';\n            import {binding} from 'cucumber-tsflow';\n\n            @binding([StateOne])\n            export class StateTwo {\n                public value: string = \"initial value\";\n                constructor(public stateOne: StateOne) {\n                }\n            }\n            \"\"\"\n        And a file named \"step_definitions/one.ts\" with:\n            \"\"\"ts\n            import {StateTwo} from '../support/state-two';\n            import {binding, when} from 'cucumber-tsflow';\n\n            @binding([StateTwo])\n            class Steps {\n                public constructor(private readonly stateTwo: StateTwo) {\n                }\n\n                @when(\"I set the state to {string}\")\n                public setState(newValue: string) {\n                    this.stateTwo.value = newValue;\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        And a file named \"step_definitions/two.ts\" with:\n            \"\"\"ts\n            import {StateOne} from '../support/state-one';\n            import {binding, then} from 'cucumber-tsflow';\n            import * as assert from 'node:assert';\n\n            @binding([StateOne])\n            class Steps {\n                public constructor(private readonly stateOne: StateOne) {}\n\n                @then(\"the state is {string}\")\n                public checkValue(value: string) {\n                    console.log(`The state is '${this.stateOne.stateTwo.value}'`);\n                    assert.equal(this.stateOne.stateTwo.value, value, \"State value does not match\");\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it fails\n        And the error output contains text:\n            \"\"\"\n            Error: Undefined dependency detected in StateOne. You possibly have an import cycle.\n            See https://nodejs.org/api/modules.html#modules_cycles\n            \"\"\"\n\n    Scenario: Cyclic state dependencies are detected and communicated to the developer\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: some feature\n              Scenario: scenario a\n                Given the state is \"initial value\"\n                When I set the state to \"step value\"\n                Then the state is \"step value\"\n            \"\"\"\n        And a file named \"support/state.ts\" with:\n            \"\"\"ts\n            import {binding} from 'cucumber-tsflow';\n\n            export class StateOne {\n                constructor(public stateTwo: StateTwo) { }\n            }\n\n            @binding([StateOne])\n            export class StateTwo {\n                public value: string = \"initial value\";\n                constructor(public stateOne: StateOne) { }\n            }\n\n            exports.StateOne = binding([StateTwo])(StateOne);\n            \"\"\"\n        And a file named \"step_definitions/one.ts\" with:\n            \"\"\"ts\n            import {StateTwo} from '../support/state';\n            import {binding, when} from 'cucumber-tsflow';\n\n            @binding([StateTwo])\n            class StepsOne {\n                public constructor(private readonly stateTwo: StateTwo) {\n                }\n\n                @when(\"I set the state to {string}\")\n                public setState(newValue: string) {\n                    this.stateTwo.value = newValue;\n                }\n            }\n\n            export = StepsOne;\n            \"\"\"\n        And a file named \"step_definitions/two.ts\" with:\n            \"\"\"ts\n            import {StateOne} from '../support/state';\n            import {binding, then} from 'cucumber-tsflow';\n            import * as assert from 'node:assert';\n\n            @binding([StateOne])\n            class StepsTwo {\n                public constructor(private readonly stateOne: StateOne) {}\n\n                @then(\"the state is {string}\")\n                public checkValue(value: string) {\n                    console.log(`The state is '${this.stateOne.stateTwo.value}'`);\n                    assert.equal(this.stateOne.stateTwo.value, value, \"State value does not match\");\n                }\n            }\n\n            export = StepsTwo;\n            \"\"\"\n        When I run cucumber-js\n        Then it fails\n        And the error output contains text:\n            \"\"\"\n            Error: Cyclic dependency detected: StateOne -> StateTwo -> StateOne\n            \"\"\"\n\n    Scenario: Cyclic single-file state dependencies are detected and communicated to the developer\n        Given a file named \"features/a.feature\" with:\n        \"\"\"feature\n        Feature: some feature\n          Scenario: scenario a\n            Given the state is \"initial value\"\n            When I set the state to \"step value\"\n            Then the state is \"step value\"\n        \"\"\"\n        And a file named \"support/circular.ts\" with:\n        \"\"\"ts\n        import {binding} from 'cucumber-tsflow';\n\n        export class StateOne {\n            constructor(public stateTwo: StateTwo) { }\n        }\n\n        @binding([StateOne])\n        export class StateTwo {\n            public value: string = \"initial value\";\n            constructor(public stateOne: StateOne) { }\n        }\n\n        exports.StateOne = binding([StateTwo])(StateOne);\n        \"\"\"\n        And a file named \"step_definitions/one.ts\" with:\n        \"\"\"ts\n        import {StateTwo} from '../support/circular';\n        import * as assert from 'node:assert';\n        import {binding, when, then} from 'cucumber-tsflow';\n\n        @binding([StateTwo])\n        class Steps {\n            public constructor(private readonly stateTwo: StateTwo) {\n            }\n\n            @when(\"I set the state to {string}\")\n            public setState(newValue: string) {\n                this.stateTwo.value = newValue;\n            }\n\n            @then(\"the state is {string}\")\n            public checkValue(value: string) {\n                console.log(`The state is '${this.stateTwo.value}'`);\n                assert.equal(this.stateTwo.value, value, \"State value does not match\");\n            }\n        }\n\n        export = Steps;\n        \"\"\"\n        When I run cucumber-js\n        Then it fails\n        And the error output contains text:\n            \"\"\"\n            Error: Cyclic dependency detected: StateOne -> StateTwo -> StateOne\n            \"\"\"\n"
  },
  {
    "path": "cucumber-tsflow-specs/features/external-context-extraction.feature",
    "content": "Feature: Extracing context objects from World externally\n\n    Scenario: Failing to retrieve state from a non-initialized World object\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: some feature\n              Scenario: scenario a\n                Given a step\n            \"\"\"\n        And a file named \"support/state.ts\" with:\n            \"\"\"ts\n            export class State {\n                public constructor() {\n                  console.log('State has been initialized');\n                }\n\n                public value: string = \"initial value\";\n            }\n            \"\"\"\n        And a file named \"step_definitions/a.ts\" with:\n            \"\"\"ts\n            import {State} from '../support/state';\n            import {Before, Given} from '@cucumber/cucumber';\n            import {getBindingFromWorld} from 'cucumber-tsflow';\n\n            Before(function() {\n              const state = getBindingFromWorld(this, State);\n\n              console.log(`Cucumber-style before. State is \"${state.value}\"`);\n\n              state.value = 'cucumber-style before';\n            });\n\n            Given('a step', function() {\n              const state = getBindingFromWorld(this, State);\n\n              console.log(`Cucumber-style step. State is \"${state.value}\"`);\n\n              state.value = 'cucumber-style step';\n            });\n            \"\"\"\n        When I run cucumber-js\n        Then it fails\n        And the output contains text:\n            \"\"\"\n            Before # step_definitions/a.ts:5\n                   Error: Scenario context have not been initialized in the provided World object.\n            \"\"\"\n\n    Scenario: Sharing a state between native Cucumber and Decorator-style steps\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: some feature\n              Scenario: scenario a\n                Given a cucumber-style step is called\n                And a decorator-style step is called\n            \"\"\"\n        And a file named \"support/state.ts\" with:\n            \"\"\"ts\n            export class State {\n                public constructor() {\n                  console.log('State has been initialized');\n                }\n\n                public value: string = \"initial value\";\n            }\n            \"\"\"\n        And a file named \"step_definitions/a.ts\" with:\n            \"\"\"ts\n            import {State} from '../support/state';\n            import {Before, Given} from '@cucumber/cucumber';\n            import {ensureWorldIsInitialized,getBindingFromWorld} from 'cucumber-tsflow';\n\n            ensureWorldIsInitialized();\n\n            Before(function() {\n              const state = getBindingFromWorld(this, State);\n\n              console.log(`Cucumber-style before. State is \"${state.value}\"`);\n\n              state.value = 'cucumber-style before';\n            });\n\n            Given('a cucumber-style step is called', function() {\n              const state = getBindingFromWorld(this, State);\n\n              console.log(`Cucumber-style step. State is \"${state.value}\"`);\n\n              state.value = 'cucumber-style step';\n            });\n            \"\"\"\n        And a file named \"step_definitions/b.ts\" with:\n            \"\"\"ts\n            import {State} from '../support/state';\n            import {binding, before, given} from 'cucumber-tsflow';\n\n            @binding([State])\n            class Steps {\n                public constructor(private readonly state: State) {}\n\n                @before()\n                public before() {\n                    console.log(`Decorator-style before. State is \"${this.state.value}\"`);\n\n                    this.state.value = 'decorator-style before';\n                }\n\n                @given('a decorator-style step is called')\n                public step() {\n                    console.log(`Decorator-style step. State is \"${this.state.value}\"`);\n\n                    this.state.value = 'decorator-style step';\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output contains text:\n            \"\"\"\n            .State has been initialized\n            Cucumber-style before. State is \"initial value\"\n            .Decorator-style before. State is \"cucumber-style before\"\n            .Cucumber-style step. State is \"decorator-style before\"\n            .Decorator-style step. State is \"cucumber-style step\"\n            \"\"\"\n\n    Scenario: Share state between underlying cucumber functionality and TSFlow functionality\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: some feature\n              Scenario: scenario before year 2k\n                Given a step for 1999-03-04T21:43:54Z\n              @y2k\n              Scenario: scenario after year 2k\n                Given a step for 2023-09-13T12:34:56.789Z\n            \"\"\"\n        And a file named \"support/state.ts\" with:\n            \"\"\"ts\n            export class State {\n                public maxDate = new Date('2000-01-01');\n            }\n            \"\"\"\n        And a file named \"step_definitions/a.ts\" with:\n            \"\"\"ts\n            import {State} from '../support/state';\n            import {defineParameterType} from '@cucumber/cucumber';\n            import {ensureWorldIsInitialized,getBindingFromWorld} from 'cucumber-tsflow';\n\n            ensureWorldIsInitialized();\n\n            defineParameterType({\n              name: 'datetime',\n              regexp: /[+-]?\\d{4,}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{1,6})?Z/,\n              preferForRegexpMatch: true,\n              useForSnippets: true,\n              transformer: function (datetime): Date {\n                const state = getBindingFromWorld(this, State);\n\n                const date = new Date(datetime);\n\n                console.log(`Parsing date up to ${state.maxDate.toISOString()}`);\n\n                if (state.maxDate.valueOf() < date.valueOf()) {\n                    throw new Error('Date after maximum date.');\n                }\n\n                return new Date(datetime)\n              }\n            });\n            \"\"\"\n        And a file named \"step_definitions/b.ts\" with:\n            \"\"\"ts\n            import {State} from '../support/state';\n            import {binding, before, given} from 'cucumber-tsflow';\n\n            @binding([State])\n            class Steps {\n                public constructor(private readonly state: State) {}\n\n                @before({tag: '@y2k'})\n                public before() {\n                    this.state.maxDate = new Date('3000-01-01');\n                }\n\n                @given('a step for {datetime}')\n                public step(datetime: Date) {\n                    console.log(`Step received a ${datetime.constructor.name}: ${datetime.toISOString()}`);\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output contains text:\n            \"\"\"\n            .Parsing date up to 2000-01-01T00:00:00.000Z\n            Step received a Date: 1999-03-04T21:43:54.000Z\n            ....Parsing date up to 3000-01-01T00:00:00.000Z\n            Step received a Date: 2023-09-13T12:34:56.789Z\n            \"\"\"\n"
  },
  {
    "path": "cucumber-tsflow-specs/features/global-hooks.feature",
    "content": "Feature: Support for Cucumber hooks\n\n    Scenario: Binding a beforeAll hook\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example one\n                    Given a step\n\n                Scenario: example two\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, beforeAll} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                @beforeAll()\n                public static hook() {\n                    console.log('hook exec')\n                }\n\n                @given(\"a step\")\n                public given() {\n                    console.log('step exec');\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output contains text once:\n            \"\"\"\n            hook exec\n            .step exec\n            ...step exec\n            ..\n            \"\"\"\n\n    Scenario: Binding a afterAll hook\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example one\n                    Given a step\n\n                Scenario: example two\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, afterAll} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                @afterAll()\n                public static hook() {\n                    console.log('hook exec')\n                }\n\n                @given(\"a step\")\n                public given() {\n                    console.log('step exec');\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output contains text once:\n            \"\"\"\n            .step exec\n            ...step exec\n            ..hook exec\n            \"\"\"\n\n    Scenario: Binding beforeAll and afterAll hooks\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example one\n                    Given a step\n\n                Scenario: example two\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, beforeAll, afterAll} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                @beforeAll()\n                public static before() {\n                    console.log('before exec')\n                }\n\n                @afterAll()\n                public static after() {\n                    console.log('after exec')\n                }\n\n                @given(\"a step\")\n                public given() {\n                    console.log('step exec');\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output contains text once:\n            \"\"\"\n            before exec\n            .step exec\n            ...step exec\n            ..after exec\n            \"\"\"\n\n    Scenario: Binding multiple beforeAll hooks\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example one\n                    Given a step\n\n                Scenario: example two\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, beforeAll} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                @beforeAll()\n                public static one() {\n                    console.log('one')\n                }\n\n                @beforeAll()\n                public static two() {\n                    console.log('two')\n                }\n\n                @given(\"a step\")\n                public given() {\n                    console.log('step');\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output contains text once:\n            \"\"\"\n            one\n            two\n            .step\n            ...step\n            ..\n            \"\"\"\n\n    Scenario: Binding multiple global hooks on the same line\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example one\n                    Given a step\n\n                Scenario: example two\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, beforeAll} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                @beforeAll()public static one() { console.log('one') }@beforeAll()public static two() { console.log('two') }\n\n                @given(\"a step\")\n                public given() {\n                    console.log('step');\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output contains text once:\n            \"\"\"\n            one\n            two\n            .step\n            ...step\n            ..\n            \"\"\"\n\n    Scenario: Binding multiple afterAll hooks\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example one\n                    Given a step\n\n                Scenario: example two\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, afterAll} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                @afterAll()\n                public static one() {\n                    console.log('one')\n                }\n\n                @afterAll()\n                public static two() {\n                    console.log('two')\n                }\n\n                @given(\"a step\")\n                public given() {\n                    console.log('step');\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output contains text once:\n            \"\"\"\n            .step\n            ...step\n            ..two\n            one\n            \"\"\"\n"
  },
  {
    "path": "cucumber-tsflow-specs/features/hooks.feature",
    "content": "Feature: Support for Cucumber hooks\n\n    Scenario: Binding a before hook\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, before} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                private state = 'hook has not executed';\n\n                @before()\n                public hook() {\n                    this.state = 'hook has executed';\n                }\n\n                @given(\"a step\")\n                public given() {\n                    console.log(this.state);\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output does not contain \"hook has not executed\"\n        And the output contains \"hook has executed\"\n\n    Scenario: Binding an after hook\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, after} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                private state = 'step has not executed';\n\n                @after()\n                public hook() {\n                    console.log(this.state);\n                }\n\n                @given(\"a step\")\n                public given() {\n                    this.state = 'step has executed';\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output does not contain \"step has not executed\"\n        And the output contains \"step has executed\"\n\n    Scenario: Binding before and after hooks\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, before, after} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                private state = 'hook has not executed';\n\n                @before()\n                public before() {\n                    this.state = 'before hook executed';\n                }\n\n                @given(\"a step\")\n                public given() {\n                    console.log(this.state);\n                    this.state = 'step has executed';\n                }\n\n                @after()\n                public after() {\n                    console.log(this.state);\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output does not contain \"step has not executed\"\n        And the output contains text:\n            \"\"\"\n            .before hook executed\n            .step has executed\n            \"\"\"\n\n    Scenario: Binding multiple before hooks\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, before} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                private state = 'no hook executed';\n\n                @before()\n                public hookOne() {\n                    console.log(this.state)\n                    this.state = 'hook one has executed';\n                }\n\n                @before()\n                public hookTwo() {\n                    console.log(this.state)\n                    this.state = 'hook two has executed';\n                }\n\n                @given(\"a step\")\n                public given() {\n                    console.log(this.state);\n                    this.state = 'step has executed';\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output does not contain \"step has not executed\"\n        And the output contains text:\n            \"\"\"\n            .no hook executed\n            .hook one has executed\n            .hook two has executed\n            \"\"\"\n\n\n    Scenario: Binding multiple hooks same line\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, before} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                private state = 'no hook executed';\n\n                @before()public hookOne() {console.log(this.state);this.state = 'hook one has executed';}@before()public hookTwo() {console.log(this.state);this.state = 'hook two has executed';}\n\n                @given(\"a step\")\n                public given() {\n                    console.log(this.state);\n                    this.state = 'step has executed';\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output does not contain \"step has not executed\"\n        And the output contains text:\n            \"\"\"\n            .no hook executed\n            .hook one has executed\n            .hook two has executed\n            \"\"\"\n\n    Scenario: Binding multiple after hooks\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, after} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                private state = 'no hook executed';\n\n                @given(\"a step\")\n                public given() {\n                    console.log(this.state)\n                    this.state = 'step has executed';\n                }\n\n                @after()\n                public hookOne() {\n                    console.log(this.state)\n                    this.state = 'hook one has executed';\n                }\n\n                @after()\n                public hookTwo() {\n                    console.log(this.state);\n                    this.state = 'hook two has executed';\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output does not contain \"step has not executed\"\n        And the output contains text:\n            \"\"\"\n            .no hook executed\n            .step has executed\n            .hook two has executed\n            \"\"\"\n\n    @oldApis\n    Scenario: Attempting to bind named hooks with old cucumber\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, before, after} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                private state = 'hook has not executed';\n\n                @before({name: 'setup environment'})\n                public before() {\n                    this.state = 'before hook executed';\n                }\n\n                @given(\"a step\")\n                public given() {\n                    console.log(this.state);\n                    this.state = 'step has executed';\n                }\n\n                @after({name: 'tear down environment'})\n                public after() {\n                    console.log(this.state);\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it fails\n        And the error output contains text:\n            \"\"\"\n            Object literal may only specify known properties, and 'name' does not exist in type 'HookOptions'.\n            \"\"\"\n\n    @newApis\n    Scenario: Binding named hooks\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, before, after} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                private state = 'hook has not executed';\n\n                @before({name: 'setup environment'})\n                public before() {\n                    this.state = 'before hook executed';\n                }\n\n                @given(\"a step\")\n                public given() {\n                    console.log(this.state);\n                    this.state = 'step has executed';\n                }\n\n                @after({name: 'tear down environment'})\n                public after() {\n                    console.log(this.state);\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the hook \"setup environment\" was executed on scenario \"example\"\n        And the hook \"tear down environment\" was executed on scenario \"example\"\n\n    Scenario: Binding a before step hook\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, beforeStep} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                private state = 'hook has not executed';\n\n                @beforeStep()\n                public hook() {\n                    this.state = 'hook has executed';\n                }\n\n                @given(\"a step\")\n                public given() {\n                    console.log(this.state);\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output does not contain \"hook has not executed\"\n        And the output contains \"hook has executed\"\n\n    Scenario: Binding a before step hook with multiple steps\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example\n                    Given a step\n                    And another step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, beforeStep, when} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                private counter = 0;\n\n                @beforeStep()\n                public hook() {\n                    console.log(`${this.counter++} execution: beforeStep`);\n                }\n\n                @given(\"a step\")\n                public given() {\n                    console.log(`${this.counter++} execution: given`);\n                }\n\n                @when(\"another step\")\n                public when() {\n                    console.log(`${this.counter++} execution: when`);\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output does not contain \"hook has not executed\"\n        And the output contains text:\n            \"\"\"\n            .0 execution: beforeStep\n            1 execution: given\n            .2 execution: beforeStep\n            3 execution: when\n            \"\"\"\n\n    Scenario: Binding multiple before step hooks\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, beforeStep} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                private state = 'no hook executed';\n\n                @beforeStep()\n                public hookOne() {\n                    console.log(this.state)\n                    this.state = 'hook one has executed';\n                }\n\n                @beforeStep()\n                public hookTwo() {\n                    console.log(this.state)\n                    this.state = 'hook two has executed';\n                }\n\n                @given(\"a step\")\n                public given() {\n                    console.log(this.state);\n                    this.state = 'step has executed';\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output does not contain \"step has not executed\"\n        And the output contains text:\n            \"\"\"\n            .no hook executed\n            hook one has executed\n            hook two has executed\n            \"\"\"\n\n    Scenario: Binding an after step hook\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, afterStep} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                private state = 'step has not executed';\n\n                @afterStep()\n                public hook() {\n                    console.log(this.state);\n                }\n\n                @given(\"a step\")\n                public given() {\n                    this.state = 'step has executed';\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output does not contain \"step has not executed\"\n        And the output contains \"step has executed\"\n\n    Scenario: Binding an after step hook with multiple steps\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example\n                    Given a step\n                    And another step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, afterStep, when} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                private counter = 0;\n\n                @afterStep()\n                public hook() {\n                    console.log(`${this.counter++} execution: afterStep`);\n                }\n\n                @given(\"a step\")\n                public given() {\n                    console.log(`${this.counter++} execution: given`);\n                }\n\n                @when(\"another step\")\n                public when() {\n                    console.log(`${this.counter++} execution: when`);\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output does not contain \"step has not executed\"\n        And the output contains text:\n        \"\"\"\n        .0 execution: given\n        1 execution: afterStep\n        .2 execution: when\n        3 execution: afterStep\n        \"\"\"\n\n    Scenario: Binding multiple after step hooks\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, afterStep} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                private state = 'no hook executed';\n\n                @given(\"a step\")\n                public given() {\n                    console.log(this.state)\n                    this.state = 'step has executed';\n                }\n\n                @afterStep()\n                public hookOne() {\n                    console.log(this.state)\n                    this.state = 'hook one has executed';\n                }\n\n                @afterStep()\n                public hookTwo() {\n                    console.log(this.state);\n                    this.state = 'hook two has executed';\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output does not contain \"step has not executed\"\n        And the output contains text:\n            \"\"\"\n            .no hook executed\n            step has executed\n            hook two has executed\n            \"\"\"\n\n    Scenario: Binding before and after step hooks\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n                Scenario: example\n                    Given a step\n            \"\"\"\n        And a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import {binding, given, beforeStep, afterStep} from 'cucumber-tsflow';\n\n            @binding()\n            class Steps {\n                private state = 'hook has not executed';\n\n                @beforeStep()\n                public beforeStep() {\n                    this.state = 'before hook executed';\n                }\n\n                @given(\"a step\")\n                public given() {\n                    console.log(this.state);\n                    this.state = 'step has executed';\n                }\n\n                @afterStep()\n                public afterStep() {\n                    console.log(this.state);\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n        And the output does not contain \"step has not executed\"\n        And the output contains text:\n            \"\"\"\n            .before hook executed\n            step has executed\n            \"\"\"\n"
  },
  {
    "path": "cucumber-tsflow-specs/features/tag-parameters.feature",
    "content": "Feature: Tag parameters\n\n    Background:\n        Given a file named \"step_definitions/steps.ts\" with:\n            \"\"\"ts\n            import * as assert from 'assert';\n            import {binding, then, ScenarioInfo} from 'cucumber-tsflow';\n\n            @binding([ScenarioInfo])\n            class Steps {\n                public constructor(private readonly scenario: ScenarioInfo) {}\n\n                @then(\"the flag {string} is enabled\")\n                public checkEnabled(name: string) {\n                    assert.ok(this.scenario.getFlag(name))\n                }\n\n                @then(\"the flag {string} is disabled\")\n                public checkDisabled(name: string) {\n                    assert.ok(!this.scenario.getFlag(name))\n                }\n\n                @then(\"the option tag {string} is set to {string}\")\n                public checkOption(name: string, value: string) {\n                    assert.strictEqual(this.scenario.getOptionTag(name), value);\n                }\n\n                @then(\"the option tag {string} is unset\")\n                public checkOptionUnset(name: string) {\n                    assert.strictEqual(this.scenario.getOptionTag(name), undefined);\n                }\n\n                @then(\"the multi option tag {string} is set to {}\")\n                public checkMultiOption(name: string, value: string) {\n                    assert.deepStrictEqual(this.scenario.getMultiOptionTag(name), JSON.parse(value));\n                }\n\n                @then(\"the attribute tag {string} is set to {}\")\n                @then(\"the attribute tag {string} is set to:\")\n                public checkAttributes(name: string, values: string) {\n                    assert.deepStrictEqual(this.scenario.getAttributeTag(name), JSON.parse(values));\n                }\n\n                @then(\"the attribute tag {string} is unset\")\n                public checkAttributesUnset(name: string) {\n                    assert.deepStrictEqual(this.scenario.getAttributeTag(name), undefined);\n                }\n            }\n\n            export = Steps;\n            \"\"\"\n\n    Scenario: Checking for an absent flag\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n              Scenario: example\n                Then the flag \"enableFoo\" is disabled\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n\n    Scenario: Checking for a flag on the feature\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            @enableFoo\n            Feature: Feature\n              Scenario: One\n                Then the flag \"enableFoo\" is enabled\n              Scenario: Two\n                Then the flag \"enableFoo\" is enabled\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n\n    Scenario: Checking for a flag on the scenario\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n              @enableFoo\n              Scenario: One\n                Then the flag \"enableFoo\" is enabled\n                Then the flag \"enableBar\" is disabled\n              @enableBar\n              Scenario: Two\n                Then the flag \"enableFoo\" is disabled\n                Then the flag \"enableBar\" is enabled\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n\n    Scenario: Checking for an absent option\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n              Scenario: example\n                Then the option tag \"foo\" is unset\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n\n    Scenario: Checking for an option on the feature\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            @foo(bar)\n            Feature: Feature\n              Scenario: One\n                Then the option tag \"foo\" is set to \"bar\"\n              Scenario: Two\n                Then the option tag \"foo\" is set to \"bar\"\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n\n    Scenario: Checking for an option on the scenario\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n              @foo(bar)\n              Scenario: One\n                Then the option tag \"foo\" is set to \"bar\"\n              @foo(baz)\n              Scenario: Two\n                Then the option tag \"foo\" is set to \"baz\"\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n\n    Scenario: Checking for an option on the scenario overriding one on the feature\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            @foo(bar)\n            Feature: Feature\n              Scenario: One\n                Then the option tag \"foo\" is set to \"bar\"\n              @foo(baz)\n              Scenario: Two\n                Then the option tag \"foo\" is set to \"baz\"\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n\n    Scenario: Checking for an absent attribute tag\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n              Scenario: example\n                Then the attribute tag \"foo\" is unset\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n\n    Scenario: Checking for multi options on the feature\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            @foo(bar)\n            @foo(baz)\n            Feature: Feature\n              Scenario: One\n                Then the multi option tag \"foo\" is set to [\"bar\", \"baz\"]\n              Scenario: Two\n                Then the multi option tag \"foo\" is set to [\"bar\", \"baz\"]\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n\n    Scenario: Checking for multi options on the scenario\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n              @foo(bar)\n              @foo(baz)\n              Scenario: One\n                Then the multi option tag \"foo\" is set to [\"bar\", \"baz\"]\n              @foo(qux)\n              @foo(zzz)\n              Scenario: Two\n                Then the multi option tag \"foo\" is set to [\"qux\", \"zzz\"]\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n\n    Scenario: Checking for multi options on the scenario combining with multi options on the feature\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            @foo(bar)\n            Feature: Feature\n              Scenario: One\n                Then the multi option tag \"foo\" is set to [\"bar\"]\n              @foo(baz)\n              Scenario: Two\n                Then the multi option tag \"foo\" is set to [\"bar\", \"baz\"]\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n\n    Scenario: Checking for an attribute tag on the feature\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            @foo({\"bar\":1})\n            Feature: Feature\n              Scenario: One\n                Then the attribute tag \"foo\" is set to { \"bar\": 1 }\n              Scenario: Two\n                Then the attribute tag \"foo\" is set to { \"bar\": 1 }\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n\n    Scenario: Checking for an attribute tag on the scenario\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            Feature: Feature\n              @foo({\"bar\":1})\n              Scenario: One\n                Then the attribute tag \"foo\" is set to { \"bar\": 1 }\n              @foo({\"bar\":2})\n              Scenario: Two\n                Then the attribute tag \"foo\" is set to { \"bar\": 2 }\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n\n    Scenario: Checking for an attribute tag on the scenario overriding one on the feature\n        Given a file named \"features/a.feature\" with:\n            \"\"\"feature\n            @foo({\"bar\":1})\n            Feature: Feature\n              Scenario: One\n                Then the attribute tag \"foo\" is set to { \"bar\": 1 }\n              @foo({\"not-bar\":2})\n              Scenario: Two\n                Then the attribute tag \"foo\" is set to { \"not-bar\": 2 }\n            \"\"\"\n        When I run cucumber-js\n        Then it passes\n"
  },
  {
    "path": "cucumber-tsflow-specs/package.json",
    "content": "{\n  \"name\": \"cucumber-tsflow-specs\",\n  \"version\": \"5.0.0\",\n  \"private\": true,\n  \"description\": \"Specification for 'cucumber-tsflow'.\",\n  \"license\": \"MIT\",\n  \"author\": \"Tim Roberts <tim@timjroberts.com>\",\n  \"main\": \"./index.js\",\n  \"typings\": \"./dist/index.d.ts\",\n  \"dependencies\": {\n    \"cucumber-tsflow\": \"file:../cucumber-tsflow\",\n    \"expect\": \"^30.2.0\",\n    \"fs-extra\": \"^11.3.3\",\n    \"verror\": \"^1.10.1\"\n  },\n  \"devDependencies\": {\n    \"@types/fs-extra\": \"^11.0.4\",\n    \"@types/verror\": \"^1.10.11\"\n  }\n}\n"
  },
  {
    "path": "cucumber-tsflow-specs/src/step_definitions/cucumber_steps.ts",
    "content": "import { binding, then, when } from \"cucumber-tsflow\";\nimport expect from \"expect\";\nimport { parseEnvString } from \"../support/helpers\";\nimport { TestRunner } from \"../support/runner\";\n\n@binding([TestRunner])\nclass CucumberSteps {\n  public constructor(private readonly runner: TestRunner) {}\n\n  @when(\"my env includes {string}\")\n  public setEnvironment(envString: string) {\n    this.runner.sharedEnv = parseEnvString(envString);\n  }\n\n  @when(\"I run cucumber-js with env `{}`\")\n  public async runCucumberWithEnv(envString: string) {\n    await this.runner.run(parseEnvString(envString));\n  }\n\n  @when(\"I run cucumber-js\")\n  public async runCucumber() {\n    await this.runner.run();\n  }\n\n  @then(\"it passes\")\n  public checkPassed() {\n    const { lastRun } = this.runner;\n\n    if (lastRun?.error != null) {\n      throw new Error(\n        `Last run errored unexpectedly. Output:\\n\\n${lastRun.output}\\n\\n` +\n          `Error Output:\\n\\n${lastRun.errorOutput}`,\n      );\n    }\n  }\n\n  @then(\"it fails\")\n  public ensureFailure() {\n    const exitCode = this.runner.lastRun.error?.code ?? 0;\n    expect(exitCode).not.toBe(0);\n\n    this.runner.verifiedLastRunError = true;\n  }\n\n  @then(\"the output contains {string}\")\n  @then(\"the output contains text:\")\n  public checkStdoutContains(text: string) {\n    expect(this.runner.lastRun.output).toContain(text);\n  }\n\n  @then(\"the output contains {string} once\")\n  @then(\"the output contains text once:\")\n  public checkStdoutContainsOnce(text: string) {\n    const { output } = this.runner.lastRun;\n\n    expect(output).toContain(text);\n\n    const firstOccurrence = output.indexOf(text);\n    const remaining = output.substring(firstOccurrence + 1);\n\n    expect(remaining).not.toContain(text);\n  }\n\n  @then(\"the output does not contain {string}\")\n  @then(\"the output does not contain text:\")\n  public checkStdoutDoesNotContains(text: string) {\n    expect(this.runner.lastRun.output).not.toContain(text);\n  }\n\n  @then(\"the error output contains {string}\")\n  @then(\"the error output contains text:\")\n  public checkStderrContains(text: string) {\n    expect(this.runner.lastRun.errorOutput).toContain(text);\n  }\n\n  @then(\"the error output does not contain {string}\")\n  @then(\"the error output does not contain text:\")\n  public checkStderrDoesNotContains(text: string) {\n    expect(this.runner.lastRun.errorOutput).not.toContain(text);\n  }\n}\n\nexport = CucumberSteps;\n\n// Then(\n//   \"the output contains these types and quantities of message:\",\n//   function(this: World, expectedMessages: DataTable) {\n//     const envelopes = this.lastRun.output\n//       .split(\"\\n\")\n//       .filter((line) => !!line)\n//       .map((line) => JSON.parse(line));\n//     expectedMessages.rows().forEach(([type, count]) => {\n//       expect(envelopes.filter((envelope) => !!envelope[type])).to.have.length(\n//         Number(count),\n//         `Didn't find expected number of ${type} messages`\n//       );\n//     });\n//   }\n// );\n"
  },
  {
    "path": "cucumber-tsflow-specs/src/step_definitions/file_steps.ts",
    "content": "import { binding, given } from \"cucumber-tsflow\";\nimport { TestRunner } from \"../support/runner\";\n\n@binding([TestRunner])\nclass FileSteps {\n  public constructor(private readonly runner: TestRunner) {}\n\n  @given(\"a file named {string} with:\")\n  public newFile(filePath: string, fileContent: string) {\n    this.runner.dir.writeFile(filePath, fileContent);\n  }\n\n  @given(\"an empty file named {string}\")\n  public newEmptyFile(filePath: string) {\n    return this.newFile(filePath, \"\");\n  }\n\n  @given(\"a directory named {string}\")\n  public async newDir(filePath: string) {\n    this.runner.dir.mkdir(filePath);\n  }\n}\n\nexport = FileSteps;\n"
  },
  {
    "path": "cucumber-tsflow-specs/src/step_definitions/prepare.ts",
    "content": "import { formatterHelpers, ITestCaseHookParameter } from \"@cucumber/cucumber\";\nimport { after, before, binding } from \"cucumber-tsflow\";\nimport fsExtra from \"fs-extra\";\nimport path from \"path\";\nimport { TestRunner } from \"../support/runner\";\n\nconst projectPath = path.join(__dirname, \"..\", \"..\", \"..\");\nconst projectNodeModulePath = path.join(projectPath, \"node_modules\");\nconst cucumberPath = path.join(projectNodeModulePath, \"@cucumber\", \"cucumber\");\nconst tsNodePath = path.join(projectNodeModulePath, \"ts-node\");\nconst projectLibPath = path.join(projectPath, \"cucumber-tsflow\");\nconst log4jsPath = path.join(projectLibPath, \"node_modules\", \"log4js\");\n\n@binding([TestRunner])\nclass Prepare {\n  public constructor(private readonly runner: TestRunner) {}\n\n  @before()\n  public setupTestDir({ gherkinDocument, pickle }: ITestCaseHookParameter) {\n    const { line } = formatterHelpers.PickleParser.getPickleLocation({\n      gherkinDocument,\n      pickle,\n    });\n\n    const tmpDir = path.join(\n      projectPath,\n      \"tmp\",\n      `${path.basename(pickle.uri)}_${line.toString()}`,\n    );\n\n    fsExtra.removeSync(tmpDir);\n\n    this.runner.dir.path = tmpDir;\n\n    this.setupNodeModules();\n\n    const tags = [\n      ...pickle.tags.map((tag) => tag.name),\n      ...(gherkinDocument.feature?.tags.map((tag) => tag.name) ?? []),\n    ];\n\n    this.writeDefaultFiles(tags);\n  }\n\n  @after()\n  public tearDownTestDir() {\n    const { lastRun } = this.runner;\n\n    if (lastRun?.error != null && !this.runner.verifiedLastRunError) {\n      throw new Error(\n        `Last run errored unexpectedly. Output:\\n\\n${lastRun.output}\\n\\n` +\n          `Error Output:\\n\\n${lastRun.errorOutput}`,\n      );\n    }\n  }\n\n  private setupNodeModules() {\n    const tmpDirNodeModulesPath = this.runner.dir.mkdir(\"node_modules\");\n\n    fsExtra.ensureSymlinkSync(\n      cucumberPath,\n      path.join(tmpDirNodeModulesPath, \"@cucumber\", \"cucumber\"),\n    );\n    fsExtra.ensureSymlinkSync(\n      tsNodePath,\n      path.join(tmpDirNodeModulesPath, \"ts-node\"),\n    );\n    fsExtra.ensureSymlinkSync(\n      projectLibPath,\n      path.join(tmpDirNodeModulesPath, \"cucumber-tsflow\"),\n    );\n    fsExtra.ensureSymlinkSync(\n      log4jsPath,\n      path.join(tmpDirNodeModulesPath, \"log4js\"),\n    );\n  }\n\n  private writeDefaultFiles(tags: string[]) {\n    if (!tags.includes(\"custom-tsconfig\")) {\n      this.runner.dir.writeFile(\n        \"tsconfig.json\",\n        JSON.stringify({\n          compilerOptions: {\n            experimentalDecorators: true,\n          },\n        }),\n      );\n    }\n\n    if (!tags.includes(\"no-logging\")) {\n      this.runner.dir.writeFile(\n        \"a-logging.ts\",\n        `\nimport * as log4js from 'log4js';\n\nlog4js.configure({\n  appenders: {\n    logfile: {\n      type: \"file\",\n      filename: \"output.log\",\n    }\n  },\n  categories: {\n    default: { appenders: [\"logfile\"], level: \"trace\" },\n  }\n});\n`,\n      );\n    }\n  }\n}\n\nexport = Prepare;\n"
  },
  {
    "path": "cucumber-tsflow-specs/src/step_definitions/scenario_steps.ts",
    "content": "import { DataTable } from \"@cucumber/cucumber\";\nimport * as messages from \"@cucumber/messages\";\nimport assert from \"assert\";\nimport { binding, then } from \"cucumber-tsflow\";\nimport expect from \"expect\";\nimport { Extractor } from \"../support/helpers\";\nimport { TestRunner } from \"../support/runner\";\n\nconst ENCODING_MAP: { [key: string]: messages.AttachmentContentEncoding } = {\n  IDENTITY: messages.AttachmentContentEncoding.IDENTITY,\n  BASE64: messages.AttachmentContentEncoding.BASE64,\n};\n\n@binding([TestRunner])\nclass ScenarioSteps {\n  public constructor(private readonly runner: TestRunner) {}\n\n  @then(\"it runs {int} scenarios\")\n  public checkScenarioCount(expectedCount: number) {\n    const startedTestCases = this.runner.lastRun.envelopes.reduce(\n      (acc, e) => (e.testCaseStarted == null ? acc : acc + 1),\n      0,\n    );\n\n    expect(startedTestCases).toBe(expectedCount);\n  }\n\n  @then(\"it runs the scenario {string}\")\n  public checkRunSingleScenario(scenario: string) {\n    const actualNames =\n      this.runner.extractor.getPickleNamesInOrderOfExecution();\n    expect(actualNames).toEqual([scenario]);\n  }\n\n  @then(\"it runs the scenarios:\")\n  public checkScenarios(table: DataTable) {\n    const expectedNames = table.rows().map((row) => row[0]);\n    const actualNames =\n      this.runner.extractor.getPickleNamesInOrderOfExecution();\n    expect(actualNames).toEqual(expectedNames);\n  }\n\n  @then(\"scenario {string} has status {string}\")\n  public checkScenarioStatus(name: string, status: string) {\n    const result = this.runner.extractor.getTestCaseResult(name);\n\n    expect(result.status).toEqual(status.toUpperCase());\n  }\n\n  @then(\"scenario {string} step {string} has the attachments:\")\n  public checkStepAttachment(\n    pickleName: string,\n    stepText: string,\n    table: DataTable,\n  ) {\n    const expectedAttachments = table.hashes().map((x) => {\n      return {\n        body: x.DATA,\n        mediaType: x[\"MEDIA TYPE\"],\n        contentEncoding: ENCODING_MAP[x[\"MEDIA ENCODING\"]],\n      };\n    });\n\n    const actualAttachments = this.runner.extractor\n      .getAttachmentsForStep(pickleName, stepText)\n      .map(Extractor.simplifyAttachment);\n\n    expect(actualAttachments).toEqual(expectedAttachments);\n  }\n\n  @then(\"scenario {string} {string} hook has the attachments:\")\n  public checkHookAttachment(\n    pickleName: string,\n    hookKeyword: string,\n    table: DataTable,\n  ) {\n    const expectedAttachments: messages.Attachment[] = table\n      .hashes()\n      .map((x) => {\n        return {\n          body: x.DATA,\n          mediaType: x[\"MEDIA TYPE\"],\n          contentEncoding: ENCODING_MAP[x[\"MEDIA ENCODING\"]],\n        };\n      });\n\n    const actualAttachments = this.runner.extractor\n      .getAttachmentsForHook(pickleName, hookKeyword === \"Before\")\n      .map(Extractor.simplifyAttachment);\n\n    expect(actualAttachments).toEqual(expectedAttachments);\n  }\n\n  @then(\"scenario {string} step {string} has the logs:\")\n  public checkStepLogs(pickleName: string, stepName: string, logs: DataTable) {\n    const expectedLogs = logs.raw().map((row) => row[0]);\n    const actualLogs = Extractor.logsFromAttachments(\n      this.runner.extractor.getAttachmentsForStep(pickleName, stepName),\n    );\n\n    expect(actualLogs).toStrictEqual(expectedLogs);\n  }\n\n  @then(\"scenario {string} step {string} has no attachments\")\n  public checkNoStepLogs(pickleName: string, stepName: string) {\n    const attachments = this.runner.extractor.getAttachmentsForStep(\n      pickleName,\n      stepName,\n    );\n\n    expect(attachments).toStrictEqual([]);\n  }\n\n  @then(\"the hook {string} was executed on scenario {string}\")\n  public checkNamedHookExecution(hookName: string, scenarioName: string) {\n    const hook = this.runner.extractor.getHookByName(hookName);\n    const executions = this.runner.extractor.getHookExecutions(\n      scenarioName,\n      hook.id,\n    );\n\n    assert(\n      executions.length === 1,\n      `Hook ${hookName} executed ${executions.length} times on scenario \"${scenarioName}\"`,\n    );\n  }\n}\n\nexport = ScenarioSteps;\n\n// Then(\n//   \"the scenario {string} has the steps:\",\n//   function(this: World, name: string, table: DataTable) {\n//     const actualTexts = getTestStepResults(this.lastRun.envelopes, name).map(\n//       (s) => s.text\n//     );\n//     const expectedTexts = table.rows().map((row) => row[0]);\n//     expect(actualTexts).to.eql(expectedTexts);\n//   }\n// );\n//\n// Then(\n//   \"scenario {string} step {string} has status {string}\",\n//   function(this: World, pickleName: string, stepText: string, status: string) {\n//     const testStepResults = getTestStepResults(\n//       this.lastRun.envelopes,\n//       pickleName\n//     );\n//     const testStepResult = testStepResults.find((x) => x.text === stepText);\n//     expect(testStepResult.result.status).to.eql(\n//       status.toUpperCase() as messages.TestStepResultStatus\n//     );\n//   }\n// );\n//\n// Then(\n//   \"scenario {string} attempt {int} step {string} has status {string}\",\n//   function(\n//     this: World,\n//     pickleName: string,\n//     attempt: number,\n//     stepText: string,\n//     status: string\n//   ) {\n//     const testStepResults = getTestStepResults(\n//       this.lastRun.envelopes,\n//       pickleName,\n//       attempt\n//     );\n//     const testStepResult = testStepResults.find((x) => x.text === stepText);\n//     expect(testStepResult.result.status).to.eql(\n//       status.toUpperCase() as messages.TestStepResultStatus\n//     );\n//   }\n// );\n//\n// Then(\n//   \"scenario {string} {string} hook has status {string}\",\n//   function(\n//     this: World,\n//     pickleName: string,\n//     hookKeyword: string,\n//     status: string\n//   ) {\n//     const testStepResults = getTestStepResults(\n//       this.lastRun.envelopes,\n//       pickleName\n//     );\n//     const testStepResult = testStepResults.find((x) => x.text === hookKeyword);\n//     expect(testStepResult.result.status).to.eql(\n//       status.toUpperCase() as messages.TestStepResultStatus\n//     );\n//   }\n// );\n//\n// Then(\n//   \"scenario {string} step {string} failed with:\",\n//   function(\n//     this: World,\n//     pickleName: string,\n//     stepText: string,\n//     errorMessage: string\n//   ) {\n//     const testStepResults = getTestStepResults(\n//       this.lastRun.envelopes,\n//       pickleName\n//     );\n//     const testStepResult = testStepResults.find((x) => x.text === stepText);\n//     if (semver.satisfies(process.version, \">=14.0.0\")) {\n//       errorMessage = errorMessage.replace(\n//         \"{ member: [Circular] }\",\n//         \"<ref *1> { member: [Circular *1] }\"\n//       );\n//     }\n//     expect(testStepResult.result.status).to.eql(\n//       messages.TestStepResultStatus.FAILED\n//     );\n//     expect(testStepResult.result.message).to.include(errorMessage);\n//   }\n// );\n//\n// Then(\n//   \"scenario {string} attempt {int} step {string} failed with:\",\n//   function(\n//     this: World,\n//     pickleName: string,\n//     attempt: number,\n//     stepText: string,\n//     errorMessage: string\n//   ) {\n//     const testStepResults = getTestStepResults(\n//       this.lastRun.envelopes,\n//       pickleName,\n//       attempt\n//     );\n//     const testStepResult = testStepResults.find((x) => x.text === stepText);\n//     expect(testStepResult.result.status).to.eql(\n//       messages.TestStepResultStatus.FAILED\n//     );\n//     expect(testStepResult.result.message).to.include(errorMessage);\n//   }\n// );\n//\n// Then(\n//   \"scenario {string} step {string} has the doc string:\",\n//   function(\n//     this: World,\n//     pickleName: string,\n//     stepText: string,\n//     docString: string\n//   ) {\n//     const pickleStep = getPickleStep(\n//       this.lastRun.envelopes,\n//       pickleName,\n//       stepText\n//     );\n//     expect(pickleStep.argument.docString.content).to.eql(docString);\n//   }\n// );\n//\n// Then(\n//   \"scenario {string} step {string} has the data table:\",\n//   function(\n//     this: World,\n//     pickleName: string,\n//     stepText: string,\n//     dataTable: DataTable\n//   ) {\n//     const pickleStep = getPickleStep(\n//       this.lastRun.envelopes,\n//       pickleName,\n//       stepText\n//     );\n//     expect(new DataTable(pickleStep.argument.dataTable)).to.eql(dataTable);\n//   }\n// );\n"
  },
  {
    "path": "cucumber-tsflow-specs/src/support/formatter_output_helpers.ts",
    "content": "import {\n  IJsonFeature,\n  IJsonScenario,\n  IJsonStep,\n} from \"@cucumber/cucumber/lib/formatter/json_formatter\";\nimport { valueOrDefault } from \"@cucumber/cucumber/lib/value_checker\";\nimport * as messages from \"@cucumber/messages\";\n\nfunction normalizeExceptionAndUri(exception: string, cwd: string): string {\n  return exception\n    .replace(cwd, \"\")\n    .replace(/\\\\/g, \"/\")\n    .replace(\"/features\", \"features\")\n    .split(\"\\n\")[0];\n}\n\nfunction normalizeMessage(obj: any, cwd: string): void {\n  if (obj.uri != null) {\n    obj.uri = normalizeExceptionAndUri(obj.uri, cwd);\n  }\n  if (obj.sourceReference?.uri != null) {\n    obj.sourceReference.uri = normalizeExceptionAndUri(\n      obj.sourceReference.uri,\n      cwd,\n    );\n  }\n  if (obj.testStepResult != null) {\n    if (obj.testStepResult.duration != null) {\n      obj.testStepResult.duration.nanos = 0;\n    }\n    if (obj.testStepResult.message != null) {\n      obj.testStepResult.message = normalizeExceptionAndUri(\n        obj.testStepResult.message,\n        cwd,\n      );\n    }\n  }\n}\n\nexport function normalizeMessageOutput(\n  envelopeObjects: messages.Envelope[],\n  cwd: string,\n): messages.Envelope[] {\n  envelopeObjects.forEach((e: any) => {\n    for (const key of Object.keys(e)) {\n      normalizeMessage(e[key], cwd);\n    }\n  });\n  return envelopeObjects;\n}\n\nexport function stripMetaMessages(\n  envelopeObjects: messages.Envelope[],\n): messages.Envelope[] {\n  return envelopeObjects.filter((e: any) => {\n    // filter off meta objects, almost none of it predictable/useful for testing\n    return e.meta == null;\n  });\n}\n\nexport function normalizeJsonOutput(str: string, cwd: string): IJsonFeature[] {\n  const json: IJsonFeature[] = JSON.parse(valueOrDefault(str, \"[]\"));\n  json.forEach((feature: IJsonFeature) => {\n    if (feature.uri != null) {\n      feature.uri = normalizeExceptionAndUri(feature.uri, cwd);\n    }\n    feature.elements.forEach((element: IJsonScenario) => {\n      element.steps.forEach((step: IJsonStep) => {\n        if (step.match != null && step.match.location != null) {\n          step.match.location = normalizeExceptionAndUri(\n            step.match.location,\n            cwd,\n          );\n        }\n        if (step.result != null) {\n          if (step.result.duration != null) {\n            step.result.duration = 0;\n          }\n          if (step.result.error_message != null) {\n            step.result.error_message = normalizeExceptionAndUri(\n              step.result.error_message,\n              cwd,\n            );\n          }\n        }\n      });\n    });\n  });\n  return json;\n}\n\nexport const ignorableKeys = [\n  \"meta\",\n  // sources\n  \"uri\",\n  \"line\",\n  // ids\n  \"astNodeId\",\n  \"astNodeIds\",\n  \"hookId\",\n  \"id\",\n  \"pickleId\",\n  \"pickleStepId\",\n  \"stepDefinitionIds\",\n  \"testCaseId\",\n  \"testCaseStartedId\",\n  \"testStepId\",\n  // time\n  \"nanos\",\n  \"seconds\",\n  // errors\n  \"message\",\n];\n"
  },
  {
    "path": "cucumber-tsflow-specs/src/support/helpers.ts",
    "content": "// Adapted from:\n// https://github.com/cucumber/cucumber-js/blob/6505e61abce385787767f270b6ce2077eb3d7c1c/features/support/message_helpers.ts\nimport { getGherkinStepMap } from \"@cucumber/cucumber/lib/formatter/helpers/gherkin_document_parser\";\nimport {\n  getPickleStepMap,\n  getStepKeyword,\n} from \"@cucumber/cucumber/lib/formatter/helpers/pickle_parser\";\nimport { doesHaveValue } from \"@cucumber/cucumber/lib/value_checker\";\nimport * as messages from \"@cucumber/messages\";\nimport * as assert from \"node:assert\";\nimport util, { inspect } from \"util\";\n\nexport function parseEnvString(str: string): NodeJS.ProcessEnv {\n  const result: NodeJS.ProcessEnv = {};\n  if (doesHaveValue(str)) {\n    try {\n      Object.assign(result, JSON.parse(str));\n    } catch {\n      str\n        .split(/\\s+/)\n        .map((keyValue) => keyValue.split(\"=\"))\n        .forEach((pair) => (result[pair[0]] = pair[1]));\n    }\n  }\n  return result;\n}\n\nexport function dump(val: unknown): string {\n  return inspect(val, { depth: null });\n}\n\nexport interface IStepTextAndResult {\n  text: string;\n\n  result: messages.TestStepResult;\n}\n\nexport type SimpleAttachment = Pick<\n  messages.Attachment,\n  \"body\" | \"mediaType\" | \"contentEncoding\"\n>;\n\nexport class Extractor {\n  public constructor(private readonly envelopes: messages.Envelope[]) {}\n\n  public static logsFromAttachments(\n    attachments: messages.Attachment[],\n  ): string[] {\n    return attachments\n      .filter(\n        (att) =>\n          att.contentEncoding === messages.AttachmentContentEncoding.IDENTITY &&\n          att.mediaType === \"text/x.cucumber.log+plain\",\n      )\n      .map((att) => att.body);\n  }\n\n  public static simplifyAttachment(\n    attachment: messages.Attachment,\n  ): SimpleAttachment {\n    return {\n      body: attachment.body,\n      mediaType: attachment.mediaType,\n      contentEncoding: attachment.contentEncoding,\n    };\n  }\n\n  public getPickleNamesInOrderOfExecution(): string[] {\n    const pickleNameMap: Record<string, string> = {};\n    const testCaseToPickleNameMap: Record<string, string> = {};\n    const result: string[] = [];\n    this.envelopes.forEach((e) => {\n      if (e.pickle != null) {\n        pickleNameMap[e.pickle.id] = e.pickle.name;\n      } else if (e.testCase != null) {\n        testCaseToPickleNameMap[e.testCase.id] =\n          pickleNameMap[e.testCase.pickleId];\n      } else if (e.testCaseStarted != null) {\n        result.push(testCaseToPickleNameMap[e.testCaseStarted.testCaseId]);\n      }\n    });\n    return result;\n  }\n\n  public getPickleStep(\n    pickleName: string,\n    stepText: string,\n  ): messages.PickleStep {\n    const pickle = this.getPickle(pickleName);\n    const gherkinDocument = this.getGherkinDocument(pickle.uri);\n    return this.getPickleStepByStepText(pickle, gherkinDocument, stepText);\n  }\n\n  public getHookByName(hookName: string): messages.Hook {\n    const hookEnvelope = this.envelopes.find(\n      ({ hook }) => hook?.name === hookName,\n    );\n\n    assert.ok(hookEnvelope, `Unknown hook ${hookName}`);\n\n    return hookEnvelope.hook!;\n  }\n\n  public getHookExecutions(\n    pickleName: string,\n    hookId: string,\n  ): messages.TestStep[] {\n    const pickle = this.getPickle(pickleName);\n    const testCase = this.getTestCase(pickle.id);\n\n    return testCase.testSteps.filter((step) => step.hookId === hookId);\n  }\n\n  public getTestCaseResult(pickleName: string): messages.TestStepResult {\n    const pickle = this.getPickle(pickleName);\n    const testCase = this.getTestCase(pickle.id);\n    const testCaseStarted = this.getTestCaseStarted(testCase.id);\n    const testStepResults: messages.TestStepResult[] = [];\n    this.envelopes.forEach((e) => {\n      if (\n        e.testStepFinished != null &&\n        e.testStepFinished.testCaseStartedId === testCaseStarted.id\n      ) {\n        testStepResults.push(e.testStepFinished.testStepResult);\n      }\n    });\n    return messages.getWorstTestStepResult(testStepResults);\n  }\n\n  public getTestStepResults(\n    pickleName: string,\n    attempt = 0,\n  ): IStepTextAndResult[] {\n    const pickle = this.getPickle(pickleName);\n    const gherkinDocument = this.getGherkinDocument(pickle.uri);\n    const testCase = this.getTestCase(pickle.id);\n    const testCaseStarted = this.getTestCaseStarted(testCase.id, attempt);\n    const testStepIdToResultMap: Record<string, messages.TestStepResult> = {};\n    this.envelopes.forEach((e) => {\n      if (\n        e.testStepFinished != null &&\n        e.testStepFinished.testCaseStartedId === testCaseStarted.id\n      ) {\n        testStepIdToResultMap[e.testStepFinished.testStepId] =\n          e.testStepFinished.testStepResult;\n      }\n    });\n    const gherkinStepMap = getGherkinStepMap(gherkinDocument);\n    const pickleStepMap = getPickleStepMap(pickle);\n    let isBeforeHook = true;\n    return testCase.testSteps.map((testStep) => {\n      let text;\n      if (testStep.pickleStepId == null) {\n        text = isBeforeHook ? \"Before\" : \"After\";\n      } else {\n        isBeforeHook = false;\n        const pickleStep = pickleStepMap[testStep.pickleStepId];\n        const keyword = getStepKeyword({ pickleStep, gherkinStepMap });\n        text = `${keyword}${pickleStep.text}`;\n      }\n      return { text, result: testStepIdToResultMap[testStep.id] };\n    });\n  }\n\n  public getAttachmentsForStep(\n    pickleName: string,\n    stepText: string,\n  ): messages.Attachment[] {\n    const pickle = this.getPickle(pickleName);\n    const gherkinDocument = this.getGherkinDocument(pickle.uri);\n    const testCase = this.getTestCase(pickle.id);\n    const pickleStep = this.getPickleStepByStepText(\n      pickle,\n      gherkinDocument,\n      stepText,\n    );\n    assert.ok(\n      pickleStep,\n      `Step \"${stepText}\" not found in pickle ${dump(pickle)}`,\n    );\n\n    const testStep = testCase.testSteps.find(\n      (s) => s.pickleStepId === pickleStep.id,\n    )!;\n    const testCaseStarted = this.getTestCaseStarted(testCase.id);\n    return this.getTestStepAttachments(testCaseStarted.id, testStep.id);\n  }\n\n  public getAttachmentsForHook(\n    pickleName: string,\n    isBeforeHook: boolean,\n  ): messages.Attachment[] {\n    const pickle = this.getPickle(pickleName);\n    const testCase = this.getTestCase(pickle.id);\n    // Ignore the first Before hook and the last After hook\n    // Those are used to set up and tear down the tsflow harness\n    const testStepIndex = isBeforeHook ? 1 : testCase.testSteps.length - 2;\n    const testStep = testCase.testSteps[testStepIndex];\n    const testCaseStarted = this.getTestCaseStarted(testCase.id);\n    return this.getTestStepAttachments(testCaseStarted.id, testStep.id);\n  }\n\n  private getPickle(pickleName: string): messages.Pickle {\n    const pickleEnvelope = this.envelopes.find(\n      (e) => e.pickle != null && e.pickle.name === pickleName,\n    );\n    if (pickleEnvelope == null) {\n      throw new Error(\n        `No pickle with name \"${pickleName}\" in this.envelopes:\\n ${util.inspect(\n          this.envelopes,\n        )}`,\n      );\n    }\n    return pickleEnvelope.pickle!;\n  }\n\n  private getGherkinDocument(uri: string): messages.GherkinDocument {\n    const gherkinDocumentEnvelope = this.envelopes.find(\n      (e) => e.gherkinDocument != null && e.gherkinDocument.uri === uri,\n    );\n    if (gherkinDocumentEnvelope == null) {\n      throw new Error(\n        `No gherkinDocument with uri \"${uri}\" in this.envelopes:\\n ${util.inspect(\n          this.envelopes,\n        )}`,\n      );\n    }\n    return gherkinDocumentEnvelope.gherkinDocument!;\n  }\n\n  private getTestCase(pickleId: string): messages.TestCase {\n    const testCaseEnvelope = this.envelopes.find(\n      (e) => e.testCase != null && e.testCase.pickleId === pickleId,\n    );\n    if (testCaseEnvelope == null) {\n      throw new Error(\n        `No testCase with pickleId \"${pickleId}\" in this.envelopes:\\n ${util.inspect(\n          this.envelopes,\n        )}`,\n      );\n    }\n    return testCaseEnvelope.testCase!;\n  }\n\n  private getTestCaseStarted(\n    testCaseId: string,\n    attempt = 0,\n  ): messages.TestCaseStarted {\n    const testCaseStartedEnvelope = this.envelopes.find(\n      (e) =>\n        e.testCaseStarted != null &&\n        e.testCaseStarted.testCaseId === testCaseId &&\n        e.testCaseStarted.attempt === attempt,\n    );\n    if (testCaseStartedEnvelope == null) {\n      throw new Error(\n        `No testCaseStarted with testCaseId \"${testCaseId}\" in this.envelopes:\\n ${util.inspect(\n          this.envelopes,\n        )}`,\n      );\n    }\n    return testCaseStartedEnvelope.testCaseStarted!;\n  }\n\n  private getPickleStepByStepText(\n    pickle: messages.Pickle,\n    gherkinDocument: messages.GherkinDocument,\n    stepText: string,\n  ): messages.PickleStep {\n    const gherkinStepMap = getGherkinStepMap(gherkinDocument);\n    return pickle.steps.find((s) => {\n      const keyword = getStepKeyword({ pickleStep: s, gherkinStepMap });\n      return `${keyword}${s.text}` === stepText;\n    })!;\n  }\n\n  private getTestStepAttachments(\n    testCaseStartedId: string,\n    testStepId: string,\n  ): messages.Attachment[] {\n    return this.envelopes\n      .filter(\n        (e) =>\n          e.attachment != null &&\n          e.attachment.testCaseStartedId === testCaseStartedId &&\n          e.attachment.testStepId === testStepId,\n      )\n      .map((e) => e.attachment!);\n  }\n}\n"
  },
  {
    "path": "cucumber-tsflow-specs/src/support/runner.ts",
    "content": "// Adapted from:\n// https://github.com/cucumber/cucumber-js/blob/6505e61abce385787767f270b6ce2077eb3d7c1c/features/support/world.ts\nimport * as messageStreams from \"@cucumber/message-streams\";\nimport * as messages from \"@cucumber/messages\";\nimport assert from \"assert\";\nimport { execFile } from \"child_process\";\nimport expect from \"expect\";\nimport path from \"path\";\nimport { Writable } from \"stream\";\nimport { finished } from \"stream/promises\";\nimport stripAnsi from \"strip-ansi\";\nimport VError from \"verror\";\nimport { Extractor } from \"./helpers\";\nimport { TestDir } from \"./testDir\";\n\nconst projectPath = path.join(__dirname, \"..\", \"..\", \"..\");\nconst cucumberBinPath = path.join(\n  projectPath,\n  \"node_modules\",\n  \".bin\",\n  \"cucumber-js\",\n);\n\ninterface IRunResult {\n  error: any;\n\n  stderr: string;\n\n  stdout: string;\n}\n\ninterface ILastRun {\n  error: any;\n\n  errorOutput: string;\n\n  envelopes: messages.Envelope[];\n\n  output: string;\n}\n\nexport class TestRunner {\n  public readonly dir = new TestDir();\n\n  public sharedEnv?: NodeJS.ProcessEnv;\n\n  public spawn: boolean = false;\n\n  public debug: boolean = false;\n\n  public worldParameters?: any;\n\n  public verifiedLastRunError!: boolean;\n\n  private _lastRun?: ILastRun;\n\n  public get lastRun(): ILastRun {\n    assert(this._lastRun, \"Cucumber has not executed yet.\");\n    return this._lastRun;\n  }\n\n  public get extractor(): Extractor {\n    return new Extractor(this.lastRun.envelopes);\n  }\n\n  public async run(\n    envOverride: NodeJS.ProcessEnv | null = null,\n  ): Promise<void> {\n    const messageFilename = \"message.ndjson\";\n    const env = { ...process.env, ...this.sharedEnv, ...envOverride };\n\n    const result = await new Promise<IRunResult>((resolve) => {\n      execFile(\n        \"node\",\n        [\n          cucumberBinPath,\n          \"--format\",\n          `message:${messageFilename}`,\n          \"--require-module\",\n          \"ts-node/register\",\n          \"--require\",\n          \"a-logging.ts\",\n          \"--require\",\n          \"step_definitions/**/*.ts\",\n        ],\n        { cwd: this.dir.path, env },\n        (error, stdout, stderr) => {\n          resolve({ error, stdout, stderr });\n        },\n      );\n    });\n\n    const stderrSuffix =\n      result.error != null ? VError.fullStack(result.error) : \"\";\n\n    const envelopes: messages.Envelope[] = [];\n    const messageOutputStream = this.dir\n      .readFileStream(messageFilename)\n      ?.pipe(new messageStreams.NdjsonToMessageStream())\n      .pipe(\n        new Writable({\n          objectMode: true,\n          write(envelope: messages.Envelope, _: BufferEncoding, callback) {\n            envelopes.push(envelope);\n            callback();\n          },\n        }),\n      );\n\n    if (messageOutputStream !== undefined) {\n      await finished(messageOutputStream);\n    }\n\n    this._lastRun = {\n      error: result.error,\n      errorOutput: result.stderr + stderrSuffix,\n      envelopes,\n      output: stripAnsi(result.stdout),\n    };\n    this.verifiedLastRunError = false;\n\n    expect(this._lastRun.output).not.toContain(\"Unhandled rejection.\");\n  }\n}\n"
  },
  {
    "path": "cucumber-tsflow-specs/src/support/testDir.ts",
    "content": "import assert from \"assert\";\nimport fs, { ReadStream, WriteStream } from \"fs\";\nimport path from \"path\";\n\nexport class TestDir {\n  private _path?: string;\n\n  public get path(): string {\n    assert(this._path, \"Test directory not configured\");\n    return this._path;\n  }\n\n  public set path(newPath: string) {\n    this._path = newPath;\n  }\n\n  public readFileStream(\n    ...pathParts: string[] | [string[]]\n  ): ReadStream | null {\n    const filePath = this.getPath(...pathParts);\n\n    if (fs.existsSync(filePath)) {\n      return fs.createReadStream(filePath, { encoding: \"utf-8\" });\n    }\n\n    return null;\n  }\n\n  public writeFileStream(...pathParts: string[] | [string[]]): WriteStream {\n    const filePath = this.getPath(...pathParts);\n\n    return fs.createWriteStream(filePath, { encoding: \"utf-8\" });\n  }\n\n  public readFile(...pathParts: string[] | [string[]]): string | null {\n    const filePath = this.getPath(...pathParts);\n\n    if (fs.existsSync(filePath)) {\n      return fs.readFileSync(filePath, { encoding: \"utf-8\" });\n    }\n\n    return null;\n  }\n\n  public writeFile(pathParts: string | string[], data: string): void {\n    const pathArgs = (\n      typeof pathParts === \"string\" ? [pathParts] : pathParts\n    ).flatMap((part) => part.split(\"/\"));\n\n    if (pathArgs.length > 1) {\n      this.mkdir(pathArgs.slice(0, pathArgs.length - 1));\n    }\n\n    const filePath = this.getPath(pathArgs);\n\n    fs.writeFileSync(filePath, data, { encoding: \"utf-8\" });\n  }\n\n  public mkdir(...pathParts: string[] | [string[]]): string {\n    const dirPath = this.getPath(...pathParts);\n    fs.mkdirSync(dirPath, { recursive: true });\n    return dirPath;\n  }\n\n  private getPath(...pathParts: string[] | [string[]]): string {\n    return path.join(\n      this.path,\n      ...(pathParts.length === 1 && Array.isArray(pathParts[0])\n        ? pathParts[0]\n        : (pathParts as string[])),\n    );\n  }\n}\n"
  },
  {
    "path": "cucumber-tsflow-specs/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"moduleResolution\": \"node\",\n    \"module\": \"umd\",\n    \"target\": \"es2018\",\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"removeComments\": false,\n    \"esModuleInterop\": true,\n    \"noEmit\": true,\n    \"experimentalDecorators\": true,\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\"\n  },\n  \"include\": [\"./src/**/*.ts\"],\n  \"references\": [\n    {\n      \"path\": \"../cucumber-tsflow\"\n    }\n  ]\n}\n"
  },
  {
    "path": "cucumber.js",
    "content": "const cucumberPkg = require(\"@cucumber/cucumber/package.json\");\n\nmodule.exports = cucumberPkg.version.startsWith(\"7.\")\n  ? {\n      default: [\n        \"--publish-quiet\",\n        \"--require-module ts-node/register\",\n        \"--require cucumber-tsflow-specs/src/**/*.ts\",\n        \"--tags 'not @newApis'\",\n        '--world-parameters \\'{\"foo\":\"bar\"}\\'',\n      ].join(\" \"),\n    }\n  : {\n      default: {\n        requireModule: [\"ts-node/register\"],\n        require: [\"cucumber-tsflow-specs/src/**/*.ts\"],\n        tags: \"not @oldApis\",\n        worldParameters: {\n          foo: \"bar\",\n        },\n      },\n    };\n"
  },
  {
    "path": "lerna.json",
    "content": "{\n  \"packages\": [\"*\"],\n  \"version\": \"5.0.0\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"cucumber-tsflow-workspace\",\n  \"version\": \"2.0.0\",\n  \"private\": true,\n  \"description\": \"Workspace for cucumber-tsflow\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/timjroberts/cucumber-js-tsflow.git\"\n  },\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"clean\": \"rm -rf tmp tsconfig.tsbuildinfo cucumber-tsflow/tsconfig.tsbuildinfo cucumber-tsflow/dist cucumber-tsflow-specs/tsconfig.tsbuildinfo\",\n    \"build\": \"tsc -p cucumber-tsflow\",\n    \"build:watch\": \"tsc --build --watch\",\n    \"preinstall\": \"cd cucumber-tsflow && npm install && cd ../cucumber-tsflow-specs && npm install\",\n    \"postinstall\": \"npm run fix-peer-cucumber\",\n    \"fix-peer-cucumber\": \"rm -rf cucumber-tsflow/node_modules/@cucumber/cucumber && ln -s ../../node_modules/@cucumber cucumber-tsflow/node_modules/@cucumber/cucumber && mkdir -p cucumber-tsflow-specs/node_modules/@cucumber && rm -rf cucumber-tsflow-specs/node_modules/@cucumber/cucumber && ln -s ../../node_modules/@cucumber/cucumber cucumber-tsflow-specs/node_modules/@cucumber/cucumber\",\n    \"precommit\": \"lint-staged && npm test\",\n    \"set-packageversion\": \"node .build/setPackageVersion.js\",\n    \"pretest\": \"npm run build\",\n    \"test\": \"cucumber-js -p default cucumber-tsflow-specs/features/**/*.feature\",\n    \"upgrade\": \"npm-check -u; cd cucumber-tsflow && npm-check -u; cd ../cucumber-tsflow-specs && npm-check -u; cd .. && npm install\"\n  },\n  \"lint-staged\": {\n    \"*.ts\": [\n      \"prettier --write\",\n      \"tslint --fix\",\n      \"git add\"\n    ],\n    \"*.json\": [\n      \"prettier --write\",\n      \"git add\"\n    ]\n  },\n  \"devDependencies\": {\n    \"@cucumber/cucumber\": \"^12.7.0\",\n    \"@types/node\": \"^25.3.3\",\n    \"@types/underscore\": \"^1.13.0\",\n    \"lerna\": \"^9.0.5\",\n    \"lint-staged\": \"^16.3.1\",\n    \"nerdbank-gitversioning\": \"^3.9.50\",\n    \"npm-check\": \"^6.0.1\",\n    \"prettier\": \"^3.8.1\",\n    \"ts-node\": \"^10.9.2\",\n    \"tslint\": \"^6.1.3\",\n    \"tslint-config-prettier\": \"^1.18.0\",\n    \"typescript\": \"^5.9.3\"\n  },\n  \"overrides\": {\n    \"semver\": \"^7.5.3\"\n  }\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"experimentalDecorators\": true,\n    \"esModuleInterop\": true\n  },\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    {\n      \"path\": \"./cucumber-tsflow\"\n    },\n    {\n      \"path\": \"./cucumber-tsflow-specs\"\n    }\n  ]\n}\n"
  },
  {
    "path": "tslint.json",
    "content": "{\n  \"defaultSeverity\": \"error\",\n  \"extends\": [\"tslint:recommended\", \"tslint-config-prettier\"],\n  \"jsRules\": {},\n  \"rules\": {\n    \"variable-name\": [\n      true,\n      \"allow-leading-underscore\",\n      \"allow-trailing-underscore\",\n      \"allow-pascal-case\"\n    ],\n    \"interface-name\": [false],\n    \"object-literal-sort-keys\": false,\n    \"member-ordering\": [false],\n    \"object-literal-shorthand\": false,\n    \"trailing-comma\": [\"multiline\"]\n  }\n}\n"
  },
  {
    "path": "version.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json\",\n  \"version\": \"5.0\",\n  \"publicReleaseRefSpec\": [\"^refs/heads/master$\", \"^refs/heads/release/\"]\n}\n"
  }
]