@brunosps00/dev-workflow 1.1.2 → 1.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/README.md +22 -15
- package/bin/dev-workflow.js +1 -1
- package/lib/constants.js +6 -2
- package/lib/doctor.js +3 -1
- package/lib/install-deps.js +3 -3
- package/lib/setup-wsl-browser.js +10 -50
- package/lib/skill-registry.js +41 -0
- package/package.json +1 -1
- package/scaffold/en/agent-instructions.md +5 -2
- package/scaffold/en/commands/dw-analyze-project.md +1 -1
- package/scaffold/en/commands/dw-brainstorm.md +4 -1
- package/scaffold/en/commands/dw-functional-doc.md +3 -3
- package/scaffold/en/commands/dw-harness-audit.md +4 -2
- package/scaffold/en/commands/dw-help.md +12 -7
- package/scaffold/en/commands/dw-opportunities.md +131 -0
- package/scaffold/en/commands/dw-refactor.md +54 -0
- package/scaffold/en/commands/dw-review.md +1 -1
- package/scaffold/en/commands/dw-skill-health.md +11 -3
- package/scaffold/en/references/refactoring-catalog.md +1 -1
- package/scaffold/en/templates/constitution-template.md +1 -1
- package/scaffold/pt-br/agent-instructions.md +5 -2
- package/scaffold/pt-br/commands/dw-analyze-project.md +1 -1
- package/scaffold/pt-br/commands/dw-brainstorm.md +4 -1
- package/scaffold/pt-br/commands/dw-functional-doc.md +3 -3
- package/scaffold/pt-br/commands/dw-harness-audit.md +4 -2
- package/scaffold/pt-br/commands/dw-help.md +14 -7
- package/scaffold/pt-br/commands/dw-opportunities.md +131 -0
- package/scaffold/pt-br/commands/dw-refactor.md +54 -0
- package/scaffold/pt-br/commands/dw-review.md +1 -1
- package/scaffold/pt-br/commands/dw-skill-health.md +11 -3
- package/scaffold/pt-br/references/refactoring-catalog.md +1 -1
- package/scaffold/pt-br/templates/constitution-template.md +1 -1
- package/scaffold/scripts/cdp-relay/Cargo.toml +1 -1
- package/scaffold/scripts/cdp-relay/bin/cdp-relay.exe +0 -0
- package/scaffold/scripts/cdp-relay/src/main.rs +120 -23
- package/scaffold/scripts/functional-doc/run-playwright-flow.mjs +55 -24
- package/scaffold/scripts/lib/capture-screenshots.mjs +56 -17
- package/scaffold/scripts/lib/resolve-browser.mjs +314 -82
- package/scaffold/skill-registry.json +1 -1
- package/scaffold/skills/api-testing-recipes/SKILL.md +12 -0
- package/scaffold/skills/docker-compose-recipes/SKILL.md +12 -0
- package/scaffold/skills/dw-codebase-intel/SKILL.md +13 -1
- package/scaffold/skills/dw-context-budget/SKILL.md +12 -0
- package/scaffold/skills/dw-council/SKILL.md +12 -0
- package/scaffold/skills/dw-debug-protocol/SKILL.md +12 -0
- package/scaffold/skills/dw-execute-phase/SKILL.md +12 -0
- package/scaffold/skills/dw-git-discipline/SKILL.md +12 -0
- package/scaffold/skills/dw-incident-response/SKILL.md +12 -0
- package/scaffold/skills/dw-llm-eval/SKILL.md +12 -0
- package/scaffold/skills/dw-memory/SKILL.md +12 -0
- package/scaffold/skills/dw-review-rigor/SKILL.md +14 -2
- package/scaffold/skills/dw-search-first/SKILL.md +12 -0
- package/scaffold/skills/dw-silent-failure/SKILL.md +12 -0
- package/scaffold/skills/dw-simplification/SKILL.md +18 -6
- package/scaffold/skills/dw-simplification/references/deep-modules.md +1 -1
- package/scaffold/skills/dw-source-grounding/SKILL.md +12 -0
- package/scaffold/skills/dw-testing-discipline/SKILL.md +12 -0
- package/scaffold/skills/dw-testing-discipline/references/playwright-recipes.md +18 -19
- package/scaffold/skills/dw-ui-discipline/SKILL.md +12 -0
- package/scaffold/skills/dw-verify/SKILL.md +10 -0
- package/scaffold/skills/humanizer/SKILL.md +12 -0
- package/scaffold/skills/remotion-best-practices/SKILL.md +12 -0
- package/scaffold/skills/security-review/SKILL.md +12 -0
- package/scaffold/skills/vercel-react-best-practices/SKILL.md +12 -0
- package/scaffold/skills/vercel-react-best-practices/references/perf-discipline.md +1 -1
|
@@ -10,8 +10,10 @@ Voce e o auditor de saude de skills e agentes do dev-workflow.
|
|
|
10
10
|
1. Leia `scaffold/skill-registry.json` ao auditar o repo dev-workflow, ou `.dw/skill-registry.json` + `.agents/skills/` em projetos consumidores.
|
|
11
11
|
2. Agrupe skills por `kind`: `protocol`, `domain-pack`, `recipe-pack`, `asset-pack`.
|
|
12
12
|
3. Valide que toda skill tem trigger, output esperado, owner, load policy e limite de contexto.
|
|
13
|
-
4.
|
|
14
|
-
5.
|
|
13
|
+
4. Valide que todo `SKILL.md` bundled tem contrato `## Structured Return` com `Status`, `Scope`, `Evidence`, `Artifacts`, `Decisions`, `Risks` e `Next Step`.
|
|
14
|
+
5. Confirme que o vocabulario de `Status` esta explicito: `PASS`, `FINDINGS`, `BLOCKED`, `NOT_APPLICABLE`.
|
|
15
|
+
6. Aponte nomes duplicados, `SKILL.md` ausente, frontmatter ausente, `SKILL.md` grande demais, metadata de output esperado desatualizada, e references/assets lidos sem trigger especifico.
|
|
16
|
+
7. Recomende manter, compactar entrypoint, mesclar, reclassificar, adicionar retorno estruturado, ou remover da visibilidade core.
|
|
15
17
|
|
|
16
18
|
## Taxonomia
|
|
17
19
|
|
|
@@ -23,7 +25,13 @@ Voce e o auditor de saude de skills e agentes do dev-workflow.
|
|
|
23
25
|
`SKILL.md` deve ser um roteador/protocolo curto. Regras longas, exemplos, paletas, receitas, services e assets ficam em references/assets/rules/recipes e sao carregados sob demanda.
|
|
24
26
|
|
|
25
27
|
## Saida
|
|
26
|
-
Retorne um relatorio conciso. Nao delete arquivos.
|
|
28
|
+
Retorne um relatorio conciso com uma secao de retorno estruturado. Nao delete arquivos.
|
|
29
|
+
|
|
30
|
+
Inclua:
|
|
31
|
+
- Status geral: `PASS`, `FINDINGS` ou `BLOCKED`.
|
|
32
|
+
- Issues de registry/schema.
|
|
33
|
+
- Cobertura de retorno estruturado, incluindo qualquer skill sem contrato ou vocabulario de status.
|
|
34
|
+
- Top 3 fixes.
|
|
27
35
|
|
|
28
36
|
Marcador final: `## SKILL-HEALTH COMPLETE`
|
|
29
37
|
</system_instructions>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Catálogo de Refatoração — Exemplos Antes/Depois
|
|
2
2
|
|
|
3
|
-
Referência para `/dw-brainstorm --refactor`. Baseado no catálogo de Fowler.
|
|
3
|
+
Referência para `/dw-refactor` e `/dw-brainstorm --mode=refactor-audit`. Baseado no catálogo de Fowler.
|
|
4
4
|
|
|
5
5
|
## 1. Função Longa → Extract Function
|
|
6
6
|
|
|
@@ -71,7 +71,7 @@ mode: defaults | custom
|
|
|
71
71
|
**P-008 — Queries N+1 são flagueadas em code review** (severity: info)
|
|
72
72
|
**Regra:** Loops ou operações em lista que disparam chamadas DB/HTTP por item devem batchar (ex: `IN (...)`, `findMany`, DataLoader) ou ser explicitamente justificadas.
|
|
73
73
|
**Why:** Padrões N+1 escalam linearmente com tamanho dos dados e silenciosamente degradam até que a carga de produção revele.
|
|
74
|
-
**Enforcement:**
|
|
74
|
+
**Enforcement:** `/dw-review` e `/dw-refactor` detectam padrões await-em-loop contra módulos de repository / API client.
|
|
75
75
|
|
|
76
76
|
---
|
|
77
77
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name = "cdp-relay"
|
|
3
3
|
version = "0.1.0"
|
|
4
4
|
edition = "2021"
|
|
5
|
-
description = "Tiny TCP
|
|
5
|
+
description = "Tiny forward/reverse TCP relay so WSL can reach a Windows browser's CDP debug port in NAT mode without admin setup."
|
|
6
6
|
|
|
7
7
|
[[bin]]
|
|
8
8
|
name = "cdp-relay"
|
|
Binary file
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
//! cdp-relay — a tiny TCP forwarder.
|
|
2
2
|
//!
|
|
3
|
-
//!
|
|
4
|
-
//!
|
|
5
|
-
//!
|
|
6
|
-
//!
|
|
7
|
-
//!
|
|
3
|
+
//! Default mode listens on 0.0.0.0:<listen> and forwards every connection to
|
|
4
|
+
//! 127.0.0.1:<target>. Reverse mode connects outbound to a WSL broker, then forwards
|
|
5
|
+
//! each broker-approved session to 127.0.0.1:<target>. dev-workflow uses reverse mode
|
|
6
|
+
//! on WSL NAT so no Windows admin/firewall rule is required. std-only: no crates, no
|
|
7
|
+
//! runtime. dev-workflow ships a prebuilt Windows x64 .exe; this source is kept for
|
|
8
|
+
//! maintainers who need to rebuild it.
|
|
8
9
|
|
|
9
10
|
use std::env;
|
|
10
|
-
use std::io;
|
|
11
|
+
use std::io::{self, Read};
|
|
11
12
|
use std::net::{Shutdown, TcpListener, TcpStream};
|
|
12
13
|
use std::thread;
|
|
14
|
+
use std::time::Duration;
|
|
15
|
+
|
|
16
|
+
const DEFAULT_REVERSE_POOL: usize = 8;
|
|
17
|
+
const RETRY_DELAY: Duration = Duration::from_millis(200);
|
|
13
18
|
|
|
14
19
|
fn pump(mut from: TcpStream, mut to: TcpStream) {
|
|
15
20
|
let _ = io::copy(&mut from, &mut to);
|
|
@@ -17,29 +22,36 @@ fn pump(mut from: TcpStream, mut to: TcpStream) {
|
|
|
17
22
|
let _ = to.shutdown(Shutdown::Write);
|
|
18
23
|
}
|
|
19
24
|
|
|
25
|
+
fn bridge(left: TcpStream, right: TcpStream) -> io::Result<()> {
|
|
26
|
+
left.set_nodelay(true).ok();
|
|
27
|
+
right.set_nodelay(true).ok();
|
|
28
|
+
|
|
29
|
+
let left_rx = left.try_clone()?;
|
|
30
|
+
let right_rx = right.try_clone()?;
|
|
31
|
+
thread::spawn(move || pump(left, right_rx));
|
|
32
|
+
thread::spawn(move || pump(right, left_rx));
|
|
33
|
+
Ok(())
|
|
34
|
+
}
|
|
35
|
+
|
|
20
36
|
fn handle(client: TcpStream, target_port: u16) -> io::Result<()> {
|
|
21
|
-
client.set_nodelay(true).ok();
|
|
22
37
|
let upstream = TcpStream::connect(("127.0.0.1", target_port))?;
|
|
23
|
-
upstream
|
|
24
|
-
|
|
25
|
-
let client_rx = client.try_clone()?;
|
|
26
|
-
let upstream_rx = upstream.try_clone()?;
|
|
27
|
-
// client -> upstream
|
|
28
|
-
thread::spawn(move || pump(client, upstream));
|
|
29
|
-
// upstream -> client
|
|
30
|
-
thread::spawn(move || pump(upstream_rx, client_rx));
|
|
31
|
-
Ok(())
|
|
38
|
+
bridge(client, upstream)
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
fn
|
|
35
|
-
let
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
fn wait_for_go(stream: &mut TcpStream) -> io::Result<()> {
|
|
42
|
+
let mut seen = Vec::with_capacity(3);
|
|
43
|
+
let mut byte = [0u8; 1];
|
|
44
|
+
while seen.len() < 3 {
|
|
45
|
+
stream.read_exact(&mut byte)?;
|
|
46
|
+
seen.push(byte[0]);
|
|
47
|
+
if seen == b"GO\n" {
|
|
48
|
+
return Ok(());
|
|
49
|
+
}
|
|
39
50
|
}
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
Err(io::Error::new(io::ErrorKind::InvalidData, "expected GO"))
|
|
52
|
+
}
|
|
42
53
|
|
|
54
|
+
fn run_listen(listen_port: u16, target_port: u16) -> io::Result<()> {
|
|
43
55
|
let listener = TcpListener::bind(("0.0.0.0", listen_port)).expect("bind failed");
|
|
44
56
|
for stream in listener.incoming() {
|
|
45
57
|
if let Ok(client) = stream {
|
|
@@ -47,4 +59,89 @@ fn main() {
|
|
|
47
59
|
let _ = handle(client, target_port);
|
|
48
60
|
}
|
|
49
61
|
}
|
|
62
|
+
Ok(())
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn handle_reverse_session(mut broker: TcpStream, target_port: u16) -> io::Result<()> {
|
|
66
|
+
broker.set_nodelay(true).ok();
|
|
67
|
+
wait_for_go(&mut broker)?;
|
|
68
|
+
let upstream = TcpStream::connect(("127.0.0.1", target_port))?;
|
|
69
|
+
bridge(broker, upstream)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fn reverse_worker(broker_host: String, broker_port: u16, target_port: u16) {
|
|
73
|
+
loop {
|
|
74
|
+
match TcpStream::connect((broker_host.as_str(), broker_port)) {
|
|
75
|
+
Ok(broker) => {
|
|
76
|
+
if handle_reverse_session(broker, target_port).is_err() {
|
|
77
|
+
thread::sleep(RETRY_DELAY);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
Err(_) => thread::sleep(RETRY_DELAY),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fn run_reverse(broker_host: String, broker_port: u16, target_port: u16, pool_size: usize) {
|
|
86
|
+
let pool_size = pool_size.max(1);
|
|
87
|
+
for _ in 0..pool_size {
|
|
88
|
+
let host = broker_host.clone();
|
|
89
|
+
thread::spawn(move || reverse_worker(host, broker_port, target_port));
|
|
90
|
+
}
|
|
91
|
+
loop {
|
|
92
|
+
thread::sleep(Duration::from_secs(3600));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fn usage() -> ! {
|
|
97
|
+
eprintln!("usage:");
|
|
98
|
+
eprintln!(" cdp-relay <listen_port> <target_port>");
|
|
99
|
+
eprintln!(" cdp-relay --reverse <broker_host> <broker_port> <target_port> [pool_size]");
|
|
100
|
+
std::process::exit(2);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fn parse_port(value: &str, name: &str) -> u16 {
|
|
104
|
+
value.parse().unwrap_or_else(|_| {
|
|
105
|
+
eprintln!("invalid {name}");
|
|
106
|
+
std::process::exit(2);
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fn parse_pool(value: Option<&String>) -> usize {
|
|
111
|
+
value
|
|
112
|
+
.map(|s| {
|
|
113
|
+
s.parse().unwrap_or_else(|_| {
|
|
114
|
+
eprintln!("invalid pool_size");
|
|
115
|
+
std::process::exit(2);
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
.unwrap_or(DEFAULT_REVERSE_POOL)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fn main() {
|
|
122
|
+
let args: Vec<String> = env::args().collect();
|
|
123
|
+
if args.get(1).map(String::as_str) == Some("--reverse") {
|
|
124
|
+
if args.len() < 5 {
|
|
125
|
+
usage();
|
|
126
|
+
}
|
|
127
|
+
let broker_host = args[2].clone();
|
|
128
|
+
let broker_port = parse_port(&args[3], "broker_port");
|
|
129
|
+
let target_port = parse_port(&args[4], "target_port");
|
|
130
|
+
run_reverse(
|
|
131
|
+
broker_host,
|
|
132
|
+
broker_port,
|
|
133
|
+
target_port,
|
|
134
|
+
parse_pool(args.get(5)),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if args.len() < 3 {
|
|
139
|
+
usage();
|
|
140
|
+
}
|
|
141
|
+
let listen_port = parse_port(&args[1], "listen_port");
|
|
142
|
+
let target_port = parse_port(&args[2], "target_port");
|
|
143
|
+
if let Err(err) = run_listen(listen_port, target_port) {
|
|
144
|
+
eprintln!("cdp-relay: {err}");
|
|
145
|
+
std::process::exit(1);
|
|
146
|
+
}
|
|
50
147
|
}
|
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
// which is the only mechanism that works over connectOverCDP (recordVideo does not).
|
|
7
7
|
// Driving directly also removes the dependency on the project's playwright.config.*.
|
|
8
8
|
//
|
|
9
|
-
// Browser selection is delegated to ../lib/resolve-browser.mjs (WSL-resilient):
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// expect, baseURL, step, shot })`.
|
|
9
|
+
// Browser selection is delegated to ../lib/resolve-browser.mjs (WSL-resilient): Playwright
|
|
10
|
+
// Chromium is primary, with a configured Windows/CDP target only as fallback. The flow module
|
|
11
|
+
// is plain .mjs exporting `async function flow({ page, context, expect, baseURL, step, shot })`.
|
|
13
12
|
|
|
14
13
|
import fs from "node:fs";
|
|
15
14
|
import path from "node:path";
|
|
@@ -79,22 +78,51 @@ async function importPlaywright() {
|
|
|
79
78
|
}
|
|
80
79
|
}
|
|
81
80
|
|
|
82
|
-
async function openBrowser(chromium, descriptor) {
|
|
81
|
+
async function openBrowser(chromium, descriptor, log) {
|
|
83
82
|
if (descriptor.mode === "cdp") {
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
let browser;
|
|
84
|
+
try {
|
|
85
|
+
browser = await chromium.connectOverCDP(descriptor.endpoint);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
await (descriptor.cleanup ?? (async () => {}))();
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
return { browser, descriptor, cleanup: descriptor.cleanup ?? (async () => {}) };
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const browser = await chromium.launch(descriptor.launchOptions);
|
|
94
|
+
return {
|
|
95
|
+
browser,
|
|
96
|
+
descriptor,
|
|
97
|
+
cleanup: async () => {
|
|
98
|
+
try {
|
|
99
|
+
await browser.close();
|
|
100
|
+
} catch {
|
|
101
|
+
// ignore
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
} catch (primaryError) {
|
|
106
|
+
if (typeof descriptor.openFallback !== "function") {
|
|
107
|
+
throw primaryError;
|
|
108
|
+
}
|
|
109
|
+
log(`browser launch failed (${primaryError.message}); trying CDP fallback from ${descriptor.fallback?.source ?? "config"}.`);
|
|
110
|
+
let fallbackDescriptor;
|
|
111
|
+
try {
|
|
112
|
+
fallbackDescriptor = await descriptor.openFallback();
|
|
113
|
+
} catch (fallbackError) {
|
|
114
|
+
throw new Error(`Playwright Chromium launch failed: ${primaryError.message}; CDP fallback failed: ${fallbackError.message}`);
|
|
115
|
+
}
|
|
116
|
+
let browser;
|
|
117
|
+
try {
|
|
118
|
+
browser = await chromium.connectOverCDP(fallbackDescriptor.endpoint);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
await (fallbackDescriptor.cleanup ?? (async () => {}))();
|
|
121
|
+
throw new Error(`Playwright Chromium launch failed: ${primaryError.message}; CDP fallback failed: ${error.message}`);
|
|
122
|
+
}
|
|
123
|
+
log(`browser fallback: mode=${fallbackDescriptor.mode} source=${fallbackDescriptor.source}`);
|
|
124
|
+
return { browser, descriptor: fallbackDescriptor, cleanup: fallbackDescriptor.cleanup ?? (async () => {}) };
|
|
86
125
|
}
|
|
87
|
-
const browser = await chromium.launch(descriptor.launchOptions);
|
|
88
|
-
return {
|
|
89
|
-
browser,
|
|
90
|
-
cleanup: async () => {
|
|
91
|
-
try {
|
|
92
|
-
await browser.close();
|
|
93
|
-
} catch {
|
|
94
|
-
// ignore
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
126
|
}
|
|
99
127
|
|
|
100
128
|
async function openContext(browser, descriptor, { resolution, baseURL }) {
|
|
@@ -168,15 +196,18 @@ async function main() {
|
|
|
168
196
|
};
|
|
169
197
|
|
|
170
198
|
const descriptor = await resolveBrowser({ projectRoot, headless: true });
|
|
171
|
-
log(
|
|
199
|
+
log(
|
|
200
|
+
`browser: mode=${descriptor.mode} source=${descriptor.source}` +
|
|
201
|
+
(descriptor.fallback ? ` fallback=${descriptor.fallback.source}` : ""),
|
|
202
|
+
);
|
|
172
203
|
if (args.browserName && args.browserName !== "chromium") {
|
|
173
204
|
log(`note: --browser-name=${args.browserName} ignored; screencast/CDP require Chromium.`);
|
|
174
205
|
}
|
|
175
206
|
|
|
176
|
-
const { browser, cleanup: cleanupBrowser } = await openBrowser(chromium, descriptor);
|
|
177
|
-
const { context, owns: ownsContext } = await openContext(browser,
|
|
207
|
+
const { browser, descriptor: activeDescriptor, cleanup: cleanupBrowser } = await openBrowser(chromium, descriptor, log);
|
|
208
|
+
const { context, owns: ownsContext } = await openContext(browser, activeDescriptor, { resolution, baseURL });
|
|
178
209
|
const page = await context.newPage();
|
|
179
|
-
if (
|
|
210
|
+
if (activeDescriptor.mode === "cdp") {
|
|
180
211
|
try {
|
|
181
212
|
await page.setViewportSize({ width: resolution.width, height: resolution.height });
|
|
182
213
|
} catch {
|
|
@@ -263,8 +294,8 @@ async function main() {
|
|
|
263
294
|
|
|
264
295
|
updateManifest(manifestPath, {
|
|
265
296
|
status,
|
|
266
|
-
browserMode:
|
|
267
|
-
browserSource:
|
|
297
|
+
browserMode: activeDescriptor.mode,
|
|
298
|
+
browserSource: activeDescriptor.source,
|
|
268
299
|
recording,
|
|
269
300
|
videoResolution: resolution.label,
|
|
270
301
|
video: videoExists ? videoPath : null,
|
|
@@ -62,6 +62,55 @@ function slugify(value) {
|
|
|
62
62
|
);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
async function openBrowser(chromium, descriptor) {
|
|
66
|
+
if (descriptor.mode === "cdp") {
|
|
67
|
+
let browser;
|
|
68
|
+
try {
|
|
69
|
+
browser = await chromium.connectOverCDP(descriptor.endpoint);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
await (descriptor.cleanup ?? (async () => {}))();
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
return { browser, descriptor, cleanup: descriptor.cleanup ?? (async () => {}) };
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const browser = await chromium.launch(descriptor.launchOptions);
|
|
78
|
+
return {
|
|
79
|
+
browser,
|
|
80
|
+
descriptor,
|
|
81
|
+
cleanup: async () => {
|
|
82
|
+
try {
|
|
83
|
+
await browser.close();
|
|
84
|
+
} catch {
|
|
85
|
+
// ignore
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
} catch (primaryError) {
|
|
90
|
+
if (typeof descriptor.openFallback !== "function") {
|
|
91
|
+
throw primaryError;
|
|
92
|
+
}
|
|
93
|
+
process.stderr.write(
|
|
94
|
+
`browser launch failed (${primaryError.message}); trying CDP fallback from ${descriptor.fallback?.source ?? "config"}.\n`,
|
|
95
|
+
);
|
|
96
|
+
let fallbackDescriptor;
|
|
97
|
+
try {
|
|
98
|
+
fallbackDescriptor = await descriptor.openFallback();
|
|
99
|
+
} catch (fallbackError) {
|
|
100
|
+
throw new Error(`Playwright Chromium launch failed: ${primaryError.message}; CDP fallback failed: ${fallbackError.message}`);
|
|
101
|
+
}
|
|
102
|
+
let browser;
|
|
103
|
+
try {
|
|
104
|
+
browser = await chromium.connectOverCDP(fallbackDescriptor.endpoint);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
await (fallbackDescriptor.cleanup ?? (async () => {}))();
|
|
107
|
+
throw new Error(`Playwright Chromium launch failed: ${primaryError.message}; CDP fallback failed: ${error.message}`);
|
|
108
|
+
}
|
|
109
|
+
process.stderr.write(`browser fallback: mode=${fallbackDescriptor.mode} source=${fallbackDescriptor.source}\n`);
|
|
110
|
+
return { browser, descriptor: fallbackDescriptor, cleanup: fallbackDescriptor.cleanup ?? (async () => {}) };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
65
114
|
async function main() {
|
|
66
115
|
const args = parseArgs(process.argv.slice(2));
|
|
67
116
|
if (!args.url) {
|
|
@@ -85,27 +134,17 @@ async function main() {
|
|
|
85
134
|
|
|
86
135
|
fs.mkdirSync(outDir, { recursive: true });
|
|
87
136
|
const descriptor = await resolveBrowser({ projectRoot: process.cwd(), headless: true });
|
|
88
|
-
process.stderr.write(
|
|
137
|
+
process.stderr.write(
|
|
138
|
+
`browser: mode=${descriptor.mode} source=${descriptor.source}` +
|
|
139
|
+
(descriptor.fallback ? ` fallback=${descriptor.fallback.source}` : "") +
|
|
140
|
+
"\n",
|
|
141
|
+
);
|
|
89
142
|
|
|
90
|
-
|
|
91
|
-
let cleanup = async () => {};
|
|
92
|
-
if (descriptor.mode === "cdp") {
|
|
93
|
-
browser = await chromium.connectOverCDP(descriptor.endpoint);
|
|
94
|
-
cleanup = descriptor.cleanup ?? (async () => {});
|
|
95
|
-
} else {
|
|
96
|
-
browser = await chromium.launch(descriptor.launchOptions);
|
|
97
|
-
cleanup = async () => {
|
|
98
|
-
try {
|
|
99
|
-
await browser.close();
|
|
100
|
-
} catch {
|
|
101
|
-
// ignore
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
}
|
|
143
|
+
const { browser, descriptor: activeDescriptor, cleanup } = await openBrowser(chromium, descriptor);
|
|
105
144
|
|
|
106
145
|
const shots = [];
|
|
107
146
|
try {
|
|
108
|
-
const existing =
|
|
147
|
+
const existing = activeDescriptor.mode === "cdp" ? browser.contexts()[0] : null;
|
|
109
148
|
const context = existing ?? (await browser.newContext());
|
|
110
149
|
const ownsContext = !existing;
|
|
111
150
|
const page = await context.newPage();
|