@bridge-ds/visual 0.0.0-stage → 0.2.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/dist/capture.d.ts +40 -0
- package/dist/composite.d.ts +16 -0
- package/dist/diff.d.ts +28 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1747 -0
- package/dist/manifest.d.ts +31 -0
- package/dist/routes.d.ts +11 -0
- package/dist/run.d.ts +69 -0
- package/dist/select.d.ts +31 -0
- package/dist/serve.d.ts +5 -0
- package/dist/types.d.ts +141 -0
- package/dist/workspace.d.ts +39 -0
- package/package.json +58 -4
- package/README.md +0 -3
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Manifest, ShotResult, Summary } from './types.js';
|
|
2
|
+
export declare const COMMENT_MARKER = "<!-- bridge-visual-impact -->";
|
|
3
|
+
export declare function summarize(shots: ShotResult[]): Summary;
|
|
4
|
+
/** Changed first (largest change first), then added, removed, errors, unchanged; stable within a group. */
|
|
5
|
+
export declare function rankShots(shots: ShotResult[]): ShotResult[];
|
|
6
|
+
export declare const hasVisualChange: (m: Pick<Manifest, "summary">) => boolean;
|
|
7
|
+
/** One-paragraph plain-text summary (CLI output, MCP tool result). */
|
|
8
|
+
export declare function summaryLine(m: Manifest): string;
|
|
9
|
+
interface Group {
|
|
10
|
+
label: string;
|
|
11
|
+
shots: ShotResult[];
|
|
12
|
+
}
|
|
13
|
+
/** Shots grouped by label, in the order of their first (largest) shot. */
|
|
14
|
+
export declare function groupByLabel(shots: ShotResult[]): Group[];
|
|
15
|
+
/**
|
|
16
|
+
* Images for the comment: variety first. App screens (up to 2) and then one image per story, largest change
|
|
17
|
+
* first, before a second mode of the same story.
|
|
18
|
+
*/
|
|
19
|
+
export declare function pickImages(shots: ShotResult[], max: number): ShotResult[];
|
|
20
|
+
export interface CommentOptions {
|
|
21
|
+
/** Base URL the manifest's relative image paths resolve against (raw.githubusercontent.com/.../pr-1/sha). */
|
|
22
|
+
imageBase?: string;
|
|
23
|
+
/** Link to the workflow run artifact with every image. */
|
|
24
|
+
artifactUrl?: string;
|
|
25
|
+
/** Composite images shown inline. */
|
|
26
|
+
maxImages?: number;
|
|
27
|
+
maxRows?: number;
|
|
28
|
+
}
|
|
29
|
+
/** The sticky PR comment: summary table plus before/after/diff composites of the largest changes. */
|
|
30
|
+
export declare function renderComment(m: Manifest, o?: CommentOptions): string;
|
|
31
|
+
export {};
|
package/dist/routes.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AppRoute, Tenant } from './types.js';
|
|
2
|
+
export declare const DEFAULT_ROUTES: AppRoute[];
|
|
3
|
+
export declare const DEFAULT_TENANTS: Tenant[];
|
|
4
|
+
export declare const THEMES: readonly ["light", "dark"];
|
|
5
|
+
export declare const BRANDS: readonly ["bridge", "ocean"];
|
|
6
|
+
/** URL of a route in the built admin (hash router), with tenant, theme and language pinned in the query. */
|
|
7
|
+
export declare function routeUrl(base: string, route: AppRoute, tenant: string, theme: string): string;
|
|
8
|
+
/** URL of one story in the built Storybook, with brand and theme set as Storybook globals. */
|
|
9
|
+
export declare function storyUrl(base: string, id: string, brand: string, theme: string): string;
|
|
10
|
+
/** File-safe shot key. */
|
|
11
|
+
export declare const shotKey: (...parts: string[]) => string;
|
package/dist/run.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { buildUsageIndex, LayeredTokenSet, type BridgeConfig } from '@bridge-ds/core';
|
|
2
|
+
import type { AppRoute, Manifest, Only, Selection, StoryEntry, Tenant, Viewport } from './types.js';
|
|
3
|
+
import { type BuildConfig, type Log } from './workspace.js';
|
|
4
|
+
export interface ImpactOptions {
|
|
5
|
+
/** Repo root (a git checkout with node_modules installed). */
|
|
6
|
+
root: string;
|
|
7
|
+
base: string;
|
|
8
|
+
head: string;
|
|
9
|
+
/** Output folder for manifest.json and images. Default: <root>/visual-impact. */
|
|
10
|
+
out?: string;
|
|
11
|
+
only?: Only[];
|
|
12
|
+
/** pixelmatch threshold, 0..1. Default 0.03 (one step of a color ramp is about 0.1). */
|
|
13
|
+
threshold?: number;
|
|
14
|
+
/** Build cache folder, keyed by commit. Default: <root>/node_modules/.cache/bridge-visual. */
|
|
15
|
+
cacheDir?: string;
|
|
16
|
+
/** Temp folder for worktrees. Default: OS temp. */
|
|
17
|
+
workDir?: string;
|
|
18
|
+
/** Only compute the selection; no build, no browser. */
|
|
19
|
+
plan?: boolean;
|
|
20
|
+
/** Raw URL base for images in comment.md (the bridge-visual branch folder of this run). */
|
|
21
|
+
imageBase?: string;
|
|
22
|
+
artifactUrl?: string;
|
|
23
|
+
maxStoriesPerComponent?: number;
|
|
24
|
+
routes?: AppRoute[];
|
|
25
|
+
tenants?: Tenant[];
|
|
26
|
+
modes?: {
|
|
27
|
+
brand: string;
|
|
28
|
+
theme: string;
|
|
29
|
+
}[];
|
|
30
|
+
viewport?: {
|
|
31
|
+
stories?: Viewport;
|
|
32
|
+
app?: Viewport;
|
|
33
|
+
};
|
|
34
|
+
build?: BuildConfig;
|
|
35
|
+
/** Pages (tabs) capturing stories in parallel in the one browser. Default 2. */
|
|
36
|
+
concurrency?: number;
|
|
37
|
+
/** Keep before/after images of unchanged shots (off: only changed shots keep images). */
|
|
38
|
+
keepUnchanged?: boolean;
|
|
39
|
+
log?: Log;
|
|
40
|
+
}
|
|
41
|
+
export interface ImpactResult {
|
|
42
|
+
manifest: Manifest;
|
|
43
|
+
outDir: string;
|
|
44
|
+
manifestPath: string;
|
|
45
|
+
commentPath?: string;
|
|
46
|
+
summary: string;
|
|
47
|
+
}
|
|
48
|
+
/** Token paths whose resolved CSS value differs in at least one mode, with the modes. Direct edits flagged. */
|
|
49
|
+
export declare function changedTokens(before: LayeredTokenSet, after: LayeredTokenSet): Selection['tokens'];
|
|
50
|
+
export interface Plan {
|
|
51
|
+
base: {
|
|
52
|
+
ref: string;
|
|
53
|
+
sha: string;
|
|
54
|
+
};
|
|
55
|
+
head: {
|
|
56
|
+
ref: string;
|
|
57
|
+
sha: string;
|
|
58
|
+
};
|
|
59
|
+
config: BridgeConfig;
|
|
60
|
+
selection: Selection;
|
|
61
|
+
sources: {
|
|
62
|
+
stories: StoryEntry[];
|
|
63
|
+
librarySrc: string;
|
|
64
|
+
usage: ReturnType<typeof buildUsageIndex>;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Works out what to capture between two refs (no build, no browser). The base is the merge base. */
|
|
68
|
+
export declare function planImpact(o: Pick<ImpactOptions, 'root' | 'base' | 'head' | 'only' | 'maxStoriesPerComponent' | 'build'>): Promise<Plan>;
|
|
69
|
+
export declare function runImpact(o: ImpactOptions): Promise<ImpactResult>;
|
package/dist/select.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type UsageIndex } from '@bridge-ds/core';
|
|
2
|
+
import type { Selection, StoryEntry } from './types.js';
|
|
3
|
+
export interface SelectInput {
|
|
4
|
+
/** Files changed between base and head (repo-relative). */
|
|
5
|
+
files: string[];
|
|
6
|
+
/** Token paths whose resolved value changed. */
|
|
7
|
+
tokens: Selection['tokens'];
|
|
8
|
+
cssVar: (path: string) => string;
|
|
9
|
+
usage: UsageIndex;
|
|
10
|
+
stories: StoryEntry[];
|
|
11
|
+
/** Root of the component library source, e.g. packages/react/src. Shared code here affects everything. */
|
|
12
|
+
librarySrc: string;
|
|
13
|
+
/** Files that change every story when edited (Storybook config, token build). Prefixes. */
|
|
14
|
+
globalPaths?: string[];
|
|
15
|
+
/** App source folder whose edits put the app screens in scope. */
|
|
16
|
+
appDir?: string;
|
|
17
|
+
maxStoriesPerComponent?: number;
|
|
18
|
+
}
|
|
19
|
+
/** Story entries grouped by component folder, or null for stories outside it (foundations). */
|
|
20
|
+
export declare function storyComponent(s: StoryEntry, componentsDir: string): string | null;
|
|
21
|
+
export declare function selectImpact(input: SelectInput): Selection;
|
|
22
|
+
/** Storybook's id rules (CSF): "Feedback/Alert" + export "AllStates" -> "feedback-alert--all-states". */
|
|
23
|
+
export declare function storyId(title: string, exportName: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Stories read from CSF source without building Storybook (plan mode). Story ids follow Storybook's rules, so
|
|
26
|
+
* they match storybook-static/index.json for the usual `title` + named exports.
|
|
27
|
+
*/
|
|
28
|
+
export declare function storiesFromSource(files: {
|
|
29
|
+
path: string;
|
|
30
|
+
text: string;
|
|
31
|
+
}[]): StoryEntry[];
|
package/dist/serve.d.ts
ADDED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export type Only = 'stories' | 'app';
|
|
2
|
+
export interface Mode {
|
|
3
|
+
brand: string;
|
|
4
|
+
theme: string;
|
|
5
|
+
}
|
|
6
|
+
export interface StoryEntry {
|
|
7
|
+
id: string;
|
|
8
|
+
title: string;
|
|
9
|
+
name: string;
|
|
10
|
+
importPath: string;
|
|
11
|
+
}
|
|
12
|
+
export interface AppRoute {
|
|
13
|
+
/** Stable key used in file names: "orders-list". */
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
/** Hash route of the admin app (ra-core uses a hash router): "/orders". */
|
|
17
|
+
path: string;
|
|
18
|
+
/** Per-tenant path when record ids differ between seeded datasets (tenant id -> path). */
|
|
19
|
+
pathByTenant?: Record<string, string>;
|
|
20
|
+
/** CSS selector that must be visible before the screenshot (a drawer, a form). */
|
|
21
|
+
waitFor?: string;
|
|
22
|
+
/** Extra selectors painted over before the screenshot (volatile content). */
|
|
23
|
+
mask?: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface Tenant {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
brand: string;
|
|
29
|
+
}
|
|
30
|
+
export interface SelectedComponent {
|
|
31
|
+
id: string;
|
|
32
|
+
reasons: string[];
|
|
33
|
+
indirect: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface Selection {
|
|
36
|
+
/** Token paths whose resolved value differs between base and head, direct edits first. */
|
|
37
|
+
tokens: {
|
|
38
|
+
path: string;
|
|
39
|
+
kind: 'direct' | 'alias';
|
|
40
|
+
modes: string[];
|
|
41
|
+
}[];
|
|
42
|
+
vars: string[];
|
|
43
|
+
files: string[];
|
|
44
|
+
components: SelectedComponent[];
|
|
45
|
+
/** Story ids to capture, AllStates first per component. */
|
|
46
|
+
stories: string[];
|
|
47
|
+
/** Why every AllStates story is captured instead of a targeted list, or null. */
|
|
48
|
+
fallback: string | null;
|
|
49
|
+
/** Whether the admin screens are captured, and why. */
|
|
50
|
+
app: {
|
|
51
|
+
include: boolean;
|
|
52
|
+
reason: string;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export interface Box {
|
|
56
|
+
x: number;
|
|
57
|
+
y: number;
|
|
58
|
+
width: number;
|
|
59
|
+
height: number;
|
|
60
|
+
}
|
|
61
|
+
export type ShotStatus = 'changed' | 'unchanged' | 'added' | 'removed' | 'error';
|
|
62
|
+
export interface ShotResult {
|
|
63
|
+
/** File-safe key: "story__feedback-alert--all-states__bridge-light". */
|
|
64
|
+
key: string;
|
|
65
|
+
kind: 'story' | 'route';
|
|
66
|
+
/** Human label: "Feedback/Alert: All states" or "Orders list". */
|
|
67
|
+
label: string;
|
|
68
|
+
storyId?: string;
|
|
69
|
+
route?: string;
|
|
70
|
+
tenant?: string;
|
|
71
|
+
brand: string;
|
|
72
|
+
theme: string;
|
|
73
|
+
status: ShotStatus;
|
|
74
|
+
changedPixels: number;
|
|
75
|
+
totalPixels: number;
|
|
76
|
+
/** Changed pixels in percent of the larger image, two decimals. */
|
|
77
|
+
percent: number;
|
|
78
|
+
boxes: Box[];
|
|
79
|
+
size: {
|
|
80
|
+
before: {
|
|
81
|
+
width: number;
|
|
82
|
+
height: number;
|
|
83
|
+
} | null;
|
|
84
|
+
after: {
|
|
85
|
+
width: number;
|
|
86
|
+
height: number;
|
|
87
|
+
} | null;
|
|
88
|
+
};
|
|
89
|
+
/** Paths relative to the manifest folder. Only changed shots keep diff and composite images. */
|
|
90
|
+
images: {
|
|
91
|
+
before?: string;
|
|
92
|
+
after?: string;
|
|
93
|
+
diff?: string;
|
|
94
|
+
composite?: string;
|
|
95
|
+
};
|
|
96
|
+
error?: string;
|
|
97
|
+
}
|
|
98
|
+
export interface Manifest {
|
|
99
|
+
version: 1;
|
|
100
|
+
tool: '@bridge-ds/visual';
|
|
101
|
+
createdAt: string;
|
|
102
|
+
durationMs: number;
|
|
103
|
+
base: {
|
|
104
|
+
ref: string;
|
|
105
|
+
sha: string;
|
|
106
|
+
};
|
|
107
|
+
head: {
|
|
108
|
+
ref: string;
|
|
109
|
+
sha: string;
|
|
110
|
+
};
|
|
111
|
+
options: {
|
|
112
|
+
only: Only[];
|
|
113
|
+
threshold: number;
|
|
114
|
+
plan?: boolean;
|
|
115
|
+
planned?: {
|
|
116
|
+
modes: number;
|
|
117
|
+
appScreens: number;
|
|
118
|
+
};
|
|
119
|
+
viewport: {
|
|
120
|
+
stories: Viewport;
|
|
121
|
+
app: Viewport;
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
selection: Selection;
|
|
125
|
+
summary: Summary;
|
|
126
|
+
shots: ShotResult[];
|
|
127
|
+
}
|
|
128
|
+
export interface Summary {
|
|
129
|
+
shots: number;
|
|
130
|
+
changed: number;
|
|
131
|
+
unchanged: number;
|
|
132
|
+
added: number;
|
|
133
|
+
removed: number;
|
|
134
|
+
errors: number;
|
|
135
|
+
/** Largest change, in percent. */
|
|
136
|
+
maxPercent: number;
|
|
137
|
+
}
|
|
138
|
+
export interface Viewport {
|
|
139
|
+
width: number;
|
|
140
|
+
height: number;
|
|
141
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type Log = (msg: string) => void;
|
|
2
|
+
/**
|
|
3
|
+
* Gives `dst` (a worktree of the same repo) a node_modules that reuses the installed packages of `src`
|
|
4
|
+
* while its workspace packages (@bridge/*, @bridge-ds/*...) point at `dst`'s own sources. Per-checkout caches
|
|
5
|
+
* stay private. Nested workspace node_modules are linked too.
|
|
6
|
+
*/
|
|
7
|
+
export declare function linkNodeModules(src: string, dst: string): Promise<void>;
|
|
8
|
+
export declare function addWorktree(repo: string, sha: string, dir: string): Promise<void>;
|
|
9
|
+
export declare function removeWorktree(repo: string, dir: string): Promise<void>;
|
|
10
|
+
export declare function run(cmd: string, args: string[], cwd: string, log: Log, root?: string, env?: Record<string, string>): Promise<void>;
|
|
11
|
+
export interface BuildPlan {
|
|
12
|
+
storybook: boolean;
|
|
13
|
+
app: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface BuildConfig {
|
|
16
|
+
/** Folder of the app to build with Vite (relative to the repo root). */
|
|
17
|
+
appDir: string;
|
|
18
|
+
storybookConfigDir: string;
|
|
19
|
+
tokensBuild: string[];
|
|
20
|
+
}
|
|
21
|
+
export declare const DEFAULT_BUILD: BuildConfig;
|
|
22
|
+
export interface Built {
|
|
23
|
+
storybook?: string;
|
|
24
|
+
app?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Builds what `plan` asks for at commit `sha`, in a temporary worktree under `work`, into `cache/<sha>`.
|
|
28
|
+
* A build already in the cache is reused, so re-running against the same base is fast.
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildRef(o: {
|
|
31
|
+
repo: string;
|
|
32
|
+
sha: string;
|
|
33
|
+
work: string;
|
|
34
|
+
cache: string;
|
|
35
|
+
plan: BuildPlan;
|
|
36
|
+
config?: BuildConfig;
|
|
37
|
+
log: Log;
|
|
38
|
+
keep?: boolean;
|
|
39
|
+
}): Promise<Built>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,60 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bridge-ds/visual",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Bridge visual impact analysis: screenshots the stories and app screens a design system change touches, at the base and head refs, and pixel-diffs them into before/after/diff images and a JSON manifest.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Florian Gouloubi",
|
|
8
|
+
"homepage": "https://github.com/peterbono/ai-design-system-demo/tree/main/packages/visual#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/peterbono/ai-design-system-demo.git",
|
|
12
|
+
"directory": "packages/visual"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/peterbono/ai-design-system-demo/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"visual-regression",
|
|
19
|
+
"screenshots",
|
|
20
|
+
"design-system",
|
|
21
|
+
"bridge"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20.19.0"
|
|
25
|
+
},
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"source": "./src/index.ts",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./package.json": "./package.json"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"README.md"
|
|
40
|
+
],
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
46
|
+
"build": "node build.mjs",
|
|
47
|
+
"build:package": "node ../../scripts/build-packages.mjs visual"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@bridge-ds/core": "^0.2.0",
|
|
51
|
+
"pixelmatch": "7.1.0",
|
|
52
|
+
"playwright-core": "1.63.0",
|
|
53
|
+
"pngjs": "7.0.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "20.19.9",
|
|
57
|
+
"@types/pngjs": "6.0.5",
|
|
58
|
+
"esbuild": "0.27.7"
|
|
59
|
+
}
|
|
60
|
+
}
|
package/README.md
DELETED