sweetlink 0.2.0 → 0.2.1
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/CHANGELOG.md +6 -2
- package/LICENSE +1 -1
- package/README.md +6 -6
- package/dist/daemon/src/codename.d.ts.map +1 -1
- package/dist/daemon/src/codename.js +11 -11
- package/dist/daemon/src/codename.js.map +1 -1
- package/dist/daemon/src/index.d.ts +1 -1
- package/dist/daemon/src/index.js +131 -122
- package/dist/daemon/src/index.js.map +1 -1
- package/dist/shared/src/env.d.ts.map +1 -1
- package/dist/shared/src/env.js +12 -12
- package/dist/shared/src/env.js.map +1 -1
- package/dist/shared/src/index.d.ts +30 -30
- package/dist/shared/src/index.d.ts.map +1 -1
- package/dist/shared/src/index.js +17 -17
- package/dist/shared/src/index.js.map +1 -1
- package/dist/shared/src/node.d.ts +1 -1
- package/dist/shared/src/node.d.ts.map +1 -1
- package/dist/shared/src/node.js +18 -18
- package/dist/shared/src/node.js.map +1 -1
- package/dist/src/codex.d.ts +1 -1
- package/dist/src/codex.js +19 -19
- package/dist/src/codex.js.map +1 -1
- package/dist/src/commands/click.d.ts +1 -1
- package/dist/src/commands/click.d.ts.map +1 -1
- package/dist/src/commands/click.js +32 -24
- package/dist/src/commands/click.js.map +1 -1
- package/dist/src/commands/run-js.d.ts +1 -1
- package/dist/src/commands/run-js.d.ts.map +1 -1
- package/dist/src/commands/run-js.js +10 -10
- package/dist/src/commands/run-js.js.map +1 -1
- package/dist/src/commands/trust-ca.d.ts +1 -1
- package/dist/src/commands/trust-ca.js +15 -15
- package/dist/src/core/config-file.d.ts.map +1 -1
- package/dist/src/core/config-file.js +49 -41
- package/dist/src/core/config-file.js.map +1 -1
- package/dist/src/core/config.d.ts +2 -2
- package/dist/src/core/config.d.ts.map +1 -1
- package/dist/src/core/config.js +22 -18
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/dev-bootstrap.d.ts +1 -1
- package/dist/src/core/dev-bootstrap.d.ts.map +1 -1
- package/dist/src/core/dev-bootstrap.js +12 -12
- package/dist/src/core/dev-bootstrap.js.map +1 -1
- package/dist/src/core/env.d.ts +1 -1
- package/dist/src/core/env.d.ts.map +1 -1
- package/dist/src/core/env.js +3 -3
- package/dist/src/core/env.js.map +1 -1
- package/dist/src/devtools-registry.d.ts.map +1 -1
- package/dist/src/devtools-registry.js +48 -46
- package/dist/src/devtools-registry.js.map +1 -1
- package/dist/src/env.d.ts +1 -1
- package/dist/src/env.js +1 -1
- package/dist/src/http.d.ts.map +1 -1
- package/dist/src/http.js +7 -6
- package/dist/src/http.js.map +1 -1
- package/dist/src/index.d.ts +7 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +399 -367
- package/dist/src/index.js.map +1 -1
- package/dist/src/runtime/browser/client.d.ts +3 -3
- package/dist/src/runtime/browser/client.d.ts.map +1 -1
- package/dist/src/runtime/browser/client.js +79 -72
- package/dist/src/runtime/browser/client.js.map +1 -1
- package/dist/src/runtime/browser/commands/index.d.ts +2 -2
- package/dist/src/runtime/browser/commands/index.js +17 -17
- package/dist/src/runtime/browser/commands/index.js.map +1 -1
- package/dist/src/runtime/browser/dom-to-image-loader.d.ts.map +1 -1
- package/dist/src/runtime/browser/dom-to-image-loader.js +7 -7
- package/dist/src/runtime/browser/dom-to-image-loader.js.map +1 -1
- package/dist/src/runtime/browser/index.d.ts +3 -3
- package/dist/src/runtime/browser/index.js +2 -2
- package/dist/src/runtime/browser/module-loader.js +2 -2
- package/dist/src/runtime/browser/screenshot/hooks.d.ts +5 -5
- package/dist/src/runtime/browser/screenshot/hooks.d.ts.map +1 -1
- package/dist/src/runtime/browser/screenshot/hooks.js +32 -29
- package/dist/src/runtime/browser/screenshot/hooks.js.map +1 -1
- package/dist/src/runtime/browser/screenshot/index.d.ts +3 -3
- package/dist/src/runtime/browser/screenshot/index.d.ts.map +1 -1
- package/dist/src/runtime/browser/screenshot/index.js +17 -15
- package/dist/src/runtime/browser/screenshot/index.js.map +1 -1
- package/dist/src/runtime/browser/screenshot/renderers/dom-to-image.d.ts +1 -1
- package/dist/src/runtime/browser/screenshot/renderers/dom-to-image.js +13 -13
- package/dist/src/runtime/browser/screenshot/renderers/dom-to-image.js.map +1 -1
- package/dist/src/runtime/browser/screenshot/renderers/html2canvas.d.ts +1 -1
- package/dist/src/runtime/browser/screenshot/renderers/html2canvas.d.ts.map +1 -1
- package/dist/src/runtime/browser/screenshot/renderers/html2canvas.js +24 -24
- package/dist/src/runtime/browser/screenshot/renderers/html2canvas.js.map +1 -1
- package/dist/src/runtime/browser/screenshot/targets.d.ts +2 -2
- package/dist/src/runtime/browser/screenshot/targets.js +9 -9
- package/dist/src/runtime/browser/screenshot/utils.d.ts +1 -1
- package/dist/src/runtime/browser/screenshot/utils.d.ts.map +1 -1
- package/dist/src/runtime/browser/screenshot/utils.js +73 -71
- package/dist/src/runtime/browser/screenshot/utils.js.map +1 -1
- package/dist/src/runtime/browser/selector-discovery.d.ts +1 -1
- package/dist/src/runtime/browser/selector-discovery.d.ts.map +1 -1
- package/dist/src/runtime/browser/selector-discovery.js +24 -22
- package/dist/src/runtime/browser/selector-discovery.js.map +1 -1
- package/dist/src/runtime/browser/storage/session-storage.d.ts +1 -1
- package/dist/src/runtime/browser/storage/session-storage.d.ts.map +1 -1
- package/dist/src/runtime/browser/storage/session-storage.js +12 -10
- package/dist/src/runtime/browser/storage/session-storage.js.map +1 -1
- package/dist/src/runtime/browser/types.d.ts +7 -7
- package/dist/src/runtime/browser/types.js +1 -1
- package/dist/src/runtime/browser/utils/console.d.ts +1 -1
- package/dist/src/runtime/browser/utils/console.js +16 -16
- package/dist/src/runtime/browser/utils/console.js.map +1 -1
- package/dist/src/runtime/browser/utils/environment.js +2 -2
- package/dist/src/runtime/browser/utils/errors.d.ts.map +1 -1
- package/dist/src/runtime/browser/utils/errors.js +11 -11
- package/dist/src/runtime/browser/utils/number.d.ts.map +1 -1
- package/dist/src/runtime/browser/utils/number.js.map +1 -1
- package/dist/src/runtime/browser/utils/object.d.ts.map +1 -1
- package/dist/src/runtime/browser/utils/object.js +2 -2
- package/dist/src/runtime/browser/utils/object.js.map +1 -1
- package/dist/src/runtime/browser/utils/sanitize.js +2 -2
- package/dist/src/runtime/browser/utils/time.d.ts.map +1 -1
- package/dist/src/runtime/browser/utils/time.js +1 -1
- package/dist/src/runtime/chrome/cookies.d.ts +4 -4
- package/dist/src/runtime/chrome/cookies.d.ts.map +1 -1
- package/dist/src/runtime/chrome/cookies.js +31 -31
- package/dist/src/runtime/chrome/cookies.js.map +1 -1
- package/dist/src/runtime/chrome/deep-link.d.ts +1 -1
- package/dist/src/runtime/chrome/deep-link.d.ts.map +1 -1
- package/dist/src/runtime/chrome/deep-link.js +46 -36
- package/dist/src/runtime/chrome/deep-link.js.map +1 -1
- package/dist/src/runtime/chrome/diagnostics.d.ts.map +1 -1
- package/dist/src/runtime/chrome/diagnostics.js +13 -11
- package/dist/src/runtime/chrome/diagnostics.js.map +1 -1
- package/dist/src/runtime/chrome/focus.d.ts.map +1 -1
- package/dist/src/runtime/chrome/focus.js +5 -5
- package/dist/src/runtime/chrome/focus.js.map +1 -1
- package/dist/src/runtime/chrome/launch.d.ts.map +1 -1
- package/dist/src/runtime/chrome/launch.js +30 -30
- package/dist/src/runtime/chrome/launch.js.map +1 -1
- package/dist/src/runtime/chrome/puppeteer.d.ts +2 -2
- package/dist/src/runtime/chrome/puppeteer.d.ts.map +1 -1
- package/dist/src/runtime/chrome/puppeteer.js +16 -11
- package/dist/src/runtime/chrome/puppeteer.js.map +1 -1
- package/dist/src/runtime/chrome/reuse.d.ts.map +1 -1
- package/dist/src/runtime/chrome/reuse.js +38 -35
- package/dist/src/runtime/chrome/reuse.js.map +1 -1
- package/dist/src/runtime/chrome/session.d.ts +1 -1
- package/dist/src/runtime/chrome/session.d.ts.map +1 -1
- package/dist/src/runtime/chrome/session.js +12 -10
- package/dist/src/runtime/chrome/session.js.map +1 -1
- package/dist/src/runtime/chrome.d.ts +8 -8
- package/dist/src/runtime/chrome.js +8 -8
- package/dist/src/runtime/cookies.d.ts +1 -16
- package/dist/src/runtime/cookies.d.ts.map +1 -1
- package/dist/src/runtime/cookies.js +175 -286
- package/dist/src/runtime/cookies.js.map +1 -1
- package/dist/src/runtime/devstack.d.ts +1 -1
- package/dist/src/runtime/devstack.d.ts.map +1 -1
- package/dist/src/runtime/devstack.js +31 -28
- package/dist/src/runtime/devstack.js.map +1 -1
- package/dist/src/runtime/devtools/background.js +22 -22
- package/dist/src/runtime/devtools/cdp.d.ts +3 -3
- package/dist/src/runtime/devtools/cdp.d.ts.map +1 -1
- package/dist/src/runtime/devtools/cdp.js +59 -55
- package/dist/src/runtime/devtools/cdp.js.map +1 -1
- package/dist/src/runtime/devtools/config.d.ts.map +1 -1
- package/dist/src/runtime/devtools/config.js +15 -15
- package/dist/src/runtime/devtools/config.js.map +1 -1
- package/dist/src/runtime/devtools/constants.d.ts.map +1 -1
- package/dist/src/runtime/devtools/constants.js +18 -10
- package/dist/src/runtime/devtools/constants.js.map +1 -1
- package/dist/src/runtime/devtools/diagnostics.d.ts +4 -4
- package/dist/src/runtime/devtools/diagnostics.d.ts.map +1 -1
- package/dist/src/runtime/devtools/diagnostics.js +40 -38
- package/dist/src/runtime/devtools/diagnostics.js.map +1 -1
- package/dist/src/runtime/devtools/oauth.d.ts +1 -1
- package/dist/src/runtime/devtools/oauth.d.ts.map +1 -1
- package/dist/src/runtime/devtools/oauth.js +35 -28
- package/dist/src/runtime/devtools/oauth.js.map +1 -1
- package/dist/src/runtime/devtools/types.d.ts +3 -3
- package/dist/src/runtime/devtools/types.d.ts.map +1 -1
- package/dist/src/runtime/devtools.d.ts +7 -7
- package/dist/src/runtime/devtools.js +7 -7
- package/dist/src/runtime/next-devtools.d.ts.map +1 -1
- package/dist/src/runtime/next-devtools.js +12 -12
- package/dist/src/runtime/next-devtools.js.map +1 -1
- package/dist/src/runtime/screenshot.d.ts +9 -9
- package/dist/src/runtime/screenshot.d.ts.map +1 -1
- package/dist/src/runtime/screenshot.js +65 -46
- package/dist/src/runtime/screenshot.js.map +1 -1
- package/dist/src/runtime/scripts.d.ts +1 -1
- package/dist/src/runtime/scripts.d.ts.map +1 -1
- package/dist/src/runtime/scripts.js +28 -28
- package/dist/src/runtime/scripts.js.map +1 -1
- package/dist/src/runtime/session.d.ts +6 -6
- package/dist/src/runtime/session.d.ts.map +1 -1
- package/dist/src/runtime/session.js +35 -35
- package/dist/src/runtime/session.js.map +1 -1
- package/dist/src/runtime/smoke.d.ts +3 -3
- package/dist/src/runtime/smoke.d.ts.map +1 -1
- package/dist/src/runtime/smoke.js +85 -79
- package/dist/src/runtime/smoke.js.map +1 -1
- package/dist/src/runtime/url.d.ts +2 -2
- package/dist/src/runtime/url.d.ts.map +1 -1
- package/dist/src/runtime/url.js +20 -20
- package/dist/src/runtime/url.js.map +1 -1
- package/dist/src/screenshot-hooks.d.ts +1 -1
- package/dist/src/screenshot-hooks.d.ts.map +1 -1
- package/dist/src/screenshot-hooks.js +12 -12
- package/dist/src/screenshot-hooks.js.map +1 -1
- package/dist/src/shared/env.d.ts.map +1 -1
- package/dist/src/shared/env.js +11 -11
- package/dist/src/shared/env.js.map +1 -1
- package/dist/src/shared/index.d.ts +30 -30
- package/dist/src/shared/index.d.ts.map +1 -1
- package/dist/src/shared/index.js +17 -17
- package/dist/src/shared/index.js.map +1 -1
- package/dist/src/shared/node.d.ts +1 -1
- package/dist/src/shared/node.d.ts.map +1 -1
- package/dist/src/shared/node.js +18 -18
- package/dist/src/shared/node.js.map +1 -1
- package/dist/src/token.d.ts +1 -1
- package/dist/src/token.d.ts.map +1 -1
- package/dist/src/token.js +16 -16
- package/dist/src/token.js.map +1 -1
- package/dist/src/types.d.ts +1 -1
- package/dist/src/util/app-label.js +4 -4
- package/dist/src/util/errors.d.ts.map +1 -1
- package/dist/src/util/errors.js +12 -10
- package/dist/src/util/errors.js.map +1 -1
- package/dist/src/util/path.js +2 -2
- package/dist/src/util/regex.js +4 -4
- package/package.json +45 -39
package/dist/src/index.js
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readFileSync } from
|
|
3
|
-
import { mkdir, readFile, rm } from
|
|
4
|
-
import { spawn } from
|
|
5
|
-
import os from
|
|
6
|
-
import path from
|
|
7
|
-
import { fileURLToPath } from
|
|
8
|
-
import { Command, CommanderError, Option } from
|
|
9
|
-
import { compact, uniq } from
|
|
10
|
-
import { createSweetLinkCommandId, } from
|
|
11
|
-
import { registerClickCommand } from
|
|
12
|
-
import { registerRunJsCommand } from
|
|
13
|
-
import { registerTrustCaCommand } from
|
|
14
|
-
import { readRootProgramOptions, resolveConfig } from
|
|
15
|
-
import { loadSweetLinkFileConfig } from
|
|
16
|
-
import { cloneProcessEnv, readCommandOptions } from
|
|
17
|
-
import { cleanupControlledChromeRegistry, registerControlledChromeInstance } from
|
|
18
|
-
import { sweetLinkCliTestMode, sweetLinkDebug, sweetLinkEnv } from
|
|
19
|
-
import { fetchJson } from
|
|
20
|
-
import { collectPuppeteerDiagnostics, focusControlledChromePage, launchChrome, launchControlledChrome, prepareChromeLaunch, reuseExistingControlledChrome, signalSweetLinkBootstrap, waitForSweetLinkSession, } from
|
|
21
|
-
import { ensureDeepLinkAuthFlow } from
|
|
22
|
-
import { buildCookieOrigins, collectChromeCookies, collectChromeCookiesForDomains, normalizePuppeteerCookie, } from
|
|
23
|
-
import { ensureDevStackRunning as ensureDevStackRunningRuntime, isAppReachable as isAppReachableRuntime, maybeInstallMkcertDispatcher, } from
|
|
24
|
-
import { attemptTwitterOauthAutoAccept, collectBootstrapDiagnostics, connectToDevTools, createEmptyDevToolsState, createNetworkEntryFromRequest, DEVTOOLS_CONSOLE_LIMIT, DEVTOOLS_NETWORK_LIMIT, DEVTOOLS_STATE_PATH, deriveDevtoolsLinkInfo, diagnosticsContainBlockingIssues, ensureBackgroundDevtoolsListener, fetchDevToolsTabs, formatConsoleArg, loadDevToolsConfig, loadDevToolsState, logBootstrapDiagnostics, saveDevToolsConfig, saveDevToolsState, serializeConsoleMessage, trimBuffer, } from
|
|
25
|
-
import { fetchNextDevtoolsErrors } from
|
|
26
|
-
import { attemptDevToolsCapture, maybeDescribeScreenshot, persistScreenshotResult, tryDevToolsRecovery, tryHtmlToImageFallback, } from
|
|
27
|
-
import { renderCommandResult } from
|
|
28
|
-
import { buildClickScript, fetchConsoleEvents, fetchSessionSummaries, formatSessionHeadline, getSessionSummaryById, isSweetLinkSelectorCandidate, isSweetLinkSelectorDiscoveryResult, resolvePromptOption, resolveSessionIdFromHint, } from
|
|
29
|
-
import { buildSmokeRouteUrl, clearSmokeProgress, consoleEventIndicatesAuthIssue, consoleEventIndicatesRuntimeError, DEFAULT_SMOKE_ROUTES, deriveSmokeRoutes, ensureSweetLinkSessionConnected, formatConsoleEventSummary, loadSmokeProgressIndex, navigateSweetLinkSession, saveSmokeProgressIndex, triggerSweetLinkCliAuto, waitForSmokeRouteReady, } from
|
|
30
|
-
import { buildWaitCandidateUrls, configurePathRedirects, normalizeUrlForMatch, trimTrailingSlash } from
|
|
31
|
-
import { buildScreenshotHooks } from
|
|
32
|
-
import { fetchCliToken } from
|
|
33
|
-
import { describeAppForPrompt, formatAppLabel } from
|
|
34
|
-
import { extractEventMessage, isErrnoException, logDebugError } from
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { mkdir, readFile, rm } from "node:fs/promises";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { Command, CommanderError, Option } from "commander";
|
|
9
|
+
import { compact, uniq } from "es-toolkit";
|
|
10
|
+
import { createSweetLinkCommandId, } from "../shared/src/index.js";
|
|
11
|
+
import { registerClickCommand } from "./commands/click.js";
|
|
12
|
+
import { registerRunJsCommand } from "./commands/run-js.js";
|
|
13
|
+
import { registerTrustCaCommand } from "./commands/trust-ca.js";
|
|
14
|
+
import { readRootProgramOptions, resolveConfig } from "./core/config.js";
|
|
15
|
+
import { loadSweetLinkFileConfig } from "./core/config-file.js";
|
|
16
|
+
import { cloneProcessEnv, readCommandOptions } from "./core/env.js";
|
|
17
|
+
import { cleanupControlledChromeRegistry, registerControlledChromeInstance, } from "./devtools-registry.js";
|
|
18
|
+
import { sweetLinkCliTestMode, sweetLinkDebug, sweetLinkEnv } from "./env.js";
|
|
19
|
+
import { fetchJson } from "./http.js";
|
|
20
|
+
import { collectPuppeteerDiagnostics, focusControlledChromePage, launchChrome, launchControlledChrome, prepareChromeLaunch, reuseExistingControlledChrome, signalSweetLinkBootstrap, waitForSweetLinkSession, } from "./runtime/chrome.js";
|
|
21
|
+
import { ensureDeepLinkAuthFlow } from "./runtime/chrome/deep-link.js";
|
|
22
|
+
import { buildCookieOrigins, collectChromeCookies, collectChromeCookiesForDomains, normalizePuppeteerCookie, } from "./runtime/cookies.js";
|
|
23
|
+
import { ensureDevStackRunning as ensureDevStackRunningRuntime, isAppReachable as isAppReachableRuntime, maybeInstallMkcertDispatcher, } from "./runtime/devstack.js";
|
|
24
|
+
import { attemptTwitterOauthAutoAccept, collectBootstrapDiagnostics, connectToDevTools, createEmptyDevToolsState, createNetworkEntryFromRequest, DEVTOOLS_CONSOLE_LIMIT, DEVTOOLS_NETWORK_LIMIT, DEVTOOLS_STATE_PATH, deriveDevtoolsLinkInfo, diagnosticsContainBlockingIssues, ensureBackgroundDevtoolsListener, fetchDevToolsTabs, formatConsoleArg, loadDevToolsConfig, loadDevToolsState, logBootstrapDiagnostics, saveDevToolsConfig, saveDevToolsState, serializeConsoleMessage, trimBuffer, } from "./runtime/devtools.js";
|
|
25
|
+
import { fetchNextDevtoolsErrors } from "./runtime/next-devtools.js";
|
|
26
|
+
import { attemptDevToolsCapture, maybeDescribeScreenshot, persistScreenshotResult, tryDevToolsRecovery, tryHtmlToImageFallback, } from "./runtime/screenshot.js";
|
|
27
|
+
import { renderCommandResult } from "./runtime/scripts.js";
|
|
28
|
+
import { buildClickScript, fetchConsoleEvents, fetchSessionSummaries, formatSessionHeadline, getSessionSummaryById, isSweetLinkSelectorCandidate, isSweetLinkSelectorDiscoveryResult, resolvePromptOption, resolveSessionIdFromHint, } from "./runtime/session.js";
|
|
29
|
+
import { buildSmokeRouteUrl, clearSmokeProgress, consoleEventIndicatesAuthIssue, consoleEventIndicatesRuntimeError, DEFAULT_SMOKE_ROUTES, deriveSmokeRoutes, ensureSweetLinkSessionConnected, formatConsoleEventSummary, loadSmokeProgressIndex, navigateSweetLinkSession, saveSmokeProgressIndex, triggerSweetLinkCliAuto, waitForSmokeRouteReady, } from "./runtime/smoke.js";
|
|
30
|
+
import { buildWaitCandidateUrls, configurePathRedirects, normalizeUrlForMatch, trimTrailingSlash, } from "./runtime/url.js";
|
|
31
|
+
import { buildScreenshotHooks } from "./screenshot-hooks.js";
|
|
32
|
+
import { fetchCliToken } from "./token.js";
|
|
33
|
+
import { describeAppForPrompt, formatAppLabel } from "./util/app-label.js";
|
|
34
|
+
import { extractEventMessage, isErrnoException, logDebugError } from "./util/errors.js";
|
|
35
35
|
const __filename = fileURLToPath(import.meta.url);
|
|
36
36
|
const __dirname = path.dirname(__filename);
|
|
37
37
|
function resolveRepoRoot(startDir) {
|
|
38
38
|
let currentDir = startDir;
|
|
39
39
|
// Walk up until a package.json exists or we reach the filesystem root.
|
|
40
40
|
// This keeps the CLI working whether we run from `src` (tsx) or `dist/src`.
|
|
41
|
-
while (!existsSync(path.join(currentDir,
|
|
41
|
+
while (!existsSync(path.join(currentDir, "package.json"))) {
|
|
42
42
|
const parent = path.dirname(currentDir);
|
|
43
43
|
if (parent === currentDir) {
|
|
44
44
|
return startDir;
|
|
@@ -48,11 +48,11 @@ function resolveRepoRoot(startDir) {
|
|
|
48
48
|
return currentDir;
|
|
49
49
|
}
|
|
50
50
|
const repoRoot = resolveRepoRoot(__dirname);
|
|
51
|
-
const packageJsonPath = path.join(repoRoot,
|
|
52
|
-
let packageVersion =
|
|
51
|
+
const packageJsonPath = path.join(repoRoot, "package.json");
|
|
52
|
+
let packageVersion = "0.0.0";
|
|
53
53
|
try {
|
|
54
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath,
|
|
55
|
-
if (typeof packageJson.version ===
|
|
54
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
55
|
+
if (typeof packageJson.version === "string") {
|
|
56
56
|
packageVersion = packageJson.version;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
@@ -65,7 +65,7 @@ const OAUTH_URL_PATTERN = /oauth|authorize/i;
|
|
|
65
65
|
const TRAILING_SLASH_PATTERN = /\/?$/;
|
|
66
66
|
function formatDuration(ms) {
|
|
67
67
|
if (!Number.isFinite(ms)) {
|
|
68
|
-
return
|
|
68
|
+
return "unknown";
|
|
69
69
|
}
|
|
70
70
|
const abs = Math.max(0, ms);
|
|
71
71
|
if (abs < 1000) {
|
|
@@ -92,11 +92,11 @@ function formatDuration(ms) {
|
|
|
92
92
|
}
|
|
93
93
|
const program = new Command();
|
|
94
94
|
program
|
|
95
|
-
.name(
|
|
96
|
-
.description(
|
|
97
|
-
.version(packageVersion,
|
|
95
|
+
.name("sweetlink")
|
|
96
|
+
.description("Interact with SweetLink daemon sessions")
|
|
97
|
+
.version(packageVersion, "-v, --version", "Show SweetLink CLI version");
|
|
98
98
|
maybeInstallMkcertDispatcher();
|
|
99
|
-
const LOOSE_PATH_SUFFIXES = new Set([
|
|
99
|
+
const LOOSE_PATH_SUFFIXES = new Set(["home", "index", "overview"]);
|
|
100
100
|
const { config: fileConfig } = loadSweetLinkFileConfig();
|
|
101
101
|
configurePathRedirects(fileConfig.redirects);
|
|
102
102
|
const { appUrl: envAppUrl, daemonUrl: envDaemonUrl, localAdminApiKey, adminApiKey: sharedAdminApiKey, prodAppUrl: envProdAppUrl, } = sweetLinkEnv;
|
|
@@ -104,15 +104,15 @@ const defaultAppLabel = formatAppLabel(fileConfig.appLabel ?? sweetLinkEnv.appLa
|
|
|
104
104
|
const defaultAppUrl = deriveDefaultAppUrl(envAppUrl, fileConfig);
|
|
105
105
|
const defaultProdAppUrl = fileConfig.prodUrl ?? envProdAppUrl;
|
|
106
106
|
const defaultDaemonUrl = fileConfig.daemonUrl ?? envDaemonUrl;
|
|
107
|
-
const defaultAdminKey = fileConfig.adminKey ?? localAdminApiKey ?? sharedAdminApiKey ??
|
|
107
|
+
const defaultAdminKey = fileConfig.adminKey ?? localAdminApiKey ?? sharedAdminApiKey ?? "";
|
|
108
108
|
const defaultHealthPaths = fileConfig.healthChecks?.paths ?? null;
|
|
109
|
-
const LOCAL_DEFAULT_APP_URL =
|
|
109
|
+
const LOCAL_DEFAULT_APP_URL = "http://localhost:3000";
|
|
110
110
|
function deriveDefaultAppUrl(envUrl, config) {
|
|
111
|
-
const configuredAppUrl = typeof config.appUrl ===
|
|
111
|
+
const configuredAppUrl = typeof config.appUrl === "string" ? config.appUrl.trim() : "";
|
|
112
112
|
if (configuredAppUrl.length > 0) {
|
|
113
113
|
return configuredAppUrl;
|
|
114
114
|
}
|
|
115
|
-
if (typeof config.port ===
|
|
115
|
+
if (typeof config.port === "number" && Number.isFinite(config.port) && config.port > 0) {
|
|
116
116
|
const baseUrl = envUrl ?? LOCAL_DEFAULT_APP_URL;
|
|
117
117
|
return applyPortToUrl(baseUrl, config.port);
|
|
118
118
|
}
|
|
@@ -131,22 +131,22 @@ function applyPortToUrl(base, port) {
|
|
|
131
131
|
function parseCliPort(value) {
|
|
132
132
|
const parsed = Number.parseInt(value, 10);
|
|
133
133
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
134
|
-
throw new CommanderError(1,
|
|
134
|
+
throw new CommanderError(1, "InvalidPort", `--port expects a positive integer, received "${value}".`);
|
|
135
135
|
}
|
|
136
136
|
return parsed;
|
|
137
137
|
}
|
|
138
138
|
program
|
|
139
|
-
.option(
|
|
140
|
-
.option(
|
|
141
|
-
.option(
|
|
142
|
-
.addOption(new Option(
|
|
143
|
-
.option(
|
|
144
|
-
.option(
|
|
145
|
-
.option(
|
|
139
|
+
.option("-a, --app-url <url>", "Application base URL for SweetLink commands", defaultAppUrl)
|
|
140
|
+
.option("--app-label <label>", "Friendly name for your application (used in help output)", defaultAppLabel)
|
|
141
|
+
.option("--url <url>", "Alias for --app-url")
|
|
142
|
+
.addOption(new Option("--port <number>", "Override local app port (defaults to config or 3000)").argParser(parseCliPort))
|
|
143
|
+
.option("-d, --daemon-url <url>", "SweetLink daemon base URL", defaultDaemonUrl)
|
|
144
|
+
.option("-k, --admin-key <key>", "Optional admin API key (defaults to SWEETLINK_LOCAL_ADMIN_API_KEY or SWEETLINK_ADMIN_API_KEY env; falls back to legacy SWEETISTICS_* keys)", defaultAdminKey)
|
|
145
|
+
.option("--oauth-script <path>", "Absolute or relative path to an OAuth automation script (ESM module). Overrides config/env defaults.");
|
|
146
146
|
program
|
|
147
|
-
.command(
|
|
148
|
-
.description(
|
|
149
|
-
.option(
|
|
147
|
+
.command("sessions")
|
|
148
|
+
.description("List active SweetLink sessions")
|
|
149
|
+
.option("--json", "Output JSON instead of a table", false)
|
|
150
150
|
.action(async (options, command) => {
|
|
151
151
|
const config = resolveConfig(command);
|
|
152
152
|
const token = await fetchCliToken(config);
|
|
@@ -169,15 +169,17 @@ program
|
|
|
169
169
|
return;
|
|
170
170
|
}
|
|
171
171
|
if (sessions.length === 0) {
|
|
172
|
-
console.log(
|
|
173
|
-
console.log(
|
|
172
|
+
console.log("No active SweetLink sessions.");
|
|
173
|
+
console.log("Hint: run `pnpm sweetlink open --controlled --path /` to launch an authenticated tab automatically.");
|
|
174
174
|
return;
|
|
175
175
|
}
|
|
176
176
|
const now = Date.now();
|
|
177
|
-
console.log(
|
|
177
|
+
console.log("Active SweetLink sessions:\n");
|
|
178
178
|
for (const session of sessions) {
|
|
179
|
-
const heartbeatMsAgo = typeof session.heartbeatMsAgo ===
|
|
180
|
-
|
|
179
|
+
const heartbeatMsAgo = typeof session.heartbeatMsAgo === "number"
|
|
180
|
+
? session.heartbeatMsAgo
|
|
181
|
+
: Math.max(0, now - session.lastSeenAt);
|
|
182
|
+
const socketState = session.socketState ?? "unknown";
|
|
181
183
|
const consoleEventsBuffered = session.consoleEventsBuffered ?? 0;
|
|
182
184
|
const consoleErrorsBuffered = session.consoleErrorsBuffered ?? 0;
|
|
183
185
|
const pendingCommandCount = session.pendingCommandCount ?? 0;
|
|
@@ -188,30 +190,30 @@ program
|
|
|
188
190
|
const openedLabel = `${formatDuration(openedMsAgo)} ago`;
|
|
189
191
|
const socketLabel = `socket ${socketState}`;
|
|
190
192
|
console.log(` - last: ${lastHeartbeatLabel} • opened ${openedLabel} • ${socketLabel}`);
|
|
191
|
-
const pendingLabel = pendingCommandCount === 1 ?
|
|
193
|
+
const pendingLabel = pendingCommandCount === 1 ? "command" : "commands";
|
|
192
194
|
const consoleLabel = consoleEventsBuffered > 0
|
|
193
|
-
? `${consoleEventsBuffered} event${consoleEventsBuffered === 1 ?
|
|
194
|
-
:
|
|
195
|
+
? `${consoleEventsBuffered} event${consoleEventsBuffered === 1 ? "" : "s"}${consoleErrorsBuffered ? ` (${consoleErrorsBuffered} error${consoleErrorsBuffered === 1 ? "" : "s"})` : ""}`
|
|
196
|
+
: "none";
|
|
195
197
|
console.log(` - queues: ${pendingCommandCount} ${pendingLabel} • console ${consoleLabel}`);
|
|
196
198
|
if (devtoolsSessionIds.has(session.sessionId)) {
|
|
197
|
-
console.log(` - devtools: linked${devtoolsEndpoint ? ` (${devtoolsEndpoint})` :
|
|
199
|
+
console.log(` - devtools: linked${devtoolsEndpoint ? ` (${devtoolsEndpoint})` : ""}`);
|
|
198
200
|
}
|
|
199
201
|
if (session.userAgent) {
|
|
200
202
|
console.log(` - ua: ${session.userAgent}`);
|
|
201
203
|
}
|
|
202
|
-
console.log(
|
|
204
|
+
console.log("");
|
|
203
205
|
}
|
|
204
|
-
console.log(
|
|
206
|
+
console.log("Tip: run `pnpm sweetlink console <sessionId> -n 50` to inspect the most recent console events for a session.");
|
|
205
207
|
});
|
|
206
208
|
program
|
|
207
|
-
.command(
|
|
208
|
-
.description(
|
|
209
|
-
.argument(
|
|
210
|
-
.option(
|
|
209
|
+
.command("cookies")
|
|
210
|
+
.description("Dump Chrome cookies for one or more domains or origins")
|
|
211
|
+
.argument("<domains...>", "Domains or fully-qualified origins (e.g. localhost, https://example.com)")
|
|
212
|
+
.option("--json", "Output JSON instead of a human-readable list", false)
|
|
211
213
|
.action(async (domains, options) => {
|
|
212
214
|
const uniqueDomains = uniq(compact(domains.map((domain) => domain.trim())));
|
|
213
215
|
if (uniqueDomains.length === 0) {
|
|
214
|
-
console.log(
|
|
216
|
+
console.log("No domains provided; nothing to collect.");
|
|
215
217
|
return;
|
|
216
218
|
}
|
|
217
219
|
const cookiesByDomain = await collectChromeCookiesForDomains(uniqueDomains);
|
|
@@ -222,29 +224,29 @@ program
|
|
|
222
224
|
for (const domain of uniqueDomains) {
|
|
223
225
|
const domainCookies = cookiesByDomain[domain];
|
|
224
226
|
const cookies = Array.isArray(domainCookies) ? domainCookies : [];
|
|
225
|
-
console.log(`\n${domain} — ${cookies.length} cookie${cookies.length === 1 ?
|
|
227
|
+
console.log(`\n${domain} — ${cookies.length} cookie${cookies.length === 1 ? "" : "s"}`);
|
|
226
228
|
if (cookies.length === 0) {
|
|
227
229
|
continue;
|
|
228
230
|
}
|
|
229
231
|
for (const cookie of cookies) {
|
|
230
|
-
const scope = cookie.domain ? `domain=${cookie.domain}` : `url=${cookie.url ??
|
|
231
|
-
const cookiePath = cookie.path ??
|
|
232
|
-
const secureFlag = cookie.secure ?
|
|
233
|
-
const httpOnlyFlag = cookie.httpOnly ?
|
|
232
|
+
const scope = cookie.domain ? `domain=${cookie.domain}` : `url=${cookie.url ?? "unknown"}`;
|
|
233
|
+
const cookiePath = cookie.path ?? "/";
|
|
234
|
+
const secureFlag = cookie.secure ? "; Secure" : "";
|
|
235
|
+
const httpOnlyFlag = cookie.httpOnly ? "; HttpOnly" : "";
|
|
234
236
|
console.log(` • ${cookie.name} (${scope} ${cookiePath}${secureFlag}${httpOnlyFlag})`);
|
|
235
237
|
console.log(` ${cookie.value}`);
|
|
236
238
|
}
|
|
237
239
|
}
|
|
238
|
-
console.log(
|
|
240
|
+
console.log("");
|
|
239
241
|
});
|
|
240
242
|
registerRunJsCommand(program);
|
|
241
243
|
registerTrustCaCommand(program);
|
|
242
244
|
registerClickCommand(program);
|
|
243
245
|
program
|
|
244
|
-
.command(
|
|
245
|
-
.description(
|
|
246
|
-
.option(
|
|
247
|
-
.option(
|
|
246
|
+
.command("console <sessionId>")
|
|
247
|
+
.description("Fetch buffered console events for a session")
|
|
248
|
+
.option("-n, --limit <count>", "Show only the last <count> console events", Number)
|
|
249
|
+
.option("--json", "Output JSON", false)
|
|
248
250
|
.action(async (sessionId, options, command) => {
|
|
249
251
|
const config = resolveConfig(command);
|
|
250
252
|
const resolvedSessionId = await resolveSessionIdFromHint(sessionId, config);
|
|
@@ -257,10 +259,10 @@ program
|
|
|
257
259
|
return;
|
|
258
260
|
}
|
|
259
261
|
if (response.events.length === 0) {
|
|
260
|
-
console.log(
|
|
262
|
+
console.log("No buffered console output.");
|
|
261
263
|
return;
|
|
262
264
|
}
|
|
263
|
-
const limit = typeof options.limit ===
|
|
265
|
+
const limit = typeof options.limit === "number" && Number.isFinite(options.limit) && options.limit > 0
|
|
264
266
|
? Math.floor(options.limit)
|
|
265
267
|
: null;
|
|
266
268
|
const events = limit ? response.events.slice(-limit) : response.events;
|
|
@@ -272,25 +274,25 @@ program
|
|
|
272
274
|
}
|
|
273
275
|
});
|
|
274
276
|
program
|
|
275
|
-
.command(
|
|
277
|
+
.command("open")
|
|
276
278
|
.description(`Open ${defaultAppLabel} in Chrome with SweetLink auto-enabled`)
|
|
277
|
-
.option(
|
|
278
|
-
.option(
|
|
279
|
-
.option(
|
|
280
|
-
.option(
|
|
281
|
-
.option(
|
|
282
|
-
.option(
|
|
283
|
-
.option(
|
|
284
|
-
.option(
|
|
285
|
-
.option(
|
|
286
|
-
.option(
|
|
279
|
+
.option("-e, --env <env>", "Environment to open (dev or prod)", "dev")
|
|
280
|
+
.option("-p, --path <path>", 'Optional path to append (default "")', "")
|
|
281
|
+
.option("--url <url>", "Explicit URL to open (overrides --path and --env)")
|
|
282
|
+
.option("--controlled", "Launch Chrome in controlled mode with DevTools enabled", false)
|
|
283
|
+
.option("--devtools-port <port>", "Specify DevTools port to use with --controlled", Number)
|
|
284
|
+
.option("--no-cookie-sync", "Disable copying cookies from your main Chrome profile into the controlled window", false)
|
|
285
|
+
.option("--timeout <seconds>", "Seconds to wait for a SweetLink session (default 15)", Number)
|
|
286
|
+
.option("--no-devtools", "Skip DevTools automation when opening in controlled mode")
|
|
287
|
+
.option("--headless", "Launch the controlled browser headlessly", false)
|
|
288
|
+
.option("--foreground", "Bring the Chrome window to the foreground after opening (macOS only)", false)
|
|
287
289
|
.action(async (options, command) => {
|
|
288
290
|
await runOpenCommand(options, command, program);
|
|
289
291
|
});
|
|
290
292
|
program
|
|
291
|
-
.command(
|
|
292
|
-
.description(
|
|
293
|
-
.argument(
|
|
293
|
+
.command("daemon")
|
|
294
|
+
.description("Launch the SweetLink daemon process")
|
|
295
|
+
.argument("[args...]", "Arguments forwarded to sweetlinkd")
|
|
294
296
|
.allowUnknownOption(true)
|
|
295
297
|
.action((_args, command) => {
|
|
296
298
|
runDaemonCommand(command);
|
|
@@ -299,16 +301,16 @@ async function runOpenCommand(options, command, rootProgram) {
|
|
|
299
301
|
const context = buildOpenCommandContext(options, command, rootProgram);
|
|
300
302
|
if (!context.controlled) {
|
|
301
303
|
if (options.devtools === false) {
|
|
302
|
-
console.log(
|
|
304
|
+
console.log("--no-devtools is ignored when launching an uncontrolled browser window.");
|
|
303
305
|
}
|
|
304
306
|
if (options.headless) {
|
|
305
|
-
console.log(
|
|
307
|
+
console.log("--headless requires --controlled; launching a regular Chrome window instead.");
|
|
306
308
|
}
|
|
307
309
|
}
|
|
308
310
|
if (context.headless && context.foreground) {
|
|
309
|
-
console.log(
|
|
311
|
+
console.log("--foreground is ignored in headless mode.");
|
|
310
312
|
}
|
|
311
|
-
if (context.env ===
|
|
313
|
+
if (context.env === "dev") {
|
|
312
314
|
await ensureDevStackRunningRuntime(context.targetUrl, {
|
|
313
315
|
repoRoot,
|
|
314
316
|
healthPaths: context.healthCheckPaths ?? undefined,
|
|
@@ -331,23 +333,23 @@ async function runOpenCommand(options, command, rootProgram) {
|
|
|
331
333
|
function runDaemonCommand(command) {
|
|
332
334
|
const parent = command.parent;
|
|
333
335
|
const rawArgs = parent?.rawArgs ?? process.argv;
|
|
334
|
-
const daemonIndex = rawArgs.findIndex((arg) => arg ===
|
|
336
|
+
const daemonIndex = rawArgs.findIndex((arg) => arg === "daemon");
|
|
335
337
|
const afterDaemon = daemonIndex === -1 ? [] : rawArgs.slice(daemonIndex + 1);
|
|
336
|
-
const passthroughIndex = afterDaemon.indexOf(
|
|
338
|
+
const passthroughIndex = afterDaemon.indexOf("--");
|
|
337
339
|
const forwarded = passthroughIndex >= 0
|
|
338
340
|
? afterDaemon.slice(passthroughIndex + 1)
|
|
339
|
-
: (command.args ?? []).filter((value) => typeof value ===
|
|
340
|
-
const child = spawn(
|
|
341
|
-
child.on(
|
|
342
|
-
if (isErrnoException(error) && error.code ===
|
|
341
|
+
: (command.args ?? []).filter((value) => typeof value === "string");
|
|
342
|
+
const child = spawn("sweetlinkd", forwarded, { stdio: "inherit", env: cloneProcessEnv() });
|
|
343
|
+
child.on("error", (error) => {
|
|
344
|
+
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
343
345
|
console.error('Unable to find "sweetlinkd" on PATH. Try `pnpm exec sweetlinkd` instead.');
|
|
344
346
|
process.exitCode = 1;
|
|
345
347
|
return;
|
|
346
348
|
}
|
|
347
|
-
console.error(
|
|
349
|
+
console.error("Failed to launch sweetlinkd:", extractEventMessage(error));
|
|
348
350
|
process.exitCode = 1;
|
|
349
351
|
});
|
|
350
|
-
child.on(
|
|
352
|
+
child.on("exit", (code) => {
|
|
351
353
|
process.exitCode = code ?? 0;
|
|
352
354
|
});
|
|
353
355
|
}
|
|
@@ -358,11 +360,11 @@ function buildOpenCommandContext(options, command, rootProgram) {
|
|
|
358
360
|
const parentOptions = readRootProgramOptions(parent);
|
|
359
361
|
const developmentBaseUrl = parentOptions.appUrl;
|
|
360
362
|
const productionBaseUrl = defaultProdAppUrl;
|
|
361
|
-
const baseUrl = env ===
|
|
363
|
+
const baseUrl = env === "prod" ? productionBaseUrl : developmentBaseUrl;
|
|
362
364
|
const explicitTarget = resolveExplicitTargetUrl(options.url);
|
|
363
365
|
const targetUrl = explicitTarget ?? buildOpenCommandTargetUrl(baseUrl, options.path);
|
|
364
366
|
const serverConfig = config.servers[env] ?? null;
|
|
365
|
-
const preferredPort = typeof options.devtoolsPort ===
|
|
367
|
+
const preferredPort = typeof options.devtoolsPort === "number" && Number.isFinite(options.devtoolsPort)
|
|
366
368
|
? options.devtoolsPort
|
|
367
369
|
: undefined;
|
|
368
370
|
const controlled = Boolean(options.controlled);
|
|
@@ -388,8 +390,8 @@ function buildOpenCommandContext(options, command, rootProgram) {
|
|
|
388
390
|
};
|
|
389
391
|
}
|
|
390
392
|
function normalizeOpenCommandEnvironment(value) {
|
|
391
|
-
const normalized = (value ??
|
|
392
|
-
if (normalized ===
|
|
393
|
+
const normalized = (value ?? "dev").trim().toLowerCase();
|
|
394
|
+
if (normalized === "dev" || normalized === "prod") {
|
|
393
395
|
return normalized;
|
|
394
396
|
}
|
|
395
397
|
throw new Error('Invalid environment. Use "dev" or "prod".');
|
|
@@ -403,13 +405,13 @@ function buildOpenCommandTargetUrl(baseUrl, rawPath) {
|
|
|
403
405
|
const message = error instanceof Error ? error.message : String(error);
|
|
404
406
|
throw new Error(`Failed to parse base URL "${baseUrl}": ${message}`);
|
|
405
407
|
}
|
|
406
|
-
targetUrl.searchParams.set(
|
|
407
|
-
const fallbackPath =
|
|
408
|
+
targetUrl.searchParams.set("sweetlink", "auto");
|
|
409
|
+
const fallbackPath = "/timeline";
|
|
408
410
|
const trimmedPath = rawPath?.trim();
|
|
409
411
|
const pathSource = trimmedPath && trimmedPath.length > 0 ? trimmedPath : fallbackPath;
|
|
410
|
-
const [pathPartRaw, queryPart] = pathSource.split(
|
|
412
|
+
const [pathPartRaw, queryPart] = pathSource.split("?", 2);
|
|
411
413
|
const pathPart = pathPartRaw && pathPartRaw.length > 0 ? pathPartRaw : fallbackPath;
|
|
412
|
-
targetUrl.pathname = pathPart.startsWith(
|
|
414
|
+
targetUrl.pathname = pathPart.startsWith("/") ? pathPart : `/${pathPart}`;
|
|
413
415
|
if (queryPart) {
|
|
414
416
|
const extraSearch = new URLSearchParams(queryPart);
|
|
415
417
|
for (const [key, value] of extraSearch.entries()) {
|
|
@@ -428,7 +430,7 @@ function resolveExplicitTargetUrl(raw) {
|
|
|
428
430
|
}
|
|
429
431
|
try {
|
|
430
432
|
const target = new URL(trimmed);
|
|
431
|
-
target.searchParams.set(
|
|
433
|
+
target.searchParams.set("sweetlink", target.searchParams.get("sweetlink") ?? "auto");
|
|
432
434
|
return target;
|
|
433
435
|
}
|
|
434
436
|
catch (error) {
|
|
@@ -437,14 +439,14 @@ function resolveExplicitTargetUrl(raw) {
|
|
|
437
439
|
}
|
|
438
440
|
}
|
|
439
441
|
function resolveCookieSyncPreference(command, cookieSyncOption) {
|
|
440
|
-
const source = command.getOptionValueSource?.(
|
|
441
|
-
if (source ===
|
|
442
|
+
const source = command.getOptionValueSource?.("cookieSync");
|
|
443
|
+
if (source === "default") {
|
|
442
444
|
return true;
|
|
443
445
|
}
|
|
444
446
|
return cookieSyncOption !== false;
|
|
445
447
|
}
|
|
446
448
|
function resolveOpenCommandTimeoutSeconds(value) {
|
|
447
|
-
if (typeof value ===
|
|
449
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
448
450
|
return Math.max(0, value);
|
|
449
451
|
}
|
|
450
452
|
return 30;
|
|
@@ -457,7 +459,7 @@ async function fetchWaitTokenIfNeeded(context) {
|
|
|
457
459
|
return await fetchCliToken(context.config);
|
|
458
460
|
}
|
|
459
461
|
catch (error) {
|
|
460
|
-
console.warn(
|
|
462
|
+
console.warn("Unable to fetch CLI token for session wait:", error);
|
|
461
463
|
return null;
|
|
462
464
|
}
|
|
463
465
|
}
|
|
@@ -479,7 +481,7 @@ async function handleControlledOpen(context, waitToken) {
|
|
|
479
481
|
await handleControlledReuse(context, waitToken, reuseResult);
|
|
480
482
|
return;
|
|
481
483
|
}
|
|
482
|
-
console.warn(
|
|
484
|
+
console.warn("No reusable controlled Chrome session matched the target; launching a fresh controlled window.");
|
|
483
485
|
await handleControlledLaunch(context, waitToken);
|
|
484
486
|
}
|
|
485
487
|
async function handleControlledReuse(context, waitToken, reuseResult) {
|
|
@@ -496,32 +498,32 @@ async function handleControlledReuse(context, waitToken, reuseResult) {
|
|
|
496
498
|
oauthScriptPath: context.oauthScriptPath,
|
|
497
499
|
});
|
|
498
500
|
if (deepLinkResult.signInClicked) {
|
|
499
|
-
console.log(
|
|
501
|
+
console.log("Triggered Sweetistics sign-in to reach the deep link.");
|
|
500
502
|
}
|
|
501
503
|
const oauthAttempt = deepLinkResult.oauthAttempt;
|
|
502
504
|
if (oauthAttempt) {
|
|
503
505
|
if (oauthAttempt.handled) {
|
|
504
|
-
console.log(`Automatically approved the OAuth prompt via ${oauthAttempt.action ??
|
|
506
|
+
console.log(`Automatically approved the OAuth prompt via ${oauthAttempt.action ?? "click"}${oauthAttempt.clickedText ? ` (${oauthAttempt.clickedText})` : ""}.`);
|
|
505
507
|
}
|
|
506
|
-
else if (oauthAttempt.reason && oauthAttempt.reason !==
|
|
508
|
+
else if (oauthAttempt.reason && oauthAttempt.reason !== "button-not-found") {
|
|
507
509
|
const locationHint = oauthAttempt.url || oauthAttempt.title || oauthAttempt.host
|
|
508
|
-
? ` (at ${oauthAttempt.title ?? oauthAttempt.host ??
|
|
509
|
-
:
|
|
510
|
+
? ` (at ${oauthAttempt.title ?? oauthAttempt.host ?? "unknown page"} ${oauthAttempt.url ?? ""})`
|
|
511
|
+
: "";
|
|
510
512
|
console.log(`OAuth auto-accept skipped: ${oauthAttempt.reason}${locationHint}.`);
|
|
511
513
|
}
|
|
512
514
|
}
|
|
513
515
|
}
|
|
514
516
|
catch (error) {
|
|
515
517
|
if (sweetLinkDebug) {
|
|
516
|
-
console.warn(
|
|
518
|
+
console.warn("Deep-link auth flow failed:", error);
|
|
517
519
|
}
|
|
518
520
|
}
|
|
519
521
|
}
|
|
520
522
|
else {
|
|
521
|
-
console.log(
|
|
523
|
+
console.log("DevTools automation disabled (--no-devtools); skipping telemetry bootstrap and OAuth auto-click.");
|
|
522
524
|
}
|
|
523
525
|
if (context.enableDevtools && !deepLinkResult?.navigatedToTarget) {
|
|
524
|
-
console.warn(
|
|
526
|
+
console.warn("Deep link did not resolve in reused controlled Chrome; launching a fresh controlled window.");
|
|
525
527
|
const freshContext = {
|
|
526
528
|
...context,
|
|
527
529
|
preferredPort: undefined,
|
|
@@ -531,24 +533,24 @@ async function handleControlledReuse(context, waitToken, reuseResult) {
|
|
|
531
533
|
}
|
|
532
534
|
console.log(`Reused controlled Chrome at ${reuseResult.devtoolsUrl} (env: ${context.env}).`);
|
|
533
535
|
if (reuseResult.targetAlreadyOpen) {
|
|
534
|
-
console.log(
|
|
536
|
+
console.log("Target tab was already open; activated existing page.");
|
|
535
537
|
}
|
|
536
538
|
else {
|
|
537
|
-
console.log(
|
|
539
|
+
console.log("Opened a new tab in the existing controlled Chrome window.");
|
|
538
540
|
}
|
|
539
541
|
if (context.enableDevtools) {
|
|
540
|
-
console.log(
|
|
542
|
+
console.log("The screenshot command will continue to use this DevTools instance.");
|
|
541
543
|
}
|
|
542
544
|
if (shouldFocus && context.enableDevtools) {
|
|
543
545
|
const focused = await focusControlledChromePage(reuseResult.devtoolsUrl, context.targetUrlString);
|
|
544
546
|
if (!focused && sweetLinkDebug) {
|
|
545
|
-
console.warn(
|
|
547
|
+
console.warn("Unable to focus controlled Chrome window automatically.");
|
|
546
548
|
}
|
|
547
549
|
}
|
|
548
550
|
else if (context.foreground && !context.enableDevtools) {
|
|
549
|
-
console.log(
|
|
551
|
+
console.log("--foreground requires DevTools automation; skipping automatic focus.");
|
|
550
552
|
}
|
|
551
|
-
console.log(
|
|
553
|
+
console.log("Remember this session codename. Run `pnpm sweetlink sessions`, copy the session id or codename, and use that handle for every follow-up command instead of rerunning `pnpm sweetlink open`.");
|
|
552
554
|
await waitForSessionAfterOpen(context, waitToken, {
|
|
553
555
|
devtoolsUrl: context.enableDevtools ? reuseResult.devtoolsUrl : undefined,
|
|
554
556
|
trigger: context.enableDevtools
|
|
@@ -558,10 +560,10 @@ async function handleControlledReuse(context, waitToken, reuseResult) {
|
|
|
558
560
|
? () => triggerSweetLinkCliAuto(reuseResult.devtoolsUrl, context.targetUrlString)
|
|
559
561
|
: undefined,
|
|
560
562
|
failureMessage: context.enableDevtools
|
|
561
|
-
?
|
|
562
|
-
:
|
|
563
|
+
? "Controlled Chrome reused but SweetLink did not register automatically."
|
|
564
|
+
: "Controlled Chrome reused. DevTools automation disabled; verify the session from the UI if SweetLink does not appear.",
|
|
563
565
|
});
|
|
564
|
-
await surfaceBlockingDiagnosticsAfterNavigation(
|
|
566
|
+
await surfaceBlockingDiagnosticsAfterNavigation("SweetLink open", context.enableDevtools ? reuseResult.devtoolsUrl : undefined, context.targetUrlString);
|
|
565
567
|
}
|
|
566
568
|
async function handleControlledLaunch(context, waitToken) {
|
|
567
569
|
const info = await launchControlledChrome(context.targetUrlString, {
|
|
@@ -570,7 +572,7 @@ async function handleControlledLaunch(context, waitToken) {
|
|
|
570
572
|
headless: context.headless,
|
|
571
573
|
foreground: context.foreground,
|
|
572
574
|
});
|
|
573
|
-
const userDataDirectoryDisplay = info.userDataDir.replace(os.homedir(),
|
|
575
|
+
const userDataDirectoryDisplay = info.userDataDir.replace(os.homedir(), "~");
|
|
574
576
|
if (context.enableDevtools) {
|
|
575
577
|
await registerControlledChromeInstance(info.devtoolsUrl, info.userDataDir);
|
|
576
578
|
await cleanupControlledChromeRegistry(info.devtoolsUrl);
|
|
@@ -582,56 +584,56 @@ async function handleControlledLaunch(context, waitToken) {
|
|
|
582
584
|
oauthScriptPath: context.oauthScriptPath,
|
|
583
585
|
});
|
|
584
586
|
if (deepLinkResult.signInClicked) {
|
|
585
|
-
console.log(
|
|
587
|
+
console.log("Triggered Sweetistics sign-in to reach the deep link.");
|
|
586
588
|
}
|
|
587
589
|
const oauthAttempt = deepLinkResult.oauthAttempt;
|
|
588
590
|
if (oauthAttempt) {
|
|
589
591
|
if (oauthAttempt.handled) {
|
|
590
|
-
console.log(`Automatically approved the OAuth prompt via ${oauthAttempt.action ??
|
|
592
|
+
console.log(`Automatically approved the OAuth prompt via ${oauthAttempt.action ?? "click"}${oauthAttempt.clickedText ? ` (${oauthAttempt.clickedText})` : ""}.`);
|
|
591
593
|
}
|
|
592
|
-
else if (oauthAttempt.reason && oauthAttempt.reason !==
|
|
594
|
+
else if (oauthAttempt.reason && oauthAttempt.reason !== "button-not-found") {
|
|
593
595
|
const locationHint = oauthAttempt.url || oauthAttempt.title || oauthAttempt.host
|
|
594
|
-
? ` (at ${oauthAttempt.title ?? oauthAttempt.host ??
|
|
595
|
-
:
|
|
596
|
+
? ` (at ${oauthAttempt.title ?? oauthAttempt.host ?? "unknown page"} ${oauthAttempt.url ?? ""})`
|
|
597
|
+
: "";
|
|
596
598
|
console.log(`OAuth auto-accept skipped: ${oauthAttempt.reason}${locationHint}.`);
|
|
597
599
|
}
|
|
598
600
|
}
|
|
599
601
|
}
|
|
600
602
|
catch (error) {
|
|
601
603
|
if (sweetLinkDebug) {
|
|
602
|
-
console.warn(
|
|
604
|
+
console.warn("Deep-link auth flow failed:", error);
|
|
603
605
|
}
|
|
604
606
|
}
|
|
605
607
|
}
|
|
606
608
|
else {
|
|
607
|
-
console.log(
|
|
609
|
+
console.log("DevTools automation disabled (--no-devtools); launched without OAuth auto-click.");
|
|
608
610
|
}
|
|
609
611
|
console.log(`Opened controlled Chrome window to ${context.targetUrlString} (env: ${context.env}).`);
|
|
610
612
|
console.log(`DevTools endpoint: ${info.devtoolsUrl}`);
|
|
611
613
|
console.log(`User data dir : ${userDataDirectoryDisplay}`);
|
|
612
614
|
if (context.enableDevtools) {
|
|
613
|
-
console.log(
|
|
615
|
+
console.log("The screenshot command will auto-detect this DevTools instance.");
|
|
614
616
|
}
|
|
615
617
|
if (context.headless) {
|
|
616
|
-
console.log(
|
|
618
|
+
console.log("Running in headless mode (--headless).");
|
|
617
619
|
}
|
|
618
620
|
const shouldFocus = context.foreground && !context.headless;
|
|
619
621
|
if (shouldFocus && context.enableDevtools) {
|
|
620
622
|
const focused = await focusControlledChromePage(info.devtoolsUrl, context.targetUrlString);
|
|
621
623
|
if (!focused && sweetLinkDebug) {
|
|
622
|
-
console.warn(
|
|
624
|
+
console.warn("Unable to focus controlled Chrome window automatically.");
|
|
623
625
|
}
|
|
624
626
|
}
|
|
625
627
|
else if (context.foreground && !context.enableDevtools) {
|
|
626
|
-
console.log(
|
|
628
|
+
console.log("--foreground requires DevTools automation; skipping automatic focus.");
|
|
627
629
|
}
|
|
628
630
|
await waitForSessionAfterOpen(context, waitToken, {
|
|
629
631
|
devtoolsUrl: context.enableDevtools ? info.devtoolsUrl : undefined,
|
|
630
632
|
failureMessage: context.enableDevtools
|
|
631
|
-
?
|
|
632
|
-
:
|
|
633
|
+
? "Controlled Chrome launched but SweetLink did not register automatically; keep the window open and retry from the UI if needed."
|
|
634
|
+
: "Controlled Chrome launched without DevTools automation; complete the login manually if SweetLink does not register.",
|
|
633
635
|
});
|
|
634
|
-
await surfaceBlockingDiagnosticsAfterNavigation(
|
|
636
|
+
await surfaceBlockingDiagnosticsAfterNavigation("SweetLink open", context.enableDevtools ? info.devtoolsUrl : undefined, context.targetUrlString);
|
|
635
637
|
}
|
|
636
638
|
async function waitForSessionAfterOpen(context, waitToken, options) {
|
|
637
639
|
if (!waitToken || context.timeoutSeconds <= 0) {
|
|
@@ -676,14 +678,14 @@ async function waitForSessionAfterOpen(context, waitToken, options) {
|
|
|
676
678
|
try {
|
|
677
679
|
const diagnostics = await collectBootstrapDiagnostics(options.devtoolsUrl, candidates);
|
|
678
680
|
if (diagnostics) {
|
|
679
|
-
console.warn(
|
|
680
|
-
logBootstrapDiagnostics(
|
|
681
|
+
console.warn("SweetLink bootstrap diagnostics (DevTools snapshot):");
|
|
682
|
+
logBootstrapDiagnostics("SweetLink", diagnostics);
|
|
681
683
|
}
|
|
682
684
|
}
|
|
683
685
|
catch (error) {
|
|
684
|
-
console.warn(
|
|
686
|
+
console.warn("Failed to collect DevTools diagnostics:", error);
|
|
685
687
|
}
|
|
686
|
-
await logPuppeteerPageSnapshot(
|
|
688
|
+
await logPuppeteerPageSnapshot("SweetLink", options.devtoolsUrl, context.targetUrlString);
|
|
687
689
|
}
|
|
688
690
|
}
|
|
689
691
|
// Runs after every navigation so CLI users see the same crash text the browser shows.
|
|
@@ -699,7 +701,8 @@ async function surfaceBlockingDiagnosticsAfterNavigation(label, devtoolsUrl, tar
|
|
|
699
701
|
const diagnostics = await collectBootstrapDiagnostics(devtoolsUrl, candidates);
|
|
700
702
|
if (diagnostics) {
|
|
701
703
|
const bootstrapIncomplete = diagnostics.autoFlag !== true ||
|
|
702
|
-
(typeof diagnostics.sessionStorageAuto ===
|
|
704
|
+
(typeof diagnostics.sessionStorageAuto === "string" &&
|
|
705
|
+
diagnostics.sessionStorageAuto.length > 0);
|
|
703
706
|
const hasBlocking = diagnosticsContainBlockingIssues(diagnostics) || bootstrapIncomplete;
|
|
704
707
|
if (hasBlocking) {
|
|
705
708
|
console.warn(`${label}: detected runtime anomalies after navigation.`);
|
|
@@ -716,7 +719,7 @@ async function surfaceBlockingDiagnosticsAfterNavigation(label, devtoolsUrl, tar
|
|
|
716
719
|
}
|
|
717
720
|
}
|
|
718
721
|
catch (error) {
|
|
719
|
-
console.warn(
|
|
722
|
+
console.warn("Failed to collect DevTools diagnostics after navigation:", error);
|
|
720
723
|
}
|
|
721
724
|
if (!loggedBlocking) {
|
|
722
725
|
const nextErrors = await fetchNextDevtoolsErrors(targetUrl);
|
|
@@ -742,7 +745,7 @@ async function logPuppeteerPageSnapshot(label, devtoolsUrl, targetUrl) {
|
|
|
742
745
|
}
|
|
743
746
|
if (snapshot.overlayText) {
|
|
744
747
|
console.warn(`${label} overlay (via Puppeteer):`);
|
|
745
|
-
for (const line of snapshot.overlayText.split(
|
|
748
|
+
for (const line of snapshot.overlayText.split("\n")) {
|
|
746
749
|
const trimmed = line.trim();
|
|
747
750
|
if (trimmed.length > 0) {
|
|
748
751
|
console.warn(` ${trimmed}`);
|
|
@@ -751,7 +754,7 @@ async function logPuppeteerPageSnapshot(label, devtoolsUrl, targetUrl) {
|
|
|
751
754
|
return;
|
|
752
755
|
}
|
|
753
756
|
if (snapshot.bodyText && snapshot.bodyText.trim().length > 0) {
|
|
754
|
-
const condensed = snapshot.bodyText.replaceAll(/\s+/g,
|
|
757
|
+
const condensed = snapshot.bodyText.replaceAll(/\s+/g, " ").trim();
|
|
755
758
|
const snippet = condensed.length > 600 ? `${condensed.slice(0, 600)}…` : condensed;
|
|
756
759
|
console.warn(`${label} body text (via Puppeteer): ${snippet}`);
|
|
757
760
|
}
|
|
@@ -767,50 +770,52 @@ async function handleUncontrolledOpen(context, waitToken) {
|
|
|
767
770
|
await launchChrome(context.targetUrlString, { foreground: context.foreground });
|
|
768
771
|
console.log(`Opened Chrome to ${context.targetUrlString} (env: ${context.env}).`);
|
|
769
772
|
await waitForSessionAfterOpen(context, waitToken, {
|
|
770
|
-
failureMessage:
|
|
773
|
+
failureMessage: "Chrome tab opened but SweetLink did not register within the timeout window. Use `pnpm sweetlink sessions` to inspect manually.",
|
|
771
774
|
});
|
|
772
775
|
}
|
|
773
776
|
program
|
|
774
|
-
.command(
|
|
775
|
-
.description(
|
|
776
|
-
.option(
|
|
777
|
-
.option(
|
|
778
|
-
.option(
|
|
779
|
-
.option(
|
|
777
|
+
.command("smoke")
|
|
778
|
+
.description("Run the SweetLink authenticated smoke test across non-admin routes")
|
|
779
|
+
.option("--session <sessionId>", "Existing SweetLink session id or codename to reuse")
|
|
780
|
+
.option("--routes <routes>", `Comma-separated list of routes (default ${DEFAULT_SMOKE_ROUTES.join(", ")})`)
|
|
781
|
+
.option("--timeout <seconds>", "Per-route timeout in seconds (default 45)", Number)
|
|
782
|
+
.option("--resume", "Resume from the last completed route", false)
|
|
780
783
|
.action(async function () {
|
|
781
784
|
const options = readCommandOptions(this);
|
|
782
785
|
const routes = deriveSmokeRoutes(options.routes, DEFAULT_SMOKE_ROUTES);
|
|
783
786
|
if (routes.length === 0) {
|
|
784
|
-
console.log(
|
|
787
|
+
console.log("No routes specified for the smoke test. Provide --routes or keep the default set.");
|
|
785
788
|
return;
|
|
786
789
|
}
|
|
787
|
-
const timeoutSeconds = typeof options.timeout ===
|
|
790
|
+
const timeoutSeconds = typeof options.timeout === "number" && Number.isFinite(options.timeout)
|
|
791
|
+
? Math.max(5, options.timeout)
|
|
792
|
+
: 45;
|
|
788
793
|
await runSweetLinkSmoke({ sessionHint: options.session, routes, timeoutSeconds, resume: options.resume === true }, this);
|
|
789
794
|
});
|
|
790
795
|
program
|
|
791
|
-
.command(
|
|
792
|
-
.description(
|
|
793
|
-
.option(
|
|
794
|
-
.option(
|
|
795
|
-
.option(
|
|
796
|
-
.option(
|
|
797
|
-
.option(
|
|
798
|
-
.option(
|
|
799
|
-
.option(
|
|
800
|
-
.option(
|
|
801
|
-
.option(
|
|
802
|
-
.option(
|
|
803
|
-
.option(
|
|
804
|
-
.option(
|
|
805
|
-
.option(
|
|
806
|
-
.option(
|
|
807
|
-
.option(
|
|
808
|
-
.addOption(program.createOption(
|
|
796
|
+
.command("screenshot <sessionId>")
|
|
797
|
+
.description("Capture a JPEG screenshot from a SweetLink session")
|
|
798
|
+
.option("-s, --selector <selector>", "CSS selector to capture (defaults to full page)")
|
|
799
|
+
.option("-q, --quality <0-1>", "JPEG quality (0-1, default 0.92)", Number)
|
|
800
|
+
.option("-o, --output <path>", "Output path (defaults to /tmp/sweetlink-<timestamp>.jpg)")
|
|
801
|
+
.option("-t, --timeout <ms>", "Command timeout in milliseconds (default 30_000)", Number, 30_000)
|
|
802
|
+
.option("--devtools-url <url>", "DevTools HTTP endpoint (default http://127.0.0.1:9222)")
|
|
803
|
+
.option("--method <method>", "Capture method: auto, puppeteer, html2canvas, html-to-image", "auto")
|
|
804
|
+
.option("--scroll-into-view", "Scroll the target into view before capturing", false)
|
|
805
|
+
.option("--scroll-selector <selector>", "Selector to scroll into view (defaults to capture selector)")
|
|
806
|
+
.option("--wait-for-selector <selector>", "Wait for a selector to appear before capturing")
|
|
807
|
+
.option("--wait-visible", "Require the wait selector to be visible", false)
|
|
808
|
+
.option("--wait-timeout <ms>", "Timeout for --wait-for-selector (default 10_000)", Number, 10_000)
|
|
809
|
+
.option("--delay <ms>", "Delay in milliseconds after hooks run (default 0)", Number)
|
|
810
|
+
.option("--before-script <codeOrPath>", "Inline JS (or @path) to run before capture")
|
|
811
|
+
.option("--preset <name>", "Hook preset compatibility alias (card-ready is applied automatically and no longer requires this flag)")
|
|
812
|
+
.option("--prompt <prompt>", "Send the saved screenshot to Codex for analysis")
|
|
813
|
+
.addOption(program.createOption("--question <prompt>").hideHelp())
|
|
809
814
|
.action(async (sessionId, options, command) => {
|
|
810
815
|
const config = resolveConfig(command);
|
|
811
816
|
const resolvedSessionId = await resolveSessionIdFromHint(sessionId, config);
|
|
812
817
|
const token = await fetchCliToken(config);
|
|
813
|
-
const mode = options.selector ?
|
|
818
|
+
const mode = options.selector ? "element" : "full";
|
|
814
819
|
const prompt = resolvePromptOption(options);
|
|
815
820
|
const suppressOutput = Boolean(prompt);
|
|
816
821
|
const logInfo = (...args) => {
|
|
@@ -819,15 +824,17 @@ program
|
|
|
819
824
|
}
|
|
820
825
|
};
|
|
821
826
|
const now = new Date();
|
|
822
|
-
const defaultOutput = path.join(os.tmpdir(), `sweetlink-${now.toISOString().replaceAll(
|
|
827
|
+
const defaultOutput = path.join(os.tmpdir(), `sweetlink-${now.toISOString().replaceAll(":", "-").replaceAll(".", "-")}.jpg`);
|
|
823
828
|
const outputPath = options.output ? path.resolve(options.output) : defaultOutput;
|
|
824
|
-
const quality = typeof options.quality ===
|
|
829
|
+
const quality = typeof options.quality === "number" && !Number.isNaN(options.quality)
|
|
830
|
+
? options.quality
|
|
831
|
+
: 0.92;
|
|
825
832
|
const method = normalizeScreenshotMethod(options.method);
|
|
826
833
|
await mkdir(path.dirname(outputPath), { recursive: true }).catch(() => {
|
|
827
834
|
/* ignore directory creation failures; writeFile will surface if it truly fails */
|
|
828
835
|
});
|
|
829
836
|
const devtoolsConfig = await loadDevToolsConfig();
|
|
830
|
-
const devtoolsUrl = options.devtoolsUrl ?? devtoolsConfig?.devtoolsUrl ??
|
|
837
|
+
const devtoolsUrl = options.devtoolsUrl ?? devtoolsConfig?.devtoolsUrl ?? "http://127.0.0.1:9222";
|
|
831
838
|
let sessionSummary;
|
|
832
839
|
try {
|
|
833
840
|
sessionSummary = await getSessionSummaryById(config, token, resolvedSessionId);
|
|
@@ -835,7 +842,7 @@ program
|
|
|
835
842
|
catch {
|
|
836
843
|
sessionSummary = undefined;
|
|
837
844
|
}
|
|
838
|
-
const wantsPuppeteer = method ===
|
|
845
|
+
const wantsPuppeteer = method === "puppeteer" || method === "auto";
|
|
839
846
|
if (wantsPuppeteer && sessionSummary?.url) {
|
|
840
847
|
const devtoolsCaptureResult = await attemptDevToolsCapture({
|
|
841
848
|
devtoolsUrl,
|
|
@@ -847,21 +854,24 @@ program
|
|
|
847
854
|
});
|
|
848
855
|
if (devtoolsCaptureResult) {
|
|
849
856
|
logInfo(`Saved screenshot to ${outputPath} (${devtoolsCaptureResult.width}x${devtoolsCaptureResult.height}, ${devtoolsCaptureResult.sizeKb.toFixed(1)} KB, method: ${devtoolsCaptureResult.renderer}).`);
|
|
850
|
-
await maybeDescribeScreenshot(prompt, outputPath, {
|
|
857
|
+
await maybeDescribeScreenshot(prompt, outputPath, {
|
|
858
|
+
silent: suppressOutput,
|
|
859
|
+
appLabel: config.appLabel,
|
|
860
|
+
});
|
|
851
861
|
return;
|
|
852
862
|
}
|
|
853
|
-
if (method ===
|
|
854
|
-
throw new Error(
|
|
863
|
+
if (method === "puppeteer") {
|
|
864
|
+
throw new Error("Failed to capture via Puppeteer. Ensure Chrome is running with a DevTools port (try `pnpm sweetlink open --controlled`).");
|
|
855
865
|
}
|
|
856
866
|
}
|
|
857
|
-
const rendererOverride = method ===
|
|
867
|
+
const rendererOverride = method === "html2canvas" || method === "html-to-image" ? method : undefined;
|
|
858
868
|
const beforeScriptCode = await resolveHookSnippet(options.beforeScript);
|
|
859
869
|
const commandOptions = readCommandOptions(command);
|
|
860
870
|
const presetCandidate = commandOptions.preset ?? options.preset;
|
|
861
|
-
const presetRaw = typeof presetCandidate ===
|
|
871
|
+
const presetRaw = typeof presetCandidate === "string" ? presetCandidate.trim() : "";
|
|
862
872
|
const presetName = presetRaw.toLowerCase();
|
|
863
873
|
if (presetName.length > 0) {
|
|
864
|
-
if (presetName ===
|
|
874
|
+
if (presetName === "card-ready") {
|
|
865
875
|
logInfo('Preset "card-ready" is now the default hook stack; the flag is optional.');
|
|
866
876
|
}
|
|
867
877
|
else {
|
|
@@ -874,31 +884,35 @@ program
|
|
|
874
884
|
scrollSelector: options.scrollSelector,
|
|
875
885
|
waitSelector: options.waitForSelector,
|
|
876
886
|
waitVisible: options.waitVisible === undefined ? undefined : Boolean(options.waitVisible),
|
|
877
|
-
waitTimeout: typeof options.waitTimeout ===
|
|
887
|
+
waitTimeout: typeof options.waitTimeout === "number" && Number.isFinite(options.waitTimeout)
|
|
878
888
|
? options.waitTimeout
|
|
879
889
|
: undefined,
|
|
880
|
-
delayMs: typeof options.delay ===
|
|
890
|
+
delayMs: typeof options.delay === "number" && Number.isFinite(options.delay)
|
|
891
|
+
? options.delay
|
|
892
|
+
: undefined,
|
|
881
893
|
beforeScript: beforeScriptCode ?? undefined,
|
|
882
894
|
};
|
|
883
895
|
const hooks = buildScreenshotHooks(hookOptions);
|
|
884
896
|
if (hooks.length > 0) {
|
|
885
|
-
logInfo(`Applying ${hooks.length} pre-capture hook${hooks.length === 1 ?
|
|
897
|
+
logInfo(`Applying ${hooks.length} pre-capture hook${hooks.length === 1 ? "" : "s"} before screenshot.`);
|
|
886
898
|
}
|
|
887
899
|
const payload = {
|
|
888
|
-
type:
|
|
900
|
+
type: "screenshot",
|
|
889
901
|
id: createSweetLinkCommandId(),
|
|
890
902
|
mode,
|
|
891
903
|
selector: options.selector,
|
|
892
904
|
quality,
|
|
893
|
-
timeoutMs: typeof options.timeout ===
|
|
905
|
+
timeoutMs: typeof options.timeout === "number" && !Number.isNaN(options.timeout)
|
|
906
|
+
? options.timeout
|
|
907
|
+
: 30_000,
|
|
894
908
|
renderer: rendererOverride,
|
|
895
909
|
hooks: hooks.length > 0 ? hooks : undefined,
|
|
896
910
|
};
|
|
897
911
|
const response = await fetchJson(`${config.daemonBaseUrl}/sessions/${encodeURIComponent(resolvedSessionId)}/command`, {
|
|
898
|
-
method:
|
|
912
|
+
method: "POST",
|
|
899
913
|
headers: {
|
|
900
914
|
Authorization: `Bearer ${token}`,
|
|
901
|
-
|
|
915
|
+
"Content-Type": "application/json",
|
|
902
916
|
},
|
|
903
917
|
body: JSON.stringify(payload),
|
|
904
918
|
});
|
|
@@ -919,7 +933,9 @@ program
|
|
|
919
933
|
if (fallbackOutcome.handled) {
|
|
920
934
|
return;
|
|
921
935
|
}
|
|
922
|
-
const fallbackError = fallbackOutcome.fallbackResult.ok
|
|
936
|
+
const fallbackError = fallbackOutcome.fallbackResult.ok
|
|
937
|
+
? null
|
|
938
|
+
: fallbackOutcome.fallbackResult.error;
|
|
923
939
|
const recovered = await tryDevToolsRecovery({
|
|
924
940
|
sessionUrl: sessionSummary?.url,
|
|
925
941
|
devtoolsUrl,
|
|
@@ -961,41 +977,44 @@ program
|
|
|
961
977
|
return;
|
|
962
978
|
}
|
|
963
979
|
await persistScreenshotResult(outputPath, result, { silent: suppressOutput });
|
|
964
|
-
await maybeDescribeScreenshot(prompt, outputPath, {
|
|
980
|
+
await maybeDescribeScreenshot(prompt, outputPath, {
|
|
981
|
+
silent: suppressOutput,
|
|
982
|
+
appLabel: config.appLabel,
|
|
983
|
+
});
|
|
965
984
|
});
|
|
966
985
|
program
|
|
967
|
-
.command(
|
|
968
|
-
.description(
|
|
969
|
-
.option(
|
|
970
|
-
.option(
|
|
971
|
-
.option(
|
|
972
|
-
.option(
|
|
973
|
-
.option(
|
|
986
|
+
.command("selectors <sessionId>")
|
|
987
|
+
.description("Discover candidate selectors within a SweetLink session")
|
|
988
|
+
.option("-l, --limit <count>", "Maximum number of candidates to return (default 20)", Number)
|
|
989
|
+
.option("-m, --max <count>", "Alias for --limit", Number)
|
|
990
|
+
.option("--scope <selector>", "Restrict discovery to elements inside this selector")
|
|
991
|
+
.option("--include-hidden", "Include off-screen or hidden elements", false)
|
|
992
|
+
.option("--json", "Output JSON payload", false)
|
|
974
993
|
.action(async (sessionId, options, command) => {
|
|
975
994
|
const config = resolveConfig(command);
|
|
976
995
|
const resolvedSessionId = await resolveSessionIdFromHint(sessionId, config);
|
|
977
996
|
const token = await fetchCliToken(config);
|
|
978
997
|
const limit = (() => {
|
|
979
|
-
if (typeof options.max ===
|
|
998
|
+
if (typeof options.max === "number" && Number.isFinite(options.max)) {
|
|
980
999
|
return Math.max(1, Math.floor(options.max));
|
|
981
1000
|
}
|
|
982
|
-
if (typeof options.limit ===
|
|
1001
|
+
if (typeof options.limit === "number" && Number.isFinite(options.limit)) {
|
|
983
1002
|
return Math.max(1, Math.floor(options.limit));
|
|
984
1003
|
}
|
|
985
1004
|
return 20;
|
|
986
1005
|
})();
|
|
987
1006
|
const payload = {
|
|
988
|
-
type:
|
|
1007
|
+
type: "discoverSelectors",
|
|
989
1008
|
id: createSweetLinkCommandId(),
|
|
990
1009
|
scopeSelector: options.scope ?? null,
|
|
991
1010
|
limit,
|
|
992
1011
|
includeHidden: Boolean(options.includeHidden),
|
|
993
1012
|
};
|
|
994
1013
|
const response = await fetchJson(`${config.daemonBaseUrl}/sessions/${encodeURIComponent(resolvedSessionId)}/command`, {
|
|
995
|
-
method:
|
|
1014
|
+
method: "POST",
|
|
996
1015
|
headers: {
|
|
997
1016
|
Authorization: `Bearer ${token}`,
|
|
998
|
-
|
|
1017
|
+
"Content-Type": "application/json",
|
|
999
1018
|
},
|
|
1000
1019
|
body: JSON.stringify(payload),
|
|
1001
1020
|
});
|
|
@@ -1018,15 +1037,15 @@ program
|
|
|
1018
1037
|
return;
|
|
1019
1038
|
}
|
|
1020
1039
|
if (candidates.length === 0) {
|
|
1021
|
-
console.log(
|
|
1022
|
-
console.log(
|
|
1040
|
+
console.log("No candidates were discovered. Ensure the target UI is mounted and try again.");
|
|
1041
|
+
console.log("Tip: use --scope to limit discovery or --include-hidden to inspect collapsed panels.");
|
|
1023
1042
|
return;
|
|
1024
1043
|
}
|
|
1025
|
-
console.log(`Discovered ${candidates.length} selector candidate${candidates.length === 1 ?
|
|
1044
|
+
console.log(`Discovered ${candidates.length} selector candidate${candidates.length === 1 ? "" : "s"} (limit ${limit}):\n`);
|
|
1026
1045
|
for (const candidate of candidates.slice(0, limit)) {
|
|
1027
1046
|
console.log(`• ${candidate.selector}`);
|
|
1028
1047
|
console.log(` Hook : ${candidate.hook} (score ${candidate.score})`);
|
|
1029
|
-
console.log(` Visible : ${candidate.visible ?
|
|
1048
|
+
console.log(` Visible : ${candidate.visible ? "yes" : "no"} (${candidate.size.width}x${candidate.size.height})`);
|
|
1030
1049
|
console.log(` Text : ${candidate.textSnippet}`);
|
|
1031
1050
|
if (candidate.dataTarget) {
|
|
1032
1051
|
console.log(` Target : data-sweetlink-target="${candidate.dataTarget}"`);
|
|
@@ -1039,64 +1058,66 @@ program
|
|
|
1039
1058
|
}
|
|
1040
1059
|
console.log(` Position : x=${candidate.position.left}, y=${candidate.position.top}`);
|
|
1041
1060
|
console.log(` Path : ${candidate.path}`);
|
|
1042
|
-
console.log(
|
|
1061
|
+
console.log("");
|
|
1043
1062
|
}
|
|
1044
1063
|
});
|
|
1045
|
-
const devtools = program
|
|
1064
|
+
const devtools = program
|
|
1065
|
+
.command("devtools")
|
|
1066
|
+
.description("Inspect DevTools-enabled Chrome sessions");
|
|
1046
1067
|
devtools
|
|
1047
|
-
.command(
|
|
1048
|
-
.description(
|
|
1049
|
-
.option(
|
|
1068
|
+
.command("status")
|
|
1069
|
+
.description("Show DevTools connection status and telemetry summary")
|
|
1070
|
+
.option("--json", "Output JSON payload", false)
|
|
1050
1071
|
.action(async (options, command) => {
|
|
1051
1072
|
await devtoolsStatus(options, command);
|
|
1052
1073
|
});
|
|
1053
1074
|
devtools
|
|
1054
|
-
.command(
|
|
1055
|
-
.description(
|
|
1056
|
-
.option(
|
|
1075
|
+
.command("tabs")
|
|
1076
|
+
.description("List open tabs in the controlled Chrome window")
|
|
1077
|
+
.option("--json", "Output JSON payload", false)
|
|
1057
1078
|
.action(async (options) => {
|
|
1058
1079
|
await devtoolsTabs(options);
|
|
1059
1080
|
});
|
|
1060
1081
|
devtools
|
|
1061
|
-
.command(
|
|
1062
|
-
.description(
|
|
1063
|
-
.option(
|
|
1064
|
-
.option(
|
|
1082
|
+
.command("console")
|
|
1083
|
+
.description("Print buffered console events from the controlled Chrome window")
|
|
1084
|
+
.option("--tail <count>", "Number of entries to show (default 50)", Number, 50)
|
|
1085
|
+
.option("--json", "Output JSON payload", false)
|
|
1065
1086
|
.action(async (options) => {
|
|
1066
1087
|
await devtoolsShowConsole(options);
|
|
1067
1088
|
});
|
|
1068
1089
|
devtools
|
|
1069
|
-
.command(
|
|
1070
|
-
.description(
|
|
1071
|
-
.option(
|
|
1072
|
-
.option(
|
|
1090
|
+
.command("network")
|
|
1091
|
+
.description("Print buffered network requests from the controlled Chrome window")
|
|
1092
|
+
.option("--tail <count>", "Number of entries to show (default 50)", Number, 50)
|
|
1093
|
+
.option("--json", "Output JSON payload", false)
|
|
1073
1094
|
.action(async (options) => {
|
|
1074
1095
|
await devtoolsShowNetwork(options);
|
|
1075
1096
|
});
|
|
1076
1097
|
devtools
|
|
1077
|
-
.command(
|
|
1078
|
-
.description(
|
|
1079
|
-
.option(
|
|
1080
|
-
.option(
|
|
1081
|
-
.option(
|
|
1098
|
+
.command("listen")
|
|
1099
|
+
.description("Attach to DevTools and buffer console/network telemetry locally")
|
|
1100
|
+
.option("--session <sessionId>", "Associate telemetry with a SweetLink session id")
|
|
1101
|
+
.option("--reset", "Clear existing telemetry buffers before listening", false)
|
|
1102
|
+
.option("--background", "Run without interactive prompts (for automation)", false)
|
|
1082
1103
|
.action(async (options, command) => {
|
|
1083
1104
|
await devtoolsListen(options, command);
|
|
1084
1105
|
});
|
|
1085
1106
|
devtools
|
|
1086
|
-
.command(
|
|
1087
|
-
.description(
|
|
1088
|
-
.option(
|
|
1107
|
+
.command("authorize")
|
|
1108
|
+
.description("Attempt to auto-click the OAuth authorize prompt in the controlled browser")
|
|
1109
|
+
.option("--url <url>", "Override the candidate OAuth URL (defaults to the tracked session)")
|
|
1089
1110
|
.action(async (options, command) => {
|
|
1090
1111
|
await devtoolsAuthorize(options, command);
|
|
1091
1112
|
});
|
|
1092
|
-
program.hook(
|
|
1113
|
+
program.hook("preAction", () => {
|
|
1093
1114
|
// Ensure commander does not swallow promise rejections so we can log helpful messages.
|
|
1094
|
-
process.on(
|
|
1115
|
+
process.on("unhandledRejection", (error) => {
|
|
1095
1116
|
reportError(error);
|
|
1096
1117
|
process.exitCode = 1;
|
|
1097
1118
|
});
|
|
1098
1119
|
});
|
|
1099
|
-
program.addHelpText(
|
|
1120
|
+
program.addHelpText("afterAll", `
|
|
1100
1121
|
DevTools commands:
|
|
1101
1122
|
devtools status Show endpoint reachability, viewport, and buffer sizes
|
|
1102
1123
|
devtools tabs List open tabs in the controlled Chrome window
|
|
@@ -1128,19 +1149,19 @@ async function resolveHookSnippet(value) {
|
|
|
1128
1149
|
if (trimmed.length === 0) {
|
|
1129
1150
|
return null;
|
|
1130
1151
|
}
|
|
1131
|
-
if (trimmed.startsWith(
|
|
1152
|
+
if (trimmed.startsWith("@")) {
|
|
1132
1153
|
const candidatePath = trimmed.slice(1).trim();
|
|
1133
1154
|
if (!candidatePath) {
|
|
1134
|
-
throw new Error(
|
|
1155
|
+
throw new Error("Expected a file path after @ for --before-script.");
|
|
1135
1156
|
}
|
|
1136
1157
|
const absolute = path.isAbsolute(candidatePath) ? candidatePath : path.resolve(candidatePath);
|
|
1137
|
-
const hookContents = await readFile(absolute,
|
|
1158
|
+
const hookContents = await readFile(absolute, "utf8");
|
|
1138
1159
|
return hookContents.toString();
|
|
1139
1160
|
}
|
|
1140
1161
|
return trimmed;
|
|
1141
1162
|
}
|
|
1142
1163
|
export function formatPathForDisplay(value) {
|
|
1143
|
-
return value.replace(os.homedir(),
|
|
1164
|
+
return value.replace(os.homedir(), "~");
|
|
1144
1165
|
}
|
|
1145
1166
|
function reportError(error) {
|
|
1146
1167
|
if (error instanceof CommanderError) {
|
|
@@ -1151,32 +1172,32 @@ function reportError(error) {
|
|
|
1151
1172
|
console.error(error.message);
|
|
1152
1173
|
return;
|
|
1153
1174
|
}
|
|
1154
|
-
console.error(
|
|
1175
|
+
console.error("Unexpected error", error);
|
|
1155
1176
|
}
|
|
1156
1177
|
function normalizeScreenshotMethod(input) {
|
|
1157
1178
|
if (!input) {
|
|
1158
|
-
return
|
|
1179
|
+
return "auto";
|
|
1159
1180
|
}
|
|
1160
1181
|
const normalized = input.toLowerCase().trim();
|
|
1161
1182
|
switch (normalized) {
|
|
1162
|
-
case
|
|
1163
|
-
return
|
|
1183
|
+
case "auto": {
|
|
1184
|
+
return "auto";
|
|
1164
1185
|
}
|
|
1165
|
-
case
|
|
1166
|
-
return
|
|
1186
|
+
case "puppeteer": {
|
|
1187
|
+
return "puppeteer";
|
|
1167
1188
|
}
|
|
1168
|
-
case
|
|
1169
|
-
case
|
|
1170
|
-
return
|
|
1189
|
+
case "html2canvas":
|
|
1190
|
+
case "html-2-canvas": {
|
|
1191
|
+
return "html2canvas";
|
|
1171
1192
|
}
|
|
1172
|
-
case
|
|
1173
|
-
case
|
|
1174
|
-
case
|
|
1175
|
-
case
|
|
1176
|
-
return
|
|
1193
|
+
case "html-to-image":
|
|
1194
|
+
case "htmltoimage":
|
|
1195
|
+
case "dom-to-image":
|
|
1196
|
+
case "domtoimage": {
|
|
1197
|
+
return "html-to-image";
|
|
1177
1198
|
}
|
|
1178
1199
|
default: {
|
|
1179
|
-
throw new Error(
|
|
1200
|
+
throw new Error("Invalid screenshot method. Use auto, puppeteer, html2canvas, or html-to-image.");
|
|
1180
1201
|
}
|
|
1181
1202
|
}
|
|
1182
1203
|
}
|
|
@@ -1184,7 +1205,7 @@ async function resolveSmokePrerequisites(params, command) {
|
|
|
1184
1205
|
const config = resolveConfig(command);
|
|
1185
1206
|
const devtoolsConfig = await loadDevToolsConfig();
|
|
1186
1207
|
if (!devtoolsConfig?.devtoolsUrl) {
|
|
1187
|
-
console.error(
|
|
1208
|
+
console.error("DevTools endpoint not found. Launch a controlled Chrome window with `pnpm sweetlink open --controlled` first.");
|
|
1188
1209
|
process.exitCode = 1;
|
|
1189
1210
|
return null;
|
|
1190
1211
|
}
|
|
@@ -1192,7 +1213,7 @@ async function resolveSmokePrerequisites(params, command) {
|
|
|
1192
1213
|
? await resolveSessionIdFromHint(params.sessionHint, config)
|
|
1193
1214
|
: (devtoolsConfig.sessionId ?? null);
|
|
1194
1215
|
if (!sessionId) {
|
|
1195
|
-
console.error(
|
|
1216
|
+
console.error("Unable to determine a SweetLink session. Pass --session or rerun `pnpm sweetlink open --controlled`.");
|
|
1196
1217
|
process.exitCode = 1;
|
|
1197
1218
|
return null;
|
|
1198
1219
|
}
|
|
@@ -1201,7 +1222,7 @@ async function resolveSmokePrerequisites(params, command) {
|
|
|
1201
1222
|
token = await fetchCliToken(config);
|
|
1202
1223
|
}
|
|
1203
1224
|
catch (error) {
|
|
1204
|
-
console.error(
|
|
1225
|
+
console.error("Unable to fetch SweetLink CLI token:", extractEventMessage(error));
|
|
1205
1226
|
process.exitCode = 1;
|
|
1206
1227
|
return null;
|
|
1207
1228
|
}
|
|
@@ -1228,18 +1249,18 @@ async function determineSmokeStartIndex(options) {
|
|
|
1228
1249
|
}
|
|
1229
1250
|
catch (error) {
|
|
1230
1251
|
if (sweetLinkDebug) {
|
|
1231
|
-
console.warn(
|
|
1252
|
+
console.warn("Failed to clear stored smoke progress before run:", error);
|
|
1232
1253
|
}
|
|
1233
1254
|
}
|
|
1234
1255
|
return 0;
|
|
1235
1256
|
}
|
|
1236
1257
|
const savedIndex = await loadSmokeProgressIndex(options.baseOrigin, options.routes);
|
|
1237
1258
|
if (savedIndex === null) {
|
|
1238
|
-
console.log(
|
|
1259
|
+
console.log("No prior smoke progress found. Starting from the beginning.");
|
|
1239
1260
|
return 0;
|
|
1240
1261
|
}
|
|
1241
1262
|
if (savedIndex >= options.routes.length) {
|
|
1242
|
-
console.log(
|
|
1263
|
+
console.log("Previous smoke run completed every route. Starting from the beginning.");
|
|
1243
1264
|
return 0;
|
|
1244
1265
|
}
|
|
1245
1266
|
if (savedIndex > 0) {
|
|
@@ -1265,12 +1286,12 @@ async function executeSmokeRoute(context, state, route, routeIndex) {
|
|
|
1265
1286
|
lastKnownUrl,
|
|
1266
1287
|
failure: {
|
|
1267
1288
|
route: `#${routeIndex + 1}`,
|
|
1268
|
-
reason:
|
|
1289
|
+
reason: "Missing route entry in smoke route list.",
|
|
1269
1290
|
},
|
|
1270
1291
|
};
|
|
1271
1292
|
}
|
|
1272
1293
|
const targetUrl = buildSmokeRouteUrl(context.baseUrl, route);
|
|
1273
|
-
const displayPath = `${targetUrl.pathname}${targetUrl.search ||
|
|
1294
|
+
const displayPath = `${targetUrl.pathname}${targetUrl.search || ""}`;
|
|
1274
1295
|
console.log(`\n→ ${displayPath}`);
|
|
1275
1296
|
const sessionConnected = await ensureSweetLinkSessionConnected({
|
|
1276
1297
|
config: context.config,
|
|
@@ -1283,23 +1304,27 @@ async function executeSmokeRoute(context, state, route, routeIndex) {
|
|
|
1283
1304
|
candidateUrls: [targetUrl.toString()],
|
|
1284
1305
|
});
|
|
1285
1306
|
if (!sessionConnected) {
|
|
1286
|
-
console.warn(
|
|
1307
|
+
console.warn(" Unable to verify active SweetLink session before navigation.");
|
|
1287
1308
|
return {
|
|
1288
1309
|
session,
|
|
1289
1310
|
lastKnownUrl,
|
|
1290
|
-
failure: { route: displayPath, reason:
|
|
1311
|
+
failure: { route: displayPath, reason: "session unavailable before navigation" },
|
|
1291
1312
|
};
|
|
1292
1313
|
}
|
|
1293
1314
|
const attemptNavigation = async () => {
|
|
1294
|
-
await navigateSweetLinkSession({
|
|
1315
|
+
await navigateSweetLinkSession({
|
|
1316
|
+
sessionId: activeSessionId,
|
|
1317
|
+
targetUrl,
|
|
1318
|
+
config: context.config,
|
|
1319
|
+
});
|
|
1295
1320
|
};
|
|
1296
1321
|
try {
|
|
1297
1322
|
await attemptNavigation();
|
|
1298
1323
|
}
|
|
1299
1324
|
catch (error) {
|
|
1300
|
-
const reason = extractEventMessage(error,
|
|
1325
|
+
const reason = extractEventMessage(error, "navigation failed");
|
|
1301
1326
|
if (SESSION_RECOVERY_PATTERN.test(reason)) {
|
|
1302
|
-
console.warn(
|
|
1327
|
+
console.warn(" Session went offline during navigation command. Attempting recovery…");
|
|
1303
1328
|
const recovered = await ensureSweetLinkSessionConnected({
|
|
1304
1329
|
config: context.config,
|
|
1305
1330
|
token: context.token,
|
|
@@ -1315,7 +1340,7 @@ async function executeSmokeRoute(context, state, route, routeIndex) {
|
|
|
1315
1340
|
await attemptNavigation();
|
|
1316
1341
|
}
|
|
1317
1342
|
catch (retryError) {
|
|
1318
|
-
const retryReason = extractEventMessage(retryError,
|
|
1343
|
+
const retryReason = extractEventMessage(retryError, "navigation failed");
|
|
1319
1344
|
console.warn(` Navigation failed after recovery: ${retryReason}`);
|
|
1320
1345
|
return { session, lastKnownUrl, failure: { route: displayPath, reason: retryReason } };
|
|
1321
1346
|
}
|
|
@@ -1338,7 +1363,7 @@ async function executeSmokeRoute(context, state, route, routeIndex) {
|
|
|
1338
1363
|
devtoolsUrl: context.devtoolsUrl,
|
|
1339
1364
|
});
|
|
1340
1365
|
if (!handshake && context.devtoolsUrl) {
|
|
1341
|
-
console.warn(
|
|
1366
|
+
console.warn(" SweetLink session did not reconnect after navigation. Retrying bootstrap…");
|
|
1342
1367
|
await triggerSweetLinkCliAuto(context.devtoolsUrl, targetUrl.toString());
|
|
1343
1368
|
handshake = await waitForSweetLinkSession({
|
|
1344
1369
|
config: context.config,
|
|
@@ -1360,7 +1385,9 @@ async function executeSmokeRoute(context, state, route, routeIndex) {
|
|
|
1360
1385
|
});
|
|
1361
1386
|
if (recovered) {
|
|
1362
1387
|
const refreshed = await getSessionSummaryById(context.config, context.token, activeSessionId);
|
|
1363
|
-
const recoveredUrl = typeof refreshed?.url ===
|
|
1388
|
+
const recoveredUrl = typeof refreshed?.url === "string" && refreshed.url.length > 0
|
|
1389
|
+
? refreshed.url
|
|
1390
|
+
: targetUrl.toString();
|
|
1364
1391
|
handshake = {
|
|
1365
1392
|
sessionId: activeSessionId,
|
|
1366
1393
|
url: recoveredUrl,
|
|
@@ -1369,43 +1396,48 @@ async function executeSmokeRoute(context, state, route, routeIndex) {
|
|
|
1369
1396
|
}
|
|
1370
1397
|
}
|
|
1371
1398
|
if (!handshake) {
|
|
1372
|
-
console.warn(
|
|
1399
|
+
console.warn(" SweetLink session did not come back online after navigation.");
|
|
1373
1400
|
return {
|
|
1374
1401
|
session,
|
|
1375
1402
|
lastKnownUrl,
|
|
1376
|
-
failure: { route: displayPath, reason:
|
|
1403
|
+
failure: { route: displayPath, reason: "session did not reconnect" },
|
|
1377
1404
|
};
|
|
1378
1405
|
}
|
|
1379
1406
|
if (handshake.sessionId && handshake.sessionId !== activeSessionId) {
|
|
1380
1407
|
setActiveSessionId(handshake.sessionId);
|
|
1381
1408
|
}
|
|
1382
1409
|
lastKnownUrl = handshake.url ?? targetUrl.toString();
|
|
1383
|
-
session =
|
|
1410
|
+
session =
|
|
1411
|
+
(await getSessionSummaryById(context.config, context.token, activeSessionId)) ?? session;
|
|
1384
1412
|
const diagnostics = await waitForSmokeRouteReady({
|
|
1385
1413
|
devtoolsUrl: context.devtoolsUrl,
|
|
1386
1414
|
targetUrl,
|
|
1387
1415
|
timeoutMs: context.timeoutSeconds * 1000,
|
|
1388
1416
|
});
|
|
1389
1417
|
if (!diagnostics) {
|
|
1390
|
-
console.warn(
|
|
1391
|
-
return {
|
|
1418
|
+
console.warn(" Timed out waiting for the route to reach a stable state.");
|
|
1419
|
+
return {
|
|
1420
|
+
session,
|
|
1421
|
+
lastKnownUrl,
|
|
1422
|
+
failure: { route: displayPath, reason: "timeout awaiting route readiness" },
|
|
1423
|
+
};
|
|
1392
1424
|
}
|
|
1393
|
-
const finalHref = diagnostics.locationHref ??
|
|
1425
|
+
const finalHref = diagnostics.locationHref ?? "unknown";
|
|
1394
1426
|
if (!urlsRoughlyMatch(finalHref, targetUrl.toString())) {
|
|
1395
1427
|
console.warn(` Expected ${targetUrl.toString()} but browser reported ${finalHref}.`);
|
|
1396
1428
|
return {
|
|
1397
1429
|
session,
|
|
1398
1430
|
lastKnownUrl,
|
|
1399
|
-
failure: { route: displayPath, reason:
|
|
1431
|
+
failure: { route: displayPath, reason: "unexpected location after navigation" },
|
|
1400
1432
|
};
|
|
1401
1433
|
}
|
|
1402
1434
|
if (diagnosticsContainBlockingIssues(diagnostics)) {
|
|
1403
|
-
console.warn(
|
|
1404
|
-
logBootstrapDiagnostics(
|
|
1435
|
+
console.warn(" Blocking diagnostics detected while loading the route:");
|
|
1436
|
+
logBootstrapDiagnostics("Smoke", diagnostics);
|
|
1405
1437
|
return {
|
|
1406
1438
|
session,
|
|
1407
1439
|
lastKnownUrl,
|
|
1408
|
-
failure: { route: displayPath, reason:
|
|
1440
|
+
failure: { route: displayPath, reason: "runtime diagnostics reported blocking issues" },
|
|
1409
1441
|
};
|
|
1410
1442
|
}
|
|
1411
1443
|
const consoleEvents = await fetchConsoleEvents(context.config, activeSessionId).catch(() => []);
|
|
@@ -1415,26 +1447,26 @@ async function executeSmokeRoute(context, state, route, routeIndex) {
|
|
|
1415
1447
|
}
|
|
1416
1448
|
const authEvents = newEvents.filter((event) => consoleEventIndicatesAuthIssue(event));
|
|
1417
1449
|
if (authEvents.length > 0) {
|
|
1418
|
-
console.warn(
|
|
1450
|
+
console.warn(" Detected authentication failures in the console log:");
|
|
1419
1451
|
for (const event of authEvents.slice(-5)) {
|
|
1420
1452
|
console.warn(` ${formatConsoleEventSummary(event)}`);
|
|
1421
1453
|
}
|
|
1422
1454
|
return {
|
|
1423
1455
|
session,
|
|
1424
1456
|
lastKnownUrl,
|
|
1425
|
-
failure: { route: displayPath, reason:
|
|
1457
|
+
failure: { route: displayPath, reason: "authentication failures detected in console output" },
|
|
1426
1458
|
};
|
|
1427
1459
|
}
|
|
1428
1460
|
const runtimeErrorEvents = newEvents.filter((event) => consoleEventIndicatesRuntimeError(event));
|
|
1429
1461
|
if (runtimeErrorEvents.length > 0) {
|
|
1430
|
-
console.warn(
|
|
1462
|
+
console.warn(" Detected console errors after the route finished loading:");
|
|
1431
1463
|
for (const event of runtimeErrorEvents.slice(-5)) {
|
|
1432
1464
|
console.warn(` ${formatConsoleEventSummary(event)}`);
|
|
1433
1465
|
}
|
|
1434
1466
|
return {
|
|
1435
1467
|
session,
|
|
1436
1468
|
lastKnownUrl,
|
|
1437
|
-
failure: { route: displayPath, reason:
|
|
1469
|
+
failure: { route: displayPath, reason: "console errors detected after load" },
|
|
1438
1470
|
};
|
|
1439
1471
|
}
|
|
1440
1472
|
try {
|
|
@@ -1442,10 +1474,10 @@ async function executeSmokeRoute(context, state, route, routeIndex) {
|
|
|
1442
1474
|
}
|
|
1443
1475
|
catch (error) {
|
|
1444
1476
|
if (sweetLinkDebug) {
|
|
1445
|
-
console.warn(
|
|
1477
|
+
console.warn("Failed to persist smoke progress after route completion:", error);
|
|
1446
1478
|
}
|
|
1447
1479
|
}
|
|
1448
|
-
console.log(
|
|
1480
|
+
console.log(" ✅ Route passed without authentication or runtime errors.");
|
|
1449
1481
|
return {
|
|
1450
1482
|
session,
|
|
1451
1483
|
lastKnownUrl,
|
|
@@ -1468,9 +1500,9 @@ async function runSweetLinkSmoke(params, command) {
|
|
|
1468
1500
|
const initialConsoleEvents = await fetchConsoleEvents(config, sessionId).catch(() => []);
|
|
1469
1501
|
const seenConsoleIds = new Set(initialConsoleEvents.map((event) => event.id));
|
|
1470
1502
|
const remainingRouteCount = params.routes.length - startIndex;
|
|
1471
|
-
console.log(`Running SweetLink smoke test across ${remainingRouteCount} route${remainingRouteCount === 1 ?
|
|
1503
|
+
console.log(`Running SweetLink smoke test across ${remainingRouteCount} route${remainingRouteCount === 1 ? "" : "s"} using session ${formatSessionHeadline(initialSession)}.`);
|
|
1472
1504
|
if (startIndex > 0) {
|
|
1473
|
-
console.log(`Skipping ${startIndex} route${startIndex === 1 ?
|
|
1505
|
+
console.log(`Skipping ${startIndex} route${startIndex === 1 ? "" : "s"} that already passed in a previous run.`);
|
|
1474
1506
|
}
|
|
1475
1507
|
let lastKnownUrl = initialSession.url ?? baseUrl.toString();
|
|
1476
1508
|
const context = {
|
|
@@ -1496,11 +1528,11 @@ async function runSweetLinkSmoke(params, command) {
|
|
|
1496
1528
|
}
|
|
1497
1529
|
}
|
|
1498
1530
|
if (failures.length > 0) {
|
|
1499
|
-
console.error(`\nSweetLink smoke test detected issues on ${failures.length} route${failures.length === 1 ?
|
|
1531
|
+
console.error(`\nSweetLink smoke test detected issues on ${failures.length} route${failures.length === 1 ? "" : "s"}:`);
|
|
1500
1532
|
for (const failure of failures) {
|
|
1501
1533
|
console.error(` - ${failure.route}: ${failure.reason}`);
|
|
1502
1534
|
}
|
|
1503
|
-
console.error(
|
|
1535
|
+
console.error("Review the diagnostics above, fix the auth flow, and rerun `pnpm sweetlink smoke`.");
|
|
1504
1536
|
if (process.exitCode === undefined || process.exitCode === 0) {
|
|
1505
1537
|
process.exitCode = 1;
|
|
1506
1538
|
}
|
|
@@ -1511,18 +1543,18 @@ async function runSweetLinkSmoke(params, command) {
|
|
|
1511
1543
|
}
|
|
1512
1544
|
catch (error) {
|
|
1513
1545
|
if (sweetLinkDebug) {
|
|
1514
|
-
console.warn(
|
|
1546
|
+
console.warn("Failed to clear smoke progress after successful run:", error);
|
|
1515
1547
|
}
|
|
1516
1548
|
}
|
|
1517
|
-
console.log(
|
|
1549
|
+
console.log("\nSweetLink smoke test passed for all routes.");
|
|
1518
1550
|
}
|
|
1519
1551
|
}
|
|
1520
1552
|
function extractPathSegments(routePath) {
|
|
1521
1553
|
const normalized = trimTrailingSlash(routePath);
|
|
1522
|
-
if (normalized ===
|
|
1554
|
+
if (normalized === "/" || normalized.length === 0) {
|
|
1523
1555
|
return [];
|
|
1524
1556
|
}
|
|
1525
|
-
return normalized.replace(LEADING_SLASH_PATTERN,
|
|
1557
|
+
return normalized.replace(LEADING_SLASH_PATTERN, "").split("/");
|
|
1526
1558
|
}
|
|
1527
1559
|
function suffixSegmentsAllowed(segments) {
|
|
1528
1560
|
if (segments.length === 0) {
|
|
@@ -1540,8 +1572,8 @@ export const __sweetlinkCliTestHelpers = {
|
|
|
1540
1572
|
buildClickScript,
|
|
1541
1573
|
};
|
|
1542
1574
|
/* biome-ignore lint/performance/noBarrelFile: CLI entrypoint intentionally re-exports runtime helpers for compatibility. */
|
|
1543
|
-
export { diagnosticsContainBlockingIssues, logBootstrapDiagnostics } from
|
|
1544
|
-
export { buildClickScript, fetchConsoleEvents, fetchSessionSummaries, formatSessionHeadline, resolvePromptOption, resolveSessionIdFromHint, } from
|
|
1575
|
+
export { diagnosticsContainBlockingIssues, logBootstrapDiagnostics } from "./runtime/devtools.js";
|
|
1576
|
+
export { buildClickScript, fetchConsoleEvents, fetchSessionSummaries, formatSessionHeadline, resolvePromptOption, resolveSessionIdFromHint, } from "./runtime/session.js";
|
|
1545
1577
|
function urlsRoughlyMatch(a, b) {
|
|
1546
1578
|
const urlA = normalizeUrlForMatch(a);
|
|
1547
1579
|
const urlB = normalizeUrlForMatch(b);
|
|
@@ -1571,7 +1603,7 @@ function urlsRoughlyMatch(a, b) {
|
|
|
1571
1603
|
async function devtoolsAuthorize(options, command) {
|
|
1572
1604
|
const devtoolsConfig = await loadDevToolsConfig();
|
|
1573
1605
|
if (!devtoolsConfig?.devtoolsUrl) {
|
|
1574
|
-
console.log(
|
|
1606
|
+
console.log("No DevTools session detected. Launch Chrome with `pnpm sweetlink open --controlled` first.");
|
|
1575
1607
|
return;
|
|
1576
1608
|
}
|
|
1577
1609
|
const cliConfig = resolveConfig(command);
|
|
@@ -1604,12 +1636,12 @@ async function devtoolsAuthorize(options, command) {
|
|
|
1604
1636
|
}
|
|
1605
1637
|
catch (error) {
|
|
1606
1638
|
if (sweetLinkDebug) {
|
|
1607
|
-
console.warn(
|
|
1639
|
+
console.warn("Failed to inspect DevTools tabs for authorize command:", error);
|
|
1608
1640
|
}
|
|
1609
1641
|
}
|
|
1610
1642
|
}
|
|
1611
1643
|
if (!sessionUrl) {
|
|
1612
|
-
console.error(
|
|
1644
|
+
console.error("Unable to determine which tab contains the OAuth consent screen. Pass --url <authorizeUrl> to override.");
|
|
1613
1645
|
if (process.exitCode === undefined) {
|
|
1614
1646
|
process.exitCode = 1;
|
|
1615
1647
|
}
|
|
@@ -1622,18 +1654,18 @@ async function devtoolsAuthorize(options, command) {
|
|
|
1622
1654
|
scriptPath: cliConfig.oauthScriptPath,
|
|
1623
1655
|
});
|
|
1624
1656
|
if (result.handled) {
|
|
1625
|
-
console.log(`Authorize prompt handled via ${result.action ??
|
|
1657
|
+
console.log(`Authorize prompt handled via ${result.action ?? "click"}${result.clickedText ? ` (${result.clickedText})` : ""}.`);
|
|
1626
1658
|
}
|
|
1627
1659
|
else {
|
|
1628
|
-
const reason = result.reason ??
|
|
1660
|
+
const reason = result.reason ?? "no authorize button detected";
|
|
1629
1661
|
console.log(`Authorize prompt not handled automatically (${reason}).`);
|
|
1630
|
-
if (reason ===
|
|
1631
|
-
console.log(
|
|
1662
|
+
if (reason === "requires-login") {
|
|
1663
|
+
console.log("Twitter login inputs detected. Complete login manually and rerun the command.");
|
|
1632
1664
|
}
|
|
1633
1665
|
}
|
|
1634
1666
|
}
|
|
1635
1667
|
catch (error) {
|
|
1636
|
-
console.error(
|
|
1668
|
+
console.error("Failed to trigger OAuth authorize automation:", extractEventMessage(error));
|
|
1637
1669
|
if (process.exitCode === undefined) {
|
|
1638
1670
|
process.exitCode = 1;
|
|
1639
1671
|
}
|
|
@@ -1642,13 +1674,13 @@ async function devtoolsAuthorize(options, command) {
|
|
|
1642
1674
|
async function devtoolsStatus(options, command) {
|
|
1643
1675
|
const config = await loadDevToolsConfig();
|
|
1644
1676
|
if (!config) {
|
|
1645
|
-
console.log(
|
|
1677
|
+
console.log("No DevTools session detected. Launch Chrome with `pnpm sweetlink open --controlled` first.");
|
|
1646
1678
|
return;
|
|
1647
1679
|
}
|
|
1648
1680
|
let reachable;
|
|
1649
1681
|
let tabs = [];
|
|
1650
1682
|
try {
|
|
1651
|
-
const response = await fetch(`${config.devtoolsUrl.replace(TRAILING_SLASH_PATTERN,
|
|
1683
|
+
const response = await fetch(`${config.devtoolsUrl.replace(TRAILING_SLASH_PATTERN, "")}/json/version`, { method: "GET" });
|
|
1652
1684
|
if (response.ok) {
|
|
1653
1685
|
reachable = true;
|
|
1654
1686
|
tabs = await fetchDevToolsTabs(config.devtoolsUrl);
|
|
@@ -1692,7 +1724,7 @@ async function devtoolsStatus(options, command) {
|
|
|
1692
1724
|
process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
|
|
1693
1725
|
return;
|
|
1694
1726
|
}
|
|
1695
|
-
const reachabilityLabel = isReachable ?
|
|
1727
|
+
const reachabilityLabel = isReachable ? "reachable" : "offline";
|
|
1696
1728
|
console.log(`DevTools endpoint : ${config.devtoolsUrl} (${reachabilityLabel})`);
|
|
1697
1729
|
console.log(`Last updated : ${new Date(config.updatedAt).toISOString()}`);
|
|
1698
1730
|
if (config.viewport) {
|
|
@@ -1701,7 +1733,7 @@ async function devtoolsStatus(options, command) {
|
|
|
1701
1733
|
}
|
|
1702
1734
|
console.log(`Tabs : ${tabs.length}`);
|
|
1703
1735
|
if (matchedSessionId) {
|
|
1704
|
-
console.log(`Linked session : ${matchedSessionId}${matchedSessionTitle ? ` (${matchedSessionTitle})` :
|
|
1736
|
+
console.log(`Linked session : ${matchedSessionId}${matchedSessionTitle ? ` (${matchedSessionTitle})` : ""}`);
|
|
1705
1737
|
}
|
|
1706
1738
|
console.log(`Console buffer : ${state?.console.length ?? 0} entries`);
|
|
1707
1739
|
console.log(`Network buffer : ${state?.network.length ?? 0} entries`);
|
|
@@ -1712,7 +1744,7 @@ async function devtoolsStatus(options, command) {
|
|
|
1712
1744
|
async function devtoolsTabs(options) {
|
|
1713
1745
|
const config = await loadDevToolsConfig();
|
|
1714
1746
|
if (!config) {
|
|
1715
|
-
console.log(
|
|
1747
|
+
console.log("No DevTools session detected. Launch Chrome with `pnpm sweetlink open --controlled` first.");
|
|
1716
1748
|
return;
|
|
1717
1749
|
}
|
|
1718
1750
|
const tabs = await fetchDevToolsTabs(config.devtoolsUrl);
|
|
@@ -1721,23 +1753,23 @@ async function devtoolsTabs(options) {
|
|
|
1721
1753
|
return;
|
|
1722
1754
|
}
|
|
1723
1755
|
if (tabs.length === 0) {
|
|
1724
|
-
console.log(
|
|
1756
|
+
console.log("No tabs reported by DevTools.");
|
|
1725
1757
|
return;
|
|
1726
1758
|
}
|
|
1727
1759
|
for (const tab of tabs) {
|
|
1728
|
-
console.log(`• ${tab.title ||
|
|
1760
|
+
console.log(`• ${tab.title || "(untitled)"}`);
|
|
1729
1761
|
console.log(` URL : ${tab.url}`);
|
|
1730
1762
|
console.log(` ID : ${tab.id}`);
|
|
1731
1763
|
if (tab.type) {
|
|
1732
1764
|
console.log(` Type: ${tab.type}`);
|
|
1733
1765
|
}
|
|
1734
|
-
console.log(
|
|
1766
|
+
console.log("");
|
|
1735
1767
|
}
|
|
1736
1768
|
}
|
|
1737
1769
|
async function devtoolsShowConsole(options) {
|
|
1738
1770
|
const state = await loadDevToolsState();
|
|
1739
1771
|
if (!state || state.console.length === 0) {
|
|
1740
|
-
console.log(
|
|
1772
|
+
console.log("No console events recorded. Run `pnpm sweetlink devtools listen` to start capturing telemetry.");
|
|
1741
1773
|
return;
|
|
1742
1774
|
}
|
|
1743
1775
|
const tail = Number.isFinite(options.tail) && options.tail > 0 ? Math.floor(options.tail) : 50;
|
|
@@ -1748,19 +1780,19 @@ async function devtoolsShowConsole(options) {
|
|
|
1748
1780
|
}
|
|
1749
1781
|
for (const entry of entries) {
|
|
1750
1782
|
const timestamp = new Date(entry.ts).toISOString();
|
|
1751
|
-
let location =
|
|
1783
|
+
let location = "";
|
|
1752
1784
|
if (entry.location?.url) {
|
|
1753
|
-
const lineSuffix = entry.location.lineNumber === undefined ?
|
|
1754
|
-
const columnSuffix = entry.location.columnNumber === undefined ?
|
|
1785
|
+
const lineSuffix = entry.location.lineNumber === undefined ? "" : `:${entry.location.lineNumber}`;
|
|
1786
|
+
const columnSuffix = entry.location.columnNumber === undefined ? "" : `:${entry.location.columnNumber}`;
|
|
1755
1787
|
location = ` (${entry.location.url}${lineSuffix}${columnSuffix})`;
|
|
1756
1788
|
}
|
|
1757
|
-
let argsSuffix =
|
|
1789
|
+
let argsSuffix = "";
|
|
1758
1790
|
if (entry.args.length > 0) {
|
|
1759
1791
|
const formattedArgs = [];
|
|
1760
1792
|
for (const value of entry.args) {
|
|
1761
1793
|
formattedArgs.push(formatConsoleArg(value));
|
|
1762
1794
|
}
|
|
1763
|
-
argsSuffix = ` ${formattedArgs.join(
|
|
1795
|
+
argsSuffix = ` ${formattedArgs.join(" ")}`;
|
|
1764
1796
|
}
|
|
1765
1797
|
console.log(`[${timestamp}] ${entry.type}: ${entry.text}${argsSuffix}${location}`);
|
|
1766
1798
|
}
|
|
@@ -1768,7 +1800,7 @@ async function devtoolsShowConsole(options) {
|
|
|
1768
1800
|
async function devtoolsShowNetwork(options) {
|
|
1769
1801
|
const state = await loadDevToolsState();
|
|
1770
1802
|
if (!state || state.network.length === 0) {
|
|
1771
|
-
console.log(
|
|
1803
|
+
console.log("No network entries recorded. Run `pnpm sweetlink devtools listen` to start capturing telemetry.");
|
|
1772
1804
|
return;
|
|
1773
1805
|
}
|
|
1774
1806
|
const tail = Number.isFinite(options.tail) && options.tail > 0 ? Math.floor(options.tail) : 50;
|
|
@@ -1779,8 +1811,8 @@ async function devtoolsShowNetwork(options) {
|
|
|
1779
1811
|
}
|
|
1780
1812
|
for (const entry of entries) {
|
|
1781
1813
|
const timestamp = new Date(entry.ts).toISOString();
|
|
1782
|
-
const status = entry.status === undefined ? (entry.failureText ??
|
|
1783
|
-
const type = entry.resourceType ? ` [${entry.resourceType}]` :
|
|
1814
|
+
const status = entry.status === undefined ? (entry.failureText ?? "failed") : entry.status;
|
|
1815
|
+
const type = entry.resourceType ? ` [${entry.resourceType}]` : "";
|
|
1784
1816
|
console.log(`[${timestamp}] ${entry.method} ${status} ${entry.url}${type}`);
|
|
1785
1817
|
}
|
|
1786
1818
|
}
|
|
@@ -1789,7 +1821,7 @@ async function devtoolsListen(options, command) {
|
|
|
1789
1821
|
const config = await loadDevToolsConfig();
|
|
1790
1822
|
if (!config) {
|
|
1791
1823
|
if (!background) {
|
|
1792
|
-
console.log(
|
|
1824
|
+
console.log("No DevTools session detected. Launch Chrome with `pnpm sweetlink open --controlled` first.");
|
|
1793
1825
|
}
|
|
1794
1826
|
return;
|
|
1795
1827
|
}
|
|
@@ -1797,12 +1829,12 @@ async function devtoolsListen(options, command) {
|
|
|
1797
1829
|
try {
|
|
1798
1830
|
await rm(DEVTOOLS_STATE_PATH, { force: true });
|
|
1799
1831
|
if (!background) {
|
|
1800
|
-
console.log(
|
|
1832
|
+
console.log("Cleared cached DevTools telemetry state.");
|
|
1801
1833
|
}
|
|
1802
1834
|
}
|
|
1803
1835
|
catch (error) {
|
|
1804
|
-
if (!isErrnoException(error) || error.code !==
|
|
1805
|
-
console.warn(
|
|
1836
|
+
if (!isErrnoException(error) || error.code !== "ENOENT") {
|
|
1837
|
+
console.warn("Failed to reset DevTools state:", error);
|
|
1806
1838
|
}
|
|
1807
1839
|
}
|
|
1808
1840
|
}
|
|
@@ -1818,10 +1850,10 @@ async function devtoolsListen(options, command) {
|
|
|
1818
1850
|
await saveDevToolsState(state);
|
|
1819
1851
|
}
|
|
1820
1852
|
catch (error) {
|
|
1821
|
-
console.warn(
|
|
1853
|
+
console.warn("Failed to persist reset DevTools state:", error);
|
|
1822
1854
|
}
|
|
1823
1855
|
if (!background) {
|
|
1824
|
-
console.log(
|
|
1856
|
+
console.log("Reset complete. Re-run without --reset to resume live capture.");
|
|
1825
1857
|
}
|
|
1826
1858
|
return;
|
|
1827
1859
|
}
|
|
@@ -1837,7 +1869,7 @@ async function devtoolsListen(options, command) {
|
|
|
1837
1869
|
await saveDevToolsConfig({ devtoolsUrl: config.devtoolsUrl, sessionId });
|
|
1838
1870
|
}
|
|
1839
1871
|
catch (error) {
|
|
1840
|
-
logDebugError(
|
|
1872
|
+
logDebugError("Failed to persist DevTools session binding", error);
|
|
1841
1873
|
}
|
|
1842
1874
|
}
|
|
1843
1875
|
if (!background) {
|
|
@@ -1849,8 +1881,8 @@ async function devtoolsListen(options, command) {
|
|
|
1849
1881
|
({ browser, page } = await connectToDevTools(config));
|
|
1850
1882
|
}
|
|
1851
1883
|
catch (error) {
|
|
1852
|
-
console.error(
|
|
1853
|
-
console.error(
|
|
1884
|
+
console.error("Failed to connect to the DevTools endpoint:", extractEventMessage(error));
|
|
1885
|
+
console.error("Hint: ensure a controlled Chrome window is running via `pnpm sweetlink open --controlled`.");
|
|
1854
1886
|
process.exitCode = 1;
|
|
1855
1887
|
return;
|
|
1856
1888
|
}
|
|
@@ -1864,7 +1896,7 @@ async function devtoolsListen(options, command) {
|
|
|
1864
1896
|
await saveDevToolsConfig({ devtoolsUrl: config.devtoolsUrl, viewport });
|
|
1865
1897
|
}
|
|
1866
1898
|
catch (error) {
|
|
1867
|
-
logDebugError(
|
|
1899
|
+
logDebugError("Failed to persist DevTools viewport configuration", error);
|
|
1868
1900
|
}
|
|
1869
1901
|
const flush = async () => {
|
|
1870
1902
|
await saveDevToolsState(devtoolsState);
|
|
@@ -1879,14 +1911,14 @@ async function devtoolsListen(options, command) {
|
|
|
1879
1911
|
timer = setTimeout(() => {
|
|
1880
1912
|
timer = null;
|
|
1881
1913
|
flush().catch((error) => {
|
|
1882
|
-
console.warn(
|
|
1914
|
+
console.warn("Failed to persist DevTools telemetry:", error);
|
|
1883
1915
|
});
|
|
1884
1916
|
}, 300);
|
|
1885
1917
|
};
|
|
1886
1918
|
})();
|
|
1887
1919
|
const attachListeners = (p) => {
|
|
1888
1920
|
// Capture console and network events for the page and trim buffers as they grow.
|
|
1889
|
-
p.on(
|
|
1921
|
+
p.on("console", (message) => {
|
|
1890
1922
|
const persistConsole = async () => {
|
|
1891
1923
|
const entry = await serializeConsoleMessage(message);
|
|
1892
1924
|
devtoolsState.console.push(entry);
|
|
@@ -1894,10 +1926,10 @@ async function devtoolsListen(options, command) {
|
|
|
1894
1926
|
scheduleFlush();
|
|
1895
1927
|
};
|
|
1896
1928
|
persistConsole().catch((error) => {
|
|
1897
|
-
console.warn(
|
|
1929
|
+
console.warn("Failed to serialize console message:", error);
|
|
1898
1930
|
});
|
|
1899
1931
|
});
|
|
1900
|
-
p.on(
|
|
1932
|
+
p.on("requestfinished", (request) => {
|
|
1901
1933
|
const persistNetworkEntry = async () => {
|
|
1902
1934
|
const response = await request.response();
|
|
1903
1935
|
const entry = createNetworkEntryFromRequest(request, response?.status());
|
|
@@ -1906,12 +1938,12 @@ async function devtoolsListen(options, command) {
|
|
|
1906
1938
|
scheduleFlush();
|
|
1907
1939
|
};
|
|
1908
1940
|
persistNetworkEntry().catch((error) => {
|
|
1909
|
-
console.warn(
|
|
1941
|
+
console.warn("Failed to serialize network request:", error);
|
|
1910
1942
|
});
|
|
1911
1943
|
});
|
|
1912
|
-
p.on(
|
|
1944
|
+
p.on("requestfailed", (request) => {
|
|
1913
1945
|
const failure = request.failure();
|
|
1914
|
-
const entry = createNetworkEntryFromRequest(request, undefined, failure?.errorText ??
|
|
1946
|
+
const entry = createNetworkEntryFromRequest(request, undefined, failure?.errorText ?? "failed");
|
|
1915
1947
|
devtoolsState.network.push(entry);
|
|
1916
1948
|
trimBuffer(devtoolsState.network, DEVTOOLS_NETWORK_LIMIT);
|
|
1917
1949
|
scheduleFlush();
|
|
@@ -1919,11 +1951,11 @@ async function devtoolsListen(options, command) {
|
|
|
1919
1951
|
};
|
|
1920
1952
|
attachListeners(page);
|
|
1921
1953
|
const context = page.context();
|
|
1922
|
-
context.on(
|
|
1954
|
+
context.on("page", (newPage) => {
|
|
1923
1955
|
attachListeners(newPage);
|
|
1924
1956
|
});
|
|
1925
1957
|
if (!background) {
|
|
1926
|
-
console.log(
|
|
1958
|
+
console.log("Listening for console and network events… Press Ctrl+C to stop.");
|
|
1927
1959
|
}
|
|
1928
1960
|
const shutdown = async () => {
|
|
1929
1961
|
try {
|
|
@@ -1931,7 +1963,7 @@ async function devtoolsListen(options, command) {
|
|
|
1931
1963
|
}
|
|
1932
1964
|
catch (error) {
|
|
1933
1965
|
if (sweetLinkDebug) {
|
|
1934
|
-
console.warn(
|
|
1966
|
+
console.warn("Failed to flush DevTools state during shutdown.", error);
|
|
1935
1967
|
}
|
|
1936
1968
|
}
|
|
1937
1969
|
try {
|
|
@@ -1939,22 +1971,22 @@ async function devtoolsListen(options, command) {
|
|
|
1939
1971
|
}
|
|
1940
1972
|
catch (error) {
|
|
1941
1973
|
if (sweetLinkDebug) {
|
|
1942
|
-
console.warn(
|
|
1974
|
+
console.warn("Failed to close DevTools browser during shutdown.", error);
|
|
1943
1975
|
}
|
|
1944
1976
|
}
|
|
1945
1977
|
process.exit(0);
|
|
1946
1978
|
};
|
|
1947
|
-
process.once(
|
|
1979
|
+
process.once("SIGINT", () => {
|
|
1948
1980
|
shutdown().catch((error) => {
|
|
1949
1981
|
if (sweetLinkDebug) {
|
|
1950
|
-
console.warn(
|
|
1982
|
+
console.warn("Graceful shutdown after SIGINT failed:", error);
|
|
1951
1983
|
}
|
|
1952
1984
|
});
|
|
1953
1985
|
});
|
|
1954
|
-
process.once(
|
|
1986
|
+
process.once("SIGTERM", () => {
|
|
1955
1987
|
shutdown().catch((error) => {
|
|
1956
1988
|
if (sweetLinkDebug) {
|
|
1957
|
-
console.warn(
|
|
1989
|
+
console.warn("Graceful shutdown after SIGTERM failed:", error);
|
|
1958
1990
|
}
|
|
1959
1991
|
});
|
|
1960
1992
|
});
|