@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 +47 -0
- package/CHANGELOG.md +279 -0
- package/LICENSE +21 -0
- package/README.md +286 -0
- package/action.yml +456 -0
- package/package.json +24 -0
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
|
+
}
|