@usecanary/cli 0.2.1 → 0.4.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 +2 -1
- package/dist/cli.cjs +223 -37
- package/dist/cli.js +223 -37
- package/dist/cli.js.map +4 -4
- package/package.json +7 -7
package/dist/cli.js
CHANGED
|
@@ -7468,15 +7468,15 @@ var require_help = __commonJS({
|
|
|
7468
7468
|
* @return {string}
|
|
7469
7469
|
*
|
|
7470
7470
|
*/
|
|
7471
|
-
wrap(str, width,
|
|
7471
|
+
wrap(str, width, indent2, minColumnWidth = 40) {
|
|
7472
7472
|
const indents = " \\f\\t\\v\xA0\u1680\u2000-\u200A\u202F\u205F\u3000\uFEFF";
|
|
7473
7473
|
const manualIndent = new RegExp(`[\\n][${indents}]+`);
|
|
7474
7474
|
if (str.match(manualIndent)) return str;
|
|
7475
|
-
const columnWidth = width -
|
|
7475
|
+
const columnWidth = width - indent2;
|
|
7476
7476
|
if (columnWidth < minColumnWidth) return str;
|
|
7477
|
-
const leadingStr = str.slice(0,
|
|
7478
|
-
const columnText = str.slice(
|
|
7479
|
-
const indentString = " ".repeat(
|
|
7477
|
+
const leadingStr = str.slice(0, indent2);
|
|
7478
|
+
const columnText = str.slice(indent2).replace("\r\n", "\n");
|
|
7479
|
+
const indentString = " ".repeat(indent2);
|
|
7480
7480
|
const zeroWidthSpace = "\u200B";
|
|
7481
7481
|
const breaks = `\\s${zeroWidthSpace}`;
|
|
7482
7482
|
const regex = new RegExp(
|
|
@@ -9965,6 +9965,181 @@ function createLogger(opts = {}) {
|
|
|
9965
9965
|
|
|
9966
9966
|
// ../../packages/cli-kit/src/index.ts
|
|
9967
9967
|
var import_pino_pretty = __toESM(require_pino_pretty(), 1);
|
|
9968
|
+
|
|
9969
|
+
// ../../packages/cli-kit/src/snippets.generated.ts
|
|
9970
|
+
var API_BROWSER = "- `browser.getPage(nameOrId)` \u2014 get-or-create a named page, or attach to an existing tab by the\n `id` from `listPages()`. Named pages persist across steps in a session \u2014 call with the same\n name to reuse the tab.\n- `browser.newPage()` \u2014 an anonymous page, auto-closed when the script ends; does not persist.\n- `browser.listPages()` \u2014 list every open tab: `[{ id, url, title, name }]` (`name` is `null`\n for tabs you never named).\n- `browser.closePage(name)` \u2014 close and forget a named page.";
|
|
9971
|
+
var API_CONSOLE = "- `console.log` / `console.info` write to stdout; `console.warn` / `console.error` write to\n stderr. Top-level `console.log` is your script's output channel.\n- `console.log` inside `page.evaluate(() => \u2026)` runs in the page and is captured into the\n session's console artifact instead.";
|
|
9972
|
+
var API_FILE_HELPERS = 'All file I/O is async (await it), sandboxed to `~/.canary/tmp/` (no filesystem escape), and\nreturns the full path to the file:\n\n- `saveScreenshot(buffer, name)` \u2014 persist a screenshot buffer; buffer first:\n `const path = await saveScreenshot(await page.screenshot(), "home.png");`\n- `writeFile(name, data)` \u2014 write a small file (e.g. JSON state):\n `await writeFile("results.json", JSON.stringify(data));`\n- `readFile(name)` \u2014 read it back (returns the contents as a string):\n `const data = JSON.parse(await readFile("results.json"));`';
|
|
9973
|
+
var API_GLOBALS = "Every script gets these globals:\n\n- `browser` \u2014 pre-connected browser handle (see the script API)\n- `console` \u2014 `log` / `info` / `warn` / `error`, captured per run\n- `setTimeout` / `clearTimeout` \u2014 basic timers\n- `saveScreenshot(buffer, name)` \u2014 save a screenshot buffer (async \u2014 await it)\n- `writeFile(name, data)` / `readFile(name)` \u2014 small-file persistence (async \u2014 await them)";
|
|
9974
|
+
var API_PLAYWRIGHT_METHODS = '- `page.goto(url, { waitUntil: "domcontentloaded" })` \u2014 navigate; `waitUntil` is `"load"` /\n `"domcontentloaded"` / `"networkidle"` (prefer `"domcontentloaded"` on dev servers)\n- `page.title()` / `page.url()` \u2014 current title / URL\n- `page.snapshotForAI(options)` \u2014 AI-optimized page outline; returns `{ full, incremental? }`;\n options `{ track?, depth?, timeout? }`\n- `page.getByRole(role, { name })` / `page.getByText(text)` \u2014 semantic locators (survive re-renders)\n- `page.textContent(sel)` / `page.innerText(sel)` / `page.innerHTML(sel)` /\n `page.getAttribute(sel, name)` \u2014 read by selector\n- `page.inputValue(sel)` / `page.isChecked(sel)` / `page.isVisible(sel)` / `page.isHidden(sel)` \u2014\n input and visibility state\n- `page.fill(sel, value)` / `page.click(sel)` / `page.type(sel, text)` / `page.press(sel, key)` \u2014\n act on elements\n- `page.waitForSelector(sel, { state, timeout })` (`state`: `"attached"` / `"visible"` /\n `"hidden"` / `"detached"`) / `page.waitForURL(pattern)` / `page.waitForLoadState(state)` /\n `page.waitForFunction(fn)` / `page.waitForTimeout(ms)` \u2014 waiting\n- `page.screenshot({ fullPage })` \u2014 capture a screenshot Buffer; save it with `saveScreenshot(...)`\n- `page.evaluate(fn[, arg])` / `page.$eval(sel, fn)` / `page.$$eval(sel, fn)` \u2014 run plain\n JavaScript in the page context (real DOM; args/returns must be serializable)\n- `page.locator(sel)` \u2014 a Locator for chained actions (`.click()`, `.fill(value)`,\n `.textContent()`, `.first()`, \u2026)\n- `page.keyboard.press/type/down/up(...)` / `page.mouse.move/click/down/up(...)` \u2014 low-level input\n- `page.reload()` / `page.goBack()` / `page.goForward()` \u2014 history;\n `page.content()` / `page.setContent(html)` \u2014 full HTML\n- `page.on("console", handler)` \u2014 observe page console events';
|
|
9975
|
+
var API_PLAYWRIGHT_NOTE = "Pages returned by `browser.getPage()` and `browser.newPage()` are full Playwright Page objects \u2014\nthe same API (`goto`, `click`, `fill`, `locator`, `evaluate`, `getByRole`, `waitForSelector`, \u2026):\nhttps://playwright.dev/docs/api/class-page";
|
|
9976
|
+
var API_SANDBOX_ENV = "Scripts execute inside a QuickJS WASM sandbox with no arbitrary access to the host system.\nThis is NOT Node.js \u2014 there is no module system and no Node API:\n\n- `require()` / `import()` \u2014 no module loading; inline any helpers in the script\n- `process`, `fs` / `path` / `os` \u2014 no process or direct filesystem access (use the file helpers)\n- `fetch` / `WebSocket` \u2014 no direct network access (the page does the networking)\n- `__dirname` / `__filename` \u2014 no path globals\n\nMemory and CPU limits are enforced, and both CPU time and wall-clock time are bounded \u2014 infinite\nloops or never-settling promises abort the script. Values crossing `evaluate` / `$eval` must be\nJSON-serializable.";
|
|
9977
|
+
var API_SNAPSHOT = '- `page.snapshotForAI()` returns `{ full, incremental? }` \u2014 `full` is an aria outline of the\n page: roles, accessible names, `[ref=eN]` markers on actionable nodes. Read it to pick a\n semantic selector \u2014 `page.getByRole("button", { name: "Continue" })`,\n `page.getByText("Sign in")` \u2014 then act.\n- Options `{ track?, depth?, timeout? }`: re-run `page.snapshotForAI({ track: "main" })` after\n the page changes to get just the `incremental` diff; `{ depth: N }` caps the tree on huge\n pages; `timeout` bounds the walk.\n- `page.locator("aria-ref=e12")` works for an immediate action in the same script only \u2014 refs go\n stale across steps and after navigations. Prefer re-deriving a semantic selector.';
|
|
9978
|
+
var EX_INSPECT_TABS = "const tabs = await browser.listPages();\nconsole.log(JSON.stringify(tabs, null, 2));";
|
|
9979
|
+
var EX_SNAPSHOT = 'const page = await browser.getPage("main");\nconst snap = await page.snapshotForAI(); // { full, incremental? }\nconsole.log(page.url(), await page.title());\nconsole.log(snap.full); // aria outline \u2014 pick a role/text selector from this\n// then act: await page.getByRole("button", { name: "Continue" }).click();\n// after changes, page.snapshotForAI({ track: "main" }) returns just the incremental diff';
|
|
9980
|
+
var RULE_DATA_PASSING = '- Browser state persists across steps: named pages (and their cookies) stay open between scripts\n within a session \u2014 reuse the same page name so each step picks up where the last left off.\n- Anonymous `newPage()` tabs are closed when each script ends.\n- To pass values between steps: `writeFile("state.json", JSON.stringify(x))` in one step,\n `JSON.parse(await readFile("state.json"))` in the next.';
|
|
9981
|
+
var RULE_DEV_SERVER = 'For local dev servers (Next.js, Vite, \u2026) prefer\n`await page.goto(url, { waitUntil: "domcontentloaded" })` \u2014 the default `"load"` wait can hang\non HMR, streaming, or other long-lived dev-server connections. Use `"load"` only when you\nspecifically need every subresource to finish loading.';
|
|
9982
|
+
var RULE_FAIL_FAST = "- End each script by logging the state you need for the next decision \u2014 stdout is your\n observation channel.\n- Use short timeouts (`canary run --timeout 10`) so a step fails fast instead of hanging on a\n missing element.\n- In assertion / extraction steps, degrade gracefully \u2014 log a `WARN` / `FAIL` line instead of\n crashing, so the step still records its evidence. While exploring, a missed selector means\n look again (snapshot, fix, retry as a new step), not a silent fallback.\n- End before you stop: `canary stop` shuts the daemon down and aborts any live session,\n skipping its report.html \u2014 always `canary session end <id>` first.";
|
|
9983
|
+
var RULE_OBSERVE_FIRST = "- Unknown page? Snapshot first, then act: read `(await page.snapshotForAI()).full` to see what\n is there, pick a semantic selector from it (`getByRole`, `getByText`), then interact. Never\n guess selectors blind.\n- Known page or selectors? Skip the snapshot and use direct selectors \u2014 faster and more reliable.";
|
|
9984
|
+
var RULE_SCREENSHOT = "After each `canary run --step`, the daemon auto-captures ONE screenshot of the step's\nlast-opened tab and binds it to that step in the report. So:\n\n- Keep one primary named page per step \u2014 the report screenshot is always the page you mean.\n- If a step opens several tabs, open the one you want featured last.\n- `saveScreenshot(...)` images land in `~/.canary/tmp/` and are NOT in the report \u2014 they're\n extras for debugging.";
|
|
9985
|
+
|
|
9986
|
+
// ../../packages/cli-kit/src/scripting-help.ts
|
|
9987
|
+
var indent = (text, prefix) => text.split("\n").map((line) => line.length > 0 ? prefix + line : line).join("\n");
|
|
9988
|
+
var SANDBOX_ENVIRONMENT = `SANDBOX ENVIRONMENT:
|
|
9989
|
+
${indent(API_SANDBOX_ENV, " ")}
|
|
9990
|
+
|
|
9991
|
+
${indent(API_GLOBALS, " ")}`;
|
|
9992
|
+
var SCRIPT_API = `Script API available inside every script:
|
|
9993
|
+
${indent(API_BROWSER, " ")}
|
|
9994
|
+
|
|
9995
|
+
${indent(API_FILE_HELPERS, " ")}
|
|
9996
|
+
|
|
9997
|
+
${indent(API_CONSOLE, " ")}`;
|
|
9998
|
+
var PLAYWRIGHT_PAGE_NOTE = API_PLAYWRIGHT_NOTE;
|
|
9999
|
+
function sandboxReference() {
|
|
10000
|
+
return `${SANDBOX_ENVIRONMENT}
|
|
10001
|
+
|
|
10002
|
+
${SCRIPT_API}
|
|
10003
|
+
|
|
10004
|
+
${PLAYWRIGHT_PAGE_NOTE}`;
|
|
10005
|
+
}
|
|
10006
|
+
var sessionExample = (scriptBody, opts) => [
|
|
10007
|
+
`canary run --session "$id" --step ${opts?.step ?? "<name>"} <<'EOF'`,
|
|
10008
|
+
scriptBody,
|
|
10009
|
+
"EOF"
|
|
10010
|
+
].join("\n");
|
|
10011
|
+
var INSPECT_PAGE_BODY = `const page = await browser.getPage("TARGET_ID_HERE");
|
|
10012
|
+
console.log(JSON.stringify({
|
|
10013
|
+
url: page.url(),
|
|
10014
|
+
title: await page.title(),
|
|
10015
|
+
}, null, 2));`;
|
|
10016
|
+
var SCREENSHOT_BODY = `const page = await browser.getPage("main");
|
|
10017
|
+
const buf = await page.screenshot();
|
|
10018
|
+
const path = await saveScreenshot(buf, "debug.png");
|
|
10019
|
+
console.log(path);`;
|
|
10020
|
+
var WAITING_BODY = `const page = await browser.getPage("search-results");
|
|
10021
|
+
await page.waitForSelector(".results");
|
|
10022
|
+
await page.waitForURL("**/success");
|
|
10023
|
+
console.log(JSON.stringify({
|
|
10024
|
+
url: page.url(),
|
|
10025
|
+
title: await page.title(),
|
|
10026
|
+
}, null, 2));`;
|
|
10027
|
+
var ERROR_RECOVERY_BODY = `const page = await browser.getPage("checkout");
|
|
10028
|
+
const path = await saveScreenshot(await page.screenshot(), "debug.png");
|
|
10029
|
+
console.log(JSON.stringify({
|
|
10030
|
+
screenshot: path,
|
|
10031
|
+
url: page.url(),
|
|
10032
|
+
title: await page.title(),
|
|
10033
|
+
}, null, 2));`;
|
|
10034
|
+
var POWERSHELL_BLOCK = ` On Windows/PowerShell, use here-strings to pipe multiline scripts:
|
|
10035
|
+
@"
|
|
10036
|
+
const page = await browser.getPage("main");
|
|
10037
|
+
console.log(await page.title());
|
|
10038
|
+
"@ | canary-browser --connect`;
|
|
10039
|
+
var CONNECTING_SECTION = ` Connecting to a running Chrome instance:
|
|
10040
|
+
Auto-discover Chrome with debugging enabled:
|
|
10041
|
+
canary-browser --connect <<'EOF'
|
|
10042
|
+
const page = await browser.getPage("main");
|
|
10043
|
+
console.log(await page.title());
|
|
10044
|
+
EOF
|
|
10045
|
+
|
|
10046
|
+
Connect to a specific CDP endpoint:
|
|
10047
|
+
canary-browser --connect http://localhost:9222 <<'EOF'
|
|
10048
|
+
const page = await browser.getPage("main");
|
|
10049
|
+
console.log(await page.title());
|
|
10050
|
+
EOF
|
|
10051
|
+
|
|
10052
|
+
To launch Chrome with debugging enabled:
|
|
10053
|
+
chrome.exe --remote-debugging-port=9222
|
|
10054
|
+
google-chrome --remote-debugging-port=9222
|
|
10055
|
+
|
|
10056
|
+
Or visit chrome://inspect/#remote-debugging to configure.`;
|
|
10057
|
+
function buildScriptingGuide(options) {
|
|
10058
|
+
const { heading, example } = options;
|
|
10059
|
+
const browserExtras = options.browserExtras === true;
|
|
10060
|
+
const intro = [
|
|
10061
|
+
`${heading}`,
|
|
10062
|
+
" Write small, focused scripts. Each script should do ONE thing: navigate, click, fill, or check.",
|
|
10063
|
+
" End each script by logging the state you need for the next decision.",
|
|
10064
|
+
' Use descriptive page names like "login", "checkout", or "results" instead of "page1".',
|
|
10065
|
+
' Named pages from browser.getPage("name") persist between script runs, so you usually do not need to re-navigate.',
|
|
10066
|
+
" Inside page.evaluate(...), write plain JavaScript only - no TypeScript syntax in the browser context.",
|
|
10067
|
+
...browserExtras ? [POWERSHELL_BLOCK] : []
|
|
10068
|
+
].join("\n");
|
|
10069
|
+
const quickInspection = [
|
|
10070
|
+
" Quick inspection:",
|
|
10071
|
+
indent(
|
|
10072
|
+
example(EX_INSPECT_TABS, { step: "inspect", connect: true }),
|
|
10073
|
+
" "
|
|
10074
|
+
),
|
|
10075
|
+
"",
|
|
10076
|
+
indent(
|
|
10077
|
+
example(INSPECT_PAGE_BODY, { step: "inspect", connect: true }),
|
|
10078
|
+
" "
|
|
10079
|
+
)
|
|
10080
|
+
].join("\n");
|
|
10081
|
+
const aiSnapshots = [
|
|
10082
|
+
" AI snapshots for element discovery:",
|
|
10083
|
+
indent(example(EX_SNAPSHOT, { step: "discover" }), " "),
|
|
10084
|
+
"",
|
|
10085
|
+
indent(API_SNAPSHOT, " ")
|
|
10086
|
+
].join("\n");
|
|
10087
|
+
const choosingApproach = [
|
|
10088
|
+
" Choosing your approach:",
|
|
10089
|
+
indent(RULE_OBSERVE_FIRST, " ")
|
|
10090
|
+
].join("\n");
|
|
10091
|
+
const screenshots = [
|
|
10092
|
+
" Screenshots for visual state:",
|
|
10093
|
+
indent(example(SCREENSHOT_BODY, { step: "capture" }), " ")
|
|
10094
|
+
].join("\n");
|
|
10095
|
+
const waiting = [
|
|
10096
|
+
" Waiting patterns:",
|
|
10097
|
+
indent(example(WAITING_BODY, { step: "wait" }), " ")
|
|
10098
|
+
].join("\n");
|
|
10099
|
+
const devServer = [
|
|
10100
|
+
" Dev server navigation:",
|
|
10101
|
+
indent(RULE_DEV_SERVER, " ")
|
|
10102
|
+
].join("\n");
|
|
10103
|
+
const errorRecovery = [
|
|
10104
|
+
" Error recovery:",
|
|
10105
|
+
" If a script fails, the page usually stays where it stopped.",
|
|
10106
|
+
" Reconnect to the same page name, take a screenshot, and log the URL/title:",
|
|
10107
|
+
indent(example(ERROR_RECOVERY_BODY, { step: "recover" }), " ")
|
|
10108
|
+
].join("\n");
|
|
10109
|
+
const playwrightMethods = [
|
|
10110
|
+
" Common Playwright Page methods:",
|
|
10111
|
+
indent(API_PLAYWRIGHT_METHODS, " ")
|
|
10112
|
+
].join("\n");
|
|
10113
|
+
const tips = [
|
|
10114
|
+
" Tips:",
|
|
10115
|
+
" - Use console.log(JSON.stringify(...)) for structured output.",
|
|
10116
|
+
" - Prefer page.snapshotForAI() for structure; use screenshots when visual layout or styling matters.",
|
|
10117
|
+
" - Keep page names stable across scripts so you can resume work after failures.",
|
|
10118
|
+
...browserExtras ? [
|
|
10119
|
+
" - Each --browser name maps to a separate daemon-managed browser instance.",
|
|
10120
|
+
" - Use --connect to attach to an existing browser; omit the URL to auto-discover Chrome with debugging enabled."
|
|
10121
|
+
] : [],
|
|
10122
|
+
" - Use short timeouts (--timeout 10) so scripts fail fast instead of hanging on missing elements.",
|
|
10123
|
+
...browserExtras ? [
|
|
10124
|
+
" - Add --headless for unattended automation; omit it when you want to watch the browser window."
|
|
10125
|
+
] : []
|
|
10126
|
+
].join("\n");
|
|
10127
|
+
return [
|
|
10128
|
+
intro,
|
|
10129
|
+
quickInspection,
|
|
10130
|
+
aiSnapshots,
|
|
10131
|
+
choosingApproach,
|
|
10132
|
+
screenshots,
|
|
10133
|
+
waiting,
|
|
10134
|
+
devServer,
|
|
10135
|
+
errorRecovery,
|
|
10136
|
+
playwrightMethods,
|
|
10137
|
+
...browserExtras ? [CONNECTING_SECTION] : [],
|
|
10138
|
+
tips
|
|
10139
|
+
].join("\n\n");
|
|
10140
|
+
}
|
|
10141
|
+
|
|
10142
|
+
// ../../packages/cli-kit/src/index.ts
|
|
9968
10143
|
function requestId(prefix) {
|
|
9969
10144
|
return `${prefix}-${Date.now()}-${process.pid}`;
|
|
9970
10145
|
}
|
|
@@ -15032,19 +15207,19 @@ WHAT IS CAPTURED (per session; toggle on \`session start\`):
|
|
|
15032
15207
|
screenshots one per step, auto-captured from the step's last-opened page
|
|
15033
15208
|
|
|
15034
15209
|
Artifacts live under ~/.canary/sessions/<id>/ (session.json, results.json, report.html, trace.zip, \u2026).
|
|
15035
|
-
Scripts
|
|
15210
|
+
Scripts run in a QuickJS sandbox (not Node.js) with a pre-connected \`browser\` global \u2014 the full
|
|
15211
|
+
reference follows; the SCRIPTING GUIDE after the command list has worked examples.
|
|
15212
|
+
|
|
15213
|
+
${sandboxReference()}`;
|
|
15036
15214
|
var USAGE_GUIDE = `SESSION WORKFLOW GUIDE:
|
|
15037
15215
|
Structure a session as a sequence of small steps \u2014 one script per step (open, act, assert).
|
|
15038
15216
|
Each \`canary run --step <name>\` is one step in the report, with its own trace group and ONE
|
|
15039
|
-
auto-captured screenshot
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
across steps within a session.
|
|
15043
|
-
- If a step opens extra tabs, open the one you want featured in the report LAST.
|
|
15217
|
+
auto-captured screenshot.
|
|
15218
|
+
|
|
15219
|
+
${indent(RULE_SCREENSHOT, " ")}
|
|
15044
15220
|
|
|
15045
15221
|
Passing data between steps:
|
|
15046
|
-
|
|
15047
|
-
- For values: writeFile("state.json", JSON.stringify(x)) in one step, readFile it in the next.
|
|
15222
|
+
${indent(RULE_DATA_PASSING, " ")}
|
|
15048
15223
|
|
|
15049
15224
|
Reading results:
|
|
15050
15225
|
canary session list List sessions (table; --json for machine output)
|
|
@@ -15052,12 +15227,18 @@ var USAGE_GUIDE = `SESSION WORKFLOW GUIDE:
|
|
|
15052
15227
|
open ~/.canary/sessions/<id>/report.html The self-contained report
|
|
15053
15228
|
canary ui Browse, search, and organize all sessions
|
|
15054
15229
|
|
|
15230
|
+
Step discipline:
|
|
15231
|
+
${indent(RULE_FAIL_FAST, " ")}
|
|
15232
|
+
|
|
15055
15233
|
Tips:
|
|
15056
15234
|
- \`--json\` (global) emits machine-readable JSON on stdout; \`-v\`/\`--verbose\` raises stderr logging.
|
|
15057
|
-
- \`canary run --timeout 30\` fails a step fast instead of hanging on a missing element.
|
|
15058
15235
|
- \`canary session end --stop-daemon\` shuts the daemon down if nothing else is using it.
|
|
15059
|
-
-
|
|
15060
|
-
|
|
15236
|
+
- Need a quick one-off with NO recording? Use \`canary-browser run\` instead of a session.
|
|
15237
|
+
|
|
15238
|
+
${buildScriptingGuide({
|
|
15239
|
+
example: sessionExample,
|
|
15240
|
+
heading: "SCRIPTING GUIDE:"
|
|
15241
|
+
})}`;
|
|
15061
15242
|
var SESSION_START_LONG_ABOUT = `Start a capture-enabled session and print its id.
|
|
15062
15243
|
|
|
15063
15244
|
Capture is on by default \u2014 disable per stream with --no-trace / --no-video / --no-har / --no-console.
|
|
@@ -15067,10 +15248,15 @@ Use --headless for unattended runs; omit it to watch the browser window.
|
|
|
15067
15248
|
id=$(canary session start --name "smoke" --headless --no-video)`;
|
|
15068
15249
|
var RUN_LONG_ABOUT = `Run a script as one step inside a session.
|
|
15069
15250
|
|
|
15070
|
-
The script (a FILE, or stdin if omitted) executes
|
|
15071
|
-
|
|
15072
|
-
owns
|
|
15251
|
+
The script (a FILE, or stdin if omitted) executes as top-level JavaScript with \`await\` in a
|
|
15252
|
+
sandboxed QuickJS runtime \u2014 full reference below. The step's name labels it in the report and
|
|
15253
|
+
owns ONE auto-captured screenshot (taken from the LAST page opened during the step). Named
|
|
15254
|
+
pages persist across steps within the session, so each step picks up where the last left off;
|
|
15255
|
+
pass values between steps with writeFile/readFile.
|
|
15256
|
+
|
|
15257
|
+
${sandboxReference()}
|
|
15073
15258
|
|
|
15259
|
+
Examples:
|
|
15074
15260
|
canary run open.js --session "$id" --step open
|
|
15075
15261
|
echo 'const p = await browser.getPage("home"); await p.goto("https://example.com");' \\
|
|
15076
15262
|
| canary run --session "$id" --step home --timeout 30`;
|
|
@@ -17034,26 +17220,25 @@ function findWorkspaceUiDir() {
|
|
|
17034
17220
|
async function resolveUiServer() {
|
|
17035
17221
|
const override = process.env.CANARY_UI_SERVER;
|
|
17036
17222
|
if (override && await isFile(override)) {
|
|
17037
|
-
return { kind: "standalone",
|
|
17223
|
+
return { kind: "standalone", serverEntry: override };
|
|
17038
17224
|
}
|
|
17039
17225
|
const workspaceDir = findWorkspaceUiDir();
|
|
17040
17226
|
if (!workspaceDir) {
|
|
17041
17227
|
return null;
|
|
17042
17228
|
}
|
|
17043
|
-
const
|
|
17229
|
+
const serverEntry = path5.join(workspaceDir, "dist", "server", "entry.mjs");
|
|
17230
|
+
if (await isFile(serverEntry)) {
|
|
17231
|
+
return { kind: "standalone", serverEntry };
|
|
17232
|
+
}
|
|
17233
|
+
const astroBin = path5.join(
|
|
17044
17234
|
workspaceDir,
|
|
17045
|
-
"
|
|
17046
|
-
"
|
|
17047
|
-
"
|
|
17048
|
-
"
|
|
17049
|
-
"server.js"
|
|
17235
|
+
"node_modules",
|
|
17236
|
+
"astro",
|
|
17237
|
+
"bin",
|
|
17238
|
+
"astro.mjs"
|
|
17050
17239
|
);
|
|
17051
|
-
if (await isFile(
|
|
17052
|
-
return { kind: "
|
|
17053
|
-
}
|
|
17054
|
-
const nextBin = path5.join(workspaceDir, "node_modules", ".bin", "next");
|
|
17055
|
-
if (await isFile(nextBin)) {
|
|
17056
|
-
return { kind: "dev", nextBin, workspaceDir };
|
|
17240
|
+
if (await isFile(astroBin)) {
|
|
17241
|
+
return { kind: "dev", astroBin, workspaceDir };
|
|
17057
17242
|
}
|
|
17058
17243
|
return null;
|
|
17059
17244
|
}
|
|
@@ -17114,9 +17299,10 @@ async function uiCommand(args) {
|
|
|
17114
17299
|
const baseEnv = { ...process.env, CANARY_UI_ROOT: root };
|
|
17115
17300
|
let child;
|
|
17116
17301
|
if (resolved.kind === "standalone") {
|
|
17117
|
-
child = spawn4(process.execPath, [resolved.
|
|
17118
|
-
cwd: path6.dirname(resolved.
|
|
17119
|
-
|
|
17302
|
+
child = spawn4(process.execPath, [resolved.serverEntry], {
|
|
17303
|
+
cwd: path6.dirname(resolved.serverEntry),
|
|
17304
|
+
// Astro's node adapter reads HOST (the old Next server read HOSTNAME).
|
|
17305
|
+
env: { ...baseEnv, HOST: host, PORT: String(port) },
|
|
17120
17306
|
stdio: ["ignore", "inherit", "inherit"]
|
|
17121
17307
|
});
|
|
17122
17308
|
} else {
|
|
@@ -17125,7 +17311,7 @@ async function uiCommand(args) {
|
|
|
17125
17311
|
);
|
|
17126
17312
|
child = spawn4(
|
|
17127
17313
|
process.execPath,
|
|
17128
|
-
[resolved.
|
|
17314
|
+
[resolved.astroBin, "dev", "--port", String(port), "--host", host],
|
|
17129
17315
|
{
|
|
17130
17316
|
cwd: resolved.workspaceDir,
|
|
17131
17317
|
env: baseEnv,
|
|
@@ -17173,7 +17359,7 @@ async function uiCommand(args) {
|
|
|
17173
17359
|
|
|
17174
17360
|
// src/cli.ts
|
|
17175
17361
|
var { Command: Command2, InvalidArgumentError: InvalidArgumentError2 } = esm_exports;
|
|
17176
|
-
var VERSION = "0.
|
|
17362
|
+
var VERSION = "0.4.0";
|
|
17177
17363
|
var ExitCodeError = class extends Error {
|
|
17178
17364
|
code;
|
|
17179
17365
|
constructor(code) {
|