Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

[*]
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
indent_size = 2
indent_style = space
2 changes: 2 additions & 0 deletions .env.local.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Provider API base URL (where the example-provider is running)
VITE_API_BASE_URL=http://localhost:8080
33 changes: 0 additions & 33 deletions .eslintrc.json

This file was deleted.

11 changes: 2 additions & 9 deletions .github/renovate.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:best-practices"
],
"extends": ["config:best-practices"],
"pre-commit": {
"enabled": true
},
Expand All @@ -16,12 +14,7 @@
"prConcurrentLimit": 0,
"packageRules": [
{
"matchUpdateTypes": [
"minor",
"patch",
"pin",
"digest"
],
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
"automerge": true
}
]
Expand Down
43 changes: 37 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,58 @@
name: Build

on:
pull_request:
branches:
- master
push:
workflow_dispatch:
branches:
- master

env:
PACT_BROKER_BASE_URL: https://testdemo.pactflow.io
PACT_BROKER_TOKEN: ${{ secrets.PACTFLOW_TOKEN_FOR_CI_CD_WORKSHOP }}
REACT_APP_API_BASE_URL: http://localhost:8080
VITE_API_BASE_URL: http://localhost:8080
GIT_SHA: ${{ github.sha }}
GIT_REF: ${{ github.ref }}
PACT_URL: https://testdemo.pactflow.io/pacts/provider/pactflow-example-provider/consumer/pactflow-example-consumer-webhookless/version/${{ github.sha }}

jobs:
complete:
name: Test completion check
if: always()

permissions:
contents: none

runs-on: ubuntu-slim
needs:
- Test
- Verify
- Can-I-Deploy

steps:
- name: Failed
run: exit 1
if: >
contains(needs.*.result, 'failure')
|| contains(needs.*.result, 'cancelled')
|| contains(needs.*.result, 'skipped')

Test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
with:
node-version: '24'
node-version: '25'
- name: Install
run: npm i
run: npm ci
- name: Type check
run: npm run type-check
- name: Lint
run: npm run check
- name: Build
run: npm run build
- name: Run tests
run: make test
- name: Publish pacts
Expand All @@ -43,9 +74,9 @@ jobs:
repository: pactflow/example-provider
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
with:
node-version: '24'
node-version: '25'
- name: Install
run: npm i
run: npm ci
- name: Verify
run: GIT_BRANCH=${GITHUB_REF:11} make ci_webhook
env:
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Default to the read only token - the read/write token will be present on Travis CI.
# It's set as a secure environment variable in the .travis.yml file
# Default to the read only token - the read/write token will be present on GitHub Actions.
# It's set as a secure environment variable in GitHub Actions (.github/workflows/build.yml)
GITHUB_ORG="pactflow"
PACTICIPANT := "pactflow-example-consumer-webhookless"
GITHUB_WEBHOOK_UUID := "04510dc1-7f0a-4ed2-997d-114bfa86f8ad"
Expand Down Expand Up @@ -28,7 +28,7 @@ fake_ci: .env
CI=true \
GIT_SHA=`git rev-parse --short HEAD`+`date +%s` \
GIT_BRANCH=`git rev-parse --abbrev-ref HEAD` \
REACT_APP_API_BASE_URL=http://localhost:8080 \
VITE_API_BASE_URL=http://localhost:8080 \
make ci

publish_pacts: .env
Expand Down
93 changes: 80 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@

[![Pact Status](https://testdemo.pactflow.io/matrix/provider/pactflow-example-provider/latest/master/consumer/pactflow-example-consumer-webhookless/latest/master/badge.svg)](https://testdemo.pactflow.io/pacts/provider/pactflow-example-provider/consumer/pactflow-example-consumer-webhookless/latest/master) (master/master pact)

## Prerequisites

- **Node.js+**: This project requires a recent Node.js version
- **npm**: Included with Node.js

## Technology Stack

- **Build Tool**: [Vite](https://vite.dev/) - Fast development server with HMR
- **Test Framework**: [Vitest](https://vitest.dev/) - Vite-native testing framework
- **Linting & Formatting**: [Biome](https://biomejs.dev/) - Unified toolchain for code quality
- **Type Safety**: TypeScript with strict mode enabled
- **React**: Function components with React Router

This is an example of how to set up a deployment pipeline for a consumer that does not make use of PactFlow/Pact Broker webhooks. This flow is a good alternative where the use of webhooks is not possible due to firewalls.

Webhooks are typically used to ensure that a recently change pact gets verified by the provider immediately, rather than waiting for a provider build to run. This workflow ensures changed pacts are verified immediately by checking out the provider codebase in the consumer's pipeline, and running the verification as part of the consumer's own tests.
Expand All @@ -18,16 +31,70 @@ The deployment pipeline runs in Github Actions, and is defined in the [build.yml

The Test, Can-I-Deploy and Deploy steps are identical to the flow that uses webhooks. The Verify step is the extra part that usually runs out of bound in the provider's CI environment.

* Test
* Run tests
* Publish pacts
* Verify (set to continue workflow on error)
* Check if a verification from the master version of the provider already exists using can-i-deploy, raise error if it *does exist* so we do not continue with the rest of the steps
* Checkout provider
* Verify pacts
* Publish verification results
* Can-I-Deploy
* Check if the current version of the consumer is compatible with the *production* version of the provider.
* Deploy
* Deploy application
* Tag the deployed version in PactFlow as 'prod'
- Test
- Run tests
- Publish pacts
- Verify (set to continue workflow on error)
- Check if a verification from the master version of the provider already exists using can-i-deploy, raise error if it *does exist* so we do not continue with the rest of the steps
- Checkout provider
- Verify pacts
- Publish verification results
- Can-I-Deploy
- Check if the current version of the consumer is compatible with the *production* version of the provider.
- Deploy
- Deploy application
- Tag the deployed version in PactFlow as 'prod'

## Local Development

This consumer requires a running provider service for local development.

### Quick Start

**In terminal 1** - Start the provider:

```bash
git clone https://github.com/pactflow/example-provider
cd example-provider
npm install
npm run dev
```

**In terminal 2** - Start this consumer:

```bash
npm install
npm run dev
```

The consumer runs at <http://localhost:3000> and connects to the provider at <http://localhost:8080> by default.

The dev server will automatically check if the provider is available and show helpful instructions if it's not running.

### Environment Variables

Copy `.env.local.example` to `.env.local` to customize the provider URL:

```bash
cp .env.local.example .env.local
```

The default provider URL is `http://localhost:8080`. Modify `VITE_API_BASE_URL` in `.env.local` if your provider runs on a different port.

### Available Scripts

- `npm run dev` - Start the Vite development server with hot module replacement
- `npm test` - Run all tests with Vitest
- `npm run test:pact` - Run only Pact contract tests
- `npm run type-check` - Check TypeScript types without emitting files
- `npm run check` - Run all Biome checks (linting + formatting)
- `npm run check:fix` - Automatically fix Biome issues (use with caution)

This project uses [Biome](https://biomejs.dev/) for linting and formatting. Configuration is in `biome.jsonc`.

The CI pipeline runs:

1. Type checking (`npm run type-check`)
2. Code quality checks (`npm run check`)
3. Build validation (`npm run build`)
4. Tests (`npm test`)
124 changes: 124 additions & 0 deletions biome.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.2/schema.json",

// Code formatting and organization suggestions
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
},

// Formatting
"formatter": {
"useEditorconfig": true
},

// Enable integration with VCS
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},

// Linting default rules
// I prefer being very strict with lints, and then disabling specific rules as
// needed.
"linter": {
"enabled": true,
"domains": {
"test": "all",
"react": "all"
},
"rules": {
"a11y": "error",
"complexity": "error",
"correctness": "error",
"nursery": "off",
"performance": "error",
"recommended": true,
"security": "error",
"style": {
"recommended": true,
// Too strict - hardcoded strings acceptable in this project
"noJsxLiterals": "off"
},
"suspicious": "error"
}
},

// Override settings
"overrides": [
{
"includes": ["**/*.tsx", "**/*.jsx"],
"linter": {
"rules": {
"performance": {
// Next.js specific rule - not applicable to Vite/React projects
"noImgElement": "off"
},
"style": {
// Allow default exports in React components
"noDefaultExport": "off",
// Allow class components (converting to function components is a larger refactor)
"useReactFunctionComponents": "off"
},
"suspicious": {
// React specific rule - not applicable to Vite/React projects
"noReactSpecificProps": "off"
}
}
}
},
{
"includes": [
"**/*.spec.ts",
"**/*.spec.tsx",
"**/*.test.ts",
"**/*.test.tsx"
],
"linter": {
"rules": {
"complexity": {
// Test files can have longer functions with multiple test cases
"noExcessiveLinesPerFunction": "off"
},
"style": {
// HTTP headers follow spec casing (Authorization, not authorization)
"useNamingConvention": "off"
},
"suspicious": {
// Pact test executeTest callbacks are inherently async
"useAwait": "off"
}
}
}
},
{
"includes": ["*.config.ts", "*.config.js"],
"linter": {
"rules": {
"style": {
// Config files conventionally use default exports
"noDefaultExport": "off"
}
}
}
},
{
"includes": ["**/*.ts", "**/*.tsx"],
"linter": {
"rules": {
"correctness": {
// TypeScript handles this
"noUnresolvedImports": "off",
// False positive (we don't use Qwik)
"useQwikValidLexicalScope": "off"
}
}
}
}
]
}
Loading