@contractspec/action.validation 1.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,47 @@
1
+ # AI Agent Guide — `@contractspec/action.validation`
2
+
3
+ Scope: `packages/apps/action-validation/*`
4
+
5
+ This is the ContractSpec GitHub Action for CI/CD integration.
6
+
7
+ ## Structure
8
+
9
+ - `action.yml` - GitHub Action definition (composite action)
10
+ - `README.md` - Usage documentation
11
+ - `package.json` - Package metadata (private, not published to npm)
12
+
13
+ ## How It Works
14
+
15
+ This is a **composite action** that:
16
+
17
+ 1. Sets up Bun
18
+ 2. Installs dependencies
19
+ 3. Runs `contractspec ci` with the specified options
20
+ 4. Uploads SARIF to GitHub Code Scanning (optional)
21
+ 5. Uploads results as artifacts
22
+
23
+ ## Modifying the Action
24
+
25
+ When making changes:
26
+
27
+ 1. Update `action.yml` for input/output changes
28
+ 2. Update `README.md` for documentation
29
+ 3. Test locally using `act` or by referencing the local action:
30
+
31
+ ```yaml
32
+ - uses: ./packages/apps/action-validation
33
+ ```
34
+
35
+ ## Inputs/Outputs
36
+
37
+ All inputs and outputs are defined in `action.yml`. The action wraps the `contractspec ci` CLI command.
38
+
39
+ ## Publishing
40
+
41
+ The action is published as part of the monorepo. Users reference it as:
42
+
43
+ ```yaml
44
+ - uses: lssm/contractspec-action@v1
45
+ ```
46
+
47
+ The action is versioned with tags on the repository.
package/CHANGELOG.md ADDED
@@ -0,0 +1,279 @@
1
+ # @contractspec/action.validation
2
+
3
+ ## 1.60.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fix: publish with bun
8
+
9
+ ## 1.59.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 1a0cf44: fix: publishConfig not supported by bun
14
+
15
+ ## 1.58.0
16
+
17
+ ### Minor Changes
18
+
19
+ - d1f0fd0: chore: Migrate non-app package builds from tsdown to shared Bun tooling, add `@contractspec/tool.bun`, and standardize `prebuild`/`build`/`typecheck` with platform-aware exports and `tsc` declaration emission into `dist`.
20
+
21
+ ## 1.57.0
22
+
23
+ ### Minor Changes
24
+
25
+ - 11a5a05: feat: improve product intent
26
+
27
+ ## 1.56.1
28
+
29
+ ### Patch Changes
30
+
31
+ - fix: improve publish config
32
+
33
+ ## 1.56.0
34
+
35
+ ### Minor Changes
36
+
37
+ - fix: release
38
+
39
+ ## 1.55.0
40
+
41
+ ### Minor Changes
42
+
43
+ - fix: unpublished packages
44
+
45
+ ## 1.54.0
46
+
47
+ ### Minor Changes
48
+
49
+ - ec5e95c: chore: upgrade dependencies
50
+
51
+ ## 1.53.0
52
+
53
+ ### Minor Changes
54
+
55
+ - f4180d4: fix: performance improvement
56
+
57
+ ### Patch Changes
58
+
59
+ - 1484fa6: Add reusable ContractSpec workflows with shared reporting and document the new automation guidance.
60
+
61
+ ## 1.52.0
62
+
63
+ ### Minor Changes
64
+
65
+ - d93e6a9: fix: improve website
66
+
67
+ ## 1.51.0
68
+
69
+ ### Minor Changes
70
+
71
+ - e6faefb: feat: add guide to import existing codebase
72
+
73
+ ## 1.50.0
74
+
75
+ ### Minor Changes
76
+
77
+ - 5325d6b: feat: improve seo
78
+
79
+ ## 1.49.0
80
+
81
+ ### Minor Changes
82
+
83
+ - cafd041: fix: impact report comments within github action
84
+
85
+ ## 1.48.0
86
+
87
+ ### Minor Changes
88
+
89
+ - b0444a4: feat: reduce adoption's friction by allowing generation of contracts from an analyse of the codebase
90
+
91
+ ## 1.47.0
92
+
93
+ ### Minor Changes
94
+
95
+ - caf8701: feat: add cli vibe command to run workflow
96
+ - c69b849: feat: add api web services (mcp & website)
97
+ - 42b8d78: feat: add cli `contractspec vibe` workflow to simplify usage
98
+ - fd38e85: feat: auto-fix contractspec issues
99
+
100
+ ### Patch Changes
101
+
102
+ - e7ded36: feat: improve stability (adding ts-morph)
103
+ - c231a8b: test: improve workspace stability
104
+
105
+ ## 1.46.2
106
+
107
+ ### Patch Changes
108
+
109
+ - 7e21625: feat: library services (landing page & api)
110
+
111
+ ## 1.46.1
112
+
113
+ ### Patch Changes
114
+
115
+ - 2d8a72b: fix: mcp for presentation
116
+
117
+ ## 1.46.0
118
+
119
+ ### Minor Changes
120
+
121
+ - 07cb19b: feat: feat: cleaude code & opencode integrations
122
+
123
+ ## 1.45.6
124
+
125
+ ### Patch Changes
126
+
127
+ - a913074: feat: improve ai agents rules management"
128
+
129
+ ## 1.45.5
130
+
131
+ ### Patch Changes
132
+
133
+ - 9ddd7fa: feat: improve versioning
134
+
135
+ ## 1.45.4
136
+
137
+ ### Patch Changes
138
+
139
+ - fix: github action
140
+
141
+ ## 1.45.3
142
+
143
+ ### Patch Changes
144
+
145
+ - e74ea9e: feat: version management
146
+
147
+ ## 1.45.2
148
+
149
+ ### Patch Changes
150
+
151
+ - 39ca241: code cleaning
152
+
153
+ ## 1.45.1
154
+
155
+ ### Patch Changes
156
+
157
+ - feat: improve app config and examples contracts
158
+
159
+ ## 1.45.0
160
+
161
+ ### Minor Changes
162
+
163
+ - e73ca1d: feat: improve app config and examples contracts
164
+ feat: Contract layers support (features, examples, app-configs)
165
+
166
+ ### New CLI Commands
167
+ - `contractspec list layers` - List all contract layers with filtering
168
+
169
+ ### Enhanced Commands
170
+ - `contractspec ci` - New `layers` check category validates features/examples/config
171
+ - `contractspec doctor` - New `layers` health checks
172
+ - `contractspec integrity` - Now shows layer statistics
173
+
174
+ ### New APIs
175
+ - `discoverLayers()` - Scan workspace for all layer files
176
+ - `scanExampleSource()` - Parse ExampleSpec from source code
177
+ - `isExampleFile()` - Check if file is an example spec
178
+
179
+ ## 1.44.1
180
+
181
+ ### Patch Changes
182
+
183
+ - 3c594fb: fix
184
+
185
+ ## 1.44.0
186
+
187
+ ### Minor Changes
188
+
189
+ - 5f3a868: chore: isolate branding to contractspec.io
190
+
191
+ ## 1.43.3
192
+
193
+ ### Patch Changes
194
+
195
+ - 9216062: fix: cross-platform compatibility
196
+
197
+ ## 1.43.2
198
+
199
+ ### Patch Changes
200
+
201
+ - 24d9759: improve documentation
202
+
203
+ ## 1.43.1
204
+
205
+ ### Patch Changes
206
+
207
+ - e147271: fix: improve stability
208
+
209
+ ## 1.43.0
210
+
211
+ ### Minor Changes
212
+
213
+ - 042d072: feat: schema declaration using json schema, including zod
214
+
215
+ ## 1.42.10
216
+
217
+ ### Patch Changes
218
+
219
+ - 1e6a0f1: fix: mcp server
220
+
221
+ ## 1.42.9
222
+
223
+ ### Patch Changes
224
+
225
+ - 9281db7: fix ModelRegistry
226
+
227
+ ## 1.42.8
228
+
229
+ ### Patch Changes
230
+
231
+ - e07b5ac: fix
232
+
233
+ ## 1.42.7
234
+
235
+ ### Patch Changes
236
+
237
+ - e9b575d: fix release
238
+
239
+ ## 1.42.6
240
+
241
+ ### Patch Changes
242
+
243
+ - 1500242: fix tooling
244
+
245
+ ## 1.42.5
246
+
247
+ ### Patch Changes
248
+
249
+ - 1299719: fix vscode
250
+
251
+ ## 1.42.4
252
+
253
+ ### Patch Changes
254
+
255
+ - ac28b99: fix: generate from openapi
256
+
257
+ ## 1.42.3
258
+
259
+ ### Patch Changes
260
+
261
+ - 3f5d015: fix(tooling): cicd
262
+
263
+ ## 1.42.2
264
+
265
+ ### Patch Changes
266
+
267
+ - 1f9ac4c: fix
268
+
269
+ ## 1.42.1
270
+
271
+ ### Patch Changes
272
+
273
+ - f043995: Fix release
274
+
275
+ ## 1.42.0
276
+
277
+ ### Minor Changes
278
+
279
+ - 8eefd9c: initial release
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Chaman Ventures, SASU
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,286 @@
1
+ # ContractSpec CI GitHub Action
2
+
3
+ > Note: This action is now an internal helper. Prefer `packages/apps/action-pr` and `packages/apps/action-drift`.
4
+
5
+ Website: https://contractspec.io/
6
+
7
+ Run ContractSpec validation checks in your CI/CD pipeline with automatic SARIF upload to GitHub Code Scanning.
8
+
9
+ ## Usage
10
+
11
+ ### Basic Usage
12
+
13
+ ```yaml
14
+ name: ContractSpec CI
15
+
16
+ on: [push, pull_request]
17
+
18
+ jobs:
19
+ contractspec:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - name: Run ContractSpec CI
25
+ uses: lssm/contractspec-action@v1
26
+ ```
27
+
28
+ ### Full Configuration
29
+
30
+ ```yaml
31
+ name: ContractSpec CI
32
+
33
+ on:
34
+ push:
35
+ branches: [main]
36
+ pull_request:
37
+
38
+ jobs:
39
+ contractspec:
40
+ runs-on: ubuntu-latest
41
+ permissions:
42
+ contents: read
43
+ security-events: write # Required for SARIF upload
44
+ steps:
45
+ - uses: actions/checkout@v4
46
+
47
+ - name: Run ContractSpec CI
48
+ id: contractspec
49
+ uses: lssm/contractspec-action@v1
50
+ with:
51
+ # Run specific checks (default: all)
52
+ checks: 'structure,integrity,deps'
53
+
54
+ # Skip specific checks
55
+ skip: 'doctor'
56
+
57
+ # Glob pattern for spec discovery
58
+ pattern: 'src/**/*.contracts.ts'
59
+
60
+ # Fail on warnings (default: false)
61
+ fail-on-warnings: false
62
+
63
+ # Include handler checks (default: false)
64
+ check-handlers: true
65
+
66
+ # Include test checks (default: false)
67
+ check-tests: true
68
+
69
+ # Upload SARIF to GitHub Code Scanning (default: true)
70
+ upload-sarif: true
71
+
72
+ # Working directory (default: .)
73
+ working-directory: '.'
74
+
75
+ # Bun version (default: latest)
76
+ bun-version: 'latest'
77
+
78
+ - name: Check results
79
+ if: always()
80
+ run: |
81
+ echo "Success: ${{ steps.contractspec.outputs.success }}"
82
+ echo "Errors: ${{ steps.contractspec.outputs.errors }}"
83
+ echo "Warnings: ${{ steps.contractspec.outputs.warnings }}"
84
+ ```
85
+
86
+ ## Inputs
87
+
88
+ ### Validation Mode Inputs
89
+
90
+ | Input | Description | Required | Default |
91
+ | ------------------- | ---------------------------------------- | -------- | ---------- |
92
+ | `mode` | Action mode: `validate` or `impact` | No | `validate` |
93
+ | `checks` | Checks to run (comma-separated) or "all" | No | `all` |
94
+ | `skip` | Checks to skip (comma-separated) | No | `''` |
95
+ | `pattern` | Glob pattern for spec discovery | No | `''` |
96
+ | `fail-on-warnings` | Fail the action on warnings | No | `false` |
97
+ | `check-handlers` | Include handler implementation checks | No | `false` |
98
+ | `check-tests` | Include test coverage checks | No | `false` |
99
+ | `upload-sarif` | Upload SARIF to GitHub Code Scanning | No | `true` |
100
+ | `working-directory` | Working directory for running checks | No | `.` |
101
+ | `bun-version` | Bun version to use | No | `latest` |
102
+
103
+ ### Impact Detection Inputs
104
+
105
+ | Input | Description | Required | Default |
106
+ | ------------------ | -------------------------------------------------- | -------- | --------------------- |
107
+ | `mode` | Set to `impact` for breaking change detection | No | `validate` |
108
+ | `baseline` | Git ref to compare against (auto-detected from PR) | No | `''` |
109
+ | `pr-comment` | Post impact results as PR comment | No | `true` |
110
+ | `fail-on-breaking` | Fail action if breaking changes detected | No | `true` |
111
+ | `github-token` | GitHub token for PR comments and check runs | No | `${{ github.token }}` |
112
+
113
+ ### Available Checks
114
+
115
+ - `structure` - Validate spec structure (meta, io, policy fields)
116
+ - `integrity` - Find orphaned specs and broken references
117
+ - `deps` - Detect circular dependencies and missing refs
118
+ - `doctor` - Check installation health
119
+ - `handlers` - Verify handler implementations exist
120
+ - `tests` - Verify test files exist
121
+
122
+ ## Outputs
123
+
124
+ ### Validation Mode Outputs
125
+
126
+ | Output | Description |
127
+ | ------------ | ------------------------------------------ |
128
+ | `success` | Whether all checks passed (`true`/`false`) |
129
+ | `errors` | Number of errors found |
130
+ | `warnings` | Number of warnings found |
131
+ | `sarif-file` | Path to SARIF output file |
132
+ | `json-file` | Path to JSON output file |
133
+
134
+ ### Impact Detection Outputs
135
+
136
+ | Output | Description |
137
+ | -------------------- | --------------------------------------------------------- |
138
+ | `impact-status` | Impact status: `no-impact`, `non-breaking`, or `breaking` |
139
+ | `breaking-count` | Number of breaking changes detected |
140
+ | `non-breaking-count` | Number of non-breaking changes detected |
141
+
142
+ ## Impact Detection
143
+
144
+ The action can detect breaking and non-breaking contract changes on PRs, providing:
145
+
146
+ - **✅ No contract impact** - No changes to contracts
147
+ - **⚠️ Contract changed (non-breaking)** - Safe additions or optional field changes
148
+ - **❌ Breaking change detected** - Removals, type changes, or required field additions
149
+
150
+ ### PR Comment Output
151
+
152
+ When `pr-comment: true`, the action posts a comment like:
153
+
154
+ ```markdown
155
+ ## 📋 ContractSpec Impact Analysis
156
+
157
+ ❌ **Breaking changes detected**
158
+
159
+ ### Summary
160
+
161
+ | Type | Count |
162
+ | --------------- | ----- |
163
+ | 🔴 Breaking | 2 |
164
+ | 🟡 Non-breaking | 3 |
165
+
166
+ ### 🔴 Breaking Changes
167
+
168
+ - **orders.create**: Required field 'userId' was removed
169
+ - **orders.get**: Response type changed from 'object' to 'array'
170
+ ```
171
+
172
+ ## GitHub Code Scanning Integration
173
+
174
+ When `upload-sarif: true` (default), the action uploads SARIF results to GitHub Code Scanning. This provides:
175
+
176
+ - **Inline annotations** on pull requests showing issues at the exact location
177
+ - **Security tab integration** for tracking issues over time
178
+ - **Code scanning alerts** for new issues
179
+
180
+ To enable this feature, ensure your workflow has the `security-events: write` permission:
181
+
182
+ ```yaml
183
+ permissions:
184
+ contents: read
185
+ security-events: write
186
+ ```
187
+
188
+ ## Exit Codes
189
+
190
+ | Code | Description |
191
+ | ---- | ---------------------------------------------------------------- |
192
+ | `0` | All checks passed |
193
+ | `1` | Errors found (or breaking changes with `fail-on-breaking: true`) |
194
+ | `2` | Warnings found (with `fail-on-warnings: true`) |
195
+
196
+ ## Examples
197
+
198
+ ### Validate on Push and PR
199
+
200
+ ```yaml
201
+ name: Validate Contracts
202
+
203
+ on: [push, pull_request]
204
+
205
+ jobs:
206
+ validate:
207
+ runs-on: ubuntu-latest
208
+ steps:
209
+ - uses: actions/checkout@v4
210
+ - uses: lssm-tech/contractspec@action-v1
211
+ ```
212
+
213
+ ### Impact Detection on PRs
214
+
215
+ ```yaml
216
+ name: Contract Impact
217
+
218
+ on: pull_request
219
+
220
+ jobs:
221
+ impact:
222
+ runs-on: ubuntu-latest
223
+ permissions:
224
+ contents: read
225
+ pull-requests: write
226
+ steps:
227
+ - uses: actions/checkout@v4
228
+ with:
229
+ fetch-depth: 0 # Required for git history comparison
230
+
231
+ - uses: lssm-tech/contractspec@action-v1
232
+ with:
233
+ mode: impact
234
+ pr-comment: 'true'
235
+ fail-on-breaking: 'true'
236
+ github-token: ${{ secrets.GITHUB_TOKEN }}
237
+ ```
238
+
239
+ ### Strict Mode (Fail on Warnings)
240
+
241
+ ```yaml
242
+ - uses: lssm/contractspec-action@v1
243
+ with:
244
+ fail-on-warnings: true
245
+ ```
246
+
247
+ ### Skip Doctor Checks in CI
248
+
249
+ ```yaml
250
+ - uses: lssm/contractspec-action@v1
251
+ with:
252
+ skip: 'doctor'
253
+ ```
254
+
255
+ ### Monorepo with Multiple Packages
256
+
257
+ ```yaml
258
+ jobs:
259
+ contractspec:
260
+ runs-on: ubuntu-latest
261
+ strategy:
262
+ matrix:
263
+ package: [api, web, shared]
264
+ steps:
265
+ - uses: actions/checkout@v4
266
+ - uses: lssm/contractspec-action@v1
267
+ with:
268
+ working-directory: packages/${{ matrix.package }}
269
+ ```
270
+
271
+ ## Using Without the Action
272
+
273
+ If you prefer to run ContractSpec directly without the action:
274
+
275
+ ```yaml
276
+ - uses: oven-sh/setup-bun@v2
277
+ - run: bun install
278
+ - run: bunx contractspec ci --format sarif --output results.sarif
279
+ - uses: github/codeql-action/upload-sarif@v4
280
+ with:
281
+ sarif_file: results.sarif
282
+ ```
283
+
284
+ ## License
285
+
286
+ MIT
package/action.yml ADDED
@@ -0,0 +1,456 @@
1
+ name: 'ContractSpec CI'
2
+ description: 'Run ContractSpec validation checks in your CI/CD pipeline'
3
+ author: 'LSSM'
4
+ branding:
5
+ icon: 'check-circle'
6
+ color: 'blue'
7
+
8
+ inputs:
9
+ checks:
10
+ description: 'Checks to run (comma-separated: structure,integrity,deps,doctor,handlers,tests) or "all"'
11
+ required: false
12
+ default: 'all'
13
+ skip:
14
+ description: 'Checks to skip (comma-separated)'
15
+ required: false
16
+ default: ''
17
+ pattern:
18
+ description: 'Glob pattern for spec discovery'
19
+ required: false
20
+ default: ''
21
+ fail-on-warnings:
22
+ description: 'Fail the action on warnings (not just errors)'
23
+ required: false
24
+ default: 'false'
25
+ check-handlers:
26
+ description: 'Include handler implementation checks'
27
+ required: false
28
+ default: 'false'
29
+ check-tests:
30
+ description: 'Include test coverage checks'
31
+ required: false
32
+ default: 'false'
33
+ upload-sarif:
34
+ description: 'Upload SARIF results to GitHub Code Scanning'
35
+ required: false
36
+ default: 'true'
37
+ working-directory:
38
+ description: 'Working directory for running checks'
39
+ required: false
40
+ default: '.'
41
+ bun-version:
42
+ description: 'Bun version to use'
43
+ required: false
44
+ default: 'latest'
45
+
46
+ # Impact detection inputs
47
+ mode:
48
+ description: 'Action mode: "validate" (run checks) or "impact" (detect breaking changes)'
49
+ required: false
50
+ default: 'validate'
51
+ baseline:
52
+ description: 'Git ref to compare against for impact detection (default: base branch from PR context)'
53
+ required: false
54
+ default: ''
55
+ pr-comment:
56
+ description: 'Post impact results as PR comment'
57
+ required: false
58
+ default: 'true'
59
+ fail-on-breaking:
60
+ description: 'Fail the action if breaking changes are detected'
61
+ required: false
62
+ default: 'true'
63
+ github-token:
64
+ description: 'GitHub token for PR comments and check runs'
65
+ required: false
66
+ default: '${{ github.token }}'
67
+
68
+ outputs:
69
+ success:
70
+ description: 'Whether all checks passed'
71
+ errors:
72
+ description: 'Number of errors found'
73
+ warnings:
74
+ description: 'Number of warnings found'
75
+ sarif-file:
76
+ description: 'Path to SARIF output file (if uploaded)'
77
+ json-file:
78
+ description: 'Path to JSON output file'
79
+
80
+ # Impact detection outputs
81
+ impact-status:
82
+ description: 'Impact status: "no-impact" | "non-breaking" | "breaking"'
83
+ breaking-count:
84
+ description: 'Number of breaking changes detected'
85
+ non-breaking-count:
86
+ description: 'Number of non-breaking changes detected'
87
+
88
+ runs:
89
+ using: 'composite'
90
+ steps:
91
+ - name: Setup Bun
92
+ uses: oven-sh/setup-bun@v2
93
+ with:
94
+ bun-version: ${{ inputs.bun-version }}
95
+
96
+ - name: Install dependencies
97
+ shell: bash
98
+ working-directory: ${{ inputs.working-directory }}
99
+ run: |
100
+ if [ -f "bun.lock" ] || [ -f "bun.lockb" ]; then
101
+ bun install --frozen-lockfile
102
+ elif [ -f "package-lock.json" ]; then
103
+ npm ci
104
+ elif [ -f "pnpm-lock.yaml" ]; then
105
+ corepack enable
106
+ pnpm install --frozen-lockfile
107
+ elif [ -f "yarn.lock" ]; then
108
+ corepack enable
109
+ yarn install --frozen-lockfile
110
+ else
111
+ bun install
112
+ fi
113
+
114
+ - name: Build ContractSpec CLI (if local)
115
+ shell: bash
116
+ working-directory: ${{ inputs.working-directory }}
117
+ run: |
118
+ # Check if this is the ContractSpec monorepo itself
119
+ if [ -f "packages/apps/cli-contractspec/package.json" ]; then
120
+ echo "Building ContractSpec CLI from source..."
121
+ bun run build --filter=@contractspec/app.cli-contractspec
122
+ fi
123
+
124
+ - name: Run ContractSpec CI checks
125
+ id: ci-checks
126
+ shell: bash
127
+ working-directory: ${{ inputs.working-directory }}
128
+ env:
129
+ CHECKS: ${{ inputs.checks }}
130
+ SKIP: ${{ inputs.skip }}
131
+ PATTERN: ${{ inputs.pattern }}
132
+ FAIL_ON_WARNINGS: ${{ inputs.fail-on-warnings }}
133
+ CHECK_HANDLERS: ${{ inputs.check-handlers }}
134
+ CHECK_TESTS: ${{ inputs.check-tests }}
135
+ run: |
136
+ # Build command arguments
137
+ ARGS=""
138
+
139
+ if [ "$CHECKS" != "all" ] && [ -n "$CHECKS" ]; then
140
+ ARGS="$ARGS --checks $CHECKS"
141
+ fi
142
+
143
+ if [ -n "$SKIP" ]; then
144
+ ARGS="$ARGS --skip $SKIP"
145
+ fi
146
+
147
+ if [ -n "$PATTERN" ]; then
148
+ ARGS="$ARGS --pattern '$PATTERN'"
149
+ fi
150
+
151
+ if [ "$FAIL_ON_WARNINGS" = "true" ]; then
152
+ ARGS="$ARGS --fail-on-warnings"
153
+ fi
154
+
155
+ if [ "$CHECK_HANDLERS" = "true" ]; then
156
+ ARGS="$ARGS --check-handlers"
157
+ fi
158
+
159
+ if [ "$CHECK_TESTS" = "true" ]; then
160
+ ARGS="$ARGS --check-tests"
161
+ fi
162
+
163
+ # Create output directory
164
+ mkdir -p .contractspec-ci
165
+
166
+ # Run checks and capture JSON output
167
+ echo "Running: contractspec ci --format - json $ARGS"
168
+ set +e
169
+ bunx contractspec ci --format json $ARGS > .contractspec-ci/results.json
170
+ EXIT_CODE=$?
171
+ set -e
172
+
173
+ # Parse results for outputs
174
+ if [ -f ".contractspec-ci/results.json" ]; then
175
+ SUCCESS=$(jq -r '.success' .contractspec-ci/results.json 2>/dev/null || echo "false")
176
+ ERRORS=$(jq -r '.summary.totalErrors // .errors // 0' .contractspec-ci/results.json 2>/dev/null || echo "0")
177
+ WARNINGS=$(jq -r '.summary.totalWarnings // .warnings // 0' .contractspec-ci/results.json 2>/dev/null || echo "0")
178
+
179
+ echo "success=$SUCCESS" >> $GITHUB_OUTPUT
180
+ echo "errors=$ERRORS" >> $GITHUB_OUTPUT
181
+ echo "warnings=$WARNINGS" >> $GITHUB_OUTPUT
182
+ echo "json-file=.contractspec-ci/results.json" >> $GITHUB_OUTPUT
183
+ else
184
+ echo "success=false" >> $GITHUB_OUTPUT
185
+ echo "errors=1" >> $GITHUB_OUTPUT
186
+ echo "warnings=0" >> $GITHUB_OUTPUT
187
+ fi
188
+
189
+
190
+ # Generate SARIF for upload (capture stderr to see any errors)
191
+ if [ "${{ inputs.upload-sarif }}" = "true" ]; then
192
+ echo "Generating SARIF output..."
193
+ set +e
194
+ bunx contractspec ci --format sarif $ARGS > .contractspec-ci/results.sarif
195
+ SARIF_EXIT_CODE=$?
196
+ set -e
197
+
198
+ # Validate SARIF is valid JSON
199
+ if [ -f ".contractspec-ci/results.sarif" ] && jq empty .contractspec-ci/results.sarif 2>/dev/null; then
200
+ echo "sarif-file=.contractspec-ci/results.sarif" >> $GITHUB_OUTPUT
201
+ echo "sarif-valid=true" >> $GITHUB_OUTPUT
202
+ else
203
+ echo "Warning: SARIF output is not valid JSON, skipping upload"
204
+ echo "sarif-valid=false" >> $GITHUB_OUTPUT
205
+ # If file exists but is invalid, show its contents for debugging
206
+ if [ -f ".contractspec-ci/results.sarif" ]; then
207
+ echo "SARIF file contents (first 50 lines):"
208
+ head -n 50 .contractspec-ci/results.sarif || true
209
+ fi
210
+ fi
211
+ fi
212
+
213
+ # Print text summary
214
+ echo ""
215
+ echo "=== ContractSpec CI Results ==="
216
+ bunx contractspec ci --format text $ARGS 2>/dev/null || true
217
+
218
+ exit $EXIT_CODE
219
+
220
+ - name: Upload SARIF to GitHub Code Scanning
221
+ if: inputs.upload-sarif == 'true' && steps.ci-checks.outputs.sarif-valid == 'true' && always()
222
+ uses: github/codeql-action/upload-sarif@v4
223
+ with:
224
+ sarif_file: ${{ inputs.working-directory }}/.contractspec-ci/results.sarif
225
+ category: contractspec
226
+ continue-on-error: true
227
+
228
+ - name: Upload results as artifact
229
+ if: always()
230
+ uses: actions/upload-artifact@v4
231
+ with:
232
+ name: contractspec-ci-results
233
+ path: |
234
+ ${{ inputs.working-directory }}/.contractspec-ci/results.json
235
+ ${{ inputs.working-directory }}/.contractspec-ci/results.sarif
236
+ ${{ inputs.working-directory }}/.contractspec-ci/impact.json
237
+ retention-days: 30
238
+ continue-on-error: true
239
+
240
+ # Impact detection mode
241
+ - name: Run ContractSpec impact detection
242
+ id: impact-detection
243
+ if: inputs.mode == 'impact'
244
+ shell: bash
245
+ working-directory: ${{ inputs.working-directory }}
246
+ run: |
247
+ # Determine baseline
248
+ BASELINE="${{ inputs.baseline }}"
249
+ if [ -z "$BASELINE" ] && [ -n "${{ github.base_ref }}" ]; then
250
+ BASELINE="origin/${{ github.base_ref }}"
251
+ # Fetch the base branch
252
+ git fetch origin "${{ github.base_ref }}" --depth=1 || true
253
+ fi
254
+
255
+ if [ -z "$BASELINE" ]; then
256
+ echo "No baseline specified and not in PR context. Comparing against HEAD~1"
257
+ BASELINE="HEAD~1"
258
+ fi
259
+
260
+ echo "Comparing against baseline: $BASELINE"
261
+
262
+ # Create output directory
263
+ mkdir -p .contractspec-ci
264
+
265
+ # Run impact detection
266
+ set +e
267
+ bunx contractspec impact --baseline "$BASELINE" --format json > .contractspec-ci/impact.json
268
+ EXIT_CODE=$?
269
+ set -e
270
+
271
+ if [ -f ".contractspec-ci/impact.json" ]; then
272
+ STATUS=$(jq -r '.status // "no-impact"' .contractspec-ci/impact.json 2>/dev/null || echo "no-impact")
273
+ BREAKING=$(jq -r '.summary.breaking // 0' .contractspec-ci/impact.json 2>/dev/null || echo "0")
274
+ NON_BREAKING=$(jq -r '.summary.nonBreaking // 0' .contractspec-ci/impact.json 2>/dev/null || echo "0")
275
+
276
+ echo "impact-status=$STATUS" >> $GITHUB_OUTPUT
277
+ echo "breaking-count=$BREAKING" >> $GITHUB_OUTPUT
278
+ echo "non-breaking-count=$NON_BREAKING" >> $GITHUB_OUTPUT
279
+
280
+ # Print summary
281
+ echo ""
282
+ echo "=== ContractSpec Impact Analysis ==="
283
+ if [ "$STATUS" == "breaking" ]; then
284
+ echo "❌ Breaking changes detected ($BREAKING breaking, $NON_BREAKING non-breaking)"
285
+ elif [ "$STATUS" == "non-breaking" ]; then
286
+ echo "⚠️ Contract changed ($NON_BREAKING non-breaking changes)"
287
+ else
288
+ echo "✅ No contract impact"
289
+ fi
290
+ else
291
+ echo "impact-status=no-impact" >> $GITHUB_OUTPUT
292
+ echo "breaking-count=0" >> $GITHUB_OUTPUT
293
+ echo "non-breaking-count=0" >> $GITHUB_OUTPUT
294
+ fi
295
+
296
+ # Fail if breaking changes and fail-on-breaking is true
297
+ if [ "${{ inputs.fail-on-breaking }}" == "true" ] && [ "$STATUS" == "breaking" ]; then
298
+ echo "::error::Breaking changes detected. Set fail-on-breaking to false to allow."
299
+ exit 1
300
+ fi
301
+
302
+ - name: Post PR comment with validation results
303
+ if: inputs.mode == 'validate' && inputs.pr-comment == 'true' && github.event_name == 'pull_request' && failure() && always()
304
+ uses: actions/github-script@v7
305
+ with:
306
+ github-token: ${{ inputs.github-token }}
307
+ script: |
308
+ const fs = require('fs');
309
+ const resultsFile = '.contractspec-ci/results.json';
310
+
311
+ let body = '## ❌ ContractSpec Validation Failed\n\n';
312
+
313
+ try {
314
+ if (fs.existsSync(resultsFile)) {
315
+ const results = JSON.parse(fs.readFileSync(resultsFile, 'utf8'));
316
+
317
+ const errors = results.issues?.filter(i => i.severity === 'error') || [];
318
+ const warnings = results.issues?.filter(i => i.severity === 'warning') || [];
319
+
320
+ body += `Found **${errors.length} errors** and **${warnings.length} warnings**.\n\n`;
321
+
322
+ if (errors.length > 0) {
323
+ body += '### 🔴 Errors\n\n';
324
+ for (const issue of errors.slice(0, 10)) {
325
+ body += `- **${issue.type}**: ${issue.message} in \`${issue.file}\`\n`;
326
+ if (issue.fixLinks && issue.fixLinks.cli) {
327
+ body += ` > 🔧 **Fix:** \`${issue.fixLinks.cli}\`\n`;
328
+ }
329
+ }
330
+ if (errors.length > 10) {
331
+ body += `\n_...and ${errors.length - 10} more_\n`;
332
+ }
333
+ body += '\n';
334
+ }
335
+ } else {
336
+ body += '⚠️ Validation results not found.\n';
337
+ }
338
+ } catch (e) {
339
+ body += `⚠️ Error reading results: ${e.message}\n`;
340
+ }
341
+
342
+ body += '\n---\n*Generated by ContractSpec CI*';
343
+
344
+ // Find existing comment
345
+ const { data: comments } = await github.rest.issues.listComments({
346
+ owner: context.repo.owner,
347
+ repo: context.repo.repo,
348
+ issue_number: context.issue.number,
349
+ });
350
+
351
+ const botComment = comments.find(c =>
352
+ c.body?.includes('ContractSpec Validation Failed') &&
353
+ c.user?.type === 'Bot'
354
+ );
355
+
356
+ if (botComment) {
357
+ await github.rest.issues.updateComment({
358
+ owner: context.repo.owner,
359
+ repo: context.repo.repo,
360
+ comment_id: botComment.id,
361
+ body
362
+ });
363
+ } else {
364
+ await github.rest.issues.createComment({
365
+ owner: context.repo.owner,
366
+ repo: context.repo.repo,
367
+ issue_number: context.issue.number,
368
+ body
369
+ });
370
+ }
371
+ continue-on-error: true
372
+
373
+ - name: Post PR comment with impact results
374
+ if: inputs.mode == 'impact' && inputs.pr-comment == 'true' && github.event_name == 'pull_request' && always()
375
+ uses: actions/github-script@v7
376
+ with:
377
+ github-token: ${{ inputs.github-token }}
378
+ script: |
379
+ const fs = require('fs');
380
+ const impactFile = '.contractspec-ci/impact.json';
381
+
382
+ let body = '## 📋 ContractSpec Impact Analysis\n\n';
383
+
384
+ try {
385
+ if (fs.existsSync(impactFile)) {
386
+ const impact = JSON.parse(fs.readFileSync(impactFile, 'utf8'));
387
+
388
+ if (impact.hasBreaking) {
389
+ body += '❌ **Breaking changes detected**\n\n';
390
+ } else if (impact.hasNonBreaking) {
391
+ body += '⚠️ **Contract changed (non-breaking)**\n\n';
392
+ } else {
393
+ body += '✅ **No contract impact**\n\n';
394
+ }
395
+
396
+ // Summary table
397
+ if (impact.summary.breaking > 0 || impact.summary.nonBreaking > 0) {
398
+ body += '### Summary\n\n';
399
+ body += '| Type | Count |\n';
400
+ body += '|------|-------|\n';
401
+ if (impact.summary.breaking > 0) body += `| 🔴 Breaking | ${impact.summary.breaking} |\n`;
402
+ if (impact.summary.nonBreaking > 0) body += `| 🟡 Non-breaking | ${impact.summary.nonBreaking} |\n`;
403
+ if (impact.summary.added > 0) body += `| ➕ Added | ${impact.summary.added} |\n`;
404
+ if (impact.summary.removed > 0) body += `| ➖ Removed | ${impact.summary.removed} |\n`;
405
+ body += '\n';
406
+ }
407
+
408
+ // Breaking changes details
409
+ const breaking = impact.deltas?.filter(d => d.severity === 'breaking') || [];
410
+ if (breaking.length > 0) {
411
+ body += '### 🔴 Breaking Changes\n\n';
412
+ for (const delta of breaking.slice(0, 10)) {
413
+ body += `- **${delta.specName}**: ${delta.description}\n`;
414
+ }
415
+ if (breaking.length > 10) {
416
+ body += `\n_...and ${breaking.length - 10} more_\n`;
417
+ }
418
+ body += '\n';
419
+ }
420
+ } else {
421
+ body += '⚠️ Impact analysis results not found.\n';
422
+ }
423
+ } catch (e) {
424
+ body += `⚠️ Error reading impact results: ${e.message}\n`;
425
+ }
426
+
427
+ body += '\n---\n*Generated by ContractSpec CI*';
428
+
429
+ // Find existing comment
430
+ const { data: comments } = await github.rest.issues.listComments({
431
+ owner: context.repo.owner,
432
+ repo: context.repo.repo,
433
+ issue_number: context.issue.number,
434
+ });
435
+
436
+ const botComment = comments.find(c =>
437
+ c.body?.includes('ContractSpec Impact Analysis') &&
438
+ c.user?.type === 'Bot'
439
+ );
440
+
441
+ if (botComment) {
442
+ await github.rest.issues.updateComment({
443
+ owner: context.repo.owner,
444
+ repo: context.repo.repo,
445
+ comment_id: botComment.id,
446
+ body
447
+ });
448
+ } else {
449
+ await github.rest.issues.createComment({
450
+ owner: context.repo.owner,
451
+ repo: context.repo.repo,
452
+ issue_number: context.issue.number,
453
+ body
454
+ });
455
+ }
456
+ continue-on-error: true
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@contractspec/action.validation",
3
+ "version": "1.60.0",
4
+ "description": "GitHub Action for running ContractSpec CI checks",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/lssm-tech/contractspec.git",
8
+ "directory": "packages/apps/action-validation"
9
+ },
10
+ "keywords": [
11
+ "github-action",
12
+ "contractspec",
13
+ "ci",
14
+ "validation",
15
+ "contracts"
16
+ ],
17
+ "author": "LSSM",
18
+ "license": "MIT",
19
+ "publishConfig": {
20
+ "registry": "https://registry.npmjs.org/",
21
+ "access": "public"
22
+ },
23
+ "homepage": "https://contractspec.io"
24
+ }