snapdrift 0.11.0 → 0.13.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/CHANGELOG.md +131 -0
- package/README.md +6 -2
- package/action.yml +2 -2
- package/actions/comment/action.yml +8 -37
- package/actions/pr-diff/action.yml +24 -121
- package/actions/scope/action.yml +15 -73
- package/docs/contracts.md +156 -11
- package/docs/integration-guide.md +9 -11
- package/docs/local-cli.md +57 -2
- package/docs/workflow-templates/refresh-baseline-on-merge.yml +1 -1
- package/lib/cli.mjs +4 -0
- package/lib/github-requests.mjs +134 -0
- package/lib/outage-policy.mjs +49 -14
- package/lib/provider.mjs +19 -1
- package/lib/snap-provider.mjs +3 -4
- package/package.json +6 -5
- package/types/visual-diff-types.d.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,136 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.0 - 2026-09-19
|
|
4
|
+
|
|
5
|
+
Workspace releases this version:
|
|
6
|
+
|
|
7
|
+
- `@snapdrift/compare-core` 1.4.0 — the semantic diff palette (`addedColor`/`removedColor`
|
|
8
|
+
options and the orange default for changed pixels).
|
|
9
|
+
- `@snapdrift/adapter-report-md` 1.3.0 — the exported `DIFF_IMAGE_LEGEND` constant rendered
|
|
10
|
+
alongside diff images.
|
|
11
|
+
- `@snapdrift/adapter-fs` 1.4.0 — cross-route browser storage isolation (#171), capture
|
|
12
|
+
profile v2 validation and the compatibility-before-pixels gate (#172), and the semantic
|
|
13
|
+
diff image handoff to the reporting packages.
|
|
14
|
+
- `@snapdrift/manifest` 1.5.0 — capture profile v2 schema, capture-profile helpers, and the
|
|
15
|
+
`captureCompatibility` summary types (#172).
|
|
16
|
+
|
|
17
|
+
The root dependency floors move to `@snapdrift/adapter-fs ^1.4.0`,
|
|
18
|
+
`@snapdrift/compare-core ^1.4.0`, and `@snapdrift/manifest ^1.5.0`; `adapter-fs` and
|
|
19
|
+
`adapter-report-md` in turn require `compare-core ^1.4.0`, `adapter-report-md ^1.3.0`, and
|
|
20
|
+
`manifest ^1.5.0`. The dispatcher pins 3967590 — the #174 merge commit on `main`, which
|
|
21
|
+
contains the newest `actions/` implementation (#173) and the `lib/`/`packages/` runtime the
|
|
22
|
+
wrappers load.
|
|
23
|
+
|
|
24
|
+
### Features
|
|
25
|
+
|
|
26
|
+
- Local manifests now persist capture profile v2: engine, browser/Playwright, OS,
|
|
27
|
+
locale/timezone, and rendering settings. Local comparison validates manifests,
|
|
28
|
+
requires exact v2 profile equality, and checks configured route paths and
|
|
29
|
+
normalized viewports before reading PNGs. Incompatibilities are
|
|
30
|
+
`incompatible_capture` errors, not product drift from changed image dimensions;
|
|
31
|
+
the summary's `captureCompatibility` status describes profiles only.
|
|
32
|
+
- Legacy missing/v1 profiles remain unverified and pixel-comparable after route
|
|
33
|
+
checks unless shared metadata conflicts or an explicit foreign engine is found.
|
|
34
|
+
The unverified diagnostic appears in CLI, Markdown, and HTML reports as well as
|
|
35
|
+
JSON summaries and PR comments. Malformed or unsupported profiles are rejected.
|
|
36
|
+
Refresh using `snapdrift baseline`
|
|
37
|
+
(`provider: "local"`) or the existing baseline action; `report-only` acknowledges
|
|
38
|
+
incompatibility nonblockingly but never bypasses it to compare pixels.
|
|
39
|
+
- Local diff images now use a semantic palette so the rendered PNG reflects the
|
|
40
|
+
kind of change, not just that pixels differ. `@snapdrift/compare-core` union-canvas
|
|
41
|
+
comparisons render pixels present only in the current capture in green (`addedColor`,
|
|
42
|
+
default `[0, 170, 0, 255]`), pixels present only in the baseline in red (`removedColor`,
|
|
43
|
+
default `[255, 0, 0, 255]`), and overlapping changed pixels in orange (`highlightColor`,
|
|
44
|
+
default `[255, 140, 0, 255]`). Metrics are unchanged — one-sided pixels still count as
|
|
45
|
+
changed under comparison policy v1. Strict same-dimension `generateDiffImage` diffs
|
|
46
|
+
switch their default highlight from red to orange for parity and cannot contain
|
|
47
|
+
added/removed pixels. Markdown, HTML, and PR-comment reports render a legend
|
|
48
|
+
("orange = changed · green = added · red = removed") whenever a local diff image is
|
|
49
|
+
embedded. Consumers comparing diff-PNG bytes between versions will see different
|
|
50
|
+
output by design; the new options restore any previous palette.
|
|
51
|
+
|
|
52
|
+
### Refactoring
|
|
53
|
+
|
|
54
|
+
- GitHub-script steps in `actions/scope`, `actions/pr-diff` (scope and PR report),
|
|
55
|
+
and `actions/comment` now delegate to the shared, dependency-injected helpers in
|
|
56
|
+
`lib/github-requests.mjs` (`fetchPullRequestFiles`, `resolveScopeDecision`,
|
|
57
|
+
`resolvePullRequestScope`, `upsertPullRequestReportComment`), so the changed-file
|
|
58
|
+
selection and comment-upsert behavior is defined once and exercised by unit and
|
|
59
|
+
real-YAML parity tests. Outputs, reasons, warnings, markers, and fallback bodies
|
|
60
|
+
are byte-for-byte identical.
|
|
61
|
+
- Providers and `captureWithPolicy`/`diffWithPolicy` results now include an explicit
|
|
62
|
+
`artifacts` descriptor (`describeCaptureArtifacts`: `localScreenshots` plus
|
|
63
|
+
`artifactsRoot` for local or hosted local-capture hybrid runs). Fallback decisions
|
|
64
|
+
prefer provider-returned capabilities, with provider-name/URL inference retained
|
|
65
|
+
for legacy results and explicit caller overrides taking precedence. Diff callers
|
|
66
|
+
without capability information or config retain the no-recapture legacy default.
|
|
67
|
+
`localScreenshots` remains, and
|
|
68
|
+
`hasLocalScreenshots` is kept as a thin wrapper. `lib/provider.mjs` exports
|
|
69
|
+
`PROVIDER_CAPABILITIES` and `providerSupports(provider, capability)` so the
|
|
70
|
+
unavailable `LocalProvider.fetchLatestBaseline` baseline-store capability is
|
|
71
|
+
discoverable without a runtime throw.
|
|
72
|
+
|
|
73
|
+
### Behavior changes
|
|
74
|
+
|
|
75
|
+
- Local capture explicitly sets `en-US` / `UTC`; localized text/date changes may
|
|
76
|
+
require baseline recapture. Fonts are not fingerprinted. See the
|
|
77
|
+
[capture profile contract](docs/contracts.md#local-capture-profile-v2) and
|
|
78
|
+
[baseline refresh guide](docs/local-cli.md#refreshing-or-acknowledging-local-baselines).
|
|
79
|
+
|
|
80
|
+
### Fixes
|
|
81
|
+
|
|
82
|
+
- Isolate each local route capture and retry in a fresh browser context, preventing
|
|
83
|
+
cookies and browser storage from leaking between routes sharing a viewport.
|
|
84
|
+
Per-viewport concurrency and result ordering are preserved. This also fixes
|
|
85
|
+
Snap local-capture hybrid runs; recapture baselines affected by leaked storage.
|
|
86
|
+
|
|
87
|
+
## 0.12.0 - 2026-09-16
|
|
88
|
+
|
|
89
|
+
### Fixes
|
|
90
|
+
|
|
91
|
+
- **PR comments no longer render `[object Object]` for a viewport.** A viewport is
|
|
92
|
+
either a preset name or a `{ width, height }` object, and the PR-comment renderer
|
|
93
|
+
stringified it directly. Hosted diff runs always build the object form, so every
|
|
94
|
+
hosted report printed the useless placeholder in its Error, Drift signals, and
|
|
95
|
+
Dimension shifts tables. All three report renderers now share one
|
|
96
|
+
`formatViewport` helper (exported from `@snapdrift/adapter-report-md`).
|
|
97
|
+
|
|
98
|
+
### Features
|
|
99
|
+
|
|
100
|
+
- **`compareImages` accepts a per-call `maxPixels` union-canvas ceiling.**
|
|
101
|
+
`MAX_COMPARISON_PIXELS` (32 Mi) is a *process memory* bound, not an algorithm
|
|
102
|
+
property, so callers with a larger envelope — a dedicated diff Lambda — can raise
|
|
103
|
+
it while callers sharing a small host keep the default. A missing, non-positive,
|
|
104
|
+
or non-finite value falls back to the default, so the guard cannot be removed by
|
|
105
|
+
accident. The `ComparisonTooLargeError` message now reports the ceiling that was
|
|
106
|
+
actually exceeded.
|
|
107
|
+
|
|
108
|
+
### Documentation
|
|
109
|
+
|
|
110
|
+
- Document that `migrate-baselines --to snap` is unsupported and point to
|
|
111
|
+
`snapdrift baseline` as the supported way to seed a hosted project. The
|
|
112
|
+
integration guide still described the retired upload direction as a working
|
|
113
|
+
one-shot command, and the contracts reference documented its request body,
|
|
114
|
+
idempotency, and storage behavior as if it succeeded. Both contradicted the
|
|
115
|
+
CLI, which has failed the direction since 0.7.0 with a pointer to
|
|
116
|
+
`snapdrift baseline`.
|
|
117
|
+
- Document the screenshot size budget's memory cost for `maxPixels` callers: the
|
|
118
|
+
comparison retains both decoded RGBA inputs plus the optional diff canvas, so the
|
|
119
|
+
floor is roughly 12 bytes per union pixel before PNG overhead (~32-35 measured end
|
|
120
|
+
to end), and the ceiling should be sized against a measured peak rather than that
|
|
121
|
+
arithmetic.
|
|
122
|
+
|
|
123
|
+
### Workspace packages
|
|
124
|
+
|
|
125
|
+
- `@snapdrift/adapter-report-md` 1.2.1 publishes the `[object Object]` viewport fix;
|
|
126
|
+
`@snapdrift/compare-core` 1.3.0 publishes the per-call `maxPixels` option and the
|
|
127
|
+
new size-budget guidance.
|
|
128
|
+
- The root package's dependency floors move to `^1.2.1` (adapter-report-md) and
|
|
129
|
+
`^1.3.0` (compare-core) so `snapdrift` 0.12.0 actually requires those fixes. A
|
|
130
|
+
retained lockfile resolution of adapter-report-md 1.2.0 still satisfies `^1.2.0`,
|
|
131
|
+
so without the floor bump an upgrade of `snapdrift` alone could install the
|
|
132
|
+
unfixed report renderer.
|
|
133
|
+
|
|
3
134
|
## 0.11.0 - 2026-09-14
|
|
4
135
|
|
|
5
136
|
### Behavior changes
|
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ You keep ownership of checkout, build, startup, readiness, and teardown. SnapDri
|
|
|
48
48
|
|
|
49
49
|
```yaml
|
|
50
50
|
- name: SnapDrift Baseline
|
|
51
|
-
uses: ranacseruet/snapdrift@v0.
|
|
51
|
+
uses: ranacseruet/snapdrift@v0.13.0
|
|
52
52
|
with:
|
|
53
53
|
mode: baseline
|
|
54
54
|
repo-config-path: .github/snapdrift.json
|
|
@@ -60,7 +60,7 @@ With `provider: "snap"`, baseline publication is a complete, fail-closed snapsho
|
|
|
60
60
|
|
|
61
61
|
```yaml
|
|
62
62
|
- name: SnapDrift Report
|
|
63
|
-
uses: ranacseruet/snapdrift@v0.
|
|
63
|
+
uses: ranacseruet/snapdrift@v0.13.0
|
|
64
64
|
with:
|
|
65
65
|
mode: pr-diff
|
|
66
66
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -110,6 +110,10 @@ Start with `report-only` while baselines settle. Move to `fail-on-changes` or st
|
|
|
110
110
|
| `fail-on-incomplete` | Captures are missing or comparison errors occur |
|
|
111
111
|
| `strict` | Any drift signal or incomplete comparison appears |
|
|
112
112
|
|
|
113
|
+
Local comparisons check capture profiles and configured route/path/viewport identity
|
|
114
|
+
before pixels. Incompatible captures produce incomplete errors, not product drift;
|
|
115
|
+
`report-only` never bypasses these checks. See [compatibility and baseline refresh](docs/local-cli.md#refreshing-or-acknowledging-local-baselines).
|
|
116
|
+
|
|
113
117
|
## Current constraints
|
|
114
118
|
|
|
115
119
|
- Ubuntu runners only (local CLI works on any OS Node 22+ supports)
|
package/action.yml
CHANGED
|
@@ -155,7 +155,7 @@ runs:
|
|
|
155
155
|
- name: SnapDrift baseline
|
|
156
156
|
id: baseline
|
|
157
157
|
if: inputs.mode == 'baseline'
|
|
158
|
-
uses: ranacseruet/snapdrift/actions/baseline@
|
|
158
|
+
uses: ranacseruet/snapdrift/actions/baseline@3967590687d420479ba5ec351e1394de174c1838 # v0.13.0
|
|
159
159
|
with:
|
|
160
160
|
repo-config-path: ${{ inputs.repo-config-path }}
|
|
161
161
|
route-ids: ${{ inputs.route-ids }}
|
|
@@ -165,7 +165,7 @@ runs:
|
|
|
165
165
|
- name: SnapDrift pull request diff
|
|
166
166
|
id: pr-diff
|
|
167
167
|
if: inputs.mode == 'pr-diff'
|
|
168
|
-
uses: ranacseruet/snapdrift/actions/pr-diff@
|
|
168
|
+
uses: ranacseruet/snapdrift/actions/pr-diff@3967590687d420479ba5ec351e1394de174c1838 # v0.13.0
|
|
169
169
|
with:
|
|
170
170
|
github-token: ${{ inputs.github-token }}
|
|
171
171
|
repo-config-path: ${{ inputs.repo-config-path }}
|
|
@@ -116,41 +116,12 @@ runs:
|
|
|
116
116
|
maxErrorRows
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
const requestsModulePath = path.resolve(process.env.ACTION_ROOT, 'lib/github-requests.mjs');
|
|
120
|
+
const { upsertPullRequestReportComment } = await import(pathToFileURL(requestsModulePath));
|
|
121
|
+
await upsertPullRequestReportComment({
|
|
122
|
+
github,
|
|
123
|
+
...context.repo,
|
|
124
|
+
issueNumber,
|
|
125
|
+
body,
|
|
126
|
+
markers: PR_COMMENT_MARKERS
|
|
124
127
|
});
|
|
125
|
-
const matchingComments = comments
|
|
126
|
-
.filter((comment) => comment.body && PR_COMMENT_MARKERS.some((marker) => comment.body.includes(marker)))
|
|
127
|
-
.sort((left, right) => {
|
|
128
|
-
const leftTime = new Date(left.updated_at || left.created_at).getTime();
|
|
129
|
-
const rightTime = new Date(right.updated_at || right.created_at).getTime();
|
|
130
|
-
return rightTime - leftTime;
|
|
131
|
-
});
|
|
132
|
-
const existing = matchingComments[0];
|
|
133
|
-
|
|
134
|
-
if (existing) {
|
|
135
|
-
await github.rest.issues.updateComment({
|
|
136
|
-
owner: context.repo.owner,
|
|
137
|
-
repo: context.repo.repo,
|
|
138
|
-
comment_id: existing.id,
|
|
139
|
-
body
|
|
140
|
-
});
|
|
141
|
-
} else {
|
|
142
|
-
await github.rest.issues.createComment({
|
|
143
|
-
owner: context.repo.owner,
|
|
144
|
-
repo: context.repo.repo,
|
|
145
|
-
issue_number: issueNumber,
|
|
146
|
-
body
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
for (const duplicate of matchingComments.slice(1)) {
|
|
151
|
-
await github.rest.issues.deleteComment({
|
|
152
|
-
owner: context.repo.owner,
|
|
153
|
-
repo: context.repo.repo,
|
|
154
|
-
comment_id: duplicate.id
|
|
155
|
-
});
|
|
156
|
-
}
|
|
@@ -137,91 +137,23 @@ runs:
|
|
|
137
137
|
const { pathToFileURL } = require('url');
|
|
138
138
|
|
|
139
139
|
const configModulePath = path.resolve(process.env.ACTION_ROOT, 'lib/snapdrift-config.mjs');
|
|
140
|
-
const { loadSnapdriftConfig
|
|
140
|
+
const { loadSnapdriftConfig } = await import(pathToFileURL(configModulePath));
|
|
141
|
+
const requestsModulePath = path.resolve(process.env.ACTION_ROOT, 'lib/github-requests.mjs');
|
|
142
|
+
const { resolvePullRequestScope } = await import(pathToFileURL(requestsModulePath));
|
|
141
143
|
const { config } = await loadSnapdriftConfig(process.env.REPO_CONFIG_PATH || undefined);
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
reason: 'explicit_route_ids',
|
|
156
|
-
selectedRouteIds: selected.selectedRouteIds
|
|
157
|
-
});
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (process.env.FORCE_RUN === 'true') {
|
|
162
|
-
setOutputs({
|
|
163
|
-
shouldRun: true,
|
|
164
|
-
reason: process.env.FORCE_RUN_REASON || 'forced',
|
|
165
|
-
selectedRouteIds: allRouteIds
|
|
166
|
-
});
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const prNumber = Number(process.env.INPUT_PR_NUMBER || context.payload.pull_request?.number || '');
|
|
171
|
-
if (!Number.isFinite(prNumber) || prNumber <= 0) {
|
|
172
|
-
setOutputs({
|
|
173
|
-
shouldRun: true,
|
|
174
|
-
reason: 'missing_pr_number',
|
|
175
|
-
selectedRouteIds: allRouteIds
|
|
176
|
-
});
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
const files = await github.paginate(github.rest.pulls.listFiles, {
|
|
182
|
-
owner: context.repo.owner,
|
|
183
|
-
repo: context.repo.repo,
|
|
184
|
-
pull_number: prNumber,
|
|
185
|
-
per_page: 100
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
if (!Array.isArray(files)) {
|
|
189
|
-
throw new Error('Malformed GitHub changed-file response: expected an array of file records.');
|
|
190
|
-
}
|
|
191
|
-
for (const [index, file] of files.entries()) {
|
|
192
|
-
if (!file || typeof file !== 'object' || typeof file.filename !== 'string' || file.filename.length === 0) {
|
|
193
|
-
throw new Error(`Malformed GitHub changed-file response: file record ${index} must include a non-empty string filename.`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if (files.length >= 3000) {
|
|
197
|
-
core.warning('GitHub returned the maximum 3000 changed-file records; the list may be truncated, so running all configured captures.');
|
|
198
|
-
setOutputs({
|
|
199
|
-
shouldRun: true,
|
|
200
|
-
reason: 'changed_files_truncated',
|
|
201
|
-
selectedRouteIds: allRouteIds
|
|
202
|
-
});
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
const changedFiles = [...new Set(files.flatMap((file) => [
|
|
206
|
-
file.filename,
|
|
207
|
-
...(file.status === 'renamed' && typeof file.previous_filename === 'string' && file.previous_filename
|
|
208
|
-
? [file.previous_filename]
|
|
209
|
-
: [])
|
|
210
|
-
]))];
|
|
211
|
-
setOutputs(
|
|
212
|
-
selectRoutesForChangedFiles(
|
|
213
|
-
config,
|
|
214
|
-
changedFiles
|
|
215
|
-
)
|
|
216
|
-
);
|
|
217
|
-
} catch (error) {
|
|
218
|
-
core.warning(`Unable to inspect PR files for SnapDrift scope; running all configured captures instead. ${error.message}`);
|
|
219
|
-
setOutputs({
|
|
220
|
-
shouldRun: true,
|
|
221
|
-
reason: 'snapdrift_scope_check_failed',
|
|
222
|
-
selectedRouteIds: allRouteIds
|
|
223
|
-
});
|
|
224
|
-
}
|
|
144
|
+
const scope = await resolvePullRequestScope({
|
|
145
|
+
github,
|
|
146
|
+
...context.repo,
|
|
147
|
+
config,
|
|
148
|
+
pullNumber: Number(process.env.INPUT_PR_NUMBER || context.payload.pull_request?.number || ''),
|
|
149
|
+
routeIds: process.env.ROUTE_IDS || '',
|
|
150
|
+
forceRun: process.env.FORCE_RUN === 'true',
|
|
151
|
+
forceRunReason: process.env.FORCE_RUN_REASON || 'forced',
|
|
152
|
+
warning: (message) => core.warning(message)
|
|
153
|
+
});
|
|
154
|
+
core.setOutput('should_run', scope.shouldRun ? 'true' : 'false');
|
|
155
|
+
core.setOutput('reason', scope.reason);
|
|
156
|
+
core.setOutput('selected_route_ids', (scope.selectedRouteIds || []).join(','));
|
|
225
157
|
|
|
226
158
|
- id: baseline
|
|
227
159
|
if: steps.scope.outputs.should_run == 'true'
|
|
@@ -739,44 +671,15 @@ runs:
|
|
|
739
671
|
maxErrorRows
|
|
740
672
|
});
|
|
741
673
|
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
674
|
+
const requestsModulePath = path.resolve(process.env.ACTION_ROOT, 'lib/github-requests.mjs');
|
|
675
|
+
const { upsertPullRequestReportComment } = await import(pathToFileURL(requestsModulePath));
|
|
676
|
+
await upsertPullRequestReportComment({
|
|
677
|
+
github,
|
|
678
|
+
...context.repo,
|
|
679
|
+
issueNumber,
|
|
680
|
+
body,
|
|
681
|
+
markers: PR_COMMENT_MARKERS
|
|
747
682
|
});
|
|
748
|
-
const matchingComments = comments
|
|
749
|
-
.filter((comment) => comment.body && PR_COMMENT_MARKERS.some((marker) => comment.body.includes(marker)))
|
|
750
|
-
.sort((left, right) => {
|
|
751
|
-
const leftTime = new Date(left.updated_at || left.created_at).getTime();
|
|
752
|
-
const rightTime = new Date(right.updated_at || right.created_at).getTime();
|
|
753
|
-
return rightTime - leftTime;
|
|
754
|
-
});
|
|
755
|
-
const existing = matchingComments[0];
|
|
756
|
-
|
|
757
|
-
if (existing) {
|
|
758
|
-
await github.rest.issues.updateComment({
|
|
759
|
-
owner: context.repo.owner,
|
|
760
|
-
repo: context.repo.repo,
|
|
761
|
-
comment_id: existing.id,
|
|
762
|
-
body
|
|
763
|
-
});
|
|
764
|
-
} else {
|
|
765
|
-
await github.rest.issues.createComment({
|
|
766
|
-
owner: context.repo.owner,
|
|
767
|
-
repo: context.repo.repo,
|
|
768
|
-
issue_number: issueNumber,
|
|
769
|
-
body
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
for (const duplicate of matchingComments.slice(1)) {
|
|
774
|
-
await github.rest.issues.deleteComment({
|
|
775
|
-
owner: context.repo.owner,
|
|
776
|
-
repo: context.repo.repo,
|
|
777
|
-
comment_id: duplicate.id
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
683
|
|
|
781
684
|
# A skipped compare writes a summary with no diff counters, so enforcement
|
|
782
685
|
# has nothing to evaluate — `warn-and-skip` means "do not fail my build".
|
package/actions/scope/action.yml
CHANGED
|
@@ -67,77 +67,19 @@ runs:
|
|
|
67
67
|
const { pathToFileURL } = require('url');
|
|
68
68
|
|
|
69
69
|
const configModulePath = path.resolve(process.env.ACTION_ROOT, 'lib/snapdrift-config.mjs');
|
|
70
|
-
const { loadSnapdriftConfig
|
|
70
|
+
const { loadSnapdriftConfig } = await import(pathToFileURL(configModulePath));
|
|
71
|
+
const requestsModulePath = path.resolve(process.env.ACTION_ROOT, 'lib/github-requests.mjs');
|
|
72
|
+
const { resolvePullRequestScope } = await import(pathToFileURL(requestsModulePath));
|
|
71
73
|
const { config } = await loadSnapdriftConfig(process.env.REPO_CONFIG_PATH || undefined);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
shouldRun: true,
|
|
85
|
-
reason: process.env.FORCE_RUN_REASON || 'forced',
|
|
86
|
-
selectedRouteIds: allRouteIds
|
|
87
|
-
});
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (!Number.isFinite(resolvedPrNumber) || resolvedPrNumber <= 0) {
|
|
92
|
-
setOutputs({
|
|
93
|
-
shouldRun: true,
|
|
94
|
-
reason: 'missing_pr_number',
|
|
95
|
-
selectedRouteIds: allRouteIds
|
|
96
|
-
});
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
const files = await github.paginate(github.rest.pulls.listFiles, {
|
|
102
|
-
owner: context.repo.owner,
|
|
103
|
-
repo: context.repo.repo,
|
|
104
|
-
pull_number: resolvedPrNumber,
|
|
105
|
-
per_page: 100
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
if (!Array.isArray(files)) {
|
|
109
|
-
throw new Error('Malformed GitHub changed-file response: expected an array of file records.');
|
|
110
|
-
}
|
|
111
|
-
for (const [index, file] of files.entries()) {
|
|
112
|
-
if (!file || typeof file !== 'object' || typeof file.filename !== 'string' || file.filename.length === 0) {
|
|
113
|
-
throw new Error(`Malformed GitHub changed-file response: file record ${index} must include a non-empty string filename.`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
if (files.length >= 3000) {
|
|
117
|
-
core.warning('GitHub returned the maximum 3000 changed-file records; the list may be truncated, so running all configured captures.');
|
|
118
|
-
setOutputs({
|
|
119
|
-
shouldRun: true,
|
|
120
|
-
reason: 'changed_files_truncated',
|
|
121
|
-
selectedRouteIds: allRouteIds
|
|
122
|
-
});
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const changedFiles = [...new Set(files.flatMap((file) => [
|
|
126
|
-
file.filename,
|
|
127
|
-
...(file.status === 'renamed' && typeof file.previous_filename === 'string' && file.previous_filename
|
|
128
|
-
? [file.previous_filename]
|
|
129
|
-
: [])
|
|
130
|
-
]))];
|
|
131
|
-
const scope = selectRoutesForChangedFiles(
|
|
132
|
-
config,
|
|
133
|
-
changedFiles
|
|
134
|
-
);
|
|
135
|
-
setOutputs(scope);
|
|
136
|
-
} catch (error) {
|
|
137
|
-
core.warning(`Unable to inspect PR files for SnapDrift scope; running all configured captures instead. ${error.message}`);
|
|
138
|
-
setOutputs({
|
|
139
|
-
shouldRun: true,
|
|
140
|
-
reason: 'snapdrift_scope_check_failed',
|
|
141
|
-
selectedRouteIds: allRouteIds
|
|
142
|
-
});
|
|
143
|
-
}
|
|
74
|
+
const scope = await resolvePullRequestScope({
|
|
75
|
+
github,
|
|
76
|
+
...context.repo,
|
|
77
|
+
config,
|
|
78
|
+
pullNumber: Number(process.env.INPUT_PR_NUMBER || context.payload.pull_request?.number || ''),
|
|
79
|
+
forceRun: process.env.FORCE_RUN === 'true',
|
|
80
|
+
forceRunReason: process.env.FORCE_RUN_REASON || 'forced',
|
|
81
|
+
warning: (message) => core.warning(message)
|
|
82
|
+
});
|
|
83
|
+
core.setOutput('should_run', scope.shouldRun ? 'true' : 'false');
|
|
84
|
+
core.setOutput('reason', scope.reason);
|
|
85
|
+
core.setOutput('selected_route_ids', (scope.selectedRouteIds || []).join(','));
|