@percy/cli-command 1.32.0-beta.9 → 1.32.1
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/dist/smartsnap.js +73 -5
- package/package.json +6 -6
package/dist/smartsnap.js
CHANGED
|
@@ -115,6 +115,60 @@ function gitProjectRoot() {
|
|
|
115
115
|
return git(['rev-parse', '--show-toplevel']).trim();
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
// Parse `git diff --unified=0` hunk headers to find which line ranges are new
|
|
119
|
+
// or changed (on the HEAD side) for each file, then key them by the file's
|
|
120
|
+
// index in the stats `files` array — the same index module/source refs use, so
|
|
121
|
+
// the graph can join them without a path lookup. `--unified=0` drops context
|
|
122
|
+
// lines so every hunk header bounds an actual change; `--no-renames` makes a
|
|
123
|
+
// rename surface as add+delete so the new path is always concrete.
|
|
124
|
+
//
|
|
125
|
+
// Files in the diff that aren't tracked in `files` (e.g. node_modules,
|
|
126
|
+
// .storybook, or anything the stats compactor didn't index) are skipped — the
|
|
127
|
+
// graph can only reason about indexed files. Returns { <fileIndex>: [[start, end], ...] }.
|
|
128
|
+
export function getAffectedFileLocations(baseRef, files) {
|
|
129
|
+
assertSafeRef(baseRef);
|
|
130
|
+
const diff = git(['diff', '--unified=0', '--no-color', '--no-renames', baseRef, 'HEAD', '--']);
|
|
131
|
+
|
|
132
|
+
// The `files` array uses the platform separator (path.relative → back-slash
|
|
133
|
+
// on Windows); git diff always emits forward slashes. Normalize both to
|
|
134
|
+
// forward-slash for the path→index lookup.
|
|
135
|
+
const toPosix = p => p.split(path.sep).join('/');
|
|
136
|
+
const indexByPath = new Map(files.map((f, i) => [toPosix(f), i]));
|
|
137
|
+
|
|
138
|
+
// @@ -a,b +c,d @@ — the new-side `+c,d` is what we want. `d` defaults to 1
|
|
139
|
+
// when omitted; `d === 0` is a pure deletion anchored at line c (no added
|
|
140
|
+
// lines), so it contributes no range.
|
|
141
|
+
const HUNK = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/;
|
|
142
|
+
const locations = {};
|
|
143
|
+
let currentIdx;
|
|
144
|
+
for (const line of diff.split('\n')) {
|
|
145
|
+
// `+++ b/<path>` opens a file's hunks with its new-side path; a pure
|
|
146
|
+
// deletion shows `+++ /dev/null`, so leave currentIdx unset and skip it.
|
|
147
|
+
if (line.startsWith('+++ ')) {
|
|
148
|
+
const p = line.slice(4);
|
|
149
|
+
if (p === '/dev/null') {
|
|
150
|
+
currentIdx = undefined;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
/* istanbul ignore next: git always prefixes the new-side path with `b/`
|
|
154
|
+
(anything else is `/dev/null`, handled above), so the bare `: p`
|
|
155
|
+
fallback is defensive and unreachable in real diff output */
|
|
156
|
+
const rel = p.startsWith('b/') ? p.slice(2) : p;
|
|
157
|
+
currentIdx = indexByPath.get(rel);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (currentIdx === undefined) continue;
|
|
161
|
+
const m = HUNK.exec(line);
|
|
162
|
+
if (!m) continue;
|
|
163
|
+
const start = parseInt(m[1], 10);
|
|
164
|
+
const count = m[2] === undefined ? 1 : parseInt(m[2], 10);
|
|
165
|
+
if (count === 0) continue;
|
|
166
|
+
if (!locations[currentIdx]) locations[currentIdx] = [];
|
|
167
|
+
locations[currentIdx].push([start, start + count - 1]);
|
|
168
|
+
}
|
|
169
|
+
return locations;
|
|
170
|
+
}
|
|
171
|
+
|
|
118
172
|
// Paths under these directories are dependencies / framework wiring rather
|
|
119
173
|
// than first-party source, so we don't track them in the file index.
|
|
120
174
|
const EXCLUDED_DIRS = new Set(['node_modules', '.storybook']);
|
|
@@ -156,6 +210,11 @@ function transformModule(m, fileIndex, projectRoot) {
|
|
|
156
210
|
if (copy.type === 'src' && typeof copy.source === 'string') {
|
|
157
211
|
copy.source = resolveAndIndex(copy.source, fileIndex, projectRoot);
|
|
158
212
|
}
|
|
213
|
+
// `loc` (when present) is an array of `{ start, end }` source spans; the
|
|
214
|
+
// graph expects each span as a `[start, end]` tuple instead.
|
|
215
|
+
if (Array.isArray(copy.loc)) {
|
|
216
|
+
copy.loc = copy.loc.map(l => [l.start, l.end]);
|
|
217
|
+
}
|
|
159
218
|
return copy;
|
|
160
219
|
};
|
|
161
220
|
if (Array.isArray(m.imports)) out.imports = m.imports.map(mapEntry);
|
|
@@ -220,7 +279,7 @@ async function pollGraphStatus(percy, buildId, log) {
|
|
|
220
279
|
const res = await percy.client.getStatus('smartsnap_graph', [buildId]);
|
|
221
280
|
const status = res === null || res === void 0 ? void 0 : res.status;
|
|
222
281
|
log.debug(`SmartSnap: graph status (attempt ${i + 1}) = ${status}`);
|
|
223
|
-
if (status === 'done' || status === '
|
|
282
|
+
if (status === 'done' || status === 'failed') return {
|
|
224
283
|
status,
|
|
225
284
|
data: res === null || res === void 0 ? void 0 : res.data
|
|
226
285
|
};
|
|
@@ -453,20 +512,25 @@ export async function runGraphGeneration(percy, buildId, payload, log) {
|
|
|
453
512
|
files,
|
|
454
513
|
modules,
|
|
455
514
|
storybookPaths,
|
|
456
|
-
affectedNodes
|
|
515
|
+
affectedNodes,
|
|
516
|
+
affectedFileLocations
|
|
457
517
|
} = payload;
|
|
458
518
|
log.debug(`SmartSnap: starting graph generation job ${JSON.stringify({
|
|
459
519
|
buildId,
|
|
460
520
|
files,
|
|
461
521
|
modules,
|
|
462
522
|
storybookPaths,
|
|
463
|
-
affectedNodes
|
|
523
|
+
affectedNodes,
|
|
524
|
+
affectedFileLocations
|
|
464
525
|
})}`);
|
|
526
|
+
// Pass camelCase to the client, which snake_cases it to `affected_file_locations`
|
|
527
|
+
// for the API (same convention as storybookPaths → storybook_paths).
|
|
465
528
|
await percy.client.generateSmartsnapGraph(buildId, {
|
|
466
529
|
files,
|
|
467
530
|
modules,
|
|
468
531
|
storybookPaths,
|
|
469
|
-
affectedNodes
|
|
532
|
+
affectedNodes,
|
|
533
|
+
affectedFileLocations
|
|
470
534
|
});
|
|
471
535
|
const {
|
|
472
536
|
status,
|
|
@@ -621,11 +685,15 @@ export async function applySmartSnap(percy, snapshots, smartSnapConfig, buildDir
|
|
|
621
685
|
if (packageAffectedNodes.length) {
|
|
622
686
|
affectedNodes = [...affectedNodes, ...packageAffectedNodes];
|
|
623
687
|
}
|
|
688
|
+
|
|
689
|
+
// Line-level diff ranges for the indexed files, keyed by their `files` index.
|
|
690
|
+
const affectedFileLocations = getAffectedFileLocations(baseRef, files);
|
|
624
691
|
const data = await runGraphGeneration(percy, buildId, {
|
|
625
692
|
files,
|
|
626
693
|
modules,
|
|
627
694
|
storybookPaths,
|
|
628
|
-
affectedNodes
|
|
695
|
+
affectedNodes,
|
|
696
|
+
affectedFileLocations
|
|
629
697
|
}, log);
|
|
630
698
|
maybeWriteTrace(trace, data, log);
|
|
631
699
|
return selectAffectedSnapshots(snapshots, data, baseline, baselineSnapshots, normalizeImportPath, log);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/cli-command",
|
|
3
|
-
"version": "1.32.
|
|
3
|
+
"version": "1.32.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"publishConfig": {
|
|
11
11
|
"access": "public",
|
|
12
|
-
"tag": "
|
|
12
|
+
"tag": "latest"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"dist",
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
"test:coverage": "yarn test --coverage"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@percy/config": "1.32.
|
|
41
|
-
"@percy/core": "1.32.
|
|
42
|
-
"@percy/logger": "1.32.
|
|
40
|
+
"@percy/config": "1.32.1",
|
|
41
|
+
"@percy/core": "1.32.1",
|
|
42
|
+
"@percy/logger": "1.32.1",
|
|
43
43
|
"glob-to-regexp": "^0.4.1",
|
|
44
44
|
"stream-json": "^1.8.0"
|
|
45
45
|
},
|
|
46
46
|
"optionalDependencies": {
|
|
47
47
|
"snyk-nodejs-lockfile-parser": "2.7.1"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "a6c8730bc89f5ba6a562b2afe317d534fb66d6eb"
|
|
50
50
|
}
|