diff --git a/packages/cli-command/package.json b/packages/cli-command/package.json
index cc94ef17f..7d69e694e 100644
--- a/packages/cli-command/package.json
+++ b/packages/cli-command/package.json
@@ -27,7 +27,6 @@
".": "./dist/index.js",
"./flags": "./dist/flags.js",
"./utils": "./dist/utils.js",
- "./smartsnap": "./dist/smartsnap.js",
"./test/helpers": "./test/helpers.js"
},
"scripts": {
@@ -39,11 +38,6 @@
"dependencies": {
"@percy/config": "1.32.2",
"@percy/core": "1.32.2",
- "@percy/logger": "1.32.2",
- "glob-to-regexp": "^0.4.1",
- "stream-json": "^1.8.0"
- },
- "optionalDependencies": {
- "snyk-nodejs-lockfile-parser": "2.7.1"
+ "@percy/logger": "1.32.2"
}
}
diff --git a/packages/cli-command/src/graphTrace.js b/packages/cli-command/src/graphTrace.js
deleted file mode 100644
index f869286c8..000000000
--- a/packages/cli-command/src/graphTrace.js
+++ /dev/null
@@ -1,154 +0,0 @@
-import fs from 'fs';
-import path from 'path';
-import url from 'url';
-
-// Template resolution mirrors core/utils.js's secretPatterns.yml lookup:
-// resolves relative to this file's URL so it works under src/ (dev) and
-// dist/ (installed) without bundler help. The .html file is copied alongside
-// by babel's copyFiles when cli-command is built.
-const TEMPLATE_PATH = path.resolve(url.fileURLToPath(import.meta.url), '../graphTraceTemplate.html');
-
-// Maps a (raw kind, changed) pair to the kind value the template expects:
-// 'package' | 'component' | 'story' | 'is_relevant'. `changed: true` wins
-// over the underlying kind so any node touched in the diff renders purple.
-function templateKindOf(v) {
- if (v.changed) return 'is_relevant';
- switch (v.kind) {
- case 'dependency': return 'package';
- case 'component': return 'component';
- case 'story': return 'story';
- default: return 'component';
- }
-}
-
-// Sort order within a column: packages left, components middle, stories right.
-// `is_relevant` shares rank with components so a changed node doesn't jump
-// out of its own group — it just recolors.
-const KIND_RANK = { package: 0, component: 1, is_relevant: 1, story: 2 };
-
-// Layout algorithm (ported from the original Ruby renderer):
-// 1. col = longest-path depth reaching the vertex (read from the
-// transitive-closure triples the API sends), with dependencies pinned
-// to col 0.
-// 2. Propagate over edges so col[target] > col[source]. Bounded loop
-// guards against degenerate inputs.
-// 3. Stories pushed past the rightmost non-story column.
-// 4. Within each column, sort by (kind-rank, name) and assign row.
-function computeLayout(rawVertices, edges, transitiveClosure) {
- const n = rawVertices.length;
- const vertices = rawVertices.map((v, i) => ({
- index: i,
- name: v.file_path,
- kind: v.kind,
- changed: !!v.changed,
- row: 0,
- col: 0
- }));
-
- // 1. Seed col from incoming transitive-closure lengths.
- const incomingMax = new Array(n).fill(0);
- for (const triple of transitiveClosure) {
- const [u, v, val] = triple;
- if (u === v || val <= 0) continue;
- if (v < 0 || v >= n) continue;
- if (val > incomingMax[v]) incomingMax[v] = val;
- }
- for (let i = 0; i < n; i++) {
- vertices[i].col = vertices[i].kind === 'dependency' ? 0 : incomingMax[i] + 1;
- }
-
- // 2. Propagate edge constraint. n+2 iterations is enough for any DAG
- // and bounds the work on accidentally-cyclic input.
- const iterations = n + 2;
- for (let iter = 0; iter < iterations; iter++) {
- let changed = false;
- for (const [s, t] of edges) {
- if (s < 0 || s >= n || t < 0 || t >= n) continue;
- if (vertices[s].col < vertices[t].col) continue;
- vertices[t].col = vertices[s].col + 1;
- changed = true;
- }
- if (!changed) break;
- }
-
- // 3. Stories rightmost. Two passes: max across non-stories first, then
- // push every story past that boundary. Folding into one loop would let
- // stories visited before the last non-story keep a stale max.
- let furthestNonStory = 0;
- for (const v of vertices) {
- if (v.kind === 'story') continue;
- if (v.col > furthestNonStory) furthestNonStory = v.col;
- }
- for (const v of vertices) {
- if (v.kind !== 'story') continue;
- if (v.col < furthestNonStory + 1) v.col = furthestNonStory + 1;
- }
-
- // 4. Group by column, sort by (kind-rank, name), assign row.
- const groups = new Map();
- for (const v of vertices) {
- let list = groups.get(v.col);
- if (!list) groups.set(v.col, list = []);
- list.push(v);
- }
- const rankOf = v => {
- const r = KIND_RANK[templateKindOf(v)];
- /* istanbul ignore next: templateKindOf always returns a kind present in
- KIND_RANK, so the `=== undefined` fallback is defensive */
- return r === undefined ? 99 : r;
- };
- for (const list of groups.values()) {
- list.sort((a, b) => {
- const ra = rankOf(a);
- const rb = rankOf(b);
- if (ra !== rb) return ra - rb;
- // Byte-wise compare on name to match Ruby's String#<=> behaviour.
- if (a.name < b.name) return -1;
- if (a.name > b.name) return 1;
- return 0;
- });
- list.forEach((v, row) => { v.row = row; });
- }
-
- // 5. Final shape the template consumes: drop `changed`, fold it into kind.
- return vertices.map(v => ({
- index: v.index,
- name: v.name,
- row: v.row,
- col: v.col,
- kind: templateKindOf(v)
- }));
-}
-
-// Escapes characters that have meaning inside a `; `` cover HTML comment confusion; U+2028
-// and U+2029 are valid JSON but illegal in JS string literals pre-ES2019 and
-// have historically been XSS sinks.
-const LS = String.fromCharCode(0x2028);
-const PS = String.fromCharCode(0x2029);
-function safeJson(obj) {
- return JSON.stringify(obj)
- .replace(/<\//g, '<\\/')
- .replace(/`;
-
- function hostileLine() {
- return embeddedJson(renderGraphTraceHtml({
- vertices: [{ kind: 'component', file_path: hostile }],
- edges: [],
- transitiveClosureMatrixSparse: []
- }), 'vertices');
- }
-
- it('escapes "" so it cannot close the script tag', () => {
- let line = hostileLine();
- expect(line).not.toContain('');
- expect(line).toContain('<\\/script>');
- });
-
- it('escapes HTML comment open and close markers', () => {
- let line = hostileLine();
- expect(line).toContain('<\\!--');
- expect(line).toContain('--\\>');
- });
-
- it('escapes U+2028 and U+2029 line/paragraph separators', () => {
- let line = hostileLine();
- expect(line).not.toContain(LS);
- expect(line).not.toContain(PS);
- expect(line).toContain('\\u2028');
- expect(line).toContain('\\u2029');
- });
-
- it('escapes only the dangerous sequences, leaving the payload intact', () => {
- // The output is embedded in a