@momentiq/dark-factory-cli 3.0.1 → 3.1.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/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/mode.d.ts.map +1 -1
- package/dist/commands/mode.js +120 -22
- package/dist/commands/mode.js.map +1 -1
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +11 -1
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +113 -8
- package/dist/commands/verify.js.map +1 -1
- package/dist/mcp/tools/skills-install.d.ts.map +1 -1
- package/dist/mcp/tools/skills-install.js +8 -3
- package/dist/mcp/tools/skills-install.js.map +1 -1
- package/dist/mode/critic-registration.d.ts +30 -10
- package/dist/mode/critic-registration.d.ts.map +1 -1
- package/dist/mode/critic-registration.js +31 -10
- package/dist/mode/critic-registration.js.map +1 -1
- package/dist/mode/guard.d.ts.map +1 -1
- package/dist/mode/guard.js +30 -2
- package/dist/mode/guard.js.map +1 -1
- package/dist/mode/init.d.ts +7 -4
- package/dist/mode/init.d.ts.map +1 -1
- package/dist/mode/init.js +146 -12
- package/dist/mode/init.js.map +1 -1
- package/dist/mode/preview-capture.d.ts +3 -0
- package/dist/mode/preview-capture.d.ts.map +1 -0
- package/dist/mode/preview-capture.js +129 -0
- package/dist/mode/preview-capture.js.map +1 -0
- package/dist/mode/preview.d.ts +160 -0
- package/dist/mode/preview.d.ts.map +1 -0
- package/dist/mode/preview.js +522 -0
- package/dist/mode/preview.js.map +1 -0
- package/dist/onboard/seeders/design-md.d.ts.map +1 -1
- package/dist/onboard/seeders/design-md.js +512 -17
- package/dist/onboard/seeders/design-md.js.map +1 -1
- package/dist/skills/install.d.ts +25 -5
- package/dist/skills/install.d.ts.map +1 -1
- package/dist/skills/install.js +34 -17
- package/dist/skills/install.js.map +1 -1
- package/dist/skills/known-skills.d.ts +2 -0
- package/dist/skills/known-skills.d.ts.map +1 -0
- package/dist/skills/known-skills.js +25 -0
- package/dist/skills/known-skills.js.map +1 -0
- package/package.json +2 -2
- package/skills/designer-build/SKILL.md.tmpl +14 -0
- package/skills/designer-handoff/SKILL.md.tmpl +43 -3
- package/skills/designer-ship/SKILL.md.tmpl +17 -14
- package/skills/verify/producer/README.md +29 -4
- package/skills/verify/producer/playwright-route.sh +10 -0
- package/skills/verify/producer/surface-declaration.ts +373 -0
- package/skills/verify/producer/ui-route.producer.spec.ts +44 -11
package/dist/mode/init.js
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
// Design constraints:
|
|
5
5
|
// - APPEND, never re-serialize the consumer's file: hand-maintained comments
|
|
6
6
|
// and formatting survive. The appended block is generated YAML.
|
|
7
|
-
// - Deterministic scope proposal:
|
|
8
|
-
//
|
|
7
|
+
// - Deterministic scope proposal: directories that actually exist are proposed
|
|
8
|
+
// (the engineer reviews the init PR — this is a seed, not policy), plus two
|
|
9
|
+
// always-on seeds independent of layout — `**/*.css` (a minimal editable
|
|
10
|
+
// surface) and `.darkfactory/**` (the mode's own workflow artifacts).
|
|
9
11
|
// - Idempotent: an existing `modes.<name>` entry short-circuits to "exists".
|
|
10
12
|
// - Fail closed: the candidate file content must round-trip through the
|
|
11
13
|
// canonical parser (@momentiq/dark-factory-schemas) before it is written.
|
|
@@ -23,6 +25,12 @@ const DEFAULT_PROTECTED_PATHS = [
|
|
|
23
25
|
"package.json",
|
|
24
26
|
"package-lock.json",
|
|
25
27
|
];
|
|
28
|
+
// Non-monorepo frontend roots (Next/Vite/CRA/Remix): the app lives in a named
|
|
29
|
+
// top-level dir rather than at the repo root or under `apps/*`. Each root is
|
|
30
|
+
// probed for the same conventional subdirs, mirrored one level deep — so a
|
|
31
|
+
// designer on a `web/`-rooted repo gets real component scopes, not css-only (#354).
|
|
32
|
+
const FRONTEND_ROOT_DIRS = ["web", "frontend", "client"];
|
|
33
|
+
const FRONTEND_ROOT_SUBDIRS = ["components", "app", "pages", "src", "lib", "styles"];
|
|
26
34
|
const ROOT_SCOPE_DIR_CANDIDATES = [
|
|
27
35
|
"src/components",
|
|
28
36
|
"src/app",
|
|
@@ -33,6 +41,7 @@ const ROOT_SCOPE_DIR_CANDIDATES = [
|
|
|
33
41
|
"pages",
|
|
34
42
|
"styles",
|
|
35
43
|
"public",
|
|
44
|
+
...FRONTEND_ROOT_DIRS.flatMap((root) => FRONTEND_ROOT_SUBDIRS.map((sub) => `${root}/${sub}`)),
|
|
36
45
|
];
|
|
37
46
|
const APP_SCOPE_SUBDIR_CANDIDATES = ["src/components", "src/app", "src/pages", "public"];
|
|
38
47
|
function isDir(path) {
|
|
@@ -44,10 +53,13 @@ function isDir(path) {
|
|
|
44
53
|
}
|
|
45
54
|
}
|
|
46
55
|
/**
|
|
47
|
-
* Propose write scopes from directories that exist.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
56
|
+
* Propose write scopes from directories that exist. Root-level frontend roots
|
|
57
|
+
* (`web/`, `frontend/`, `client/`) and the Next `src/` conventions are probed,
|
|
58
|
+
* plus monorepo `apps/<name>` workspaces one level deep. Two always-on seeds
|
|
59
|
+
* are appended regardless of layout: `**\/*.css` (a designer with no reachable
|
|
60
|
+
* component surface still gets a minimal editable seed to correct in the init
|
|
61
|
+
* PR) and `.darkfactory/**` (the mode's own workflow — `designer-brief` and
|
|
62
|
+
* `df objectives derive --apply` — writes there, so its outputs stay in scope).
|
|
51
63
|
*/
|
|
52
64
|
export function detectWriteScopes(repoRoot) {
|
|
53
65
|
const scopes = [];
|
|
@@ -55,6 +67,17 @@ export function detectWriteScopes(repoRoot) {
|
|
|
55
67
|
if (isDir(join(repoRoot, candidate)))
|
|
56
68
|
scopes.push(`${candidate}/**`);
|
|
57
69
|
}
|
|
70
|
+
// Bare top-level `src/` fallback (#354): repos that keep the whole frontend
|
|
71
|
+
// under a single `src/` (no Next `src/<area>` split) match none of the
|
|
72
|
+
// `src/<subdir>` candidates above and would otherwise fall through to
|
|
73
|
+
// css-only. Propose the broad `src/**` only when `src/` exists AND no
|
|
74
|
+
// more-specific `src/<subdir>` scope already matched, so the seed never lists
|
|
75
|
+
// `src/**` beside a scope it already contains. (The web/frontend/client roots
|
|
76
|
+
// need no such guard — their subdirs are siblings, not nested under a broader
|
|
77
|
+
// candidate.)
|
|
78
|
+
if (isDir(join(repoRoot, "src")) && !scopes.some((s) => s.startsWith("src/"))) {
|
|
79
|
+
scopes.push("src/**");
|
|
80
|
+
}
|
|
58
81
|
const appsRoot = join(repoRoot, "apps");
|
|
59
82
|
if (isDir(appsRoot)) {
|
|
60
83
|
for (const entry of readdirSync(appsRoot).sort()) {
|
|
@@ -65,24 +88,135 @@ export function detectWriteScopes(repoRoot) {
|
|
|
65
88
|
}
|
|
66
89
|
}
|
|
67
90
|
}
|
|
68
|
-
|
|
91
|
+
// Always-on seeds, independent of repo layout: `**\/*.css` (a designer with no
|
|
92
|
+
// reachable component surface still gets a minimal editable seed) and
|
|
93
|
+
// `.darkfactory/**` — the mode's OWN workflow writes there (`designer-brief`
|
|
94
|
+
// → .darkfactory/briefs/…, `df objectives derive --apply` →
|
|
95
|
+
// .darkfactory/objectives.yaml), so seeding it keeps those outputs in scope
|
|
96
|
+
// and the mode doesn't fail its own `mode-scope-critic` gate (#361).
|
|
97
|
+
scopes.push("**/*.css", ".darkfactory/**");
|
|
69
98
|
return scopes;
|
|
70
99
|
}
|
|
71
|
-
|
|
100
|
+
// Best-effort default preview URL. The seed is reviewed in the init PR; the
|
|
101
|
+
// engineer corrects the port if the detected server binds elsewhere.
|
|
102
|
+
const DEFAULT_DEV_URL = "http://localhost:3000";
|
|
103
|
+
// GNU make reads the first of these that exists (GNUmakefile → makefile →
|
|
104
|
+
// Makefile); we mirror that precedence.
|
|
105
|
+
const MAKEFILE_NAMES = ["GNUmakefile", "makefile", "Makefile"];
|
|
106
|
+
// foreman/honcho read `Procfile.dev` for development when present, else `Procfile`.
|
|
107
|
+
const PROCFILE_NAMES = ["Procfile.dev", "Procfile"];
|
|
108
|
+
// Docker Compose v2 file precedence — the highest-precedence file that exists
|
|
109
|
+
// is authoritative; the rest are ignored by the tool, so we don't read them.
|
|
110
|
+
const COMPOSE_FILE_NAMES = [
|
|
111
|
+
"compose.yaml",
|
|
112
|
+
"compose.yml",
|
|
113
|
+
"docker-compose.yaml",
|
|
114
|
+
"docker-compose.yml",
|
|
115
|
+
];
|
|
116
|
+
// Service names that plausibly ARE the dev server, in priority order.
|
|
117
|
+
const COMPOSE_DEV_SERVICE_NAMES = ["dev", "web", "app", "frontend", "client"];
|
|
118
|
+
function detectPackageDevServer(repoRoot) {
|
|
72
119
|
const pkgPath = join(repoRoot, "package.json");
|
|
73
120
|
if (!existsSync(pkgPath))
|
|
74
121
|
return undefined;
|
|
75
122
|
try {
|
|
76
123
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
77
|
-
if (pkg.scripts?.["dev"])
|
|
78
|
-
return { command: "npm run dev", url:
|
|
79
|
-
}
|
|
124
|
+
if (pkg.scripts?.["dev"])
|
|
125
|
+
return { command: "npm run dev", url: DEFAULT_DEV_URL };
|
|
80
126
|
}
|
|
81
127
|
catch {
|
|
82
|
-
// Malformed package.json → no
|
|
128
|
+
// Malformed package.json → no proposal from this source; init still succeeds.
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
function detectMakefileDevServer(repoRoot) {
|
|
133
|
+
for (const name of MAKEFILE_NAMES) {
|
|
134
|
+
const filePath = join(repoRoot, name);
|
|
135
|
+
if (!existsSync(filePath))
|
|
136
|
+
continue;
|
|
137
|
+
let content;
|
|
138
|
+
try {
|
|
139
|
+
content = readFileSync(filePath, "utf8");
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
for (const line of content.split("\n")) {
|
|
145
|
+
// A `dev` target is defined at column 0 as `dev:` / `dev: prereqs` /
|
|
146
|
+
// `dev::` (double-colon rule). Inspect the TARGET's own colon only: a
|
|
147
|
+
// `dev :=` / `dev ::=` / `dev :::=` variable assignment has `=`
|
|
148
|
+
// immediately after the colon run, while a real target does not. Keying
|
|
149
|
+
// on the target colon (not a `:=` substring anywhere on the line) keeps a
|
|
150
|
+
// valid target with a target-specific variable (`dev: FOO := bar`)
|
|
151
|
+
// correctly detected.
|
|
152
|
+
if (/^dev[ \t]*:{1,2}(?![:=])/.test(line)) {
|
|
153
|
+
return { command: "make dev", url: DEFAULT_DEV_URL };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
function detectProcfileDevServer(repoRoot) {
|
|
160
|
+
for (const name of PROCFILE_NAMES) {
|
|
161
|
+
const filePath = join(repoRoot, name);
|
|
162
|
+
if (!existsSync(filePath))
|
|
163
|
+
continue;
|
|
164
|
+
let content;
|
|
165
|
+
try {
|
|
166
|
+
content = readFileSync(filePath, "utf8");
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
// Procfile lines are `<process>: <command>`; the `web` process is the HTTP
|
|
172
|
+
// server a designer previews. Propose its command verbatim.
|
|
173
|
+
const match = content.match(/^web:\s*(.+)$/m);
|
|
174
|
+
const command = (match?.[1] ?? "").trim();
|
|
175
|
+
if (command)
|
|
176
|
+
return { command, url: DEFAULT_DEV_URL };
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
function detectComposeDevServer(repoRoot) {
|
|
181
|
+
for (const name of COMPOSE_FILE_NAMES) {
|
|
182
|
+
const filePath = join(repoRoot, name);
|
|
183
|
+
if (!existsSync(filePath))
|
|
184
|
+
continue;
|
|
185
|
+
// The highest-precedence compose file that exists is authoritative; Compose
|
|
186
|
+
// ignores the lower-precedence files, so we don't fall through to them.
|
|
187
|
+
try {
|
|
188
|
+
const doc = parseYaml(readFileSync(filePath, "utf8"));
|
|
189
|
+
const services = doc?.services;
|
|
190
|
+
if (services && typeof services === "object") {
|
|
191
|
+
for (const svc of COMPOSE_DEV_SERVICE_NAMES) {
|
|
192
|
+
if (Object.prototype.hasOwnProperty.call(services, svc)) {
|
|
193
|
+
return { command: `docker compose up ${svc}`, url: DEFAULT_DEV_URL };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// Malformed compose file → no proposal; init still succeeds.
|
|
200
|
+
}
|
|
201
|
+
return undefined;
|
|
83
202
|
}
|
|
84
203
|
return undefined;
|
|
85
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Propose a dev-server command for the mode's mandatory visual loop. Priority:
|
|
207
|
+
* an explicit npm `dev` script first, then the container-first / make / Procfile
|
|
208
|
+
* layouts that repos without a `scripts.dev` use — so a designer on a
|
|
209
|
+
* make-driven or compose-driven repo gets a real preview command instead of
|
|
210
|
+
* nothing (#357; the component-isolation preview harness is a separate PR).
|
|
211
|
+
* Only a source whose file/target actually exists yields a proposal; undefined
|
|
212
|
+
* when none match.
|
|
213
|
+
*/
|
|
214
|
+
function detectDevServer(repoRoot) {
|
|
215
|
+
return (detectPackageDevServer(repoRoot) ??
|
|
216
|
+
detectMakefileDevServer(repoRoot) ??
|
|
217
|
+
detectProcfileDevServer(repoRoot) ??
|
|
218
|
+
detectComposeDevServer(repoRoot));
|
|
219
|
+
}
|
|
86
220
|
export function buildDefaultModeEntry(repoRoot, modeName) {
|
|
87
221
|
const writeScopes = detectWriteScopes(repoRoot);
|
|
88
222
|
const devServer = detectDevServer(repoRoot);
|
package/dist/mode/init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/mode/init.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,8CAA8C;AAC9C,EAAE;AACF,sBAAsB;AACtB,8EAA8E;AAC9E,mEAAmE;AACnE,
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/mode/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-capture.d.ts","sourceRoot":"","sources":["../../src/mode/preview-capture.ts"],"names":[],"mappings":"AAiBA,OAAO,EAA2B,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AA2BzE,eAAO,MAAM,kBAAkB,EAAE,WA8DhC,CAAC"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Default `CaptureDeps` for `df mode preview` — the real Vite-server + Playwright
|
|
2
|
+
// wiring. Kept in its own file (never imported by tests, which inject fakes) so
|
|
3
|
+
// the pure harness/detection core in `preview.ts` stays browser- and
|
|
4
|
+
// bundler-free.
|
|
5
|
+
//
|
|
6
|
+
// RESOLUTION CONTEXT (load-bearing). Vite and Playwright are NOT dependencies of
|
|
7
|
+
// this CLI — like the `ui-visual` route, they are resolved from the CONSUMER's
|
|
8
|
+
// repo. A bare `import("vite")` would resolve against the CLI's own node_modules
|
|
9
|
+
// (wrong copy, or absent). So we resolve every consumer module against `cwd`
|
|
10
|
+
// explicitly and dynamic-import the resolved path. When a module or the browser
|
|
11
|
+
// is missing, we THROW — `runPreview` catches it and degrades with an actionable
|
|
12
|
+
// message (never a crash, never fabricated evidence).
|
|
13
|
+
import { createRequire } from "node:module";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { pathToFileURL } from "node:url";
|
|
16
|
+
import { BrowserUnavailableError } from "./preview.js";
|
|
17
|
+
/** Dynamic-import a module resolved from the consumer repo (`cwd`), NOT the CLI's
|
|
18
|
+
* own node_modules. Throws (with the module name) when it is not installed. */
|
|
19
|
+
async function importFromCwd(mod, cwd) {
|
|
20
|
+
// The anchor file need not exist — node uses its dirname as the resolution base.
|
|
21
|
+
const req = createRequire(join(cwd, "package.json"));
|
|
22
|
+
let resolved;
|
|
23
|
+
try {
|
|
24
|
+
resolved = req.resolve(mod);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new Error(`cannot resolve "${mod}" from ${cwd} — is it installed here?`);
|
|
28
|
+
}
|
|
29
|
+
return (await import(pathToFileURL(resolved).href));
|
|
30
|
+
}
|
|
31
|
+
/** CJS/ESM interop: a CJS module imported via dynamic `import()` exposes its
|
|
32
|
+
* `module.exports` under `.default`; a real ESM module exposes named exports
|
|
33
|
+
* directly. Vite's and Playwright's resolved (CJS) builds land on `.default`,
|
|
34
|
+
* so read the member from whichever object actually has it. */
|
|
35
|
+
function member(mod, name) {
|
|
36
|
+
const direct = mod[name];
|
|
37
|
+
if (direct !== undefined)
|
|
38
|
+
return direct;
|
|
39
|
+
const def = mod["default"];
|
|
40
|
+
return def?.[name];
|
|
41
|
+
}
|
|
42
|
+
export const defaultCaptureDeps = {
|
|
43
|
+
async startServer({ cwd, harnessDir, port }) {
|
|
44
|
+
// Vite transforms the component's TSX/JSX (+ CSS, path aliases) on the fly —
|
|
45
|
+
// the "detected framework's isolated render" the harness relies on.
|
|
46
|
+
const viteMod = await importFromCwd("vite", cwd);
|
|
47
|
+
const createServer = member(viteMod, "createServer");
|
|
48
|
+
if (typeof createServer !== "function") {
|
|
49
|
+
throw new Error("resolved `vite` but it exposes no createServer() — unexpected build shape");
|
|
50
|
+
}
|
|
51
|
+
const reactPluginMod = await importFromCwd("@vitejs/plugin-react", cwd);
|
|
52
|
+
const reactPlugin = (typeof reactPluginMod["default"] === "function" ? reactPluginMod["default"] : reactPluginMod);
|
|
53
|
+
// root = harnessDir (inside cwd), so its index.html is served at "/", bare
|
|
54
|
+
// `react` imports resolve up into cwd/node_modules, and `fs.allow` lets the
|
|
55
|
+
// entry import the component that lives elsewhere under the repo root.
|
|
56
|
+
const server = await createServer({
|
|
57
|
+
root: harnessDir,
|
|
58
|
+
configFile: false,
|
|
59
|
+
logLevel: "silent",
|
|
60
|
+
plugins: [reactPlugin()],
|
|
61
|
+
server: {
|
|
62
|
+
port,
|
|
63
|
+
strictPort: false,
|
|
64
|
+
host: "127.0.0.1",
|
|
65
|
+
fs: { allow: [cwd, harnessDir] },
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
await server.listen();
|
|
69
|
+
const url = server.resolvedUrls?.local?.[0] ?? `http://127.0.0.1:${port}/`;
|
|
70
|
+
return {
|
|
71
|
+
url,
|
|
72
|
+
close: async () => {
|
|
73
|
+
await server.close();
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
async capture({ url, cwd }) {
|
|
78
|
+
// Resolve Playwright from the SAME consumer repo (`cwd`) as the server —
|
|
79
|
+
// accept either `playwright` or `@playwright/test` (the ui-visual route uses
|
|
80
|
+
// the latter). Resolution + launch failures are a genuinely-unavailable
|
|
81
|
+
// browser → BrowserUnavailableError (soft-degrade). Anything AFTER launch
|
|
82
|
+
// (navigation, the render, the screenshot) is a REAL failure and propagates
|
|
83
|
+
// as a plain Error so runPreview surfaces it loudly.
|
|
84
|
+
const browser = await launchBrowser(cwd);
|
|
85
|
+
try {
|
|
86
|
+
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
|
|
87
|
+
await page.goto(url, { waitUntil: "networkidle", timeout: 30_000 });
|
|
88
|
+
// Structural render floor — the harness wraps the component in <main>.
|
|
89
|
+
await page.locator("main").first().waitFor({ state: "visible", timeout: 15_000 });
|
|
90
|
+
const aria = await page.locator("body").ariaSnapshot();
|
|
91
|
+
const png = await page.screenshot({ fullPage: true });
|
|
92
|
+
return { aria, png };
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
await browser.close();
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
/** Resolve + launch Chromium from the consumer repo. Every failure here is a
|
|
100
|
+
* browser-unavailable condition (not installed / no binary / missing system
|
|
101
|
+
* deps), so normalize it to `BrowserUnavailableError` — the ONLY capture failure
|
|
102
|
+
* runPreview soft-degrades. */
|
|
103
|
+
async function launchBrowser(cwd) {
|
|
104
|
+
let pwMod;
|
|
105
|
+
try {
|
|
106
|
+
pwMod = await importFromCwd("playwright", cwd);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
try {
|
|
110
|
+
pwMod = await importFromCwd("@playwright/test", cwd);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
throw new BrowserUnavailableError(`Playwright is not installed in ${cwd} (${err instanceof Error ? err.message : String(err)}). ` +
|
|
114
|
+
`Install with \`npm i -D playwright\`.`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const chromium = member(pwMod, "chromium");
|
|
118
|
+
if (!chromium || typeof chromium.launch !== "function") {
|
|
119
|
+
throw new BrowserUnavailableError("resolved Playwright but it exposes no chromium.launch() — unexpected shape");
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
return await chromium.launch();
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
// A launch failure is a missing/unusable browser binary or system deps.
|
|
126
|
+
throw new BrowserUnavailableError(err instanceof Error ? err.message : String(err));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=preview-capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-capture.js","sourceRoot":"","sources":["../../src/mode/preview-capture.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/** One file the harness writes into its temp dir. */
|
|
2
|
+
export interface HarnessFile {
|
|
3
|
+
/** Path relative to the harness dir (e.g. "index.html", ENTRY_BASENAME). */
|
|
4
|
+
path: string;
|
|
5
|
+
contents: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const ENTRY_BASENAME = "df-preview-entry.tsx";
|
|
8
|
+
export declare const INDEX_HTML_BASENAME = "index.html";
|
|
9
|
+
export interface GenerateHarnessInput {
|
|
10
|
+
/** Absolute path to the component file being previewed. */
|
|
11
|
+
componentAbsPath: string;
|
|
12
|
+
/** Absolute path to the (temp) harness dir the files are written into. The
|
|
13
|
+
* entry imports the component by a path relative to here. */
|
|
14
|
+
harnessDir: string;
|
|
15
|
+
/** Human component name (from the filename) — used in the title + wrapper. */
|
|
16
|
+
componentName: string;
|
|
17
|
+
/** Named export to mount. When omitted the harness imports the DEFAULT export. */
|
|
18
|
+
exportName?: string;
|
|
19
|
+
/** React major (18/19 → `createRoot`; <18 → `ReactDOM.render`). */
|
|
20
|
+
reactMajor: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Generate the minimal isolated-render harness for a React component: an entry
|
|
24
|
+
* that mounts JUST that component (placeholder/default props) inside a `<main>`
|
|
25
|
+
* landmark, plus an `index.html`. The `<main>` wrapper guarantees the same
|
|
26
|
+
* structural render floor the `ui-visual` producer asserts (`getByRole("main")`),
|
|
27
|
+
* regardless of what the component itself renders. PURE — returns file contents;
|
|
28
|
+
* the caller writes them.
|
|
29
|
+
*/
|
|
30
|
+
export declare function generateHarness(input: GenerateHarnessInput): HarnessFile[];
|
|
31
|
+
export type PreviewBlockerCode = "not-a-component-file" | "no-react" | "no-vite" | "server-component" | "no-export";
|
|
32
|
+
export interface DetectInput {
|
|
33
|
+
/** Repo root (consumer cwd). */
|
|
34
|
+
cwd: string;
|
|
35
|
+
/** Absolute path to the component file. */
|
|
36
|
+
componentAbsPath: string;
|
|
37
|
+
/** The component file's source. */
|
|
38
|
+
source: string;
|
|
39
|
+
/** Parsed consumer package.json (deps ∪ devDeps ∪ peerDeps merged), or null. */
|
|
40
|
+
packageDeps: Record<string, string> | null;
|
|
41
|
+
}
|
|
42
|
+
export interface DetectResult {
|
|
43
|
+
/** `vite-harness` = renderable via the generated harness; `unsupported` = a
|
|
44
|
+
* blocker prevents an honest render (see `blocker`). */
|
|
45
|
+
engine: "vite-harness" | "unsupported";
|
|
46
|
+
componentName: string;
|
|
47
|
+
reactMajor: number;
|
|
48
|
+
/** Resolved export to mount (undefined = default export). */
|
|
49
|
+
exportName?: string;
|
|
50
|
+
/** An isolation tool is present — reported (v1 still renders via the generated
|
|
51
|
+
* harness; native story capture is deferred, see the PR body). */
|
|
52
|
+
storybook: boolean;
|
|
53
|
+
ladle: boolean;
|
|
54
|
+
/** Non-fatal advisories surfaced to the operator. */
|
|
55
|
+
warnings: string[];
|
|
56
|
+
/** Non-null when the component genuinely cannot be rendered — `runPreview`
|
|
57
|
+
* degrades (exit 2) with this message rather than a wrong render. */
|
|
58
|
+
blocker?: {
|
|
59
|
+
code: PreviewBlockerCode;
|
|
60
|
+
message: string;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Best-effort PascalCase component name from a filename (`user-card.tsx` →
|
|
64
|
+
* `UserCard`, `Button.tsx` → `Button`). */
|
|
65
|
+
export declare function componentNameFromPath(componentAbsPath: string): string;
|
|
66
|
+
/**
|
|
67
|
+
* Resolve which export the harness should mount. Preference order:
|
|
68
|
+
* 1. `export default` present → default import (undefined exportName).
|
|
69
|
+
* 2. a named export matching the PascalCase filename.
|
|
70
|
+
* 3. the first `export function|const <PascalCaseName>`.
|
|
71
|
+
* Returns `{ kind: "none" }` when no export is found (a blocker).
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveExport(source: string, componentName: string): {
|
|
74
|
+
kind: "default";
|
|
75
|
+
} | {
|
|
76
|
+
kind: "named";
|
|
77
|
+
name: string;
|
|
78
|
+
} | {
|
|
79
|
+
kind: "none";
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Decide whether the component can be rendered via the generated Vite harness,
|
|
83
|
+
* and gather advisories. PURE — no fs, no network.
|
|
84
|
+
*/
|
|
85
|
+
export declare function detectStrategy(input: DetectInput): DetectResult;
|
|
86
|
+
/** Injected boundary so the Vite-server + Playwright wiring is never imported by
|
|
87
|
+
* tests. The default implementation lives in `preview-capture.ts`. */
|
|
88
|
+
export interface CaptureDeps {
|
|
89
|
+
/** Start an isolated dev server rooted at the harness dir; resolve to its base
|
|
90
|
+
* URL + a close(). Throws when the transform tool (Vite) is unavailable. */
|
|
91
|
+
startServer(input: {
|
|
92
|
+
cwd: string;
|
|
93
|
+
harnessDir: string;
|
|
94
|
+
port: number;
|
|
95
|
+
}): Promise<{
|
|
96
|
+
url: string;
|
|
97
|
+
close: () => Promise<void>;
|
|
98
|
+
}>;
|
|
99
|
+
/** Drive a browser: navigate, wait for the `main` landmark, return the ARIA
|
|
100
|
+
* snapshot + a full-page PNG. `cwd` is threaded so the browser is resolved
|
|
101
|
+
* from the SAME consumer repo as the server (not `process.cwd()`). Throws a
|
|
102
|
+
* `BrowserUnavailableError` when the browser genuinely can't launch; any OTHER
|
|
103
|
+
* throw is treated as a real render/transform failure (surfaced loudly). */
|
|
104
|
+
capture(input: {
|
|
105
|
+
url: string;
|
|
106
|
+
cwd: string;
|
|
107
|
+
}): Promise<{
|
|
108
|
+
aria: string;
|
|
109
|
+
png: Buffer;
|
|
110
|
+
}>;
|
|
111
|
+
}
|
|
112
|
+
/** Thrown by a `CaptureDeps.capture` when the browser cannot be resolved/launched
|
|
113
|
+
* (Playwright not installed, Chromium binary absent, missing system deps). This
|
|
114
|
+
* is the ONLY capture failure that degrades softly (exit 2, "reached the capture
|
|
115
|
+
* step") — a Vite transform error, a component runtime exception, missing props,
|
|
116
|
+
* or an `fs.allow`-blocked import is a REAL failure and surfaces loudly (exit 1). */
|
|
117
|
+
export declare class BrowserUnavailableError extends Error {
|
|
118
|
+
constructor(message: string);
|
|
119
|
+
}
|
|
120
|
+
/** Secondary heuristic (belt-and-suspenders alongside `BrowserUnavailableError`):
|
|
121
|
+
* recognize a genuine missing-browser / launch / install failure from its message
|
|
122
|
+
* so a raw Playwright error is still classified as degrade-soft, while a render or
|
|
123
|
+
* transform error is NOT. Kept deliberately narrow. */
|
|
124
|
+
export declare function isBrowserUnavailable(err: unknown): boolean;
|
|
125
|
+
export interface RunPreviewOptions {
|
|
126
|
+
/** Component path as given by the operator (relative to cwd or absolute). */
|
|
127
|
+
componentPath: string;
|
|
128
|
+
cwd: string;
|
|
129
|
+
/** Local port for the isolated harness server (default 5199). */
|
|
130
|
+
port?: number;
|
|
131
|
+
/** Override the evidence slug (default: sanitized component filename). */
|
|
132
|
+
slug?: string;
|
|
133
|
+
}
|
|
134
|
+
export type PreviewStatus = "captured" | "degraded" | "error";
|
|
135
|
+
export interface RunPreviewResult {
|
|
136
|
+
status: PreviewStatus;
|
|
137
|
+
/** 0 captured · 2 degraded (tool/browser/unsupported) · 1 hard usage error. */
|
|
138
|
+
exitCode: 0 | 1 | 2;
|
|
139
|
+
slug: string;
|
|
140
|
+
message: string;
|
|
141
|
+
warnings: string[];
|
|
142
|
+
/** Absolute evidence dir when `captured`. */
|
|
143
|
+
evidenceDir?: string;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* A safe evidence slug is a SINGLE path segment: filesystem-safe chars only, and
|
|
147
|
+
* never `.` / `..` / anything with a separator. The slug is interpolated into the
|
|
148
|
+
* evidence path (`agent-reviews/quality-gates/ui/<sha>/<slug>/…`), so an operator
|
|
149
|
+
* `--slug ../../..` would otherwise traverse OUT of the evidence tree and clobber
|
|
150
|
+
* arbitrary files. The auto-derived slug is already safe (`sanitizeSlug`); this
|
|
151
|
+
* fail-closes an explicit `--slug`. Exported for the traversal regression test.
|
|
152
|
+
*/
|
|
153
|
+
export declare function isSafeSlug(slug: string): boolean;
|
|
154
|
+
/**
|
|
155
|
+
* Render one component in isolation and write `ui-visual`-shaped evidence.
|
|
156
|
+
* Never throws for an expected failure — returns a `degraded`/`error` result
|
|
157
|
+
* with an actionable message and the right exit code.
|
|
158
|
+
*/
|
|
159
|
+
export declare function runPreview(opts: RunPreviewOptions, deps: CaptureDeps): Promise<RunPreviewResult>;
|
|
160
|
+
//# sourceMappingURL=preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/mode/preview.ts"],"names":[],"mappings":"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"}
|