From af6f12e3918eae27dad532fd0f2e936c683d22bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Fri, 28 Aug 2026 11:42:58 +0200 Subject: [PATCH] chore: adopt shared oxlint config (#2115) * chore: adopt shared oxlint config * fix: preserve project lint boundaries * fix: remove redundant oxlint config --- .fallowrc.json | 6 +- .oxlintrc.json | 74 --- examples/test-app/app/(tabs)/settings.tsx | 4 +- examples/test-app/src/components.tsx | 2 +- examples/test-app/src/data.ts | 4 +- examples/test-app/src/lab-state.tsx | 18 +- examples/test-app/src/screens/AudioScreen.tsx | 2 +- .../src/screens/AutomationLabScreen.tsx | 2 +- .../test-app/src/screens/CatalogScreen.tsx | 14 +- examples/test-app/src/screens/FormScreen.tsx | 33 +- examples/test-app/src/screens/GestureLab.tsx | 2 +- examples/test-app/src/screens/HomeScreen.tsx | 6 +- .../test-app/src/screens/ProductScreen.tsx | 6 +- .../test-app/src/screens/SettingsScreen.tsx | 8 +- .../test-app/src/screens/WebViewScreen.tsx | 2 +- oxlint.config.ts | 124 ++++ package.json | 4 + .../src/internal/__tests__/script.test.ts | 16 +- .../__tests__/target-annotation-serde.test.ts | 4 +- .../src/internal/target-annotation-serde.ts | 2 +- .../capture-kit/src/network-traffic-value.ts | 2 +- packages/contracts/src/click-button.ts | 2 +- .../contracts/src/snapshot-diagnostics.ts | 2 +- packages/contracts/src/snapshot-text.ts | 2 +- packages/contracts/src/viewport.ts | 2 +- .../internal/archive-extraction-tar.test.ts | 2 +- .../internal/archive-extraction-zip.test.ts | 2 +- .../src/internal/archive-safety.test.ts | 2 +- packages/host-kit/src/internal/diagnostics.ts | 4 +- packages/host-kit/src/internal/exec.ts | 6 +- .../host-kit/src/internal/host-process.ts | 8 +- .../host-kit/src/internal/request-cancel.ts | 2 +- packages/host-kit/src/internal/retry.ts | 8 +- .../host-kit/src/internal/shell-quote.test.ts | 4 +- packages/host-kit/src/internal/shell-quote.ts | 2 +- packages/kernel/src/device.ts | 2 +- packages/kernel/src/source-value.ts | 4 +- .../internal/__tests__/selector-regex.test.ts | 6 +- .../maestro/src/internal/engine-context.ts | 2 +- .../maestro/src/internal/selector-regex.ts | 2 +- packages/maestro/src/internal/shared.ts | 2 +- .../src/adb-executor-host.test.ts | 2 +- .../platform-android/src/inventory-parsers.ts | 2 +- packages/platform-android/src/inventory.ts | 4 +- .../platform-android/src/network/runtime.ts | 2 +- packages/platform-android/src/observation.ts | 4 +- packages/platform-android/src/perf/runtime.ts | 2 +- .../platform-android/src/readiness/runtime.ts | 2 +- .../platform-apple/src/logs/log-predicate.ts | 2 +- .../platform-apple/src/network/runtime.ts | 2 +- packages/platform-apple/src/perf/runtime.ts | 2 +- .../__tests__/runner-macos-products.test.ts | 2 +- .../runner-usbmux-boundary-faults.test.ts | 6 +- .../runner/__tests__/runner-usbmux.test.ts | 2 +- .../src/runner/runner-artifact-env.ts | 6 +- .../src/runner/runner-artifact.ts | 7 +- .../src/runner/runner-disposal.ts | 2 +- .../platform-apple/src/runner/runner-lease.ts | 2 +- .../src/runner/runner-lifecycle.ts | 24 +- .../src/runner/runner-sequence.ts | 5 +- .../src/runner/runner-startup-transport.ts | 12 +- packages/provider-limrun/src/android.ts | 2 +- .../src/provider-definitions.ts | 2 +- .../src/install-source-download.test.ts | 4 +- .../src/internal/session-test-artifacts.ts | 4 +- .../src/internal/session-test-discovery.ts | 4 +- .../replay-test/src/internal/session-test.ts | 4 +- .../__tests__/property-arbitraries.ts | 4 +- packages/selectors/src/internal/find.ts | 4 +- packages/selectors/src/internal/match.test.ts | 2 +- packages/selectors/src/internal/parse.test.ts | 20 +- packages/xml/src/internal/entities.ts | 2 +- packages/xml/src/internal/parser.ts | 6 +- pnpm-lock.yaml | 583 ++++++++++++++++++ .../__tests__/package-closure-audit.test.ts | 2 +- scripts/check-affected/model.ts | 3 +- scripts/check-affected/run.test.ts | 4 +- scripts/coverage-changed/model.test.ts | 2 +- scripts/coverage-changed/model.ts | 4 +- scripts/di-seams/model.ts | 2 +- scripts/fuzz/arbitraries.ts | 4 +- scripts/fuzz/targets.ts | 2 +- scripts/fuzz/validation-values.ts | 2 +- scripts/gate/model.ts | 2 +- scripts/gate/shell.ts | 2 +- scripts/gate/workflows.ts | 6 +- scripts/help-conformance-bench.mjs | 2 +- scripts/integration-progress-model.ts | 4 +- scripts/mutation/workspace-aliases.ts | 2 +- scripts/perf/report.ts | 2 +- scripts/perf/run.ts | 4 +- scripts/perf/stats.ts | 2 +- scripts/swift-conditional-compilation.ts | 2 +- scripts/xctest-run-summary.ts | 2 +- src/__tests__/cli-diff.test.ts | 8 +- src/__tests__/cli-help.test.ts | 6 +- src/__tests__/cli-network.test.ts | 2 +- src/__tests__/cli-remote-diagnostics.test.ts | 2 +- ...platform-runtime-runtime-hints.fixtures.ts | 11 +- src/cli-schema/cli-help-command-usage.test.ts | 42 +- src/cli-schema/cli-help-topics.test.ts | 38 +- src/cli.ts | 12 +- .../__tests__/maestro-support-matrix.test.ts | 2 +- .../command-surface-metadata.test.ts | 4 +- src/commands/command-projection.ts | 6 +- .../__tests__/selector-read-policy.test.ts | 4 +- .../interaction/runtime/selector-wait.test.ts | 2 +- .../interaction/runtime/wait-polling.ts | 2 +- .../post-action-observation-grammar.ts | 2 +- src/commands/recording/runtime/recording.ts | 4 +- src/commands/system/navigation-projection.ts | 4 +- src/core/__tests__/dispatch-resolve.test.ts | 8 +- src/core/__tests__/gesture-plan.test.ts | 4 +- src/core/command-descriptor/command-result.ts | 46 +- src/core/interaction-positionals.ts | 2 +- src/core/snapshot-tree-ingestion.ts | 6 +- src/core/wait-positionals.ts | 2 +- src/daemon/__tests__/daemon-process.test.ts | 2 +- .../__tests__/device-claim-admission.test.ts | 2 +- .../materialized-path-registry.test.ts | 4 +- src/daemon/__tests__/resumable-upload.test.ts | 2 +- .../session-event-log-window.test.ts | 2 +- .../__tests__/session-event-request.test.ts | 2 +- src/daemon/__tests__/session-store.test.ts | 4 +- src/daemon/artifact-download.ts | 2 +- src/daemon/client/daemon-client-timeout.ts | 4 +- src/daemon/client/daemon-client-transport.ts | 4 +- src/daemon/daemon-process.ts | 6 +- .../interaction-ios-tap-outcome.test.ts | 2 +- ...action-response-construction-guard.test.ts | 2 +- .../interaction-target-evidence.test.ts | 2 +- .../__tests__/session-replay-runtime.test.ts | 2 +- .../session-replay-selector-routes.test.ts | 8 +- .../session-script-publication.test.ts | 2 +- .../__tests__/wait-landmark-recording.test.ts | 7 +- src/daemon/handlers/find-match-resolution.ts | 2 +- src/daemon/handlers/session-doctor-metro.ts | 2 +- src/daemon/handlers/session-inventory.ts | 4 +- src/daemon/handlers/session-observability.ts | 2 +- src/daemon/handlers/session-perf-runtime.ts | 2 +- .../handlers/session-replay-action-runtime.ts | 6 +- src/daemon/handlers/session-replay-heal.ts | 2 +- src/daemon/handlers/session-replay-runtime.ts | 4 +- .../handlers/session-test-shard-devices.ts | 2 +- .../handlers/session-test-suite-command.ts | 4 +- src/daemon/http-errors.ts | 1 + src/daemon/lease-registry.ts | 2 +- src/daemon/scroll-runtime.ts | 4 +- src/daemon/selector-operation-binding.ts | 8 +- src/daemon/server/http-server.ts | 2 +- src/daemon/server/server-lifecycle.ts | 6 +- src/daemon/server/transport.ts | 4 +- src/daemon/session-event-action.ts | 4 +- src/daemon/session-event-log-window.ts | 4 +- src/daemon/session-paths.ts | 2 +- src/daemon/session-script-writer.ts | 2 +- src/daemon/session-store.ts | 2 +- src/daemon/wait-current-surface.ts | 2 +- src/mcp/__tests__/command-tools.test.ts | 2 +- src/platform-runtime-runtime-hints.ts | 2 +- ...time-screen-recording-apple-runner-host.ts | 2 +- .../android/__tests__/adb-executor.test.ts | 14 +- .../__tests__/adb-host-binding.test.ts | 2 +- .../android/__tests__/app-deployment.test.ts | 2 +- .../__tests__/device-input-state.test.ts | 2 +- .../android/__tests__/text-input.test.ts | 6 +- src/platforms/android/alert-detection.ts | 2 +- .../android/app-deployment-resolution.ts | 2 +- src/platforms/android/app-parsers.ts | 2 +- src/platforms/android/device-input-state.ts | 2 +- src/platforms/android/emulator-lifecycle.ts | 2 +- src/platforms/android/fill-verification.ts | 2 +- .../android/helper-package-install.ts | 2 +- src/platforms/android/perf-frame-parser.ts | 2 +- .../android/perf-native-artifacts.ts | 4 +- src/platforms/android/perf.ts | 6 +- .../snapshot-helper-session-lifecycle.ts | 2 +- src/platforms/android/text-input.ts | 2 +- src/platforms/android/ui-hierarchy.ts | 16 +- src/platforms/android/window-state.ts | 2 +- src/platforms/apple/alert.ts | 6 +- .../apple/core/__tests__/apps.test.ts | 4 +- src/platforms/apple/core/app-device-io.ts | 2 +- src/platforms/apple/core/devicectl.ts | 2 +- src/platforms/apple/core/perf.ts | 2 +- src/platforms/apple/core/screenshot.ts | 2 +- src/platforms/apple/os/macos/host-provider.ts | 2 +- src/platforms/harmonyos/app-lifecycle.ts | 4 +- src/platforms/linux/tool-provider.ts | 4 +- src/platforms/vega/__tests__/doctor.test.ts | 38 +- .../web/agent-browser-process-record.ts | 2 +- src/platforms/web/agent-browser-snapshot.ts | 10 +- .../daemon-artifacts-save-script.test.ts | 2 +- src/remote/remote-connection-state.ts | 2 +- src/remote/upload-client-artifact.ts | 4 +- src/replay/test/__tests__/progress.test.ts | 4 +- src/replay/test/progress.ts | 8 +- src/snapshot/snapshot-lines.ts | 10 +- .../snapshot-presentation/ios/action-shelf.ts | 4 +- src/snapshot/snapshot-presentation/tree.ts | 2 +- src/utils/__tests__/daemon-client.test.ts | 34 +- src/utils/__tests__/device.test.ts | 8 +- src/utils/__tests__/output.test.ts | 70 +-- .../snapshot-lines-custom-actions.test.ts | 7 +- .../android-helper-presentation/labels.ts | 2 +- .../structural-noise.ts | 2 +- src/utils/install-source-config.ts | 2 +- src/utils/output.ts | 6 +- src/utils/repeated-nav-subtree.ts | 2 +- src/utils/text-surface.ts | 2 +- test/ci/trusted-fixture-artifact.test.mjs | 22 +- .../android-recording-manifest-fixtures.ts | 2 +- .../provider-scenarios/android-world.ts | 8 +- .../provider-scenarios/transcript.ts | 12 +- test/integration/recording-overlay.test.ts | 4 +- .../smoke-clean-xcuitest-derived.test.ts | 2 +- test/integration/test-helpers.ts | 4 +- test/wire-compat/declaration-digest.ts | 2 +- 218 files changed, 1291 insertions(+), 634 deletions(-) delete mode 100644 .oxlintrc.json create mode 100644 oxlint.config.ts diff --git a/.fallowrc.json b/.fallowrc.json index 66eb67b5b..e587ff5e1 100644 --- a/.fallowrc.json +++ b/.fallowrc.json @@ -54,6 +54,10 @@ // @agent-device/provider-limrun owns the source import, while the published // root build externalizes @limrun/api and retains runtime imports in packed chunks. "@limrun/api", + // Oxlint resolves the shared config's JS plugins dynamically from their package names. + "@nkzw/eslint-plugin", + "eslint-plugin-no-only-tests", + "eslint-plugin-perfectionist", // Packaging linters with no importable API surface here: scripts/check-package.ts runs their // `node_modules/.bin` shims as subprocesses, which dependency analysis cannot follow to an import. "publint", @@ -116,7 +120,7 @@ }, { "comment": "Tool config default exports, loaded by the tool rather than imported.", - "file": "{tsdown.config.ts,vitest.mutation.config.ts,website/rspress.config.ts}", + "file": "{oxlint.config.ts,tsdown.config.ts,vitest.mutation.config.ts,website/rspress.config.ts}", "exports": ["default"] }, { diff --git a/.oxlintrc.json b/.oxlintrc.json deleted file mode 100644 index c88ec9613..000000000 --- a/.oxlintrc.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "$schema": "./node_modules/oxlint/configuration_schema.json", - "plugins": ["typescript", "import", "node"], - "env": { - "node": true, - "es6": true - }, - "categories": { - "correctness": "error" - }, - "rules": { - "eslint/no-unused-vars": [ - "error", - { - "argsIgnorePattern": "^_", - "caughtErrorsIgnorePattern": "^_", - "destructuredArrayIgnorePattern": "^_", - "fix": { - "imports": "safe-fix", - "variables": "suggestion" - }, - "varsIgnorePattern": "^_" - } - ], - "eslint/prefer-const": "error", - "eslint/no-useless-escape": "error", - "eslint/no-empty": [ - "error", - { - "allowEmptyCatch": true - } - ], - "typescript/no-explicit-any": "off", - "typescript/only-throw-error": "off", - "typescript/no-var-requires": "error", - "import/no-duplicates": "error" - }, - "overrides": [ - { - "files": ["src/**/*.ts", "packages/capture-kit/src/**/*.ts"], - "rules": { - "eslint/no-restricted-imports": [ - "error", - { - "paths": [ - { - "name": "node:child_process", - "message": "Use process helpers from @agent-device/capture-kit/exec instead of importing node:child_process directly." - } - ] - } - ] - } - }, - { - "files": [ - "packages/capture-kit/src/exec.ts", - "packages/capture-kit/src/*.test.ts", - "src/**/*.test.ts", - "src/**/__tests__/**/*.ts" - ], - "rules": { - "eslint/no-restricted-imports": ["error", { "paths": [] }] - } - }, - { - "files": ["examples/test-app/app.config.js"], - "rules": { - "typescript/no-var-requires": "off" - } - } - ], - "ignorePatterns": ["dist/**", "node_modules/**"] -} diff --git a/examples/test-app/app/(tabs)/settings.tsx b/examples/test-app/app/(tabs)/settings.tsx index 9f86565c5..1b561aa66 100644 --- a/examples/test-app/app/(tabs)/settings.tsx +++ b/examples/test-app/app/(tabs)/settings.tsx @@ -15,12 +15,12 @@ export default function SettingsRoute() { diagnosticsLoading={state.diagnosticsLoading} diagnosticsState={state.diagnosticsState} notificationsEnabled={state.notificationsEnabled} + onConfirmReset={state.resetLabState} + onLoadDiagnostics={state.loadDiagnostics} onOpenAccessorySetup={() => router.push('/accessory-setup')} onOpenAutomationLab={() => router.push('/automation')} onOpenInertSurface={() => router.push('/inert')} onOpenWebViewLab={() => router.push('/webview')} - onConfirmReset={state.resetLabState} - onLoadDiagnostics={state.loadDiagnostics} onRetryDiagnostics={state.retryDiagnostics} onSetNotificationsEnabled={state.setNotificationsEnabled} onSetReducedMotionEnabled={state.setReducedMotionEnabled} diff --git a/examples/test-app/src/components.tsx b/examples/test-app/src/components.tsx index e7262e426..0724971bd 100644 --- a/examples/test-app/src/components.tsx +++ b/examples/test-app/src/components.tsx @@ -147,10 +147,10 @@ export function ToggleRow(props: { accessibilityRole="switch" accessibilityState={{ checked: props.value }} ios_backgroundColor={colors.lineStrong} + onValueChange={props.onValueChange} thumbColor={props.value ? colors.accent : colors.card} trackColor={{ false: colors.lineStrong, true: colors.accentSoft }} value={props.value} - onValueChange={props.onValueChange} /> ); diff --git a/examples/test-app/src/data.ts b/examples/test-app/src/data.ts index a3bb8e925..8ef40eb40 100644 --- a/examples/test-app/src/data.ts +++ b/examples/test-app/src/data.ts @@ -3,12 +3,12 @@ export const PRODUCT_CATEGORIES = ['All', 'Starter Kits', 'Produce', 'Bakery', ' export type ProductCategory = (typeof PRODUCT_CATEGORIES)[number]; export interface LabProduct { + badge: string; + category: Exclude; id: string; name: string; - category: Exclude; price: string; subtitle: string; - badge: string; } export const LAB_PRODUCTS: LabProduct[] = [ diff --git a/examples/test-app/src/lab-state.tsx b/examples/test-app/src/lab-state.tsx index 8cd197b46..515991353 100644 --- a/examples/test-app/src/lab-state.tsx +++ b/examples/test-app/src/lab-state.tsx @@ -22,35 +22,33 @@ interface ProductDraft { interface LabStateContextValue { activeCategory: ProductCategory; + addToCart: (productId: string, quantity?: number) => void; cartCount: number; cartCounts: Record; catalogProducts: typeof LAB_PRODUCTS; + decreaseProductQuantity: (productId: string) => void; detailDrafts: Record; diagnosticsExpanded: boolean; diagnosticsLoading: boolean; diagnosticsState: 'idle' | 'ready' | 'error'; + dismissNotice: () => void; favoriteIds: string[]; form: CheckoutFormState; formErrors: string[]; + increaseProductQuantity: (productId: string) => void; isOnline: boolean; isRefreshing: boolean; lastSyncLabel: string; + loadDiagnostics: () => void; noticeVisible: boolean; notificationsEnabled: boolean; reducedMotionEnabled: boolean; - searchDraft: string; - submittedSummary: string | null; - toastMessage: string | null; - addToCart: (productId: string, quantity?: number) => void; - decreaseProductQuantity: (productId: string) => void; - dismissNotice: () => void; - increaseProductQuantity: (productId: string) => void; - loadDiagnostics: () => void; + refreshMetrics: () => void; resetForm: () => void; resetLabState: () => void; - refreshMetrics: () => void; retryDiagnostics: () => void; saveProductToCart: (productId: string) => void; + searchDraft: string; setActiveCategory: (value: ProductCategory) => void; setDiagnosticsExpanded: (value: boolean) => void; setIsOnline: (value: boolean) => void; @@ -59,6 +57,8 @@ interface LabStateContextValue { setReducedMotionEnabled: (value: boolean) => void; setSearchDraft: (value: string) => void; submitOrder: () => void; + submittedSummary: string | null; + toastMessage: string | null; toggleFavorite: (productId: string) => void; updateForm: (field: K, value: CheckoutFormState[K]) => void; } diff --git a/examples/test-app/src/screens/AudioScreen.tsx b/examples/test-app/src/screens/AudioScreen.tsx index dd75ddfee..4f8e9c7da 100644 --- a/examples/test-app/src/screens/AudioScreen.tsx +++ b/examples/test-app/src/screens/AudioScreen.tsx @@ -129,8 +129,8 @@ export function AudioScreen() { diff --git a/examples/test-app/src/screens/AutomationLabScreen.tsx b/examples/test-app/src/screens/AutomationLabScreen.tsx index 23d7cea37..029436fa1 100644 --- a/examples/test-app/src/screens/AutomationLabScreen.tsx +++ b/examples/test-app/src/screens/AutomationLabScreen.tsx @@ -130,8 +130,8 @@ export function AutomationLabScreen(props: { diff --git a/examples/test-app/src/screens/CatalogScreen.tsx b/examples/test-app/src/screens/CatalogScreen.tsx index 086f47ea4..d54867760 100644 --- a/examples/test-app/src/screens/CatalogScreen.tsx +++ b/examples/test-app/src/screens/CatalogScreen.tsx @@ -24,13 +24,13 @@ export interface CatalogScreenProps { activeCategory: ProductCategory; cart: Record; favorites: Set; - products: LabProduct[]; - searchDraft: string; onAddToCart: (productId: string) => void; onOpenDetails: (productId: string) => void; onSearchDraftChange: (value: string) => void; onSelectCategory: (value: ProductCategory) => void; onToggleFavorite: (productId: string) => void; + products: LabProduct[]; + searchDraft: string; } export function CatalogScreen(props: CatalogScreenProps) { @@ -70,8 +70,8 @@ export function CatalogScreen(props: CatalogScreenProps) { Catalog scroll: {scrollState} @@ -93,7 +93,7 @@ export function CatalogScreen(props: CatalogScreenProps) { label={category} onPress={() => props.onSelectCategory(category)} selected={props.activeCategory === category} - testID={`category-${category.toLowerCase().replace(/\s+/g, '-')}`} + testID={`category-${category.toLowerCase().replaceAll(/\s+/g, '-')}`} /> ))} @@ -107,8 +107,8 @@ export function CatalogScreen(props: CatalogScreenProps) { @@ -146,12 +146,12 @@ export function CatalogScreen(props: CatalogScreenProps) { If your run reaches this card, you already exercised long-list navigation. The durable - text here is "Seasonal footer target". + text here is "Seasonal footer target". diff --git a/examples/test-app/src/screens/FormScreen.tsx b/examples/test-app/src/screens/FormScreen.tsx index e9c8273a6..0315ee094 100644 --- a/examples/test-app/src/screens/FormScreen.tsx +++ b/examples/test-app/src/screens/FormScreen.tsx @@ -11,24 +11,24 @@ import { import { useAppColors, type AppColors } from '../theme'; export interface CheckoutFormState { - name: string; - email: string; - phone: string; - imeCaptureTarget: string; - notes: string; - shipping: 'Delivery' | 'Pickup'; - payment: 'Card' | 'Cash'; - subscribe: boolean; agree: boolean; + email: string; + imeCaptureTarget: string; + name: string; + notes: string; + payment: 'Card' | 'Cash'; + phone: string; + shipping: 'Delivery' | 'Pickup'; + subscribe: boolean; } export interface FormScreenProps { errors: string[]; form: CheckoutFormState; - submittedSummary: string | null; onChange: (field: K, value: CheckoutFormState[K]) => void; onReset: () => void; onSubmit: () => void; + submittedSummary: string | null; } function CheckboxRow(props: { @@ -70,16 +70,16 @@ export function FormScreen(props: FormScreenProps) { {props.errors.length > 0 ? ( {props.errors.map((error) => ( @@ -92,8 +92,8 @@ export function FormScreen(props: FormScreenProps) { {props.submittedSummary ? ( {props.submittedSummary} @@ -137,8 +137,8 @@ export function FormScreen(props: FormScreenProps) { {/* Fixture: static diagnostic copy, not live state. */} Android fill input was captured by the active keyboard instead of the app field - targetInput id="field-ime-capture-target" - actualInput packageName="com.google.android.inputmethod.latin" inputMethodOwned=true + targetInput id="field-ime-capture-target" + + + actualInput packageName="com.google.android.inputmethod.latin" + inputMethodOwned=true diff --git a/examples/test-app/src/screens/GestureLab.tsx b/examples/test-app/src/screens/GestureLab.tsx index 1e709dd21..b06863abb 100644 --- a/examples/test-app/src/screens/GestureLab.tsx +++ b/examples/test-app/src/screens/GestureLab.tsx @@ -360,8 +360,8 @@ export function GestureLab() { return ( diff --git a/examples/test-app/src/screens/HomeScreen.tsx b/examples/test-app/src/screens/HomeScreen.tsx index e89831b2a..665c2dca9 100644 --- a/examples/test-app/src/screens/HomeScreen.tsx +++ b/examples/test-app/src/screens/HomeScreen.tsx @@ -46,8 +46,8 @@ export function HomeScreen(props: HomeScreenProps) { @@ -55,9 +55,9 @@ export function HomeScreen(props: HomeScreenProps) { {props.noticeVisible ? ( The bakery list was refreshed this morning. Seasonal items moved to the bottom of the @@ -105,8 +105,8 @@ export function HomeScreen(props: HomeScreenProps) { Session health diff --git a/examples/test-app/src/screens/ProductScreen.tsx b/examples/test-app/src/screens/ProductScreen.tsx index 5ad953e84..d027eed40 100644 --- a/examples/test-app/src/screens/ProductScreen.tsx +++ b/examples/test-app/src/screens/ProductScreen.tsx @@ -7,14 +7,14 @@ import { useAppColors, type AppColors } from '../theme'; export interface ProductScreenProps { detailNote: string; isFavorite: boolean; - product: LabProduct; - quantity: number; onBack: () => void; onChangeDetailNote: (value: string) => void; onDecreaseQuantity: () => void; onIncreaseQuantity: () => void; onSave: () => void; onToggleFavorite: () => void; + product: LabProduct; + quantity: number; } export function ProductScreen(props: ProductScreenProps) { @@ -26,8 +26,8 @@ export function ProductScreen(props: ProductScreenProps) { diff --git a/examples/test-app/src/screens/SettingsScreen.tsx b/examples/test-app/src/screens/SettingsScreen.tsx index cc52a7c15..4fb28d899 100644 --- a/examples/test-app/src/screens/SettingsScreen.tsx +++ b/examples/test-app/src/screens/SettingsScreen.tsx @@ -16,17 +16,17 @@ export interface SettingsScreenProps { diagnosticsLoading: boolean; diagnosticsState: 'idle' | 'ready' | 'error'; notificationsEnabled: boolean; - reducedMotionEnabled: boolean; + onConfirmReset: () => void; + onLoadDiagnostics: () => void; onOpenAccessorySetup: () => void; onOpenAutomationLab: () => void; onOpenInertSurface: () => void; onOpenWebViewLab: () => void; - onLoadDiagnostics: () => void; onRetryDiagnostics: () => void; onSetNotificationsEnabled: (value: boolean) => void; onSetReducedMotionEnabled: (value: boolean) => void; onToggleDiagnostics: () => void; - onConfirmReset: () => void; + reducedMotionEnabled: boolean; } export function SettingsScreen(props: SettingsScreenProps) { @@ -59,8 +59,8 @@ export function SettingsScreen(props: SettingsScreenProps) { void }) { })} - + {formatLoadStatus(site.label, loadState)} diff --git a/oxlint.config.ts b/oxlint.config.ts new file mode 100644 index 000000000..58843a741 --- /dev/null +++ b/oxlint.config.ts @@ -0,0 +1,124 @@ +import nkzw from '@nkzw/oxlint-config'; +import { defineConfig } from 'oxlint'; + +export default defineConfig({ + env: { + builtin: true, + es2024: true, + node: true, + }, + extends: [nkzw], + ignorePatterns: ['dist/**', 'node_modules/**'], + rules: { + '@typescript-eslint/array-type': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@nkzw/no-instanceof': 'off', + curly: 'off', + 'import/default': 'error', + 'import/no-duplicates': 'error', + 'import/namespace': 'error', + 'import/no-namespace': 'off', + 'no-caller': 'error', + 'no-console': 'off', + 'no-empty': ['error', { allowEmptyCatch: true }], + 'no-eval': 'error', + 'no-iterator': 'error', + 'no-unassigned-vars': 'error', + 'no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_', + fix: { + imports: 'safe-fix', + variables: 'suggestion', + }, + varsIgnorePattern: '^_', + }, + ], + 'perfectionist/sort-object-types': 'off', + 'perfectionist/sort-objects': 'off', + 'typescript/no-this-alias': 'error', + 'typescript/no-unnecessary-parameter-property-assignment': 'error', + 'typescript/no-useless-empty-export': 'error', + 'unicorn/consistent-function-scoping': 'off', + 'unicorn/numeric-separators-style': 'off', + 'unicorn/prefer-top-level-await': 'off', + 'unicorn/prefer-structured-clone': 'off', + }, + overrides: [ + { + files: ['scripts/**/*.ts', 'scripts/**/*.mts', 'scripts/**/*.cts'], + rules: { + 'no-undef': 'error', + }, + }, + { + files: ['src/**/*.ts', 'packages/host-kit/src/**/*.ts'], + rules: { + 'no-restricted-imports': [ + 'error', + { + paths: [ + { + name: 'node:child_process', + message: + 'Use process helpers from @agent-device/host-kit/command instead of importing node:child_process directly.', + }, + ], + }, + ], + }, + }, + { + files: [ + 'packages/host-kit/src/internal/exec.ts', + 'packages/host-kit/src/**/*.test.ts', + 'src/**/*.test.ts', + 'src/**/__tests__/**/*.ts', + ], + rules: { + 'no-restricted-imports': ['error', { paths: [] }], + }, + }, + { + files: ['examples/test-app/src/**/*.tsx'], + rules: { + 'react/immutability': 'off', + 'react/purity': 'off', + 'react/refs': 'off', + }, + }, + { + files: ['examples/test-app/app.config.js'], + rules: { + '@typescript-eslint/no-require-imports': 'off', + }, + }, + { + files: [ + 'src/daemon/client/daemon-client-rpc.ts', + 'src/daemon/downloadable-artifact-http.ts', + 'src/remote/remote-request-diagnostics.ts', + 'src/remote/upload-stream.ts', + ], + rules: { + 'unicorn/catch-error-name': 'off', + 'unicorn/prefer-string-replace-all': 'off', + }, + }, + { + files: ['src/daemon/handlers/__tests__/session-replay-target-verification-runtime.test.ts'], + rules: { + 'unicorn/prefer-string-raw': 'off', + }, + }, + { + files: ['scripts/maestro-conformance/corpus/authored/runscript.js'], + globals: { + output: 'writable', + }, + }, + ], +}); diff --git a/package.json b/package.json index 8fb221e51..b3411919c 100644 --- a/package.json +++ b/package.json @@ -295,6 +295,8 @@ "@agent-device/xml": "workspace:*", "@arethetypeswrong/cli": "^0.18.5", "@chenglou/freerange": "^0.0.4", + "@nkzw/eslint-plugin": "^2.0.0", + "@nkzw/oxlint-config": "^1.3.0", "@stryker-mutator/core": "10.0.0", "@stryker-mutator/vitest-runner": "10.0.0", "@types/json-schema": "^7.0.15", @@ -304,6 +306,8 @@ "@types/yauzl": "^3.4.0", "@vitest/coverage-v8": "4.1.11", "ai": "^7.0.68", + "eslint-plugin-no-only-tests": "^3.4.0", + "eslint-plugin-perfectionist": "^5.10.1", "fallow": "^2.104.0", "fast-check": "^4.9.0", "oxc-parser": "^0.146.0", diff --git a/packages/ad-script/src/internal/__tests__/script.test.ts b/packages/ad-script/src/internal/__tests__/script.test.ts index d4f30e9fd..484f9c7e5 100644 --- a/packages/ad-script/src/internal/__tests__/script.test.ts +++ b/packages/ad-script/src/internal/__tests__/script.test.ts @@ -286,10 +286,10 @@ test('formatScriptStringLiteral escapes device labels with quotes and backslashe // Same assembly the live session-script-writer uses // (daemon/session-script-writer.ts's formatScript): `context platform=... // device= kind=... theme=...`. - const header = `context platform=android device=${formatScriptStringLiteral('Pixel "QA" \\ Lab')} kind=emulator theme=unknown`; + const header = `context platform=android device=${formatScriptStringLiteral(String.raw`Pixel "QA" \ Lab`)} kind=emulator theme=unknown`; assert.equal( header, - 'context platform=android device="Pixel \\"QA\\" \\\\ Lab" kind=emulator theme=unknown', + String.raw`context platform=android device="Pixel \"QA\" \\ Lab" kind=emulator theme=unknown`, ); // And it round-trips through the reader as ordinary context metadata. assert.equal(readReplayScriptMetadata(`${header}\nopen "Demo"\n`).platform, 'android'); @@ -318,7 +318,7 @@ test('a rewritten script preserves significant whitespace and empty string argum { ts: Date.now(), command: 'screenshot', - positionals: ['foo\\nbar.png'], + positionals: [String.raw`foo\nbar.png`], flags: {}, }, { @@ -336,7 +336,7 @@ test('a rewritten script preserves significant whitespace and empty string argum const script = formatReplayScriptForTest(actions); - assert.match(script, /type " leading\\ttrailing "/); + assert.match(script, /type " {2}leading\\ttrailing {2}"/); assert.match(script, /fill @e2 ""/); assert.match(script, /screenshot " \.\/screens\/final\.png "/); assert.match(script, /screenshot "foo\\\\nbar\.png"/); @@ -346,7 +346,7 @@ test('a rewritten script preserves significant whitespace and empty string argum assert.deepEqual(parsed[0]?.positionals, [' leading\ttrailing ']); assert.deepEqual(parsed[1]?.positionals, ['@e2', '']); assert.deepEqual(parsed[2]?.positionals, [' ./screens/final.png ']); - assert.deepEqual(parsed[3]?.positionals, ['foo\\nbar.png']); + assert.deepEqual(parsed[3]?.positionals, [String.raw`foo\nbar.png`]); assert.deepEqual(parsed[4]?.positionals, ['Demo']); assert.equal(parsed[4]?.runtime?.metroHost, ' host\t'); assert.equal(parsed[4]?.runtime?.launchUrl, 'myapp://dev '); @@ -589,7 +589,7 @@ test('a malformed target-v1 payload is rejected as INVALID_ARGS, not silently dr // Found by the nightly parser fuzz lane (#1414): the closing-quote scan accepted these // literals and the JSON decode behind it leaked a raw SyntaxError. test.each([ - ['invalid escape', 'fill @e1 --text "hello wor\\ld"'], + ['invalid escape', String.raw`fill @e1 --text "hello wor\ld"`], ['raw control character', 'fill @e1 --text "hel\u0000lo"'], ['raw tab', 'fill @e1 --text "hello\tworld"'], ])('a quoted value with an %s is rejected as INVALID_ARGS with a hint', (_case, script) => { @@ -656,7 +656,7 @@ test('formatDivergenceActionLabel categorically drops fill/type text but keeps t // fill selector text (selector token is script-quoted, text dropped) assert.equal( formatDivergenceActionLabel(mk('fill', ['label="Email"', secret])), - 'fill "label=\\"Email\\"" ', + String.raw`fill "label=\"Email\"" `, ); // fill @ref text assert.equal(formatDivergenceActionLabel(mk('fill', ['@e5', secret])), 'fill @e5 '); @@ -675,7 +675,7 @@ test('formatDivergenceActionLabel categorically drops fill/type text but keeps t // non-typing commands are unchanged (full summary, script-quoted). assert.equal( formatDivergenceActionLabel(mk('click', ['label="Save"'])), - 'click "label=\\"Save\\""', + String.raw`click "label=\"Save\""`, ); }); diff --git a/packages/ad-script/src/internal/__tests__/target-annotation-serde.test.ts b/packages/ad-script/src/internal/__tests__/target-annotation-serde.test.ts index 04a9a2c32..97e58bc0d 100644 --- a/packages/ad-script/src/internal/__tests__/target-annotation-serde.test.ts +++ b/packages/ad-script/src/internal/__tests__/target-annotation-serde.test.ts @@ -187,9 +187,9 @@ test('scrollRegion.label serializes and round trips like every other label field test('embedded quotes and backslashes in labels round trip losslessly', () => { const evidence = baseEvidence({ label: 'Say "hi" \\ backslash', id: undefined }); const json = serializeTargetAnnotationV1(evidence); - assert.ok(json.includes('\\"hi\\"')); + assert.ok(json.includes(String.raw`\"hi\"`)); const parsed = parseTargetAnnotationV1Payload(json); - assert.equal(parsed.label, 'Say "hi" \\ backslash'); + assert.equal(parsed.label, String.raw`Say "hi" \ backslash`); }); test('Unicode labels (including astral code points) round trip losslessly', () => { diff --git a/packages/ad-script/src/internal/target-annotation-serde.ts b/packages/ad-script/src/internal/target-annotation-serde.ts index 2fdd050df..6c41ad7d0 100644 --- a/packages/ad-script/src/internal/target-annotation-serde.ts +++ b/packages/ad-script/src/internal/target-annotation-serde.ts @@ -82,7 +82,7 @@ export function normalizeRoleField(value: string): string { /** label fields: NFC, trim, collapse internal whitespace runs to one space. */ export function normalizeLabelField(value: string | undefined): string | undefined { if (value === undefined) return undefined; - const collapsed = nfc(value).trim().replace(/\s+/g, ' '); + const collapsed = nfc(value).trim().replaceAll(/\s+/g, ' '); return collapsed.length > 0 ? collapsed : undefined; } diff --git a/packages/capture-kit/src/network-traffic-value.ts b/packages/capture-kit/src/network-traffic-value.ts index 84e17ab99..f25caa7ee 100644 --- a/packages/capture-kit/src/network-traffic-value.ts +++ b/packages/capture-kit/src/network-traffic-value.ts @@ -75,7 +75,7 @@ export function readNetworkBody( ): string | undefined { for (const key of keys) { if (json?.[key] !== undefined) return stringifyNetworkValue(json[key]); - const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const escaped = key.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`); const match = new RegExp(`\\b${escaped}["'=: ]+(.+)$`, 'i').exec(line); if (match?.[1]) return match[1].trim(); } diff --git a/packages/contracts/src/click-button.ts b/packages/contracts/src/click-button.ts index f369bf720..2bc4019b1 100644 --- a/packages/contracts/src/click-button.ts +++ b/packages/contracts/src/click-button.ts @@ -54,6 +54,6 @@ export function getClickButtonValidationError(options: { return null; } -export function buttonTag(button: ClickButton): {} | { button: ClickButton } { +export function buttonTag(button: ClickButton): Partial<{ button: ClickButton }> { return button === 'primary' ? {} : { button }; } diff --git a/packages/contracts/src/snapshot-diagnostics.ts b/packages/contracts/src/snapshot-diagnostics.ts index 5618fa04d..b309d6edb 100644 --- a/packages/contracts/src/snapshot-diagnostics.ts +++ b/packages/contracts/src/snapshot-diagnostics.ts @@ -129,7 +129,7 @@ function buildSnapshotTimingStats(samples: SnapshotTimingSample[]): SnapshotTimi count: durations.length, p50Ms: percentileNearestRank(durations, 50), p95Ms: percentileNearestRank(durations, 95), - maxMs: durations[durations.length - 1] ?? 0, + maxMs: durations.at(-1) ?? 0, slowThresholdMs: SLOW_SNAPSHOT_P95_WARNING_MS, ...singlePlatform(samples), ...backendCounts(samples), diff --git a/packages/contracts/src/snapshot-text.ts b/packages/contracts/src/snapshot-text.ts index de99c12ff..b22f112e9 100644 --- a/packages/contracts/src/snapshot-text.ts +++ b/packages/contracts/src/snapshot-text.ts @@ -4,7 +4,7 @@ import type { RawSnapshotNode } from '@agent-device/kernel/snapshot'; export function normalizeType(type: string): string { let normalized = type .trim() - .replace(/XCUIElementType/gi, '') + .replaceAll(/XCUIElementType/gi, '') .replace(/^AX/, '') .toLowerCase(); const lastSeparator = Math.max(normalized.lastIndexOf('.'), normalized.lastIndexOf('/')); diff --git a/packages/contracts/src/viewport.ts b/packages/contracts/src/viewport.ts index 58f8125dd..16029db91 100644 --- a/packages/contracts/src/viewport.ts +++ b/packages/contracts/src/viewport.ts @@ -1,7 +1,7 @@ import { AppError } from '@agent-device/kernel/errors'; function readViewportDimension(value: string | undefined, label: 'width' | 'height'): number { - const parsed = value === undefined ? NaN : Number(value); + const parsed = value === undefined ? Number.NaN : Number(value); if (!Number.isInteger(parsed) || parsed < 1) { throw new AppError('INVALID_ARGS', `viewport ${label} must be a positive integer`); } diff --git a/packages/host-kit/src/internal/archive-extraction-tar.test.ts b/packages/host-kit/src/internal/archive-extraction-tar.test.ts index 54bb3b9e8..81f9d93c7 100644 --- a/packages/host-kit/src/internal/archive-extraction-tar.test.ts +++ b/packages/host-kit/src/internal/archive-extraction-tar.test.ts @@ -18,7 +18,7 @@ test('tar inspection rejects declared bytes before draining an over-budget entry budget, }).then( () => null, - (cause: unknown) => cause, + (error: unknown) => error, ); assert.equal(error instanceof AppError, true); diff --git a/packages/host-kit/src/internal/archive-extraction-zip.test.ts b/packages/host-kit/src/internal/archive-extraction-zip.test.ts index 9ad4aa011..fea4bcb45 100644 --- a/packages/host-kit/src/internal/archive-extraction-zip.test.ts +++ b/packages/host-kit/src/internal/archive-extraction-zip.test.ts @@ -20,7 +20,7 @@ test('zip inspection stops at the entry budget before inspecting later entries', budget, }).then( () => null, - (cause: unknown) => cause, + (error: unknown) => error, ); assert.equal(error instanceof AppError, true); diff --git a/packages/host-kit/src/internal/archive-safety.test.ts b/packages/host-kit/src/internal/archive-safety.test.ts index 9b0f4f8ae..22697566e 100644 --- a/packages/host-kit/src/internal/archive-safety.test.ts +++ b/packages/host-kit/src/internal/archive-safety.test.ts @@ -29,7 +29,7 @@ test('archive paths reject absolute, escaping, drive, UNC, NUL, and backslash fo 'safe/../../escape', 'C:/drive', '//server/share', - 'safe\\windows', + String.raw`safe\windows`, 'nul\0entry', '.', ]) { diff --git a/packages/host-kit/src/internal/diagnostics.ts b/packages/host-kit/src/internal/diagnostics.ts index 881ba5807..01fa77d7d 100644 --- a/packages/host-kit/src/internal/diagnostics.ts +++ b/packages/host-kit/src/internal/diagnostics.ts @@ -235,7 +235,7 @@ export function flushDiagnosticsToSessionFile( const dayDir = new Date().toISOString().slice(0, 10); const baseDir = path.join(os.homedir(), '.agent-device', 'logs', sessionDir, dayDir); fs.mkdirSync(baseDir, { recursive: true }); - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-'); const filePath = path.join(baseDir, `${timestamp}-${scope.diagnosticId}.ndjson`); // Same single-value-replacement re-pass as the logPath branch above. const lines = scope.events.map((entry) => @@ -282,7 +282,7 @@ function replaceSensitiveValues(value: unknown, sensitiveValues: readonly string } function sanitizePathPart(value: string): string { - return value.replace(/[^a-zA-Z0-9._-]/g, '_'); + return value.replaceAll(/[^a-zA-Z0-9._-]/g, '_'); } function appendDiagnosticLine(scope: DiagnosticsScope, logPath: string, line: string): void { diff --git a/packages/host-kit/src/internal/exec.ts b/packages/host-kit/src/internal/exec.ts index 166853c7b..0e71f66f1 100644 --- a/packages/host-kit/src/internal/exec.ts +++ b/packages/host-kit/src/internal/exec.ts @@ -163,10 +163,10 @@ function runSpawnedCommand( if (!options.binaryStdout) child.stdout.setEncoding('utf8'); child.stderr.setEncoding('utf8'); - void writeChildStdin(child, options.stdin).catch((err: unknown) => { + void writeChildStdin(child, options.stdin).catch((error: unknown) => { if (abort.didAbort || didTimeout) return; - if (isEpipeError(err)) return; - reject(createStdinError(executable, cmd, args, err)); + if (isEpipeError(error)) return; + reject(createStdinError(executable, cmd, args, error)); killProcessTree(child, options.detached); }); diff --git a/packages/host-kit/src/internal/host-process.ts b/packages/host-kit/src/internal/host-process.ts index 5ce210c8d..94349eab3 100644 --- a/packages/host-kit/src/internal/host-process.ts +++ b/packages/host-kit/src/internal/host-process.ts @@ -38,8 +38,8 @@ export function isProcessAlive(pid: number): boolean { try { process.kill(pid, 0); return true; - } catch (err) { - return (err as NodeJS.ErrnoException).code === 'EPERM'; + } catch (error) { + return (error as NodeJS.ErrnoException).code === 'EPERM'; } } @@ -48,8 +48,8 @@ export function isProcessGroupAlive(pid: number): boolean { try { process.kill(-pid, 0); return true; - } catch (err) { - return (err as NodeJS.ErrnoException).code === 'EPERM'; + } catch (error) { + return (error as NodeJS.ErrnoException).code === 'EPERM'; } } diff --git a/packages/host-kit/src/internal/request-cancel.ts b/packages/host-kit/src/internal/request-cancel.ts index c5f07a3be..6d0b65516 100644 --- a/packages/host-kit/src/internal/request-cancel.ts +++ b/packages/host-kit/src/internal/request-cancel.ts @@ -22,7 +22,7 @@ export function resolveRequestTrackingId( const normalizedSeed = rawSeed .trim() - .replace(/[^a-zA-Z0-9_-]/g, '_') + .replaceAll(/[^a-zA-Z0-9_-]/g, '_') .slice(0, 32) || 'generated'; const nonce = Math.random().toString(36).slice(2, 10); return `req:${normalizedSeed}:${process.pid}:${Date.now()}:${nonce}`; diff --git a/packages/host-kit/src/internal/retry.ts b/packages/host-kit/src/internal/retry.ts index 46d17b7b2..36668d0e3 100644 --- a/packages/host-kit/src/internal/retry.ts +++ b/packages/host-kit/src/internal/retry.ts @@ -120,9 +120,9 @@ export async function retryWithPolicy( remainingMs: options.deadline?.remainingMs(), }); return result; - } catch (err) { - lastError = err; - const reason = options.classifyReason?.(err); + } catch (error) { + lastError = error; + const reason = options.classifyReason?.(error); const failedEvent: RetryTelemetryEvent = { phase: options.phase, event: 'attempt_failed', @@ -135,7 +135,7 @@ export async function retryWithPolicy( options.onEvent?.(failedEvent); publishRetryEvent(failedEvent); if (attempt >= merged.maxAttempts) break; - if (merged.shouldRetry && !merged.shouldRetry(err, attempt)) break; + if (merged.shouldRetry && !merged.shouldRetry(error, attempt)) break; const delay = computeDelay(merged.baseDelayMs, merged.maxDelayMs, merged.jitter, attempt); const boundedDelay = options.deadline ? Math.min(delay, options.deadline.remainingMs()) diff --git a/packages/host-kit/src/internal/shell-quote.test.ts b/packages/host-kit/src/internal/shell-quote.test.ts index 17094de55..337adf97e 100644 --- a/packages/host-kit/src/internal/shell-quote.test.ts +++ b/packages/host-kit/src/internal/shell-quote.test.ts @@ -5,7 +5,7 @@ import { shellQuote, shellQuoteIfNeeded } from './shell-quote.ts'; test('shellQuote single-quotes and escapes POSIX shell arguments', () => { assert.equal(shellQuote('plain'), "'plain'"); assert.equal(shellQuote(''), "''"); - assert.equal(shellQuote("qa ios; echo 'oops'"), "'qa ios; echo '\\''oops'\\'''"); + assert.equal(shellQuote("qa ios; echo 'oops'"), String.raw`'qa ios; echo '\''oops'\'''`); }); test('shellQuoteIfNeeded keeps safe command arguments readable', () => { @@ -17,5 +17,5 @@ test('shellQuoteIfNeeded quotes unsafe command arguments', () => { assert.equal(shellQuoteIfNeeded('my session'), "'my session'"); assert.equal(shellQuoteIfNeeded(''), "''"); assert.equal(shellQuoteIfNeeded('café'), "'café'"); - assert.equal(shellQuoteIfNeeded("qa ios; echo 'oops'"), "'qa ios; echo '\\''oops'\\'''"); + assert.equal(shellQuoteIfNeeded("qa ios; echo 'oops'"), String.raw`'qa ios; echo '\''oops'\'''`); }); diff --git a/packages/host-kit/src/internal/shell-quote.ts b/packages/host-kit/src/internal/shell-quote.ts index a2122a615..371315ecd 100644 --- a/packages/host-kit/src/internal/shell-quote.ts +++ b/packages/host-kit/src/internal/shell-quote.ts @@ -1,7 +1,7 @@ const SAFE_SHELL_ARG = /^[A-Za-z0-9_@%+=:,./-]+$/; export function shellQuote(value: string): string { - return `'${value.replaceAll("'", "'\\''")}'`; + return `'${value.replaceAll("'", String.raw`'\''`)}'`; } export function shellQuoteIfNeeded(value: string): string { diff --git a/packages/kernel/src/device.ts b/packages/kernel/src/device.ts index d637156fa..f66c86143 100644 --- a/packages/kernel/src/device.ts +++ b/packages/kernel/src/device.ts @@ -530,7 +530,7 @@ function throwNoDevicesFound(selector: DeviceSelector, context: DeviceSelectionC } function normalizeDeviceName(value: string): string { - return value.toLowerCase().replace(/_/g, ' ').replace(/\s+/g, ' ').trim(); + return value.toLowerCase().replaceAll('_', ' ').replaceAll(/\s+/g, ' ').trim(); } function compareAppleDevicesForSelection( diff --git a/packages/kernel/src/source-value.ts b/packages/kernel/src/source-value.ts index 0020cc708..98a18d6c8 100644 --- a/packages/kernel/src/source-value.ts +++ b/packages/kernel/src/source-value.ts @@ -14,8 +14,8 @@ const BOOLEAN_FALSE_VALUES = new Set(['0', 'false', 'no', 'off']); export function buildPrimaryEnvVarName(key: string): string { return `AGENT_DEVICE_${key - .replace(/([A-Z])/g, '_$1') - .replace(/[^A-Za-z0-9_]/g, '_') + .replaceAll(/([A-Z])/g, '_$1') + .replaceAll(/[^A-Za-z0-9_]/g, '_') .toUpperCase()}`; } diff --git a/packages/maestro/src/internal/__tests__/selector-regex.test.ts b/packages/maestro/src/internal/__tests__/selector-regex.test.ts index 198aedcd4..ef53e9894 100644 --- a/packages/maestro/src/internal/__tests__/selector-regex.test.ts +++ b/packages/maestro/src/internal/__tests__/selector-regex.test.ts @@ -2,14 +2,14 @@ import { expect, test } from 'vitest'; import { literalFromMaestroRegex, matchesMaestroRegex } from '../selector-regex.ts'; test('matches Maestro regex as case-insensitive full patterns', () => { - expect(matchesMaestroRegex('Item 22\nready', 'item \\d{2}.+ready')).toBe(true); + expect(matchesMaestroRegex('Item 22\nready', String.raw`item \d{2}.+ready`)).toBe(true); expect(matchesMaestroRegex('Item 22 ready later', 'Item 22 ready')).toBe(false); expect(matchesMaestroRegex('Item [ready', 'item [ready')).toBe(true); }); test('extracts only patterns that are exact literals', () => { expect(literalFromMaestroRegex('checkout-submit')).toBe('checkout-submit'); - expect(literalFromMaestroRegex('price\\.usd')).toBe('price.usd'); - expect(literalFromMaestroRegex('item \\d{2}')).toBeUndefined(); + expect(literalFromMaestroRegex(String.raw`price\.usd`)).toBe('price.usd'); + expect(literalFromMaestroRegex(String.raw`item \d{2}`)).toBeUndefined(); expect(literalFromMaestroRegex('item [ready')).toBe('item [ready'); }); diff --git a/packages/maestro/src/internal/engine-context.ts b/packages/maestro/src/internal/engine-context.ts index 104d4c05a..4c5782841 100644 --- a/packages/maestro/src/internal/engine-context.ts +++ b/packages/maestro/src/internal/engine-context.ts @@ -108,7 +108,7 @@ function resolveValue( resolving = new Set(), failOnUnresolved = true, ): string { - const resolved = value.replace(/\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/g, (match, key: string) => { + const resolved = value.replaceAll(/\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/g, (match, key: string) => { if (!Object.hasOwn(values, key)) { if (!failOnUnresolved) return match; throw new AppError('INVALID_ARGS', `Maestro variable "${key}" is not defined.`); diff --git a/packages/maestro/src/internal/selector-regex.ts b/packages/maestro/src/internal/selector-regex.ts index 0514ef9ce..b03d09825 100644 --- a/packages/maestro/src/internal/selector-regex.ts +++ b/packages/maestro/src/internal/selector-regex.ts @@ -1,4 +1,4 @@ -const REGEX_SYNTAX = new Set('^$\\.*+?()[]{}|'); +const REGEX_SYNTAX = new Set(String.raw`^$\.*+?()[]{}|`); export function matchesMaestroRegex(value: string, pattern: string): boolean { try { diff --git a/packages/maestro/src/internal/shared.ts b/packages/maestro/src/internal/shared.ts index fa9dd74c6..2170823f9 100644 --- a/packages/maestro/src/internal/shared.ts +++ b/packages/maestro/src/internal/shared.ts @@ -8,7 +8,7 @@ export { canonicalJson } from '@agent-device/kernel/collections'; export { pointInsideRect } from '@agent-device/kernel/rect-center'; export function normalizeText(value: string): string { - return value.trim().toLowerCase().replace(/\s+/g, ' '); + return value.trim().toLowerCase().replaceAll(/\s+/g, ' '); } export function extractNodeText(node: SnapshotNode): string { diff --git a/packages/platform-android/src/adb-executor-host.test.ts b/packages/platform-android/src/adb-executor-host.test.ts index 416a7df04..30cc73b20 100644 --- a/packages/platform-android/src/adb-executor-host.test.ts +++ b/packages/platform-android/src/adb-executor-host.test.ts @@ -37,7 +37,7 @@ test('the local host arm always obtains a result before applying the shared fail const error = await runAndroidHostAdb(['devices']).then( () => assert.fail('expected the host adb call to reject'), - (cause: unknown) => cause, + (error: unknown) => error, ); assert.deepEqual(receivedOptions, { allowFailure: true }); diff --git a/packages/platform-android/src/inventory-parsers.ts b/packages/platform-android/src/inventory-parsers.ts index 7c17936ee..768ace23f 100644 --- a/packages/platform-android/src/inventory-parsers.ts +++ b/packages/platform-android/src/inventory-parsers.ts @@ -5,7 +5,7 @@ export function isAndroidEmulatorSerial(serial: string): boolean { } export function normalizeAndroidDeviceName(value: string): string { - return value.toLowerCase().replace(/_/g, ' ').replace(/\s+/g, ' ').trim(); + return value.toLowerCase().replaceAll('_', ' ').replaceAll(/\s+/g, ' ').trim(); } export type AndroidDeviceEntry = Readonly<{ diff --git a/packages/platform-android/src/inventory.ts b/packages/platform-android/src/inventory.ts index 91d3acac2..e56969586 100644 --- a/packages/platform-android/src/inventory.ts +++ b/packages/platform-android/src/inventory.ts @@ -137,10 +137,10 @@ async function resolveDeviceName( context: AndroidInventoryContext, entry: AndroidDeviceEntry, ): Promise { - const model = entry.rawModel.replace(/_/g, ' ').trim(); + const model = entry.rawModel.replaceAll('_', ' ').trim(); if (!isAndroidEmulatorSerial(entry.serial)) return model || entry.serial; const avdName = await resolveEmulatorAvdName(context, entry.serial); - return avdName?.replace(/_/g, ' ') || model || entry.serial; + return avdName?.replaceAll('_', ' ') || model || entry.serial; } async function resolveEmulatorAvdName( diff --git a/packages/platform-android/src/network/runtime.ts b/packages/platform-android/src/network/runtime.ts index 7f419d742..3799e5375 100644 --- a/packages/platform-android/src/network/runtime.ts +++ b/packages/platform-android/src/network/runtime.ts @@ -169,7 +169,7 @@ function collectPackagePids( currentPid: string | undefined, ): readonly string[] { const pids = new Set(currentPid ? [currentPid] : []); - const packagePattern = appBundleId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const packagePattern = appBundleId.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`); const patterns = [ new RegExp(`\\bStart proc\\s+(\\d+):${packagePattern}(?:\\b|/)`, 'i'), new RegExp(`\\b(\\d+):${packagePattern}(?:\\b|/)`, 'i'), diff --git a/packages/platform-android/src/observation.ts b/packages/platform-android/src/observation.ts index 0ae6eb97e..ab0c37c7c 100644 --- a/packages/platform-android/src/observation.ts +++ b/packages/platform-android/src/observation.ts @@ -186,7 +186,7 @@ function parseDialogFocus(segment: string, raw: string): AndroidBlockingDialogFo raw, }; } - const responding = RESPONDING_TITLE.exec(segment)?.[1]?.trim().replace(/\s+/g, ' '); + const responding = RESPONDING_TITLE.exec(segment)?.[1]?.trim().replaceAll(/\s+/g, ' '); if (!responding) return null; const packageName = PACKAGE_NAME.exec(responding)?.[1]; return { @@ -201,5 +201,5 @@ function markerPattern(markers: readonly string[]): RegExp { } function escapeRegExp(value: string): string { - return value.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`); + return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`); } diff --git a/packages/platform-android/src/perf/runtime.ts b/packages/platform-android/src/perf/runtime.ts index 2c5e752f6..7658cc243 100644 --- a/packages/platform-android/src/perf/runtime.ts +++ b/packages/platform-android/src/perf/runtime.ts @@ -97,5 +97,5 @@ function androidMemorySnapshotSupport(): PerfData { } function timestampToken(): string { - return new Date().toISOString().replace(/[:.]/g, '-'); + return new Date().toISOString().replaceAll(/[:.]/g, '-'); } diff --git a/packages/platform-android/src/readiness/runtime.ts b/packages/platform-android/src/readiness/runtime.ts index 80aac7400..5635ecf37 100644 --- a/packages/platform-android/src/readiness/runtime.ts +++ b/packages/platform-android/src/readiness/runtime.ts @@ -161,5 +161,5 @@ function isRunningEmulator(device: DeviceInfo): boolean { } function normalizeName(value: string): string { - return value.toLowerCase().replace(/_/g, ' ').replace(/\s+/g, ' ').trim(); + return value.toLowerCase().replaceAll('_', ' ').replaceAll(/\s+/g, ' ').trim(); } diff --git a/packages/platform-apple/src/logs/log-predicate.ts b/packages/platform-apple/src/logs/log-predicate.ts index b1718ed4a..8669df554 100644 --- a/packages/platform-apple/src/logs/log-predicate.ts +++ b/packages/platform-apple/src/logs/log-predicate.ts @@ -58,5 +58,5 @@ export function buildIosDeviceConsoleLaunchArgs(deviceId: string, appBundleId: s } function escapePredicateString(value: string): string { - return value.replaceAll('\\', '\\\\').replaceAll('"', '\\"'); + return value.replaceAll('\\', String.raw`\\`).replaceAll('"', String.raw`\"`); } diff --git a/packages/platform-apple/src/network/runtime.ts b/packages/platform-apple/src/network/runtime.ts index bbf67110f..25541c324 100644 --- a/packages/platform-apple/src/network/runtime.ts +++ b/packages/platform-apple/src/network/runtime.ts @@ -104,7 +104,7 @@ async function recoverSimulatorTraffic( } function buildPredicate(appBundleId: string): string { - const value = appBundleId.replaceAll('\\', '\\\\').replaceAll('"', '\\"'); + const value = appBundleId.replaceAll('\\', String.raw`\\`).replaceAll('"', String.raw`\"`); return [ `subsystem == "${value}"`, `subsystem CONTAINS "${value}"`, diff --git a/packages/platform-apple/src/perf/runtime.ts b/packages/platform-apple/src/perf/runtime.ts index b0bfd14ba..b821ff7ca 100644 --- a/packages/platform-apple/src/perf/runtime.ts +++ b/packages/platform-apple/src/perf/runtime.ts @@ -73,5 +73,5 @@ export function createApplePerfOperations( } function timestampToken(): string { - return new Date().toISOString().replace(/[:.]/g, '-'); + return new Date().toISOString().replaceAll(/[:.]/g, '-'); } diff --git a/packages/platform-apple/src/runner/__tests__/runner-macos-products.test.ts b/packages/platform-apple/src/runner/__tests__/runner-macos-products.test.ts index dfa508d93..1e64378d8 100644 --- a/packages/platform-apple/src/runner/__tests__/runner-macos-products.test.ts +++ b/packages/platform-apple/src/runner/__tests__/runner-macos-products.test.ts @@ -46,7 +46,7 @@ test('repair fails when re-signing leaves the product unverifiable', async () => XCTESTRUN_PATH, ).then( () => null, - (thrown: unknown) => thrown, + (error: unknown) => error, ); assert.ok(error instanceof AppError); diff --git a/packages/platform-apple/src/runner/__tests__/runner-usbmux-boundary-faults.test.ts b/packages/platform-apple/src/runner/__tests__/runner-usbmux-boundary-faults.test.ts index e0275d16c..49fc824c7 100644 --- a/packages/platform-apple/src/runner/__tests__/runner-usbmux-boundary-faults.test.ts +++ b/packages/platform-apple/src/runner/__tests__/runner-usbmux-boundary-faults.test.ts @@ -28,7 +28,7 @@ test('usbmux read timeout is bounded at the packet seam', async () => { await waitForClose(socket); }); - const error = await post(socketPath, 60).catch((value: unknown) => value); + const error = await post(socketPath, 60).catch((error: unknown) => error); assert.ok(error instanceof AppError); assert.equal(error.code, 'COMMAND_FAILED'); assert.match(error.message, /Timed out reading usbmuxd response/); @@ -69,7 +69,7 @@ test('usbmux partial packets fail as a closed response, not as a successful devi socket.end(header); }); - const error = await post(socketPath, 1_000).catch((value: unknown) => value); + const error = await post(socketPath, 1_000).catch((error: unknown) => error); assert.ok(error instanceof AppError); assert.equal(error.code, 'COMMAND_FAILED'); assert.equal(error.message, 'usbmuxd closed the connection unexpectedly'); @@ -84,7 +84,7 @@ test('usbmux malformed packet lengths stay typed at the protocol seam', async () socket.end(header); }); - const error = await post(socketPath, 1_000).catch((value: unknown) => value); + const error = await post(socketPath, 1_000).catch((error: unknown) => error); assert.ok(error instanceof AppError); assert.equal(error.code, 'COMMAND_FAILED'); assert.equal(error.message, 'Invalid usbmuxd response length'); diff --git a/packages/platform-apple/src/runner/__tests__/runner-usbmux.test.ts b/packages/platform-apple/src/runner/__tests__/runner-usbmux.test.ts index 81cc06d0d..9f3362d3d 100644 --- a/packages/platform-apple/src/runner/__tests__/runner-usbmux.test.ts +++ b/packages/platform-apple/src/runner/__tests__/runner-usbmux.test.ts @@ -124,7 +124,7 @@ async function serveUsbmuxDevices( } const connectRequest = await readPacket(socket); const match = /DeviceID<\/key>(\d+)<\/integer>/.exec( - connectRequest.replace(/\s+/g, ''), + connectRequest.replaceAll(/\s+/g, ''), ); connectedDeviceId = match ? Number(match[1]) : undefined; socket.end( diff --git a/packages/platform-apple/src/runner/runner-artifact-env.ts b/packages/platform-apple/src/runner/runner-artifact-env.ts index 319911d82..e385f2985 100644 --- a/packages/platform-apple/src/runner/runner-artifact-env.ts +++ b/packages/platform-apple/src/runner/runner-artifact-env.ts @@ -41,7 +41,7 @@ export async function prepareXctestrunWithEnv( const configuredEnvDir = options.iosXctestEnvDir?.trim(); const dir = configuredEnvDir ? path.resolve(configuredEnvDir) : path.dirname(xctestrunPath); fs.mkdirSync(dir, { recursive: true }); - const safeSuffix = suffix.replace(/[^a-zA-Z0-9._-]/g, '_'); + const safeSuffix = suffix.replaceAll(/[^a-zA-Z0-9._-]/g, '_'); const tmpJsonPath = path.join(dir, `AgentDeviceRunner.env.${safeSuffix}.json`); const tmpXctestrunPath = path.join(dir, `AgentDeviceRunner.env.${safeSuffix}.xctestrun`); const parsed = await readXctestrunPlist(xctestrunPath); @@ -76,10 +76,10 @@ async function readXctestrunPlist(xctestrunPath: string): Promise result.ok).length; + const detectedFailedStepIndex = results.findIndex((result) => !result.ok); const failedStepIndex = typeof data.failedStepIndex === 'number' && Number.isFinite(data.failedStepIndex) ? data.failedStepIndex - : results.findIndex((result) => !result.ok) >= 0 - ? results.findIndex((result) => !result.ok) + : detectedFailedStepIndex >= 0 + ? detectedFailedStepIndex : undefined; if (failedStepIndex !== undefined) { diff --git a/packages/platform-apple/src/runner/runner-startup-transport.ts b/packages/platform-apple/src/runner/runner-startup-transport.ts index 4503ae386..b80643c94 100644 --- a/packages/platform-apple/src/runner/runner-startup-transport.ts +++ b/packages/platform-apple/src/runner/runner-startup-transport.ts @@ -373,11 +373,11 @@ async function tryRunnerEndpoints( Math.min(RUNNER_CONNECT_REQUEST_TIMEOUT_MS, remainingMs), signal, ); - } catch (err) { - if (signal?.aborted || isRequestCanceledError(err)) { + } catch (error) { + if (signal?.aborted || isRequestCanceledError(error)) { throw createRequestCanceledError(); } - onError(endpoint, err); + onError(endpoint, error); } } return null; @@ -399,11 +399,11 @@ async function tryRunnerSimulatorEndpoint( try { const simResponse = await postCommandViaSimulator(device, port, command, remainingMs, signal); return new Response(simResponse.body, { status: simResponse.status }); - } catch (err) { - if (signal?.aborted || isRequestCanceledError(err)) { + } catch (error) { + if (signal?.aborted || isRequestCanceledError(error)) { throw createRequestCanceledError(); } - onError(err); + onError(error); return null; } } diff --git a/packages/provider-limrun/src/android.ts b/packages/provider-limrun/src/android.ts index 721d8fb3e..e20a25eab 100644 --- a/packages/provider-limrun/src/android.ts +++ b/packages/provider-limrun/src/android.ts @@ -244,6 +244,6 @@ function tcpEndpoint(port: number): LimrunPortReverseEndpoint { function buildAndroidAssetName(packageName: string | undefined, artifactPath: string): string { const extension = path.extname(artifactPath) || '.apk'; - const prefix = packageName?.replace(/[^a-zA-Z0-9_.-]+/g, '-') || 'android-app'; + const prefix = packageName?.replaceAll(/[^a-zA-Z0-9_.-]+/g, '-') || 'android-app'; return `${prefix}${extension}`; } diff --git a/packages/provider-webdriver/src/provider-definitions.ts b/packages/provider-webdriver/src/provider-definitions.ts index 81289f42b..b2de85b68 100644 --- a/packages/provider-webdriver/src/provider-definitions.ts +++ b/packages/provider-webdriver/src/provider-definitions.ts @@ -348,5 +348,5 @@ function readAwsInteractionMode( } function dasherize(value: string): string { - return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`); + return value.replaceAll(/[A-Z]/g, (match) => `-${match.toLowerCase()}`); } diff --git a/packages/provision-kit/src/install-source-download.test.ts b/packages/provision-kit/src/install-source-download.test.ts index 5484ea0a4..d71ae77be 100644 --- a/packages/provision-kit/src/install-source-download.test.ts +++ b/packages/provision-kit/src/install-source-download.test.ts @@ -70,7 +70,7 @@ test('download errors do not disclose URL credentials or query values', async () url: 'https://private-user:private-pass@example.com/app?token=private-query', headers: { authorization: 'private-header' }, signal: new AbortController().signal, - }).catch((caught: unknown) => caught); + }).catch((error: unknown) => error); assert.match((error as Error).message, /credentials are not allowed/i); const serialized = JSON.stringify(error); for (const secret of ['private-user', 'private-pass', 'private-query', 'private-header']) { @@ -104,7 +104,7 @@ test('download identifies the daemon as the requester and preserves the network tempDir: tempRoot, url: 'https://example.com/app.apk', signal: new AbortController().signal, - }).catch((caught: unknown) => caught); + }).catch((error: unknown) => error); assert.ok(error instanceof Error); assert.equal(error.message, 'The daemon failed to fetch the app source'); diff --git a/packages/replay-test/src/internal/session-test-artifacts.ts b/packages/replay-test/src/internal/session-test-artifacts.ts index 90dbc459c..1336e1818 100644 --- a/packages/replay-test/src/internal/session-test-artifacts.ts +++ b/packages/replay-test/src/internal/session-test-artifacts.ts @@ -27,8 +27,8 @@ export function buildReplayTestArtifactSlug(filePath: string, cwd?: string): str trimEdgeDashes( value .toLowerCase() - .replace(/[\\/]+/g, '__') - .replace(/[^a-z0-9._-]+/g, '-'), + .replaceAll(/[\\/]+/g, '__') + .replaceAll(/[^a-z0-9._-]+/g, '-'), ) || 'test' ); } diff --git a/packages/replay-test/src/internal/session-test-discovery.ts b/packages/replay-test/src/internal/session-test-discovery.ts index 2e7cebb52..630c8e2fb 100644 --- a/packages/replay-test/src/internal/session-test-discovery.ts +++ b/packages/replay-test/src/internal/session-test-discovery.ts @@ -88,14 +88,14 @@ export function buildReplayTestSessionName( attemptIndex = 0, ): string { const baseName = path.basename(filePath, path.extname(filePath)); - const slug = trimEdgeDashes(baseName.toLowerCase().replace(/[^a-z0-9]+/g, '-')); + const slug = trimEdgeDashes(baseName.toLowerCase().replaceAll(/[^a-z0-9]+/g, '-')); const testNumber = caseIndex + 1; return `${sessionName}:test:${suiteInvocationId}:${testNumber}${slug ? `-${slug}` : ''}:attempt-${attemptIndex + 1}`; } export function buildReplayTestInvocationId(requestId?: string): string { const raw = requestId?.trim() || `${process.pid}-${Date.now().toString(36)}`; - const normalized = trimEdgeDashes(raw.toLowerCase().replace(/[^a-z0-9]+/g, '-')); + const normalized = trimEdgeDashes(raw.toLowerCase().replaceAll(/[^a-z0-9]+/g, '-')); return normalized || 'suite'; } diff --git a/packages/replay-test/src/internal/session-test.ts b/packages/replay-test/src/internal/session-test.ts index af48e3ccd..e2319f769 100644 --- a/packages/replay-test/src/internal/session-test.ts +++ b/packages/replay-test/src/internal/session-test.ts @@ -126,8 +126,8 @@ export async function runReplayTestSuite( const data = summarizeReplayTestResults(plan.total, results, Date.now() - suiteStartedAt); return { status: 'completed', data }; - } catch (err) { - const appErr = asAppError(err); + } catch (error) { + const appErr = asAppError(error); return { status: 'failed', error: { code: appErr.code, message: appErr.message } }; } } diff --git a/packages/selectors/src/internal/__tests__/property-arbitraries.ts b/packages/selectors/src/internal/__tests__/property-arbitraries.ts index 991aa5168..d34121d12 100644 --- a/packages/selectors/src/internal/__tests__/property-arbitraries.ts +++ b/packages/selectors/src/internal/__tests__/property-arbitraries.ts @@ -36,8 +36,8 @@ const SELECTOR_VALUE_HAZARDS = [ "it's", 'say "hi"', '\\', - '\\"', - 'a\\\\b', + String.raw`\"`, + String.raw`a\\b`, 'a || b', 'key=value', 'line\nbreak', diff --git a/packages/selectors/src/internal/find.ts b/packages/selectors/src/internal/find.ts index b79f6a321..31352ffa7 100644 --- a/packages/selectors/src/internal/find.ts +++ b/packages/selectors/src/internal/find.ts @@ -130,14 +130,14 @@ function matchRole(value: string | undefined, query: string): number { } export function normalizeText(value: string): string { - return value.trim().toLowerCase().replace(/\s+/g, ' '); + return value.trim().toLowerCase().replaceAll(/\s+/g, ' '); } function normalizeRole(value: string): string { let normalized = value.trim(); if (!normalized) return ''; const lastSegment = normalized.split('.').pop() ?? normalized; - normalized = lastSegment.replace(/XCUIElementType/gi, '').toLowerCase(); + normalized = lastSegment.replaceAll(/XCUIElementType/gi, '').toLowerCase(); return normalized; } diff --git a/packages/selectors/src/internal/match.test.ts b/packages/selectors/src/internal/match.test.ts index f1f0401d9..1b22bdda9 100644 --- a/packages/selectors/src/internal/match.test.ts +++ b/packages/selectors/src/internal/match.test.ts @@ -18,7 +18,7 @@ test('resolveSelectorChain matches newline labels decoded from replay selectors' hittable: true, }, ]; - const chain = parseSelectorChain('label="Switch\\nMy Community"'); + const chain = parseSelectorChain(String.raw`label="Switch\nMy Community"`); const resolved = resolveSelectorChain(newlineNodes, chain, { platform: 'ios', requireRect: true, diff --git a/packages/selectors/src/internal/parse.test.ts b/packages/selectors/src/internal/parse.test.ts index 8561fa85c..1905a8fea 100644 --- a/packages/selectors/src/internal/parse.test.ts +++ b/packages/selectors/src/internal/parse.test.ts @@ -53,7 +53,7 @@ test('parseSelectorChain rejects unknown keys and malformed quotes', () => { }); test('parseSelectorChain handles quoted values ending in escaped backslashes', () => { - const chain = parseSelectorChain('label="path\\\\" || id=auth_continue'); + const chain = parseSelectorChain(String.raw`label="path\\" || id=auth_continue`); assert.equal(chain.selectors.length, 2); assert.equal(chain.selectors[0]!.terms[0]!.value, 'path\\'); }); @@ -61,10 +61,10 @@ test('parseSelectorChain handles quoted values ending in escaped backslashes', ( test('parseSelectorChain decodes escaped selector string values', () => { const chain = parseSelectorChain( [ - 'label="Switch\\nMy Community"', - 'value="A\\tB\\rC\\bD\\fE\\/F"', - 'id="item_\\u0031\\uD83D\\uDE00"', - "text='It\\'s OK'", + String.raw`label="Switch\nMy Community"`, + String.raw`value="A\tB\rC\bD\fE\/F"`, + String.raw`id="item_\u0031\uD83D\uDE00"`, + String.raw`text='It\'s OK'`, ].join(' '), ); @@ -75,14 +75,14 @@ test('parseSelectorChain decodes escaped selector string values', () => { }); test('parseSelectorChain preserves malformed and unknown selector string escapes', () => { - const chain = parseSelectorChain('label="bad\\u12" value="keep\\q"'); + const chain = parseSelectorChain(String.raw`label="bad\u12" value="keep\q"`); - assert.equal(chain.selectors[0]!.terms[0]!.value, 'bad\\u12'); - assert.equal(chain.selectors[0]!.terms[1]!.value, 'keep\\q'); + assert.equal(chain.selectors[0]!.terms[0]!.value, String.raw`bad\u12`); + assert.equal(chain.selectors[0]!.terms[1]!.value, String.raw`keep\q`); }); test('parseSelectorChain preserves literal escaped control sequences when double escaped', () => { - const chain = parseSelectorChain('label="foo\\\\nbar"'); + const chain = parseSelectorChain(String.raw`label="foo\\nbar"`); - assert.equal(chain.selectors[0]!.terms[0]!.value, 'foo\\nbar'); + assert.equal(chain.selectors[0]!.terms[0]!.value, String.raw`foo\nbar`); }); diff --git a/packages/xml/src/internal/entities.ts b/packages/xml/src/internal/entities.ts index fa879fe2e..1fbcfb9f3 100644 --- a/packages/xml/src/internal/entities.ts +++ b/packages/xml/src/internal/entities.ts @@ -1,5 +1,5 @@ export function decodeXmlCharacterReferences(value: string): string { - return value.replace( + return value.replaceAll( /&(#(?:x|X)[0-9a-fA-F]+|#[0-9]+|amp|lt|gt|quot|apos);/g, (entity, body: string) => { switch (body) { diff --git a/packages/xml/src/internal/parser.ts b/packages/xml/src/internal/parser.ts index 91c230e8b..8ca1b58fa 100644 --- a/packages/xml/src/internal/parser.ts +++ b/packages/xml/src/internal/parser.ts @@ -66,7 +66,7 @@ class LimitedXmlParser { private assertFullyClosed(): void { if (this.stack.length > 0) { - const node = this.stack[this.stack.length - 1]; + const node = this.stack.at(-1); throw new Error(`Unclosed XML tag <${node?.name ?? 'unknown'}>.`); } } @@ -170,7 +170,7 @@ class LimitedXmlParser { private appendText(text: string, decodeEntities: boolean): void { const trimmed = text.trim(); if (!trimmed) return; - const node = this.stack[this.stack.length - 1]; + const node = this.stack.at(-1); if (!node) return; // Preserve fast-xml-parser's trimValues behavior for each text segment we keep. node.text = `${node.text ?? ''}${ @@ -179,7 +179,7 @@ class LimitedXmlParser { } private addNode(node: XmlNode): void { - const parent = this.stack[this.stack.length - 1]; + const parent = this.stack.at(-1); if (parent) { parent.children.push(node); } else { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d086575e7..eaba2f807 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,6 +99,12 @@ importers: '@chenglou/freerange': specifier: ^0.0.4 version: 0.0.4 + '@nkzw/eslint-plugin': + specifier: ^2.0.0 + version: 2.0.0(eslint@10.9.1(supports-color@7.2.0)) + '@nkzw/oxlint-config': + specifier: ^1.3.0 + version: 1.3.0(eslint@10.9.1(supports-color@7.2.0))(oxlint@1.79.0)(supports-color@7.2.0)(typescript@7.0.2) '@stryker-mutator/core': specifier: 10.0.0 version: 10.0.0(@types/node@22.20.1) @@ -126,6 +132,12 @@ importers: ai: specifier: ^7.0.68 version: 7.0.68(zod@4.4.3) + eslint-plugin-no-only-tests: + specifier: ^3.4.0 + version: 3.4.0 + eslint-plugin-perfectionist: + specifier: ^5.10.1 + version: 5.10.1(eslint@10.9.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@7.0.2) fallow: specifier: ^2.104.0 version: 2.104.0 @@ -673,6 +685,36 @@ packages: '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.7.0': + resolution: {integrity: sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.2': + resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@fallow-cli/darwin-arm64@2.104.0': resolution: {integrity: sha512-5kKt3JuoYVz5/9PpHdu8u41vOZ6CzIu+5SqNBR8MktGLVMBSERm85+NN63XqgbZrVwIC/5uiP8eHQ0RYw0qhHg==} cpu: [arm64] @@ -713,6 +755,26 @@ packages: cpu: [x64] os: [win32] + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@inquirer/ansi@2.0.7': resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} @@ -881,6 +943,16 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@nkzw/eslint-plugin@2.0.0': + resolution: {integrity: sha512-IoU8kOqHfnf7se2dGxMD/7RPm6WVjzjOjWSo7FulRx3lJzuZvXioSkT5Nd82l66DH3Pl2whw74lPT1di3KMwFA==} + peerDependencies: + eslint: '>= 9' + + '@nkzw/oxlint-config@1.3.0': + resolution: {integrity: sha512-ibyyevSgtMwFJ36qP5SVHI86zLA3Zs3Z3e0j4c/69500G6pqe2GhYhEsYaKOxk2gGCXi0CLb1uyxd/xzbVx1OA==} + peerDependencies: + oxlint: '>=1.79.0' + '@oxc-parser/binding-android-arm-eabi@0.146.0': resolution: {integrity: sha512-jBCZIsff/9J/zdK1KSKZREScXOtRE4wYO+MUjrE+5NHGdsslrP4biJA9QJyKwErwS8lkwxH6Z537HaL4CQr0ow==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1552,6 +1624,9 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} @@ -1606,6 +1681,43 @@ packages: '@types/yauzl@3.4.0': resolution: {integrity: sha512-NRPn5w6h8dhcnmx3YIRQcqMywY/+nND/uOkJessedcrowO3C0AssHp3tMJpxKAwOhFOo0OV1y9VtsC5hbKKBAw==} + '@typescript-eslint/project-service@8.68.0': + resolution: {integrity: sha512-5GQtWZCXFcFYux955pvoS02WLc49pXNlvIxocKjS0clvwo3in1RdlzVKyiqQH9vE5AKWFLTaUgeQkOrTS+0Qxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.68.0': + resolution: {integrity: sha512-T5eXpcaJNg8bhjHJ8Rjp68Vq/QBteYtTKY8TZqVNPaUbuz0f6jI9t6aDkylwvalpAB9XTTFeFOjrjXAZ3YvmVA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.68.0': + resolution: {integrity: sha512-F7zrGQfiJHojPwi8vhxZQC1tWtJzvL74cK/nqri2lk8YUXvYaYwl263xOJ69jDWPUk1hmcdoayFwk9lX09npVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.68.0': + resolution: {integrity: sha512-9RnpsGJjrAllCMefGVVsImJM24YurhC0Q1h4UbvivtvOqXmR/vEJge2OoE++z9m6hyg8T1Q8t5SNT6tHSbrxcg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.68.0': + resolution: {integrity: sha512-OKKsD0tYmoNiU5PW2zehO1yO56jYOm1ShYlxon/Z0SJNidAkdVg86eg9ruRuoXf8xfnuWZGbwDsStkoXbZtIIA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.68.0': + resolution: {integrity: sha512-PB5gJMMOg0Q5P1tsgWtEAqQacJXq0qEqRHDX/YJ4FaTMLfZPpHB3gjl2EJuiZyPABxmj4ZQYiY9m1bdAJ5y7tQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.68.0': + resolution: {integrity: sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript/typescript-aix-ppc64@7.0.2': resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} engines: {node: '>=16.20.0'} @@ -1964,6 +2076,9 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} @@ -2199,6 +2314,9 @@ packages: decode-named-character-reference@1.3.0: resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + defu@6.1.7: resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} @@ -2281,10 +2399,62 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} + eslint-plugin-no-only-tests@3.4.0: + resolution: {integrity: sha512-4S3/9Nb7A2tiMcpzEQE9bQSlpeOz6WJkgryBuou/SA8W2x2c8Zf4j0NvTKBjv6qNhF9T79tmkecm/0CHqV0UGg==} + engines: {node: '>=5.0.0'} + + eslint-plugin-perfectionist@5.10.1: + resolution: {integrity: sha512-Kprsp9Us0GqAesYaAIzUViw57xYp5WBqzXrcE0Mtww++E5fexWXYBipMuuD7yvyH4vvpBH0+oJ+OMAmZ0oYXkw==} + engines: {node: ^20.0.0 || >=22.0.0} + peerDependencies: + eslint: ^8.45.0 || ^9.0.0 || ^10.0.0 + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.9.1: + resolution: {integrity: sha512-9VaAkDURekixUQJy0oJYl2DcN6oKMfxay7XzaGYAWQwsb6qfKf+x76R2k1L8kb1boc+FyCAaTA9GmiKaaiaF+A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + estree-util-attach-comments@3.0.0: resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} @@ -2306,6 +2476,10 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + events-universal@1.0.1: resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} @@ -2347,6 +2521,12 @@ packages: fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-string-truncated-width@3.0.3: resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} @@ -2375,6 +2555,21 @@ packages: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} + flexsearch@0.8.212: resolution: {integrity: sha512-wSyJr1GUWoOOIISRu+X2IXiOcVfg9qqBRyCPRUdLMIGJqPzMo+jMRlvE83t14v1j0dRMEaBbER/adQjp6Du2pw==} @@ -2414,6 +2609,10 @@ packages: resolution: {integrity: sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==} engines: {node: '>=20.20.0'} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -2493,6 +2692,10 @@ packages: resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} engines: {node: '>=0.10.0'} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + ignore@7.0.6: resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} engines: {node: '>= 4'} @@ -2504,6 +2707,10 @@ packages: resolution: {integrity: sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==} engines: {node: ^22.18.0 || >=24.0.0} + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -2527,10 +2734,18 @@ packages: is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} @@ -2572,20 +2787,36 @@ packages: engines: {node: '>=6'} hasBin: true + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-rpc-2.0@1.7.1: resolution: {integrity: sha512-JqZjhjAanbpkXIzFE7u8mE/iFblawwlXtONaCvRqI+pyABVz7B4M1EUNpyVW+dZjqgQ2L5HFmZCmOCgUKm00hg==} + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + lightningcss-android-arm64@1.33.0: resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} engines: {node: '>= 12.0.0'} @@ -2660,6 +2891,10 @@ packages: resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} engines: {node: '>= 12.0.0'} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + lodash.groupby@4.6.0: resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} @@ -2926,6 +3161,13 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + natural-orderby@5.0.0: + resolution: {integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==} + engines: {node: '>=18'} + node-emoji@2.2.0: resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==} engines: {node: '>=18'} @@ -2959,6 +3201,10 @@ packages: oniguruma-to-es@4.3.5: resolution: {integrity: sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + oxc-parser@0.146.0: resolution: {integrity: sha512-hrt0Ou2mCL8dLuvfvKWFjRlWaJKt0Xs7CfacAgb9mha0gKW1lSisUzNRyALgItMPIfThxkl8jH2ONP6ZbQXpaA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2989,6 +3235,14 @@ packages: vite-plus: optional: true + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + package-manager-detector@1.8.0: resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} @@ -3011,6 +3265,10 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -3040,6 +3298,10 @@ packages: resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==} engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + pretty-ms@9.3.0: resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} engines: {node: '>=18'} @@ -3060,6 +3322,10 @@ packages: engines: {node: '>=18'} hasBin: true + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + pure-rand@8.4.2: resolution: {integrity: sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==} @@ -3384,6 +3650,12 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + tsdown@0.22.14: resolution: {integrity: sha512-ule7Y+fsAN2iZbLDoo7C4KYljFJNJJ+fLshyn+9gozeTspVersWHxwdGB+Dm2hzA38s6muFnUTl0jK3vJm9ifQ==} engines: {node: ^22.18.0 || >=24.11.0} @@ -3425,6 +3697,10 @@ packages: resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + typed-inject@5.0.0: resolution: {integrity: sha512-0Ql2ORqBORLMdAW89TQKZsb1PQkFGImFfVmncXWe7a+AA3+7dh7Se9exxZowH4kbnlvKEFkMxUYdHUpjYWFJaA==} engines: {node: '>=18'} @@ -3508,6 +3784,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + validate-npm-package-name@5.0.1: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -3625,6 +3904,10 @@ packages: engines: {node: '>=8'} hasBin: true + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -3662,6 +3945,10 @@ packages: resolution: {integrity: sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==} engines: {node: '>=12'} + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + yoctocolors@2.2.0: resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==} engines: {node: '>=18'} @@ -4019,6 +4306,36 @@ snapshots: tslib: 2.8.1 optional: true + '@eslint-community/eslint-utils@4.10.1(eslint@10.9.1(supports-color@7.2.0))': + dependencies: + eslint: 10.9.1(supports-color@7.2.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5(supports-color@7.2.0)': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3(supports-color@7.2.0) + minimatch: 10.2.6 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.7.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.2': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + '@fallow-cli/darwin-arm64@2.104.0': optional: true @@ -4043,6 +4360,22 @@ snapshots: '@fallow-cli/win32-x64-msvc@2.104.0': optional: true + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@inquirer/ansi@2.0.7': {} '@inquirer/checkbox@5.2.1(@types/node@22.20.1)': @@ -4236,6 +4569,21 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true + '@nkzw/eslint-plugin@2.0.0(eslint@10.9.1(supports-color@7.2.0))': + dependencies: + eslint: 10.9.1(supports-color@7.2.0) + + '@nkzw/oxlint-config@1.3.0(eslint@10.9.1(supports-color@7.2.0))(oxlint@1.79.0)(supports-color@7.2.0)(typescript@7.0.2)': + dependencies: + '@nkzw/eslint-plugin': 2.0.0(eslint@10.9.1(supports-color@7.2.0)) + eslint-plugin-no-only-tests: 3.4.0 + eslint-plugin-perfectionist: 5.10.1(eslint@10.9.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@7.0.2) + oxlint: 1.79.0 + transitivePeerDependencies: + - eslint + - supports-color + - typescript + '@oxc-parser/binding-android-arm-eabi@0.146.0': optional: true @@ -4745,6 +5093,8 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/esrecurse@4.3.1': {} + '@types/estree-jsx@1.0.5': dependencies: '@types/estree': 1.0.8 @@ -4799,6 +5149,57 @@ snapshots: dependencies: '@types/node': 26.2.0 + '@typescript-eslint/project-service@8.68.0(supports-color@7.2.0)(typescript@7.0.2)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.68.0(typescript@7.0.2) + '@typescript-eslint/types': 8.68.0 + debug: 4.4.3(supports-color@7.2.0) + typescript: 7.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.68.0': + dependencies: + '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/visitor-keys': 8.68.0 + + '@typescript-eslint/tsconfig-utils@8.68.0(typescript@7.0.2)': + dependencies: + typescript: 7.0.2 + + '@typescript-eslint/types@8.68.0': {} + + '@typescript-eslint/typescript-estree@8.68.0(supports-color@7.2.0)(typescript@7.0.2)': + dependencies: + '@typescript-eslint/project-service': 8.68.0(supports-color@7.2.0)(typescript@7.0.2) + '@typescript-eslint/tsconfig-utils': 8.68.0(typescript@7.0.2) + '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/visitor-keys': 8.68.0 + debug: 4.4.3(supports-color@7.2.0) + minimatch: 10.2.6 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@7.0.2) + typescript: 7.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.68.0(eslint@10.9.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@7.0.2)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.1(supports-color@7.2.0)) + '@typescript-eslint/scope-manager': 8.68.0 + '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/typescript-estree': 8.68.0(supports-color@7.2.0)(typescript@7.0.2) + eslint: 10.9.1(supports-color@7.2.0) + typescript: 7.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.68.0': + dependencies: + '@typescript-eslint/types': 8.68.0 + eslint-visitor-keys: 5.0.1 + '@typescript/typescript-aix-ppc64@7.0.2': optional: true @@ -5018,6 +5419,13 @@ snapshots: '@ai-sdk/provider-utils': 5.0.27(zod@4.4.3) zod: 4.4.3 + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 @@ -5210,6 +5618,8 @@ snapshots: dependencies: character-entities: 2.0.2 + deep-is@0.1.4: {} + defu@6.1.7: {} dequal@2.0.3: {} @@ -5275,8 +5685,83 @@ snapshots: escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} + eslint-plugin-no-only-tests@3.4.0: {} + + eslint-plugin-perfectionist@5.10.1(eslint@10.9.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@7.0.2): + dependencies: + '@typescript-eslint/utils': 8.68.0(eslint@10.9.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@7.0.2) + eslint: 10.9.1(supports-color@7.2.0) + natural-orderby: 5.0.0 + transitivePeerDependencies: + - supports-color + - typescript + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.9 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.9.1(supports-color@7.2.0): + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.1(supports-color@7.2.0)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5(supports-color@7.2.0) + '@eslint/config-helpers': 0.7.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.2 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.3(supports-color@7.2.0) + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.6 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + estree-util-attach-comments@3.0.0: dependencies: '@types/estree': 1.0.8 @@ -5310,6 +5795,8 @@ snapshots: dependencies: '@types/estree': 1.0.9 + esutils@2.0.3: {} + events-universal@1.0.1: dependencies: bare-events: 2.9.1 @@ -5364,6 +5851,10 @@ snapshots: fast-fifo@1.3.2: {} + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + fast-string-truncated-width@3.0.3: {} fast-string-width@3.0.2: @@ -5386,6 +5877,22 @@ snapshots: dependencies: is-unicode-supported: 2.1.0 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.4 + keyv: 4.5.4 + + flatted@3.4.4: {} + flexsearch@0.8.212: {} fsevents@2.3.3: @@ -5426,6 +5933,10 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + gopd@1.2.0: {} has-flag@4.0.0: {} @@ -5579,12 +6090,16 @@ snapshots: dependencies: safer-buffer: 2.1.2 + ignore@5.3.2: {} + ignore@7.0.6: {} import-meta-resolve@4.2.0: {} import-without-cache@0.4.0: {} + imurmurhash@0.1.4: {} + inherits@2.0.4: {} inline-style-parser@0.2.7: {} @@ -5602,8 +6117,14 @@ snapshots: is-decimal@2.0.1: {} + is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + is-hexadecimal@2.0.1: {} is-plain-obj@4.1.0: {} @@ -5633,14 +6154,29 @@ snapshots: jsesc@3.1.0: {} + json-buffer@3.0.1: {} + json-rpc-2.0@1.7.1: {} + json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} json-schema@0.4.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} + json5@2.2.3: {} + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + lightningcss-android-arm64@1.33.0: optional: true @@ -5690,6 +6226,10 @@ snapshots: lightningcss-win32-arm64-msvc: 1.33.0 lightningcss-win32-x64-msvc: 1.33.0 + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + lodash.groupby@4.6.0: {} longest-streak@3.1.0: {} @@ -6222,6 +6762,10 @@ snapshots: nanoid@3.3.18: {} + natural-compare@1.4.0: {} + + natural-orderby@5.0.0: {} + node-emoji@2.2.0: dependencies: '@sindresorhus/is': 4.6.0 @@ -6252,6 +6796,15 @@ snapshots: regex: 6.1.0 regex-recursion: 6.0.2 + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + oxc-parser@0.146.0: dependencies: '@oxc-project/types': 0.146.0 @@ -6322,6 +6875,14 @@ snapshots: '@oxlint/binding-win32-ia32-msvc': 1.79.0 '@oxlint/binding-win32-x64-msvc': 1.79.0 + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + package-manager-detector@1.8.0: {} parse-entities@4.0.2: @@ -6348,6 +6909,8 @@ snapshots: dependencies: entities: 6.0.1 + path-exists@4.0.0: {} + path-key@3.1.1: {} path-key@4.0.0: {} @@ -6368,6 +6931,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + prelude-ls@1.2.1: {} + pretty-ms@9.3.0: dependencies: parse-ms: 4.0.0 @@ -6385,6 +6950,8 @@ snapshots: picocolors: 1.1.1 sade: 1.8.1 + punycode@2.3.1: {} + pure-rand@8.4.2: {} qs@6.15.2: @@ -6775,6 +7342,10 @@ snapshots: trough@2.2.0: {} + ts-api-utils@2.5.0(typescript@7.0.2): + dependencies: + typescript: 7.0.2 + tsdown@0.22.14(@arethetypeswrong/core@0.18.5)(publint@0.3.24)(typescript@7.0.2): dependencies: ansis: 4.3.1 @@ -6806,6 +7377,10 @@ snapshots: tunnel@0.0.6: {} + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + typed-inject@5.0.0: {} typed-rest-client@2.3.1: @@ -6917,6 +7492,10 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + validate-npm-package-name@5.0.1: {} verkit@0.3.2: {} @@ -6989,6 +7568,8 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + word-wrap@1.2.5: {} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -7017,6 +7598,8 @@ snapshots: dependencies: pend: 1.2.0 + yocto-queue@0.1.0: {} + yoctocolors@2.2.0: {} yuku-ast@0.8.7: diff --git a/scripts/__tests__/package-closure-audit.test.ts b/scripts/__tests__/package-closure-audit.test.ts index f195ed7d0..4f2742eb2 100644 --- a/scripts/__tests__/package-closure-audit.test.ts +++ b/scripts/__tests__/package-closure-audit.test.ts @@ -133,7 +133,7 @@ test('the audit reads every shipped extension, not only the bundled .js files', const audit = fixturePackage({ [file]: `const mod = require(\`${PRIVATE}\`);` }); assert.throws( audit, - new RegExp(`${PRIVATE.replace('/', '\\/')} in ${file.replace('/', '\\/')}`), + new RegExp(`${PRIVATE.replace('/', String.raw`\/`)} in ${file.replace('/', String.raw`\/`)}`), ); } }); diff --git a/scripts/check-affected/model.ts b/scripts/check-affected/model.ts index 26da66c7d..29bc7c3af 100644 --- a/scripts/check-affected/model.ts +++ b/scripts/check-affected/model.ts @@ -187,7 +187,8 @@ const ROOT_TOOLING = new Set([ 'tsconfig.lib.json', 'tsdown.config.ts', 'vitest.config.ts', - '.oxlintrc.json', + '.fallowrc.json', + 'oxlint.config.ts', '.oxfmtrc.json', '.npmrc', ]); diff --git a/scripts/check-affected/run.test.ts b/scripts/check-affected/run.test.ts index 4aecec7e5..52de0069f 100644 --- a/scripts/check-affected/run.test.ts +++ b/scripts/check-affected/run.test.ts @@ -140,7 +140,7 @@ test('runChecks runs local checks in order and stops on the first failure', asyn assert.equal(code, 1); // format then lint, then it stops — nothing after the failing check runs. assert.deepEqual( - executed.map((command) => command[command.length - 1]), + executed.map((command) => command.at(-1)), ['format:check', 'lint'], ); }); @@ -188,7 +188,7 @@ test('runChecks skips GitHub-authoritative checks and passes when locals succeed assert.equal(plan.failOpen, true); const code = await runChecks(plan, { scripts: ALL_SCRIPTS }, ARGS, { execute, cwd: '.' }); assert.equal(code, 0); - const ran = executed.map((command) => command[command.length - 1]); + const ran = executed.map((command) => command.at(-1)); // Derived from the catalog rather than hand-listed. A hand-written name goes vacuous the // moment a check is repointed: this list still asserted `build:android-snapshot-helper` // after `android-helpers` moved to `build:android`, so it could not have failed however diff --git a/scripts/coverage-changed/model.test.ts b/scripts/coverage-changed/model.test.ts index bb7eb1a76..217372333 100644 --- a/scripts/coverage-changed/model.test.ts +++ b/scripts/coverage-changed/model.test.ts @@ -16,7 +16,7 @@ test('normalizePath strips root, b/ and ./ prefixes and posixifies', () => { assert.equal(normalizePath('/repo/src/a.ts', '/repo'), 'src/a.ts'); assert.equal(normalizePath('b/src/a.ts'), 'src/a.ts'); assert.equal(normalizePath('./src/a.ts'), 'src/a.ts'); - assert.equal(normalizePath('src\\a.ts'), 'src/a.ts'); + assert.equal(normalizePath(String.raw`src\a.ts`), 'src/a.ts'); }); test('parseLcov reads DA hits and BRDA taken, treating "-" as not taken', () => { diff --git a/scripts/coverage-changed/model.ts b/scripts/coverage-changed/model.ts index 63f9b6a58..2efe6f7d9 100644 --- a/scripts/coverage-changed/model.ts +++ b/scripts/coverage-changed/model.ts @@ -37,9 +37,9 @@ export type ChangedFileDiff = { // absolute-path prefix some coverage providers emit; `./` and `b/` prefixes are // dropped so lcov and diff paths join on the same key. export function normalizePath(raw: string, rootDir?: string): string { - let p = raw.trim().replace(/\\/g, '/'); + let p = raw.trim().replaceAll('\\', '/'); if (rootDir) { - const root = rootDir.replace(/\\/g, '/').replace(/\/$/, ''); + const root = rootDir.replaceAll('\\', '/').replace(/\/$/, ''); if (p === root) return ''; if (p.startsWith(`${root}/`)) p = p.slice(root.length + 1); } diff --git a/scripts/di-seams/model.ts b/scripts/di-seams/model.ts index 36bf50b7e..098eb6ab4 100644 --- a/scripts/di-seams/model.ts +++ b/scripts/di-seams/model.ts @@ -129,7 +129,7 @@ export function findSeamMatches(files: readonly SourceFile[]): SeamMatch[] { line: lineOf(source, start), field, target, - text: source.slice(start, end).replace(/\s+/g, ' ').trim(), + text: source.slice(start, end).replaceAll(/\s+/g, ' ').trim(), approvalReason: approvalReason(source, comments, start), }); } diff --git a/scripts/fuzz/arbitraries.ts b/scripts/fuzz/arbitraries.ts index 204a72a14..4fb892f85 100644 --- a/scripts/fuzz/arbitraries.ts +++ b/scripts/fuzz/arbitraries.ts @@ -19,8 +19,8 @@ const SELECTOR_VALUE_HAZARDS = [ "it's", 'say "hi"', '\\', - '\\"', - 'a\\\\b', + String.raw`\"`, + String.raw`a\\b`, 'a || b', 'key=value', 'line\nbreak', diff --git a/scripts/fuzz/targets.ts b/scripts/fuzz/targets.ts index d166b810e..c344b47ea 100644 --- a/scripts/fuzz/targets.ts +++ b/scripts/fuzz/targets.ts @@ -52,7 +52,7 @@ export const FUZZ_TARGETS: readonly FuzzTarget[] = [ 'id=com.example:id/button[2]', 'role=button and is=enabled', 'text~=partial', - 'label="quoted \\"inner\\" value"', + String.raw`label="quoted \"inner\" value"`, '@e1', 'text=🚀', 'text=', diff --git a/scripts/fuzz/validation-values.ts b/scripts/fuzz/validation-values.ts index bf869eaa9..6d37e1fc5 100644 --- a/scripts/fuzz/validation-values.ts +++ b/scripts/fuzz/validation-values.ts @@ -13,7 +13,7 @@ export const SAFE_VALUES = [ 'Ünïcøde', '😀 emoji', 'say "hi"', - 'a\\b', + String.raw`a\b`, '', ] as const; diff --git a/scripts/gate/model.ts b/scripts/gate/model.ts index 16f4ce289..7b9c9cec4 100644 --- a/scripts/gate/model.ts +++ b/scripts/gate/model.ts @@ -42,7 +42,7 @@ function expandGlob(pattern: string): string[] { const dir = path.dirname(pattern); const rest = path.basename(pattern); const matcher = new RegExp( - `^${rest.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '[^/]*')}$`, + `^${rest.replaceAll(/[.+^${}()|[\]\\]/g, String.raw`\$&`).replaceAll('*', '[^/]*')}$`, ); if (!fs.existsSync(dir)) return []; return fs diff --git a/scripts/gate/shell.ts b/scripts/gate/shell.ts index 9fd404593..00be128df 100644 --- a/scripts/gate/shell.ts +++ b/scripts/gate/shell.ts @@ -10,7 +10,7 @@ export const ENV_PREFIX = /^(?:[A-Z_][A-Z0-9_]*=(?:"[^"]*"|'[^']*'|\S*)\s+)+/; */ export function commandSegments(body: string): string[] { return body - .replace(/\\\n/g, ' ') + .replaceAll('\\\n', ' ') .split('\n') .map(stripComment) .join('\n') diff --git a/scripts/gate/workflows.ts b/scripts/gate/workflows.ts index 42ca5a013..5c04b9c63 100644 --- a/scripts/gate/workflows.ts +++ b/scripts/gate/workflows.ts @@ -188,7 +188,7 @@ export function loadLanes( export function matchesGlob(pattern: string, file: string): boolean { const escape = (part: string) => - part.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '[^/]*'); + part.replaceAll(/[.+^${}()|[\]\\]/g, String.raw`\$&`).replaceAll('*', '[^/]*'); return new RegExp( `^${pattern .split(/(\/\*\*\/|\/\*\*|\*\*\/|\*\*)/) @@ -213,8 +213,8 @@ export function verbatimScripts( command: string, scripts: Readonly>, ): string[] { - const wanted = commandSegments(command).map((segment) => segment.replace(/\s+/g, ' ')); + const wanted = commandSegments(command).map((segment) => segment.replaceAll(/\s+/g, ' ')); return Object.entries(scripts) - .filter(([, body]) => wanted.includes(body.replace(/\s+/g, ' '))) + .filter(([, body]) => wanted.includes(body.replaceAll(/\s+/g, ' '))) .map(([name]) => name); } diff --git a/scripts/help-conformance-bench.mjs b/scripts/help-conformance-bench.mjs index 8c1ce0f58..06bcf178f 100644 --- a/scripts/help-conformance-bench.mjs +++ b/scripts/help-conformance-bench.mjs @@ -509,7 +509,7 @@ function countPassingChecks(checks) { } function safeName(name) { - return basename(name).replace(/[^a-z0-9_.-]+/gi, '-'); + return basename(name).replaceAll(/[^a-z0-9_.-]+/gi, '-'); } function parsePositiveInteger(value, label) { diff --git a/scripts/integration-progress-model.ts b/scripts/integration-progress-model.ts index 9f4815ea4..06f06b6bd 100644 --- a/scripts/integration-progress-model.ts +++ b/scripts/integration-progress-model.ts @@ -207,7 +207,7 @@ function summarizeProviderScenarioFlagCoverage(files) { } function countFlagReferences(text, key) { - const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const escaped = key.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`); return text.match(new RegExp(`\\b${escaped}\\s*:`, 'g'))?.length ?? 0; } @@ -621,7 +621,7 @@ function extractLiteralCommandReferences(text) { function extractClientCommandReferences(text, clientCommandMethods) { const commands = []; for (const [method, command] of clientCommandMethods) { - const escapedMethod = method.replace('.', '\\.'); + const escapedMethod = method.replace('.', String.raw`\.`); const matches = countPatternReferences(text, new RegExp(`\\.${escapedMethod}\\s*\\(`, 'g')); for (let index = 0; index < matches; index += 1) commands.push(command); } diff --git a/scripts/mutation/workspace-aliases.ts b/scripts/mutation/workspace-aliases.ts index 0261dec36..31e016736 100644 --- a/scripts/mutation/workspace-aliases.ts +++ b/scripts/mutation/workspace-aliases.ts @@ -10,7 +10,7 @@ export type WorkspaceSourceAlias = { export type ManifestSource = 'tracked-manifests' | 'disk-manifests'; function exactSpecifier(specifier: string): RegExp { - return new RegExp(`^${specifier.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`); + return new RegExp(`^${specifier.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`)}$`); } function specifierTargets(repoRoot: string, source: ManifestSource): ReadonlyMap { diff --git a/scripts/perf/report.ts b/scripts/perf/report.ts index f02763db6..536639ead 100644 --- a/scripts/perf/report.ts +++ b/scripts/perf/report.ts @@ -12,7 +12,7 @@ function wallCells(s: Stat | null): string { } function stampName(platform: string, startedAt: string): string { - return `perf-${platform}-${startedAt.replace(/[:.]/g, '-')}`; + return `perf-${platform}-${startedAt.replaceAll(/[:.]/g, '-')}`; } function measurementRow(m: Measurement): string { diff --git a/scripts/perf/run.ts b/scripts/perf/run.ts index 330d618a3..f1f390cc4 100644 --- a/scripts/perf/run.ts +++ b/scripts/perf/run.ts @@ -60,8 +60,8 @@ function main(): void { }; const { jsonPath, mdPath } = writeReports(run, cfg.outDir); process.stderr.write(`\n[perf] report: ${mdPath}\n[perf] json: ${jsonPath}\n`); - } catch (e) { - process.stderr.write(`[perf] error: ${(e as Error).stack ?? String(e)}\n`); + } catch (error) { + process.stderr.write(`[perf] error: ${(error as Error).stack ?? String(error)}\n`); exitCode = 1; } finally { cleanup(); diff --git a/scripts/perf/stats.ts b/scripts/perf/stats.ts index 5a41ac4ea..14b846cac 100644 --- a/scripts/perf/stats.ts +++ b/scripts/perf/stats.ts @@ -17,6 +17,6 @@ export function summarize(values: number[]): Stat | null { min: sorted[0], median: percentile(sorted, 50), p95: percentile(sorted, 95), - max: sorted[sorted.length - 1], + max: sorted.at(-1), }; } diff --git a/scripts/swift-conditional-compilation.ts b/scripts/swift-conditional-compilation.ts index 9193c2ee6..d24351ac6 100644 --- a/scripts/swift-conditional-compilation.ts +++ b/scripts/swift-conditional-compilation.ts @@ -118,7 +118,7 @@ function applyDirective( frames.push({ parentActive: outer, active: outer && taken, taken }); return; } - const frame = frames[frames.length - 1]; + const frame = frames.at(-1); if (!frame) throw new UnsupportedConditionError(`#${keyword} without a matching #if`); if (keyword === 'endif') { frames.pop(); diff --git a/scripts/xctest-run-summary.ts b/scripts/xctest-run-summary.ts index 3f2709762..eaa14c63c 100644 --- a/scripts/xctest-run-summary.ts +++ b/scripts/xctest-run-summary.ts @@ -53,7 +53,7 @@ function duration(summary: ResultSummary): string { function failureLine(failure: Failure): string { const name = failure.testName ?? failure.testIdentifierString ?? '(unnamed)'; const text = String(failure.failureText ?? '') - .replace(/\s+/g, ' ') + .replaceAll(/\s+/g, ' ') .slice(0, MAX_FAILURE_TEXT); return `- \`${name}\`${text ? ` — ${text}` : ''}`; } diff --git a/src/__tests__/cli-diff.test.ts b/src/__tests__/cli-diff.test.ts index 8a26b992d..5c934888f 100644 --- a/src/__tests__/cli-diff.test.ts +++ b/src/__tests__/cli-diff.test.ts @@ -103,8 +103,8 @@ describe('cli diff commands', () => { assert.equal(result.code, null); assert.equal(result.calls.length, 1); assert.match(result.stdout, /^@e2 \[window\]/m); - assert.match(result.stdout, /^- @e3 \[text\] "67"$/m); - assert.match(result.stdout, /^\+ @e3 \[text\] "134"$/m); + assert.match(result.stdout, /^- {2}@e3 \[text\] "67"$/m); + assert.match(result.stdout, /^\+ {2}@e3 \[text\] "134"$/m); assert.match(result.stdout, /1 additions, 1 removals, 1 unchanged/); assert.equal(result.stderr, ''); }); @@ -139,8 +139,8 @@ describe('cli diff commands', () => { assert.deepEqual(request.positionals, ['snapshot']); assert.equal(request.flags?.snapshotDiff, undefined); assert.match(result.stdout, /^@e2 \[window\]/m); - assert.match(result.stdout, /^- @e3 \[text\] "67"$/m); - assert.match(result.stdout, /^\+ @e3 \[text\] "134"$/m); + assert.match(result.stdout, /^- {2}@e3 \[text\] "67"$/m); + assert.match(result.stdout, /^\+ {2}@e3 \[text\] "134"$/m); assert.match(result.stdout, /1 additions, 1 removals, 1 unchanged/); assert.equal(result.stderr, ''); }); diff --git a/src/__tests__/cli-help.test.ts b/src/__tests__/cli-help.test.ts index f3ffea0dc..99d82a84f 100644 --- a/src/__tests__/cli-help.test.ts +++ b/src/__tests__/cli-help.test.ts @@ -18,7 +18,7 @@ test('help longpress prints command help and skips daemon dispatch', async () => assert.equal(result.calls.length, 0); assert.match( result.stdout, - /Usage:\n agent-device longpress \[durationMs\]/, + /Usage:\n {2}agent-device longpress \[durationMs\]/, ); }); @@ -28,7 +28,7 @@ test('help long-press resolves to longpress help and skips daemon dispatch', asy assert.equal(result.calls.length, 0); assert.match( result.stdout, - /Usage:\n agent-device longpress \[durationMs\]/, + /Usage:\n {2}agent-device longpress \[durationMs\]/, ); assert.doesNotMatch(result.stdout, /agent-device long-press/); }); @@ -37,7 +37,7 @@ test('appstate --help prints command help and skips daemon dispatch', async () = const result = await runCliCapture(['appstate', '--help']); assert.equal(result.code, 0); assert.equal(result.calls.length, 0); - assert.match(result.stdout, /Usage:\n agent-device appstate/); + assert.match(result.stdout, /Usage:\n {2}agent-device appstate/); assert.doesNotMatch(result.stdout, /Global flags:/); assert.doesNotMatch(result.stdout, /Global Flags:/); }); diff --git a/src/__tests__/cli-network.test.ts b/src/__tests__/cli-network.test.ts index 42cffac0b..fc41a909c 100644 --- a/src/__tests__/cli-network.test.ts +++ b/src/__tests__/cli-network.test.ts @@ -159,7 +159,7 @@ test('test command prints suite summary and exits non-zero on failures', async ( assert.doesNotMatch(result.stderr, /Running replay suite\.\.\./); assert.doesNotMatch(result.stdout, /✓ 01-pass\.ad \(0\.01s\)/); assert.doesNotMatch(result.stdout, /⨯ "Checkout failure" in 02-fail\.ad/); - assert.match(result.stdout, /Failures:\n Checkout failure\n file: 02-fail\.ad/); + assert.match(result.stdout, /Failures:\n {2}Checkout failure\n {4}file: 02-fail\.ad/); assert.match(result.stdout, /Replay failed at step 1 \(open Demo\): boom/); assert.match(result.stdout, /artifacts: \/tmp\/test-artifacts\/02-fail/); assert.doesNotMatch(result.stdout, /SKIP \/tmp\/03-skip\.ad/); diff --git a/src/__tests__/cli-remote-diagnostics.test.ts b/src/__tests__/cli-remote-diagnostics.test.ts index 679208e0d..24bf18219 100644 --- a/src/__tests__/cli-remote-diagnostics.test.ts +++ b/src/__tests__/cli-remote-diagnostics.test.ts @@ -176,5 +176,5 @@ test('an unfetchable remote record says so instead of naming a path', async (t) }); function escapeRegExp(value: string): string { - return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`); } diff --git a/src/__tests__/platform-runtime-runtime-hints.fixtures.ts b/src/__tests__/platform-runtime-runtime-hints.fixtures.ts index 50a5eef40..f13761653 100644 --- a/src/__tests__/platform-runtime-runtime-hints.fixtures.ts +++ b/src/__tests__/platform-runtime-runtime-hints.fixtures.ts @@ -36,7 +36,7 @@ export async function withMockedAdb( ' shift', ' shift', 'fi', - 'printf "%s\\n" "$*" >> "$AGENT_DEVICE_TEST_ARGS_FILE"', + String.raw`printf "%s\n" "$*" >> "$AGENT_DEVICE_TEST_ARGS_FILE"`, 'if [ "$1" = "shell" ] && [ "$2" = "run-as" ] && [ "$4" = "cat" ]; then', ' key=$(printf "%s" "$5" | tr "/" "_")', ' if [ -f "$AGENT_DEVICE_TEST_SEED_DIR/$key" ]; then', @@ -49,7 +49,7 @@ export async function withMockedAdb( ' if [ -n "$AGENT_DEVICE_TEST_RUN_AS_ID_STDOUT" ]; then', ' printf "%s" "$AGENT_DEVICE_TEST_RUN_AS_ID_STDOUT"', ' else', - ' printf "%s\\n" "uid=10162(u0_a162) gid=10162(u0_a162) groups=10162(u0_a162)"', + String.raw` printf "%s\n" "uid=10162(u0_a162) gid=10162(u0_a162) groups=10162(u0_a162)"`, ' fi', ' if [ -n "$AGENT_DEVICE_TEST_RUN_AS_ID_STDERR" ]; then', ' printf "%s" "$AGENT_DEVICE_TEST_RUN_AS_ID_STDERR" >&2', @@ -144,7 +144,12 @@ export async function withMockedXcrun( const argsLogPath = path.join(tmpDir, 'args.log'); await fs.writeFile( xcrunPath, - ['#!/bin/sh', 'printf "%s\\n" "$*" >> "$AGENT_DEVICE_TEST_ARGS_FILE"', 'exit 0', ''].join('\n'), + [ + '#!/bin/sh', + String.raw`printf "%s\n" "$*" >> "$AGENT_DEVICE_TEST_ARGS_FILE"`, + 'exit 0', + '', + ].join('\n'), 'utf8', ); await fs.chmod(xcrunPath, 0o755); diff --git a/src/cli-schema/cli-help-command-usage.test.ts b/src/cli-schema/cli-help-command-usage.test.ts index 0d3718476..eb504a483 100644 --- a/src/cli-schema/cli-help-command-usage.test.ts +++ b/src/cli-schema/cli-help-command-usage.test.ts @@ -428,22 +428,28 @@ test('replay --save-script help describes arming a repair transaction, not open/ assert.doesNotMatch(help, /Arm evidence capture on open, publish the armed recording on close/); }); -test('open --save-script help keeps the shared open\\/close authoring description', async () => { - const help = await usageForCommand('open'); - if (help === null) throw new Error('Expected open help text'); - assert.match( - help, - /Arm evidence capture on open, publish the armed recording on close; close --save-script alone \(without an armed open\) is rejected/, - ); - assert.doesNotMatch(help, /Arm a repair transaction from this replay/); -}); +test( + String.raw`open --save-script help keeps the shared open\/close authoring description`, + async () => { + const help = await usageForCommand('open'); + if (help === null) throw new Error('Expected open help text'); + assert.match( + help, + /Arm evidence capture on open, publish the armed recording on close; close --save-script alone \(without an armed open\) is rejected/, + ); + assert.doesNotMatch(help, /Arm a repair transaction from this replay/); + }, +); -test('close --save-script help keeps the shared open\\/close authoring description', async () => { - const help = await usageForCommand('close'); - if (help === null) throw new Error('Expected close help text'); - assert.match( - help, - /Arm evidence capture on open, publish the armed recording on close; close --save-script alone \(without an armed open\) is rejected/, - ); - assert.doesNotMatch(help, /Arm a repair transaction from this replay/); -}); +test( + String.raw`close --save-script help keeps the shared open\/close authoring description`, + async () => { + const help = await usageForCommand('close'); + if (help === null) throw new Error('Expected close help text'); + assert.match( + help, + /Arm evidence capture on open, publish the armed recording on close; close --save-script alone \(without an armed open\) is rejected/, + ); + assert.doesNotMatch(help, /Arm a repair transaction from this replay/); + }, +); diff --git a/src/cli-schema/cli-help-topics.test.ts b/src/cli-schema/cli-help-topics.test.ts index ef4f4c789..8affb909c 100644 --- a/src/cli-schema/cli-help-topics.test.ts +++ b/src/cli-schema/cli-help-topics.test.ts @@ -20,12 +20,12 @@ test('commands topic includes concise command catalog entries', async () => { ); assert.doesNotMatch(usageText, /prepare ios-runner --platform ios\|macos/); assert.doesNotMatch(usageText, /metro prepare --public-base-url /); - assert.doesNotMatch(usageText, /^ network dump/m); + assert.doesNotMatch(usageText, /^ {2}network dump/m); assert.doesNotMatch(usageText, /trigger-app-event \[payloadJson\]/); - assert.doesNotMatch(usageText, /^ pan \[durationMs\]/m); - assert.doesNotMatch(usageText, /^ fling /m); - assert.doesNotMatch(usageText, /^ pinch \[x\] \[y\]/m); - assert.doesNotMatch(usageText, /^ rotate-gesture /m); + assert.doesNotMatch(usageText, /^ {2}pan \[durationMs\]/m); + assert.doesNotMatch(usageText, /^ {2}fling /m); + assert.doesNotMatch(usageText, /^ {2}pinch \[x\] \[y\]/m); + assert.doesNotMatch(usageText, /^ {2}rotate-gesture /m); assert.match(usageText, /orientation /); assert.match(usageText, /record start \[path\] \| record stop/); assert.match(usageText, /trace start \| trace stop /); @@ -688,19 +688,19 @@ test('commands topic includes swipe and press series options', async () => { test('commands topic renders concise commands inline with descriptions', async () => { const help = await usageForCommand('commands'); if (help === null) throw new Error('Expected commands help text'); - assert.match(help, /Commands:[\s\S]*\n boot\s{2,}Boot target device\/simulator/); - assert.match(help, /Commands:[\s\S]*\n shutdown\s{2,}Shutdown target simulator\/emulator/); - assert.match(help, / prepare\s{2,}Pre-warm platform helpers/); - assert.match(help, / metro\s{2,}Prepare the dev server or reload apps/); - assert.match(help, / perf\s{2,}Check frames, memory, or native profiles/); - assert.match(help, / cdp\s{2,}Inspect CDP targets, JS heap, and leaks/); - assert.match(help, / react-devtools\s{2,}Inspect components, hooks, and render profiles/); - assert.match(help, / proxy\s{2,}Expose a local daemon through an HTTP tunnel/); - assert.match(help, / batch --steps \| --steps-file \s{2,}Run multiple commands/); - assert.match(help, / test \.\.\.\s{2,}Run replay test suites/); - assert.match(help, / screenshot \[path\]\s{2,}Capture a screenshot/); - assert.match(help, / session\s{2,}List sessions, show the state dir, or publish a script/); - assert.doesNotMatch(help, / metro prepare[^\n]*--project-root/); - assert.doesNotMatch(help, /\n batch\s{2,}Run multiple commands/); + assert.match(help, /Commands:[\s\S]*\n {2}boot\s{2,}Boot target device\/simulator/); + assert.match(help, /Commands:[\s\S]*\n {2}shutdown\s{2,}Shutdown target simulator\/emulator/); + assert.match(help, / {2}prepare\s{2,}Pre-warm platform helpers/); + assert.match(help, / {2}metro\s{2,}Prepare the dev server or reload apps/); + assert.match(help, / {2}perf\s{2,}Check frames, memory, or native profiles/); + assert.match(help, / {2}cdp\s{2,}Inspect CDP targets, JS heap, and leaks/); + assert.match(help, / {2}react-devtools\s{2,}Inspect components, hooks, and render profiles/); + assert.match(help, / {2}proxy\s{2,}Expose a local daemon through an HTTP tunnel/); + assert.match(help, / {2}batch --steps \| --steps-file \s{2,}Run multiple commands/); + assert.match(help, / {2}test \.\.\.\s{2,}Run replay test suites/); + assert.match(help, / {2}screenshot \[path\]\s{2,}Capture a screenshot/); + assert.match(help, / {2}session\s{2,}List sessions, show the state dir, or publish a script/); + assert.doesNotMatch(help, / {2}metro prepare[^\n]*--project-root/); + assert.doesNotMatch(help, /\n {2}batch\s{2,}Run multiple commands/); assert.doesNotMatch(help, /agent-device-proxy/); }); diff --git a/src/cli.ts b/src/cli.ts index ca639e69d..c0c96773b 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -168,8 +168,8 @@ export async function runCli(argv: string[], deps: CliDeps = DEFAULT_CLI_DEPS): }), }); await dispatchCliCommand(ctx, client, replayTestReporterRuntime); - } catch (err) { - await handleRunCliFailure(err, ctx, logTailStopper); + } catch (error) { + await handleRunCliFailure(error, ctx, logTailStopper); } finally { if (logTailStopper) logTailStopper(); } @@ -318,8 +318,8 @@ async function resolveRunContextOrExit( connectionMetadata: connectionDefaults?.connection, parsedBatchSteps: undefined, }; - } catch (err) { - const appErr = asAppError(err); + } catch (error) { + const appErr = asAppError(error); const normalized = normalizeError(appErr, { diagnosticId: getDiagnosticsMeta().diagnosticId, logPath: flushDiagnosticsToSessionFile({ force: true })?.path, @@ -841,8 +841,8 @@ function guessSessionFromArgv(argv: string[]): string | null { const isDirectRun = pathToFileURL(process.argv[1] ?? '').href === import.meta.url; if (isDirectRun) { - runCli(process.argv.slice(2)).catch(async (err) => { - const appErr = asAppError(err); + runCli(process.argv.slice(2)).catch(async (error) => { + const appErr = asAppError(error); printHumanError(normalizeError(appErr), { showDetails: true }); await exitAfterFlush(1); }); diff --git a/src/cli/parser/__tests__/maestro-support-matrix.test.ts b/src/cli/parser/__tests__/maestro-support-matrix.test.ts index 4169a58bb..8f99969d5 100644 --- a/src/cli/parser/__tests__/maestro-support-matrix.test.ts +++ b/src/cli/parser/__tests__/maestro-support-matrix.test.ts @@ -31,7 +31,7 @@ test('Maestro help covers the supported subset and operational boundaries', asyn test('Maestro replay docs stay in sync with versioned compatibility help', () => { const docs = fs.readFileSync('website/docs/docs/replay-e2e.md', 'utf8'); - const plainDocs = docs.replace(/`/g, ''); + const plainDocs = docs.replaceAll('`', ''); for (const statement of [ ...MAESTRO_COMPAT_SUPPORTED_CAPABILITIES, ...MAESTRO_COMPAT_LIMITATIONS, diff --git a/src/commands/__tests__/command-surface-metadata.test.ts b/src/commands/__tests__/command-surface-metadata.test.ts index 0de1814aa..e015ee48d 100644 --- a/src/commands/__tests__/command-surface-metadata.test.ts +++ b/src/commands/__tests__/command-surface-metadata.test.ts @@ -97,11 +97,11 @@ test('every command states a summary that is shorter than its description', () = assert.notEqual( summary .toLowerCase() - .replace(/[^a-z0-9 ]/g, '') + .replaceAll(/[^a-z0-9 ]/g, '') .trim(), description .toLowerCase() - .replace(/[^a-z0-9 ]/g, '') + .replaceAll(/[^a-z0-9 ]/g, '') .trim(), `${name}: summary duplicates the description`, ); diff --git a/src/commands/command-projection.ts b/src/commands/command-projection.ts index a012848a3..4ccabc535 100644 --- a/src/commands/command-projection.ts +++ b/src/commands/command-projection.ts @@ -31,13 +31,13 @@ async function prepareBatchDaemonCommandRequest( metadata, metadata.readInput(input) as CommandInput, ); - } catch (err) { - const message = err instanceof Error ? err.message : String(err); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); throw new AppError( 'INVALID_ARGS', `Batch step ${stepNumber} ${command} input is invalid: ${message}`, undefined, - err, + error, ); } } diff --git a/src/commands/interaction/runtime/__tests__/selector-read-policy.test.ts b/src/commands/interaction/runtime/__tests__/selector-read-policy.test.ts index a2a248d97..69a628c4e 100644 --- a/src/commands/interaction/runtime/__tests__/selector-read-policy.test.ts +++ b/src/commands/interaction/runtime/__tests__/selector-read-policy.test.ts @@ -49,7 +49,7 @@ test('get attrs fails closed on the same ambiguous selector (readUnique row)', a .getAttrs(selector(AMBIGUOUS_SELECTOR), { session: 'default' }) .then( () => null, - (thrown: unknown) => thrown, + (error: unknown) => error, ); assert.ok(error instanceof AppError, 'get attrs must refuse rather than guess a duplicate'); @@ -63,7 +63,7 @@ test('is fails closed on the same ambiguous selector (readUnique row)', async () .is({ session: 'default', predicate: 'visible', selector: AMBIGUOUS_SELECTOR }) .then( () => null, - (thrown: unknown) => thrown, + (error: unknown) => error, ); assert.ok(error instanceof AppError, 'is must refuse rather than answer about one duplicate'); diff --git a/src/commands/interaction/runtime/selector-wait.test.ts b/src/commands/interaction/runtime/selector-wait.test.ts index 7682450d4..c710015c8 100644 --- a/src/commands/interaction/runtime/selector-wait.test.ts +++ b/src/commands/interaction/runtime/selector-wait.test.ts @@ -228,7 +228,7 @@ test('runtime wait fails closed at the deadline when only impostors matched the }) .then( () => undefined, - (thrown: unknown) => thrown, + (error: unknown) => error, ); assert.ok(error instanceof AppError); diff --git a/src/commands/interaction/runtime/wait-polling.ts b/src/commands/interaction/runtime/wait-polling.ts index fe5b6e1b7..f86b9bc82 100644 --- a/src/commands/interaction/runtime/wait-polling.ts +++ b/src/commands/interaction/runtime/wait-polling.ts @@ -195,7 +195,7 @@ function runnerRestartTimeoutEvidence(error: unknown): Partial( details: AppErrorDetails | undefined, key: Key, -): Pick | {} { +): Partial> { const value = details?.[key]; return typeof value === 'string' ? ({ [key]: value } as Pick) : {}; } diff --git a/src/commands/post-action-observation-grammar.ts b/src/commands/post-action-observation-grammar.ts index efdf0c880..bb04d9130 100644 --- a/src/commands/post-action-observation-grammar.ts +++ b/src/commands/post-action-observation-grammar.ts @@ -45,7 +45,7 @@ export type PostActionObservationFields = ? VerifyFieldMap & SettleFieldMap : PostActionObservationSupportFor extends 'settle' ? SettleFieldMap - : {}; + : Record; export function postActionObservationFields( command: TName, diff --git a/src/commands/recording/runtime/recording.ts b/src/commands/recording/runtime/recording.ts index d184da212..1f41308d0 100644 --- a/src/commands/recording/runtime/recording.ts +++ b/src/commands/recording/runtime/recording.ts @@ -104,9 +104,7 @@ export const traceCommand: RuntimeCommand< }) : undefined; try { - const backendOptions: BackendTraceOptions = { - ...(output?.path ? { outPath: output.path } : {}), - }; + const backendOptions: BackendTraceOptions = output?.path ? { outPath: output.path } : {}; const result = await method.call( runtime.backend, toBackendContext(runtime, options), diff --git a/src/commands/system/navigation-projection.ts b/src/commands/system/navigation-projection.ts index 61a0e9669..1a426d934 100644 --- a/src/commands/system/navigation-projection.ts +++ b/src/commands/system/navigation-projection.ts @@ -60,7 +60,7 @@ export const NAVIGATION_COMMAND_PROJECTIONS = { required: ['action', 'mode', 'message'], }, }), - home: defineNavigationCommandProjection<{}, HomeCommandResult, false, 'home'>({ + home: defineNavigationCommandProjection, HomeCommandResult, false, 'home'>({ clientMethod: 'home', outputSchema: { type: 'object', @@ -89,7 +89,7 @@ export const NAVIGATION_COMMAND_PROJECTIONS = { }, }), 'app-switcher': defineNavigationCommandProjection< - {}, + Record, AppSwitcherCommandResult, false, 'appSwitcher' diff --git a/src/core/__tests__/dispatch-resolve.test.ts b/src/core/__tests__/dispatch-resolve.test.ts index 95bb708b9..63b751609 100644 --- a/src/core/__tests__/dispatch-resolve.test.ts +++ b/src/core/__tests__/dispatch-resolve.test.ts @@ -242,8 +242,8 @@ test('platform-less resolution refuses to pick between equally booted devices', async () => { try { await resolveTargetDeviceInContext({}, { appleSimulatorAppTarget: 'com.example.demo' }); - } catch (thrown) { - return thrown; + } catch (error) { + return error; } throw new assert.AssertionError({ message: 'expected ambiguous device resolution to refuse', @@ -284,7 +284,7 @@ test('resolveTargetDevice refuses booted simulator selection when the requested const error = await resolveTargetDevice( { platform: 'ios' }, { appleSimulatorAppTarget: 'com.example.demo' }, - ).catch((cause) => cause); + ).catch((error) => error); assert.ok(error instanceof AppError); assert.equal(error.code, 'APP_NOT_INSTALLED'); @@ -305,7 +305,7 @@ test('resolveTargetDevice refuses ambiguous booted simulator app matches', async const error = await resolveTargetDevice( { platform: 'ios' }, { appleSimulatorAppTarget: 'com.example.demo' }, - ).catch((cause) => cause); + ).catch((error) => error); assert.ok(error instanceof AppError); assert.equal(error.code, 'AMBIGUOUS_MATCH'); diff --git a/src/core/__tests__/gesture-plan.test.ts b/src/core/__tests__/gesture-plan.test.ts index 2c9ab6643..5b6975ab3 100644 --- a/src/core/__tests__/gesture-plan.test.ts +++ b/src/core/__tests__/gesture-plan.test.ts @@ -211,7 +211,7 @@ test('invalid and non-finite values fail before execution', () => { const cases = [ () => buildGesturePlan( - { intent: 'pan', origin: { x: NaN, y: 1 }, delta: { x: 1, y: 1 } }, + { intent: 'pan', origin: { x: Number.NaN, y: 1 }, delta: { x: 1, y: 1 } }, PORTRAIT, ), () => @@ -220,7 +220,7 @@ test('invalid and non-finite values fail before execution', () => { PORTRAIT, ), () => buildGesturePlan({ intent: 'pinch', scale: Infinity }, PORTRAIT), - () => buildGesturePlan({ intent: 'rotate', degrees: NaN }, PORTRAIT), + () => buildGesturePlan({ intent: 'rotate', degrees: Number.NaN }, PORTRAIT), () => buildGesturePlan( { diff --git a/src/core/command-descriptor/command-result.ts b/src/core/command-descriptor/command-result.ts index 1b8aa4ea2..1647318fa 100644 --- a/src/core/command-descriptor/command-result.ts +++ b/src/core/command-descriptor/command-result.ts @@ -56,34 +56,34 @@ import type { ReplayCommandResult, ReplaySuiteResult } from '@agent-device/contr * re-read of the handler's literal return; see the per-type docstrings. */ export interface CommandResultMap { - press: PressCommandResponseData; - click: ClickCommandResponseData; - fill: FillCommandResponseData; - longpress: LongPressCommandResponseData; - hover: HoverCommandResponseData; - find: FindCommandResponseData; - boot: BootCommandResult; - shutdown: ShutdownCommandResult; - viewport: ViewportCommandResult; - home: HomeCommandResult; - back: BackCommandResult; - orientation: OrientationCommandResult; 'app-switcher': AppSwitcherCommandResult; - clipboard: ClipboardCommandResult; appstate: AppStateCommandResult; - keyboard: KeyboardCommandResult; - 'tv-remote': TvRemoteCommandResult; - wait: WaitCommandResult; - scroll: ScrollCommandResult; - prepare: PrepareCommandResult; - push: PushCommandResult; - 'trigger-app-event': TriggerAppEventCommandResult; - doctor: DoctorCommandResult; + back: BackCommandResult; + boot: BootCommandResult; + click: ClickCommandResponseData; + clipboard: ClipboardCommandResult; diff: DiffSnapshotCommandResult; - replay: ReplayCommandResult; - test: ReplaySuiteResult; + doctor: DoctorCommandResult; + fill: FillCommandResponseData; + find: FindCommandResponseData; + home: HomeCommandResult; + hover: HoverCommandResponseData; + keyboard: KeyboardCommandResult; + longpress: LongPressCommandResponseData; + orientation: OrientationCommandResult; + prepare: PrepareCommandResult; + press: PressCommandResponseData; + push: PushCommandResult; record: RecordingCommandResult; + replay: ReplayCommandResult; + scroll: ScrollCommandResult; + shutdown: ShutdownCommandResult; + test: ReplaySuiteResult; trace: TraceCommandResult; + 'trigger-app-event': TriggerAppEventCommandResult; + 'tv-remote': TvRemoteCommandResult; + viewport: ViewportCommandResult; + wait: WaitCommandResult; } /** diff --git a/src/core/interaction-positionals.ts b/src/core/interaction-positionals.ts index def7139fa..0ffb42f3a 100644 --- a/src/core/interaction-positionals.ts +++ b/src/core/interaction-positionals.ts @@ -164,7 +164,7 @@ function mergeRestIntoSelectorValue( } function quoteSelectorValue(value: string): string { - return `"${value.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`; + return `"${value.replaceAll('\\', String.raw`\\`).replaceAll('"', String.raw`\"`)}"`; } function optionalTrimmedText(parts: string[]): string | undefined { diff --git a/src/core/snapshot-tree-ingestion.ts b/src/core/snapshot-tree-ingestion.ts index d9c71d2da..5d3daab3a 100644 --- a/src/core/snapshot-tree-ingestion.ts +++ b/src/core/snapshot-tree-ingestion.ts @@ -16,7 +16,7 @@ export function normalizeSnapshotTree(nodes: RawSnapshotNode[]): RawSnapshotNode for (const [position, node] of nodes.entries()) { const depth = Math.max(0, node.depth ?? 0); - while (ancestorStack.length > 0 && depth <= ancestorStack[ancestorStack.length - 1]!.depth) { + while (ancestorStack.length > 0 && depth <= ancestorStack.at(-1)!.depth) { ancestorStack.pop(); } @@ -28,7 +28,7 @@ export function normalizeSnapshotTree(nodes: RawSnapshotNode[]): RawSnapshotNode const parentIndex = typeof explicitParentIndex === 'number' && explicitParentIndex < index ? explicitParentIndex - : ancestorStack[ancestorStack.length - 1]?.index; + : ancestorStack.at(-1)?.index; normalized.push({ ...node, index, @@ -46,7 +46,7 @@ export function pruneGroupNodes(nodes: RawSnapshotNode[]): RawSnapshotNode[] { const result: RawSnapshotNode[] = []; for (const node of nodes) { const depth = node.depth ?? 0; - while (skippedDepths.length > 0 && depth <= skippedDepths[skippedDepths.length - 1]!) { + while (skippedDepths.length > 0 && depth <= skippedDepths.at(-1)!) { skippedDepths.pop(); } const type = normalizeType(node.type ?? ''); diff --git a/src/core/wait-positionals.ts b/src/core/wait-positionals.ts index 3a982163c..16252226e 100644 --- a/src/core/wait-positionals.ts +++ b/src/core/wait-positionals.ts @@ -28,7 +28,7 @@ export function parseWaitPositionals(args: string[]): WaitParsed | null { if (firstArg === undefined) return null; const sleepMs = parseTimeout(firstArg); if (sleepMs !== null) return { kind: 'sleep', durationMs: sleepMs }; - const timeoutMs = parseTimeout(args[args.length - 1]); + const timeoutMs = parseTimeout(args.at(-1)); if (firstArg === 'text') return parseTextKeyword(args, timeoutMs); if (firstArg === 'stable') return parseStableKeyword(args); if (firstArg.startsWith('@')) return { kind: 'ref', rawRef: firstArg, timeoutMs }; diff --git a/src/daemon/__tests__/daemon-process.test.ts b/src/daemon/__tests__/daemon-process.test.ts index 41cc9e97e..291e63150 100644 --- a/src/daemon/__tests__/daemon-process.test.ts +++ b/src/daemon/__tests__/daemon-process.test.ts @@ -36,7 +36,7 @@ test('isAgentDeviceDaemonCommand matches daemons from branch-named checkouts', ( ); assert.equal( isAgentDeviceDaemonCommand( - '"C:\\Program Files\\nodejs\\node.exe" "C:\\Users\\dev\\wt\\fix-1545\\dist\\src\\daemon.js"', + String.raw`"C:\Program Files\nodejs\node.exe" "C:\Users\dev\wt\fix-1545\dist\src\daemon.js"`, ), true, ); diff --git a/src/daemon/__tests__/device-claim-admission.test.ts b/src/daemon/__tests__/device-claim-admission.test.ts index fdf7f6c25..514558ece 100644 --- a/src/daemon/__tests__/device-claim-admission.test.ts +++ b/src/daemon/__tests__/device-claim-admission.test.ts @@ -76,7 +76,7 @@ test('a foreign live claim refuses the command before it can reach device operat const admission = makeAdmission('transient-exclusive', stateDir, 'shutdown'); const error = asAppError( - await admission.admit(ANDROID_EMULATOR, localAndroid).catch((thrown: unknown) => thrown), + await admission.admit(ANDROID_EMULATOR, localAndroid).catch((error: unknown) => error), ); expect(error.code).toBe('DEVICE_IN_USE'); diff --git a/src/daemon/__tests__/materialized-path-registry.test.ts b/src/daemon/__tests__/materialized-path-registry.test.ts index 4065dbc69..f33b55f83 100644 --- a/src/daemon/__tests__/materialized-path-registry.test.ts +++ b/src/daemon/__tests__/materialized-path-registry.test.ts @@ -60,7 +60,7 @@ test('cleanupRetainedMaterializedPathsForSession removes retained paths bound to test('cleanup of unknown materialization reports expiry with a recovery hint', async () => { const error = await cleanupRetainedMaterializedPaths('missing-id').then( () => null, - (err: unknown) => err, + (error: unknown) => error, ); assert.equal(error instanceof AppError, true); const appError = error as AppError; @@ -83,7 +83,7 @@ test('cleanup of tenant-owned materialization rejects other tenants', async () = const error = await cleanupRetainedMaterializedPaths(retained.materializationId, 'tenant-b').then( () => null, - (err: unknown) => err, + (error: unknown) => error, ); assert.equal(error instanceof AppError, true); assert.equal((error as AppError).code, 'UNAUTHORIZED'); diff --git a/src/daemon/__tests__/resumable-upload.test.ts b/src/daemon/__tests__/resumable-upload.test.ts index 818566f98..c5d7c8829 100644 --- a/src/daemon/__tests__/resumable-upload.test.ts +++ b/src/daemon/__tests__/resumable-upload.test.ts @@ -17,7 +17,7 @@ import { runCmdSync } from '@agent-device/host-kit/command'; test('finalizing an unknown upload reports expiry with a recovery hint', async () => { const error = await finalizeResumableUpload('missing-upload-id').then( () => null, - (err: unknown) => err, + (error: unknown) => error, ); assert.equal(error instanceof AppError, true); const appError = error as AppError; diff --git a/src/daemon/__tests__/session-event-log-window.test.ts b/src/daemon/__tests__/session-event-log-window.test.ts index aabbde00c..57a2fd132 100644 --- a/src/daemon/__tests__/session-event-log-window.test.ts +++ b/src/daemon/__tests__/session-event-log-window.test.ts @@ -143,7 +143,7 @@ test('a reader that lands between the window write and the rename still resolves version: number; generations: { firstLineIndex: number; lineCount: number; firstLineDigest: string }[]; }; - const newest = sidecar.generations[sidecar.generations.length - 1]!; + const newest = sidecar.generations.at(-1)!; const activeLines = fs.readFileSync(eventLogPath, 'utf8').split('\n').filter(Boolean); const activeStart = newest.firstLineIndex + newest.lineCount; fs.writeFileSync( diff --git a/src/daemon/__tests__/session-event-request.test.ts b/src/daemon/__tests__/session-event-request.test.ts index 73103aa86..3a4094794 100644 --- a/src/daemon/__tests__/session-event-request.test.ts +++ b/src/daemon/__tests__/session-event-request.test.ts @@ -265,7 +265,7 @@ test('screenshot events preserve only the requested client filename', () => { durationMs: 123, requestedFileName: 'requested-shot.png', }); - assert.equal(JSON.stringify(event).includes('C:\\workspace'), false); + assert.equal(JSON.stringify(event).includes(String.raw`C:\workspace`), false); }); test('screenshot filenames are bounded for durable event rendering', () => { diff --git a/src/daemon/__tests__/session-store.test.ts b/src/daemon/__tests__/session-store.test.ts index 32b4b03d9..d0d597946 100644 --- a/src/daemon/__tests__/session-store.test.ts +++ b/src/daemon/__tests__/session-store.test.ts @@ -640,7 +640,7 @@ test('writeSessionLog optimizes selector chains and scopes fallback snapshots', test('writeSessionLog escapes device labels with quotes and backslashes', () => { const fixture = makeFixture('agent-device-session-log-device-label-'); - fixture.session.device.name = 'QA "Lab" \\ Shelf'; + fixture.session.device.name = String.raw`QA "Lab" \ Shelf`; recordOpen(fixture.store, fixture.session); const script = writeScript(fixture); @@ -683,7 +683,7 @@ test('writeSessionLog preserves significant whitespace and empty string argument const script = writeScript(fixture); assertScriptMatches(script, [ - /type " leading\\ttrailing "/, + /type " {2}leading\\ttrailing {2}"/, /fill @e5 "Search field" ""/, /screenshot " \.\/screens\/final\.png "/, /--metro-host " host\\t" --launch-url "myapp:\/\/dev "/, diff --git a/src/daemon/artifact-download.ts b/src/daemon/artifact-download.ts index 2506d195f..92057433d 100644 --- a/src/daemon/artifact-download.ts +++ b/src/daemon/artifact-download.ts @@ -117,6 +117,6 @@ async function removePartialFile(output: fs.WriteStream, destPath: string): Prom function sanitizeRequestId(raw: string | undefined): string { const trimmed = raw?.trim(); if (!trimmed) return 'request'; - const normalized = trimmed.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, ''); + const normalized = trimmed.replaceAll(/[^a-zA-Z0-9._-]+/g, '-').replaceAll(/^-+|-+$/g, ''); return normalized.length > 0 ? normalized.slice(0, 48) : 'request'; } diff --git a/src/daemon/client/daemon-client-timeout.ts b/src/daemon/client/daemon-client-timeout.ts index e7e135911..a58127da5 100644 --- a/src/daemon/client/daemon-client-timeout.ts +++ b/src/daemon/client/daemon-client-timeout.ts @@ -22,8 +22,8 @@ import { const IOS_RUNNER_XCODEBUILD_KILL_PATTERNS = [ 'xcodebuild .*AgentDeviceRunnerUITests/RunnerTests/testCommand', - 'xcodebuild .*AgentDeviceRunner\\.env\\.session-', - 'xcodebuild build-for-testing .*apple/runner/AgentDeviceRunner/AgentDeviceRunner\\.xcodeproj', + String.raw`xcodebuild .*AgentDeviceRunner\.env\.session-`, + String.raw`xcodebuild build-for-testing .*apple/runner/AgentDeviceRunner/AgentDeviceRunner\.xcodeproj`, ]; // `--platform` selectors that AFFIRMATIVELY name (or alias) an Apple device. diff --git a/src/daemon/client/daemon-client-transport.ts b/src/daemon/client/daemon-client-transport.ts index 93f95dd0a..9584684c9 100644 --- a/src/daemon/client/daemon-client-transport.ts +++ b/src/daemon/client/daemon-client-transport.ts @@ -422,14 +422,14 @@ async function sendHttpRequest( reject, }); }) - .catch((err: unknown) => { + .catch((error: unknown) => { if (timeoutHandle) clearTimeout(timeoutHandle); reject( new AppError( 'COMMAND_FAILED', 'Failed to read daemon response', { requestId: req.meta?.requestId }, - err instanceof Error ? err : undefined, + error instanceof Error ? error : undefined, ), ); }); diff --git a/src/daemon/daemon-process.ts b/src/daemon/daemon-process.ts index 3f0fd19c8..c42205a9c 100644 --- a/src/daemon/daemon-process.ts +++ b/src/daemon/daemon-process.ts @@ -42,10 +42,10 @@ export function trySignalProcess(pid: number, signal: NodeJS.Signals): boolean { try { process.kill(pid, signal); return true; - } catch (err) { - const code = (err as NodeJS.ErrnoException).code; + } catch (error) { + const code = (error as NodeJS.ErrnoException).code; if (code === 'ESRCH' || code === 'EPERM') return false; - throw err; + throw error; } } diff --git a/src/daemon/handlers/__tests__/interaction-ios-tap-outcome.test.ts b/src/daemon/handlers/__tests__/interaction-ios-tap-outcome.test.ts index 550376dd1..22315d78f 100644 --- a/src/daemon/handlers/__tests__/interaction-ios-tap-outcome.test.ts +++ b/src/daemon/handlers/__tests__/interaction-ios-tap-outcome.test.ts @@ -661,7 +661,7 @@ test('corroborated runtime taps retain target evidence through save and replay', const savedScript = fs.readFileSync(written.path, 'utf8'); expect(savedScript).toContain('# agent-device:target-v1'); expect(savedScript).toContain( - 'click "id=\\"unfollow\\" || role=\\"button\\" label=\\"Unfollow\\" || label=\\"Unfollow\\""', + String.raw`click "id=\"unfollow\" || role=\"button\" label=\"Unfollow\" || label=\"Unfollow\""`, ); recording = false; diff --git a/src/daemon/handlers/__tests__/interaction-response-construction-guard.test.ts b/src/daemon/handlers/__tests__/interaction-response-construction-guard.test.ts index 0d14a359d..8cc3edb9a 100644 --- a/src/daemon/handlers/__tests__/interaction-response-construction-guard.test.ts +++ b/src/daemon/handlers/__tests__/interaction-response-construction-guard.test.ts @@ -39,7 +39,7 @@ const ALLOWED_RHS = [ function findHandRolledResponseData(source: string): string[] { // Collapse whitespace so multi-line hand-rolled literals cannot hide. - const collapsed = source.replace(/\s+/g, ' '); + const collapsed = source.replaceAll(/\s+/g, ' '); const offenders: string[] = []; const assignment = /\bresponseData\s*[:=]\s*/g; for (let match = assignment.exec(collapsed); match; match = assignment.exec(collapsed)) { diff --git a/src/daemon/handlers/__tests__/interaction-target-evidence.test.ts b/src/daemon/handlers/__tests__/interaction-target-evidence.test.ts index 88743eae6..ae2240bf5 100644 --- a/src/daemon/handlers/__tests__/interaction-target-evidence.test.ts +++ b/src/daemon/handlers/__tests__/interaction-target-evidence.test.ts @@ -343,7 +343,7 @@ test('press on an identity-empty container: container-based daemon response, des // And the WRITTEN .ad line re-resolves the descendant, not the container. const script = writeSessionScript(recordedSession); expect(script).toContain( - 'press "role=\\"textview\\" label=\\"Connected devices\\" || label=\\"Connected devices\\""', + String.raw`press "role=\"textview\" label=\"Connected devices\" || label=\"Connected devices\""`, ); expect(script).toContain('"role":"textview","label":"Connected devices"'); }); diff --git a/src/daemon/handlers/__tests__/session-replay-runtime.test.ts b/src/daemon/handlers/__tests__/session-replay-runtime.test.ts index 3e97e0a20..cb713ffa5 100644 --- a/src/daemon/handlers/__tests__/session-replay-runtime.test.ts +++ b/src/daemon/handlers/__tests__/session-replay-runtime.test.ts @@ -511,7 +511,7 @@ test('replay rejects malformed .ad lines with unclosed quotes', async () => { const sessionStore = new SessionStore(path.join(root, 'sessions')); const sessionName = 'default'; sessionStore.set(sessionName, makeIosSession(sessionName)); - const filePath = writeReplayFile(root, ['click "id=\\"broken\\"']); + const filePath = writeReplayFile(root, [String.raw`click "id=\"broken\"`]); const response = await runReplayScriptSource({ req: baseReq({ positionals: [filePath] }), diff --git a/src/daemon/handlers/__tests__/session-replay-selector-routes.test.ts b/src/daemon/handlers/__tests__/session-replay-selector-routes.test.ts index 4015862fe..3aabd0fea 100644 --- a/src/daemon/handlers/__tests__/session-replay-selector-routes.test.ts +++ b/src/daemon/handlers/__tests__/session-replay-selector-routes.test.ts @@ -38,11 +38,11 @@ test('replay executes selector reads before reporting a covered-target divergenc sessionStore.set(sessionName, makeIosSession(sessionName)); const filePath = writeReplayFile(root, [ 'open "Demo"', - 'get text "id=\\"field-name\\""', - 'get attrs "id=\\"field-name\\""', - 'is visible "id=\\"field-name\\""', + String.raw`get text "id=\"field-name\""`, + String.raw`get attrs "id=\"field-name\""`, + String.raw`is visible "id=\"field-name\""`, 'find id "field-name" get attrs', - 'click "id=\\"field-name\\""', + String.raw`click "id=\"field-name\""`, ]); const invoked: string[] = []; diff --git a/src/daemon/handlers/__tests__/session-script-publication.test.ts b/src/daemon/handlers/__tests__/session-script-publication.test.ts index 08945fb0c..ac0300440 100644 --- a/src/daemon/handlers/__tests__/session-script-publication.test.ts +++ b/src/daemon/handlers/__tests__/session-script-publication.test.ts @@ -98,7 +98,7 @@ test('publishes without close, returns the path/count, and leaves a terminal liv ok: true, data: { session: 'authoring', savedScript: outputPath, actionCount: 3 }, }); - expect(fs.readFileSync(outputPath, 'utf8')).toContain('wait "id=\\"screen-x\\""'); + expect(fs.readFileSync(outputPath, 'utf8')).toContain(String.raw`wait "id=\"screen-x\""`); expect(fs.readFileSync(outputPath, 'utf8')).not.toContain('\nclose'); expect(store.get('authoring')).toBe(session); expect(session.scriptPublication).toMatchObject({ kind: 'authoring', status: 'published' }); diff --git a/src/daemon/handlers/__tests__/wait-landmark-recording.test.ts b/src/daemon/handlers/__tests__/wait-landmark-recording.test.ts index fa27a7d66..456802678 100644 --- a/src/daemon/handlers/__tests__/wait-landmark-recording.test.ts +++ b/src/daemon/handlers/__tests__/wait-landmark-recording.test.ts @@ -84,9 +84,10 @@ function waitReq(overrides: Partial = {}): DaemonRequest { async function runWait(options: { recording?: boolean; req?: DaemonRequest } = {}) { const sessionStore = makeSessionStore(); - const session = makeAndroidSession('default', { - ...(options.recording ? { scriptPublication: authoringPublication('armed') } : {}), - }); + const session = makeAndroidSession( + 'default', + options.recording ? { scriptPublication: authoringPublication('armed') } : {}, + ); sessionStore.set('default', session); const response = await dispatchWaitViaRuntime({ req: options.req ?? waitReq(), diff --git a/src/daemon/handlers/find-match-resolution.ts b/src/daemon/handlers/find-match-resolution.ts index fa5912cab..ae9a22ab1 100644 --- a/src/daemon/handlers/find-match-resolution.ts +++ b/src/daemon/handlers/find-match-resolution.ts @@ -85,7 +85,7 @@ function narrowMultipleMatches( flags: DaemonRequest['flags'], ): SnapshotState['nodes'] | null { if (flags?.findFirst) return [matches[0]!]; - if (flags?.findLast) return [matches[matches.length - 1]!]; + if (flags?.findLast) return [matches.at(-1)!]; return null; } diff --git a/src/daemon/handlers/session-doctor-metro.ts b/src/daemon/handlers/session-doctor-metro.ts index ed4d8578f..83fc948d6 100644 --- a/src/daemon/handlers/session-doctor-metro.ts +++ b/src/daemon/handlers/session-doctor-metro.ts @@ -97,7 +97,7 @@ async function findListeningProcessId(port: number): Promise if (result.exitCode !== 0) return undefined; return result.stdout .split('\n') - .map((line) => (line.startsWith('p') ? Number.parseInt(line.slice(1), 10) : NaN)) + .map((line) => (line.startsWith('p') ? Number.parseInt(line.slice(1), 10) : Number.NaN)) .find((pid) => Number.isInteger(pid) && pid > 0); } diff --git a/src/daemon/handlers/session-inventory.ts b/src/daemon/handlers/session-inventory.ts index ab14ffea8..192b0ff85 100644 --- a/src/daemon/handlers/session-inventory.ts +++ b/src/daemon/handlers/session-inventory.ts @@ -129,8 +129,8 @@ async function devicesInventoryResponse(req: DaemonRequest): Promise', + String.raw`Grep the file for token-efficient debugging, e.g. grep -n "Error\|Exception" `, notes: status.notes, }, }; diff --git a/src/daemon/handlers/session-perf-runtime.ts b/src/daemon/handlers/session-perf-runtime.ts index a75675432..569d06963 100644 --- a/src/daemon/handlers/session-perf-runtime.ts +++ b/src/daemon/handlers/session-perf-runtime.ts @@ -414,7 +414,7 @@ function validatePerfFlagPlacement(req: DaemonRequest): void { } function timestampToken(): string { - return new Date().toISOString().replace(/[:.]/g, '-'); + return new Date().toISOString().replaceAll(/[:.]/g, '-'); } function readString(value: unknown): string | undefined { return typeof value === 'string' && value.length > 0 ? value : undefined; diff --git a/src/daemon/handlers/session-replay-action-runtime.ts b/src/daemon/handlers/session-replay-action-runtime.ts index c8357f845..2723220a2 100644 --- a/src/daemon/handlers/session-replay-action-runtime.ts +++ b/src/daemon/handlers/session-replay-action-runtime.ts @@ -75,15 +75,15 @@ export async function invokeReplayAction(params: { sourceAction: action, invoke, }); - } catch (dispatchErr) { + } catch (error) { // Only an expected AppError dispatch failure (e.g. a selector-miss) gets // normalized into a `{ok:false}` response so the loop's `if // (!response.ok)` divergence wrapping applies. A plain TypeError/ // ReferenceError or other programmer bug must propagate to the outer // internal-error path rather than being coerced into a repairable // REPLAY_DIVERGENCE that would mask a crash. - if (!(dispatchErr instanceof AppError)) throw dispatchErr; - response = { ok: false, error: normalizeError(dispatchErr) }; + if (!(error instanceof AppError)) throw error; + response = { ok: false, error: normalizeError(error) }; } const finishedAt = Date.now(); diff --git a/src/daemon/handlers/session-replay-heal.ts b/src/daemon/handlers/session-replay-heal.ts index b571f6d6f..e1d58fc14 100644 --- a/src/daemon/handlers/session-replay-heal.ts +++ b/src/daemon/handlers/session-replay-heal.ts @@ -18,7 +18,7 @@ function parseSelectorWaitPositionals(positionals: string[]): { selectorTimeout: string | null; } { if (positionals.length === 0) return { selectorExpression: null, selectorTimeout: null }; - const maybeTimeout = positionals[positionals.length - 1]; + const maybeTimeout = positionals.at(-1); const selectorTimeout = maybeTimeout !== undefined && /^\d+$/.test(maybeTimeout) ? maybeTimeout : null; const hasTimeout = selectorTimeout !== null; diff --git a/src/daemon/handlers/session-replay-runtime.ts b/src/daemon/handlers/session-replay-runtime.ts index dd7855e51..da746bdb2 100644 --- a/src/daemon/handlers/session-replay-runtime.ts +++ b/src/daemon/handlers/session-replay-runtime.ts @@ -180,8 +180,8 @@ export async function runReplayScriptSource( coordinator, keepSession, }); - } catch (err) { - const appErr = asAppError(err); + } catch (error) { + const appErr = asAppError(error); return errorResponse( appErr.code, appErr.message, diff --git a/src/daemon/handlers/session-test-shard-devices.ts b/src/daemon/handlers/session-test-shard-devices.ts index 03ef5adc3..30de11fc6 100644 --- a/src/daemon/handlers/session-test-shard-devices.ts +++ b/src/daemon/handlers/session-test-shard-devices.ts @@ -168,7 +168,7 @@ function isShardDeviceCandidate(device: DeviceInfo, flags: CommandFlags | undefi } function normalizeDeviceName(value: string): string { - return value.toLowerCase().replace(/_/g, ' ').replace(/\s+/g, ' ').trim(); + return value.toLowerCase().replaceAll('_', ' ').replaceAll(/\s+/g, ' ').trim(); } function compareShardDevices(a: DeviceInfo, b: DeviceInfo): number { diff --git a/src/daemon/handlers/session-test-suite-command.ts b/src/daemon/handlers/session-test-suite-command.ts index 94b4147e0..db6811e7a 100644 --- a/src/daemon/handlers/session-test-suite-command.ts +++ b/src/daemon/handlers/session-test-suite-command.ts @@ -201,8 +201,8 @@ export async function runReplayTestSuiteCommand( try { suiteRequest = toReplayTestSuiteRequest(req, sessionName); sourceBundles = requireReplayTestScriptSources(req); - } catch (err) { - const appErr = asAppError(err); + } catch (error) { + const appErr = asAppError(error); return errorResponse(appErr.code, appErr.message); } const sourceBundlesByPath = new Map(sourceBundles.map((bundle) => [bundle.entry, bundle])); diff --git a/src/daemon/http-errors.ts b/src/daemon/http-errors.ts index 51bddf643..ad1631d4f 100644 --- a/src/daemon/http-errors.ts +++ b/src/daemon/http-errors.ts @@ -12,6 +12,7 @@ export function statusCodeForNormalizedError(code: string): number { case 'SESSION_NOT_FOUND': // ADR 0012 R7 (C5a): a reaped repair session is a gone-session state, like // SESSION_NOT_FOUND — not an internal error. + // fallthrough case 'REPAIR_SESSION_EXPIRED': return 404; default: diff --git a/src/daemon/lease-registry.ts b/src/daemon/lease-registry.ts index beb307c72..cc58f7627 100644 --- a/src/daemon/lease-registry.ts +++ b/src/daemon/lease-registry.ts @@ -115,7 +115,7 @@ function normalizeLeaseBackend(raw: string | undefined): LeaseBackend { function normalizeDeviceKey(raw: string | undefined): string | undefined { if (raw === undefined) return undefined; const value = raw.trim(); - if (!value || value.length > 256 || !/^[\x20-\x7E]+$/.test(value)) { + if (!value || value.length > 256 || !/^[\u0020-\u007E]+$/.test(value)) { throw new AppError('INVALID_ARGS', 'Invalid device key. Use 1-256 printable characters.'); } return value; diff --git a/src/daemon/scroll-runtime.ts b/src/daemon/scroll-runtime.ts index 5e0d987ac..bcdd50029 100644 --- a/src/daemon/scroll-runtime.ts +++ b/src/daemon/scroll-runtime.ts @@ -87,9 +87,7 @@ export async function resolveBoundScrollRuntime( const target = parseScrollTarget(directionInput); const options = resolveScrollExecutionOptions({ amount, pixels, durationMs }, target.edge); - const plan = resolveScrollRuntimePlan({ - ...(target.edge === undefined ? {} : { edge: target.edge }), - }); + const plan = resolveScrollRuntimePlan(target.edge === undefined ? {} : { edge: target.edge }); const admission = { command: 'scroll', device: params.device, diff --git a/src/daemon/selector-operation-binding.ts b/src/daemon/selector-operation-binding.ts index a327270bd..2453a9761 100644 --- a/src/daemon/selector-operation-binding.ts +++ b/src/daemon/selector-operation-binding.ts @@ -30,14 +30,14 @@ export function selectElementTextOperation( ): Pick { const { readTextAtPoint } = runtime.operations; const selected = readTextAtPoint ? { operations: { readTextAtPoint } } : undefined; - return Object.freeze({ - ...(selected + return Object.freeze( + selected ? { readTextAtPoint: async (input: ReadTextAtPointInput) => await selected.operations.readTextAtPoint(input), } - : {}), - }); + : {}, + ); } /** Projects only the fact-conditional observations admitted for `wait`. */ diff --git a/src/daemon/server/http-server.ts b/src/daemon/server/http-server.ts index 017d987d9..9cf5f6bd6 100644 --- a/src/daemon/server/http-server.ts +++ b/src/daemon/server/http-server.ts @@ -215,7 +215,7 @@ function readRequiredGitHubArtifactText( function readGitHubArtifactInteger(record: Record, key: 'artifactId' | 'runId') { const value = record[key]; const parsed = - typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : NaN; + typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : Number.NaN; if (!Number.isInteger(parsed)) { throw new AppError('INVALID_ARGS', `Invalid params: source.${key} must be an integer`); } diff --git a/src/daemon/server/server-lifecycle.ts b/src/daemon/server/server-lifecycle.ts index 77adf7165..992b27f42 100644 --- a/src/daemon/server/server-lifecycle.ts +++ b/src/daemon/server/server-lifecycle.ts @@ -80,9 +80,9 @@ export function acquireDaemonLock( try { fs.writeFileSync(lockPath, payload, { flag: 'wx', mode: 0o600 }); return true; - } catch (err) { - if ((err as NodeJS.ErrnoException).code === 'EEXIST') return false; - throw err; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'EEXIST') return false; + throw error; } }; diff --git a/src/daemon/server/transport.ts b/src/daemon/server/transport.ts index 49fd03638..595472ce6 100644 --- a/src/daemon/server/transport.ts +++ b/src/daemon/server/transport.ts @@ -85,8 +85,8 @@ export function createSocketServer(handleRequest: DaemonInvokeFn): DaemonServer : undefined, async () => await handleRequest(req), ); - } catch (err) { - response = { ok: false, error: normalizeError(err) }; + } catch (error) { + response = { ok: false, error: normalizeError(error) }; } finally { inFlightRequests -= 1; if (requestAbortRegistration) { diff --git a/src/daemon/session-event-action.ts b/src/daemon/session-event-action.ts index b9a64e28e..7be18d2a3 100644 --- a/src/daemon/session-event-action.ts +++ b/src/daemon/session-event-action.ts @@ -89,9 +89,7 @@ function buildIdentityActionDetails(action: SessionAction): Record { const result = action.result ?? {}; if (action.command === PUBLIC_COMMANDS.snapshot) { - return { - ...(Array.isArray(result.nodes) ? { nodeCount: result.nodes.length } : {}), - }; + return Array.isArray(result.nodes) ? { nodeCount: result.nodes.length } : {}; } if ( action.command !== PUBLIC_COMMANDS.wait && diff --git a/src/daemon/session-event-log-window.ts b/src/daemon/session-event-log-window.ts index d0960044b..87c5bb448 100644 --- a/src/daemon/session-event-log-window.ts +++ b/src/daemon/session-event-log-window.ts @@ -112,7 +112,7 @@ function placeSessionEventLogWindow(eventLogPath: string): SessionEventLogWindow } if (sidecar === undefined) return placeNeverRotatedWindow(eventLogPath, rotatedPath); - const newest = sidecar[sidecar.length - 1]; + const newest = sidecar.at(-1); if (!newest) throw unverifiedWindowError(eventLogPath, 'window file records no generation'); const activeExists = fileExists(eventLogPath); @@ -206,7 +206,7 @@ export async function rotateSessionEventLogIfNeeded( const measured = await measureEventLogFile(eventLogPath); if (measured.firstLineDigest !== undefined) { const generations = sidecar ?? []; - const newest = generations[generations.length - 1]; + const newest = generations.at(-1); // A record whose digest already matches this exact file means a previous // rotation wrote the record but died before its rename; reuse it instead of // counting the same generation twice. diff --git a/src/daemon/session-paths.ts b/src/daemon/session-paths.ts index 544f9dd3a..7735ef6a5 100644 --- a/src/daemon/session-paths.ts +++ b/src/daemon/session-paths.ts @@ -1,7 +1,7 @@ import { resolveUserPath } from '@agent-device/host-kit/file'; export function safeSessionName(name: string): string { - return name.replace(/[^a-zA-Z0-9._-]/g, '_'); + return name.replaceAll(/[^a-zA-Z0-9._-]/g, '_'); } /** diff --git a/src/daemon/session-script-writer.ts b/src/daemon/session-script-writer.ts index acf914850..b427cf1b8 100644 --- a/src/daemon/session-script-writer.ts +++ b/src/daemon/session-script-writer.ts @@ -163,7 +163,7 @@ export class SessionScriptWriter { return expandSessionPath(targetPath); } const safeName = safeSessionName(session.name); - const timestamp = new Date(session.createdAt).toISOString().replace(/[:.]/g, '-'); + const timestamp = new Date(session.createdAt).toISOString().replaceAll(/[:.]/g, '-'); return path.join(this.sessionsDir, `${safeName}-${timestamp}.ad`); } } diff --git a/src/daemon/session-store.ts b/src/daemon/session-store.ts index e848b2e20..5a387058f 100644 --- a/src/daemon/session-store.ts +++ b/src/daemon/session-store.ts @@ -296,7 +296,7 @@ export class SessionStore { defaultTracePath(session: SessionState): string { const safeName = safeSessionName(session.name); - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-'); return path.join(this.sessionsDir, `${safeName}-${timestamp}.trace.log`); } diff --git a/src/daemon/wait-current-surface.ts b/src/daemon/wait-current-surface.ts index 950946671..343e08946 100644 --- a/src/daemon/wait-current-surface.ts +++ b/src/daemon/wait-current-surface.ts @@ -139,7 +139,7 @@ function extractSurfaceText(node: SnapshotNode, options: { includeIdentifiers: b const value = candidates .map((candidate) => (typeof candidate === 'string' ? candidate.trim() : '')) .find((candidate) => candidate.length > 0); - return value ? value.replace(/\s+/g, ' ').slice(0, 80) : ''; + return value ? value.replaceAll(/\s+/g, ' ').slice(0, 80) : ''; } function isChromeLikeNode(node: SnapshotNode): boolean { diff --git a/src/mcp/__tests__/command-tools.test.ts b/src/mcp/__tests__/command-tools.test.ts index b1b73c3c5..99cfb0f0e 100644 --- a/src/mcp/__tests__/command-tools.test.ts +++ b/src/mcp/__tests__/command-tools.test.ts @@ -112,7 +112,7 @@ test('MCP command tool executor renders JSON text when requested', async () => { const result = await executor.execute('snapshot', { mcpOutputFormat: 'json' }); - assert.match(result.content[0]?.text ?? '', /^\{\n "nodes": \[/); + assert.match(result.content[0]?.text ?? '', /^\{\n {2}"nodes": \[/); assert.match(result.content[0]?.text ?? '', /"label": "Continue"/); }); diff --git a/src/platform-runtime-runtime-hints.ts b/src/platform-runtime-runtime-hints.ts index 04954f2db..1fae783ae 100644 --- a/src/platform-runtime-runtime-hints.ts +++ b/src/platform-runtime-runtime-hints.ts @@ -363,7 +363,7 @@ async function assertAndroidRuntimePackageName(packageName: string): Promise 0 ? { wallClockAtMs, uptimeMs: result.currentUptimeMs } : undefined; - } catch (_error) { + } catch { signal?.throwIfAborted(); return undefined; } diff --git a/src/platforms/android/__tests__/adb-executor.test.ts b/src/platforms/android/__tests__/adb-executor.test.ts index 9322ef767..328c39265 100644 --- a/src/platforms/android/__tests__/adb-executor.test.ts +++ b/src/platforms/android/__tests__/adb-executor.test.ts @@ -444,7 +444,7 @@ test('the local adb executor attaches classified hints to thrown command failure const error = await adb(['shell', 'echo', 'hi']).then( () => assert.fail('expected the adb call to reject'), - (err: unknown) => err, + (error: unknown) => error, ); assert.ok(error instanceof AppError); @@ -473,7 +473,7 @@ test('the local adb executor flags transient transport failures retriable', asyn const error = await adb(['shell', 'echo', 'hi']).then( () => assert.fail('expected the adb call to reject'), - (err: unknown) => err, + (error: unknown) => error, ); assert.ok(error instanceof AppError); @@ -505,7 +505,7 @@ test('the local adb executor classifies exec-layer timeouts as a wedged adb serv const error = await adb(['devices', '-l']).then( () => assert.fail('expected the adb call to reject'), - (err: unknown) => err, + (error: unknown) => error, ); assert.ok(error instanceof AppError); @@ -568,7 +568,7 @@ test('port reverse removal failures surface as classified AppErrors, not bare Er const error = await provider.remove('tcp:8081').then( () => assert.fail('expected the removal to reject'), - (err: unknown) => err, + (error: unknown) => error, ); assert.ok(error instanceof AppError); @@ -649,7 +649,7 @@ test('semantic provider install failures carry classified hints', async () => { async () => await installAndroidAdbPackage('/app.apk', { replace: true }).then( () => assert.fail('expected the provider install to reject'), - (err: unknown) => err, + (error: unknown) => error, ), ); @@ -672,7 +672,7 @@ test('explicitly passed provider pull failures carry classified hints', async () }, }).then( () => assert.fail('expected the provider pull to reject'), - (err: unknown) => err, + (error: unknown) => error, ); assert.ok(error instanceof AppError); @@ -702,7 +702,7 @@ test('provider-scoped adb failures get the same classified hints as local execut async () => await resolveAndroidAdbExecutor(device)(['shell', 'echo', 'hi']).then( () => assert.fail('expected the provider-scoped call to reject'), - (err: unknown) => err, + (error: unknown) => error, ), ); diff --git a/src/platforms/android/__tests__/adb-host-binding.test.ts b/src/platforms/android/__tests__/adb-host-binding.test.ts index 43c7b2b2b..8d4ccb2bc 100644 --- a/src/platforms/android/__tests__/adb-host-binding.test.ts +++ b/src/platforms/android/__tests__/adb-host-binding.test.ts @@ -22,7 +22,7 @@ test.skipIf(process.platform === 'win32')( try { const error = await runAndroidHostAdb(['devices']).then( () => assert.fail('expected local adb to reject'), - (cause: unknown) => cause, + (error: unknown) => error, ); assert.ok(error instanceof AppError); diff --git a/src/platforms/android/__tests__/app-deployment.test.ts b/src/platforms/android/__tests__/app-deployment.test.ts index af2aab4d1..bed96c529 100644 --- a/src/platforms/android/__tests__/app-deployment.test.ts +++ b/src/platforms/android/__tests__/app-deployment.test.ts @@ -90,7 +90,7 @@ test('installAndroidInstallablePath installs .aab via bundletool build-apks + in bundletoolPath, [ '#!/bin/sh', - 'printf "bundletool %s\\n" "$*" >> "$AGENT_DEVICE_TEST_ARGS_FILE"', + String.raw`printf "bundletool %s\n" "$*" >> "$AGENT_DEVICE_TEST_ARGS_FILE"`, 'if [ "$1" = "build-apks" ]; then', ' out=""', ' while [ "$#" -gt 0 ]; do', diff --git a/src/platforms/android/__tests__/device-input-state.test.ts b/src/platforms/android/__tests__/device-input-state.test.ts index eabb3fae2..7c655d690 100644 --- a/src/platforms/android/__tests__/device-input-state.test.ts +++ b/src/platforms/android/__tests__/device-input-state.test.ts @@ -57,7 +57,7 @@ test('getAndroidKeyboardStatusWithAdb classifies tolerated adb failures with act const error = await getAndroidKeyboardStatusWithAdb(adb).then( () => assert.fail('expected the keyboard query to reject'), - (err: unknown) => err, + (error: unknown) => error, ); assert.ok(error instanceof AppError); diff --git a/src/platforms/android/__tests__/text-input.test.ts b/src/platforms/android/__tests__/text-input.test.ts index f0525f84c..68f29c35f 100644 --- a/src/platforms/android/__tests__/text-input.test.ts +++ b/src/platforms/android/__tests__/text-input.test.ts @@ -197,7 +197,7 @@ test('fillAndroid tolerates delayed React Native text verification', async () => return undefined; } if (isShellInput(args, 'text')) { - state += (args[3] ?? '').replace(/%s/g, ' '); + state += (args[3] ?? '').replaceAll('%s', ' '); return undefined; } return { stderr: `unexpected args: ${args.join(' ')}`, exitCode: 1 }; @@ -289,7 +289,7 @@ test('fillAndroid tolerates delayed React Native text verification', async () => return undefined; } if (isShellInput(args, 'text')) { - state += (args[3] ?? '').replace(/%s/g, ' '); + state += (args[3] ?? '').replaceAll('%s', ' '); return undefined; } return { stderr: `unexpected args: ${args.join(' ')}`, exitCode: 1 }; @@ -345,7 +345,7 @@ test('fillAndroid tolerates delayed React Native text verification', async () => return undefined; } if (isShellInput(args, 'text')) { - state += (args[3] ?? '').replace(/%s/g, ' '); + state += (args[3] ?? '').replaceAll('%s', ' '); return undefined; } return { stderr: `unexpected args: ${args.join(' ')}`, exitCode: 1 }; diff --git a/src/platforms/android/alert-detection.ts b/src/platforms/android/alert-detection.ts index 23b70429a..3f2a2ec63 100644 --- a/src/platforms/android/alert-detection.ts +++ b/src/platforms/android/alert-detection.ts @@ -216,7 +216,7 @@ function findCommonAncestorIndex( } } } - return common[common.length - 1]; + return common.at(-1); } function ancestorIndexes(nodes: RawSnapshotNode[], index: number): number[] { diff --git a/src/platforms/android/app-deployment-resolution.ts b/src/platforms/android/app-deployment-resolution.ts index 06170ec7a..9345717ed 100644 --- a/src/platforms/android/app-deployment-resolution.ts +++ b/src/platforms/android/app-deployment-resolution.ts @@ -96,7 +96,7 @@ export function inferAndroidAppName(packageName: string): string { .flatMap((segment) => segment.split(/[_-]+/)) .map((token) => token.trim().toLowerCase()) .filter((token) => token.length > 0); - let chosen = tokens[tokens.length - 1] ?? packageName; + let chosen = tokens.at(-1) ?? packageName; for (let index = tokens.length - 1; index >= 0; index -= 1) { const token = tokens[index]; if (token && !ignoredTokens.has(token)) { diff --git a/src/platforms/android/app-parsers.ts b/src/platforms/android/app-parsers.ts index 349b838f1..941111ae8 100644 --- a/src/platforms/android/app-parsers.ts +++ b/src/platforms/android/app-parsers.ts @@ -111,7 +111,7 @@ function parseAndroidBlockingDialogFromSegment( const focusedWindowTitle = respondingMatch[1]; if (focusedWindowTitle === undefined) return null; - const focusedWindow = focusedWindowTitle.trim().replace(/\s+/g, ' '); + const focusedWindow = focusedWindowTitle.trim().replaceAll(/\s+/g, ' '); const packageName = ANDROID_PACKAGE_PATTERN.exec(focusedWindow)?.[1]; return { ...(packageName ? { package: packageName } : {}), diff --git a/src/platforms/android/device-input-state.ts b/src/platforms/android/device-input-state.ts index 722b01ba1..b8afbe2ca 100644 --- a/src/platforms/android/device-input-state.ts +++ b/src/platforms/android/device-input-state.ts @@ -335,7 +335,7 @@ async function runAndroidClipboardShellCommand( } function normalizeAndroidClipboardText(stdout: string): string { - const normalized = stdout.replace(/\r\n/g, '\n').replace(/\n$/, ''); + const normalized = stdout.replaceAll('\r\n', '\n').replace(/\n$/, ''); const prefixed = normalized.match(/^clipboard text:\s*(.*)$/i); if (prefixed) return prefixed[1] ?? ''; if (normalized.trim().toLowerCase() === 'null') return ''; diff --git a/src/platforms/android/emulator-lifecycle.ts b/src/platforms/android/emulator-lifecycle.ts index 278c72d68..d417381e9 100644 --- a/src/platforms/android/emulator-lifecycle.ts +++ b/src/platforms/android/emulator-lifecycle.ts @@ -117,7 +117,7 @@ function isAndroidEmulatorSerial(serial: string): boolean { } function normalizeAndroidDeviceName(value: string): string { - return value.toLowerCase().replace(/_/g, ' ').replace(/\s+/g, ' ').trim(); + return value.toLowerCase().replaceAll('_', ' ').replaceAll(/\s+/g, ' ').trim(); } async function waitForEmulatorDiscovery(params: { diff --git a/src/platforms/android/fill-verification.ts b/src/platforms/android/fill-verification.ts index 145eef7b5..fca1d9480 100644 --- a/src/platforms/android/fill-verification.ts +++ b/src/platforms/android/fill-verification.ts @@ -369,7 +369,7 @@ function isAcceptableAndroidFillMatch(value: string, expected: string): boolean } function normalizeFillVerificationText(value: string): string { - return value.replace(/\s+/g, ' ').trim(); + return value.replaceAll(/\s+/g, ' ').trim(); } function isSentenceAutocapitalizeMatch(actual: string, expected: string): boolean { diff --git a/src/platforms/android/helper-package-install.ts b/src/platforms/android/helper-package-install.ts index 4a5a06c41..6d704876b 100644 --- a/src/platforms/android/helper-package-install.ts +++ b/src/platforms/android/helper-package-install.ts @@ -278,5 +278,5 @@ async function sha256File(filePath: string): Promise { } function escapeRegExp(value: string): string { - return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`); } diff --git a/src/platforms/android/perf-frame-parser.ts b/src/platforms/android/perf-frame-parser.ts index 56ebaa948..ffbc2c4f9 100644 --- a/src/platforms/android/perf-frame-parser.ts +++ b/src/platforms/android/perf-frame-parser.ts @@ -366,7 +366,7 @@ function computeFrameWindowMs(frames: AndroidFrameStatsRow[]): number | undefine } function matchSummaryInteger(text: string, label: string): number | undefined { - const escapedLabel = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const escapedLabel = label.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`); const match = text.match(new RegExp(`^\\s*${escapedLabel}:\\s*([0-9][0-9,]*)`, 'im')); if (!match) return undefined; const token = match[1]; diff --git a/src/platforms/android/perf-native-artifacts.ts b/src/platforms/android/perf-native-artifacts.ts index b01935046..de98b1ed6 100644 --- a/src/platforms/android/perf-native-artifacts.ts +++ b/src/platforms/android/perf-native-artifacts.ts @@ -75,7 +75,7 @@ export function buildAndroidNativeRemotePath( fileName: string, remoteDir = ANDROID_NATIVE_REMOTE_DIR, ): string { - const safePackage = packageName.replace(/[^A-Za-z0-9_.-]/g, '_'); + const safePackage = packageName.replaceAll(/[^A-Za-z0-9_.-]/g, '_'); return `${remoteDir}/agent-device-${safePackage}-${Date.now()}-${fileName}`; } @@ -115,7 +115,7 @@ export async function readFileSize(filePath: string): Promise { } export function shellQuote(value: string): string { - return `'${value.replace(/'/g, `'\\''`)}'`; + return `'${value.replaceAll("'", `'\\''`)}'`; } async function stopAndroidBackgroundTool( diff --git a/src/platforms/android/perf.ts b/src/platforms/android/perf.ts index 4e4c06f03..edc870ea3 100644 --- a/src/platforms/android/perf.ts +++ b/src/platforms/android/perf.ts @@ -205,7 +205,7 @@ async function resolveAndroidAppPid(adb: AndroidAdbExecutor, packageName: string const pid = result.stdout .trim() .split(/\s+/) - .map((token) => Number(token)) + .map(Number) .find((value) => Number.isInteger(value) && value > 0); if (result.exitCode === 0 && pid !== undefined) return pid; throw new AppError('COMMAND_FAILED', `No running Android process found for ${packageName}`, { @@ -218,7 +218,7 @@ async function resolveAndroidAppPid(adb: AndroidAdbExecutor, packageName: string } function buildAndroidRemoteHeapPath(packageName: string): string { - const safePackage = packageName.replace(/[^a-zA-Z0-9._-]/g, '_'); + const safePackage = packageName.replaceAll(/[^a-zA-Z0-9._-]/g, '_'); return `${ANDROID_REMOTE_HEAP_DIR}/agent-device-${safePackage}-${Date.now()}.hprof`; } @@ -297,7 +297,7 @@ function annotateAndroidPerfSamplingError( } function matchLabeledNumber(text: string, label: string): number | undefined { - const escapedLabel = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const escapedLabel = label.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`); const match = text.match(new RegExp(`${escapedLabel}:\\s*([0-9][0-9,]*)`, 'i')); if (!match) return undefined; const token = match[1]; diff --git a/src/platforms/android/snapshot-helper-session-lifecycle.ts b/src/platforms/android/snapshot-helper-session-lifecycle.ts index a9f2d7b1e..d3c82efff 100644 --- a/src/platforms/android/snapshot-helper-session-lifecycle.ts +++ b/src/platforms/android/snapshot-helper-session-lifecycle.ts @@ -192,7 +192,7 @@ async function startAndroidSnapshotHelperSession(params: { outputPath: undefined, emitChunks: false, }); - const runner = args[args.length - 1]; + const runner = args.at(-1); if (!runner) { throw new AppError('INVALID_ARGS', 'Android snapshot helper runner was not resolved'); } diff --git a/src/platforms/android/text-input.ts b/src/platforms/android/text-input.ts index 3502f4f97..2bdb6a665 100644 --- a/src/platforms/android/text-input.ts +++ b/src/platforms/android/text-input.ts @@ -386,7 +386,7 @@ function isAndroidShellTextSupported(text: string): boolean { function encodeAndroidInputText(text: string): string { // Android shell input uses `%s` as the escaped token for spaces. - return text.replace(/ /g, '%s'); + return text.replaceAll(' ', '%s'); } function isAndroidInputTextUnsupported(error: unknown): boolean { diff --git a/src/platforms/android/ui-hierarchy.ts b/src/platforms/android/ui-hierarchy.ts index ea31c1232..123247c8e 100644 --- a/src/platforms/android/ui-hierarchy.ts +++ b/src/platforms/android/ui-hierarchy.ts @@ -58,7 +58,7 @@ export type AndroidUiNodeMetadata = { */ function createAndroidChromeSubtreeTracker() { const openElements: boolean[] = []; - const inChromeNow = (): boolean => openElements[openElements.length - 1] === true; + const inChromeNow = (): boolean => openElements.at(-1) === true; return { /** Enters an opening tag; returns whether that node is inside the chrome subtree. */ open(resourceId: string | null | undefined, selfClosing: boolean): boolean { @@ -117,23 +117,23 @@ function readNodeAttributes(node: string): Omit { const optionalNumberAttr = ( key: Key, name: string, - ): Pick | {} => { + ): Partial> => { const value = numberAttr(name); - return value === undefined ? {} : { [key]: value }; + return value === undefined ? {} : ({ [key]: value } as Pick); }; const optionalRectAttr = ( key: Key, name: string, - ): Pick | {} => { + ): Partial> => { const value = parseBounds(getAttr(name)); - return value === undefined ? {} : { [key]: value }; + return value === undefined ? {} : ({ [key]: value } as Pick); }; const optionalBoolAttr = ( key: Key, name: string, - ): Pick | {} => { + ): Partial> => { const value = boolAttr(name); - return value === undefined ? {} : { [key]: value }; + return value === undefined ? {} : ({ [key]: value } as Pick); }; return { text: getAttr('text'), @@ -269,7 +269,7 @@ export function parseUiHierarchyTree(xml: string): AndroidUiHierarchy { continue; } const attrs = readAndroidUiNodeMetadata(token); - const parent = stack[stack.length - 1]!; + const parent = stack.at(-1)!; const node = normalizeAndroidUiHierarchyNode(attrs, parent.depth + 1, parent); parent.children.push(node); if (!token.endsWith('/>')) { diff --git a/src/platforms/android/window-state.ts b/src/platforms/android/window-state.ts index fd0308ec0..f85b60caa 100644 --- a/src/platforms/android/window-state.ts +++ b/src/platforms/android/window-state.ts @@ -206,7 +206,7 @@ function markerSectionPattern(markers: readonly string[]): RegExp { } function escapeRegExp(value: string): string { - return value.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`); + return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`); } function parseLegacyAndroidForegroundApp(text: string): AppStateRuntimeResult | null { diff --git a/src/platforms/apple/alert.ts b/src/platforms/apple/alert.ts index d72a4399c..dc74bf5b3 100644 --- a/src/platforms/apple/alert.ts +++ b/src/platforms/apple/alert.ts @@ -116,9 +116,9 @@ export async function actOnAppleAlert( : remainingBudgetMs(start, ALERT_ACTION_RETRY_MS); firstAttempt = false; return await runAlert(action, budgetMs); - } catch (err) { - lastError = err; - if (!isAlertNotFoundError(err)) break; + } catch (error) { + lastError = error; + if (!isAlertNotFoundError(error)) break; } await sleep(ALERT_POLL_INTERVAL_MS); } diff --git a/src/platforms/apple/core/__tests__/apps.test.ts b/src/platforms/apple/core/__tests__/apps.test.ts index 5d9b53194..57da6fec9 100644 --- a/src/platforms/apple/core/__tests__/apps.test.ts +++ b/src/platforms/apple/core/__tests__/apps.test.ts @@ -503,7 +503,7 @@ test('closeIosApp on macOS uses helper quit for bundle identifiers', async () => await withMockedMacOsHelper( [ '#!/bin/sh', - 'printf "%s\\n" "$@" > "$AGENT_DEVICE_TEST_ARGS_FILE"', + String.raw`printf "%s\n" "$@" > "$AGENT_DEVICE_TEST_ARGS_FILE"`, "cat <<'JSON'", '{"ok":true,"data":{"bundleId":"com.example.foobar","running":false,"terminated":false,"forceTerminated":false}}', 'JSON', @@ -896,7 +896,7 @@ test('setIosSetting permission grant accessibility uses macOS helper', async () await withMockedMacOsHelper( [ '#!/bin/sh', - 'printf "%s\\n" "$@" > "$AGENT_DEVICE_TEST_ARGS_FILE"', + String.raw`printf "%s\n" "$@" > "$AGENT_DEVICE_TEST_ARGS_FILE"`, "cat <<'JSON'", '{"ok":true,"data":{"target":"accessibility","action":"grant","granted":true,"requested":true,"openedSettings":false}}', 'JSON', diff --git a/src/platforms/apple/core/app-device-io.ts b/src/platforms/apple/core/app-device-io.ts index 912a53a6d..f29e9497e 100644 --- a/src/platforms/apple/core/app-device-io.ts +++ b/src/platforms/apple/core/app-device-io.ts @@ -17,7 +17,7 @@ export async function readIosClipboardText(device: DeviceInfo): Promise await runSimctl(device, ['pbpaste', device.id], { allowFailure: true }), 'Failed to read iOS simulator clipboard', ); - return result.stdout.replace(/\r\n/g, '\n').replace(/\n$/, ''); + return result.stdout.replaceAll('\r\n', '\n').replace(/\n$/, ''); } export async function writeIosClipboardText(device: DeviceInfo, text: string): Promise { diff --git a/src/platforms/apple/core/devicectl.ts b/src/platforms/apple/core/devicectl.ts index e0c80933e..8a709140a 100644 --- a/src/platforms/apple/core/devicectl.ts +++ b/src/platforms/apple/core/devicectl.ts @@ -276,7 +276,7 @@ export function parseIosDeviceProcessesPayload(payload: unknown): IosDeviceProce const pid = typeof entry.processIdentifier === 'number' && Number.isFinite(entry.processIdentifier) ? entry.processIdentifier - : NaN; + : Number.NaN; if (!executable || !Number.isFinite(pid)) continue; parsed.push({ executable, pid }); } diff --git a/src/platforms/apple/core/perf.ts b/src/platforms/apple/core/perf.ts index 175de0d32..c9bcee516 100644 --- a/src/platforms/apple/core/perf.ts +++ b/src/platforms/apple/core/perf.ts @@ -887,7 +887,7 @@ function summarizeIosDeviceMemorySnapshot( } async function resolveMacOsBundlePath(appBundleId: string): Promise { - const query = `kMDItemCFBundleIdentifier == "${appBundleId.replaceAll('"', '\\"')}"`; + const query = `kMDItemCFBundleIdentifier == "${appBundleId.replaceAll('"', String.raw`\"`)}"`; const result = requireExecSuccess( await runAppleToolCommand('mdfind', [query], { allowFailure: true, diff --git a/src/platforms/apple/core/screenshot.ts b/src/platforms/apple/core/screenshot.ts index 5a5845e33..658d0d8cb 100644 --- a/src/platforms/apple/core/screenshot.ts +++ b/src/platforms/apple/core/screenshot.ts @@ -377,7 +377,7 @@ export function resolveSimulatorRunnerScreenshotCandidatePaths( }; const relativeFromRoot = rawRemotePath.replace(/^\/+/, ''); - const remotePosixPath = relativeFromRoot.replace(/\\/g, '/'); + const remotePosixPath = relativeFromRoot.replaceAll('\\', '/'); if (relativeFromRoot) { pushUnique(path.join(normalizedContainerPath, relativeFromRoot)); } diff --git a/src/platforms/apple/os/macos/host-provider.ts b/src/platforms/apple/os/macos/host-provider.ts index dce442825..82f5b6b42 100644 --- a/src/platforms/apple/os/macos/host-provider.ts +++ b/src/platforms/apple/os/macos/host-provider.ts @@ -29,7 +29,7 @@ export function createLocalAppleMacOsHostProvider( await runCommand('pbpaste', [], { allowFailure: true }), 'Failed to read macOS clipboard', ); - return result.stdout.replace(/\r\n/g, '\n').replace(/\n$/, ''); + return result.stdout.replaceAll('\r\n', '\n').replace(/\n$/, ''); }, writeClipboard: async (text) => { requireExecSuccess( diff --git a/src/platforms/harmonyos/app-lifecycle.ts b/src/platforms/harmonyos/app-lifecycle.ts index 4727d4b27..fa83a07b9 100644 --- a/src/platforms/harmonyos/app-lifecycle.ts +++ b/src/platforms/harmonyos/app-lifecycle.ts @@ -17,10 +17,10 @@ export interface HarmonyLaunchTarget { } interface HarmonyBundleDump { - applicationInfo?: { + appInfo?: { isSystemApp?: boolean; }; - appInfo?: { + applicationInfo?: { isSystemApp?: boolean; }; hapModuleInfos?: Array<{ diff --git a/src/platforms/linux/tool-provider.ts b/src/platforms/linux/tool-provider.ts index 540e30088..44fd5bef1 100644 --- a/src/platforms/linux/tool-provider.ts +++ b/src/platforms/linux/tool-provider.ts @@ -142,11 +142,11 @@ function createLocalLinuxDesktopProvider( } if (await whichCommand(target)) { - runCommand(target, [], { allowFailure: true }).catch((err) => { + runCommand(target, [], { allowFailure: true }).catch((error) => { emitDiagnostic({ level: 'warn', phase: 'linux_app_launch', - data: { app: target, error: String(err) }, + data: { app: target, error: String(error) }, }); }); await sleep(500); diff --git a/src/platforms/vega/__tests__/doctor.test.ts b/src/platforms/vega/__tests__/doctor.test.ts index 68d2905ce..f4c3a2f42 100644 --- a/src/platforms/vega/__tests__/doctor.test.ts +++ b/src/platforms/vega/__tests__/doctor.test.ts @@ -42,15 +42,13 @@ test('Vega doctor reports CLI version and connected-device readiness through sem test('Vega doctor does not report an unvalidated physical TV as supported readiness', async () => { const check: DoctorCheck = await withVegaToolProvider(makeVegaProvider(), async () => vegaToolchainCheck( - contextWith(async () => - Promise.reject( - new AppError( - 'DEVICE_NOT_FOUND', - 'Vega CLI found devices, but no supported Vega Virtual Device is running.', - { listedSerials: ['G071R20720350DT6'] }, - ), - ), - ), + contextWith(async () => { + throw new AppError( + 'DEVICE_NOT_FOUND', + 'Vega CLI found devices, but no supported Vega Virtual Device is running.', + { listedSerials: ['G071R20720350DT6'] }, + ); + }), ), ); @@ -66,7 +64,9 @@ test('Vega doctor propagates canonical request cancellation', async () => { await assert.rejects( withVegaToolProvider(makeVegaProvider(), async () => vegaToolchainCheck( - contextWith(async () => Promise.reject(new AppError('COMMAND_FAILED', 'request canceled'))), + contextWith(async () => { + throw new AppError('COMMAND_FAILED', 'request canceled'); + }), ), ), (error: unknown) => error instanceof AppError && error.message === 'request canceled', @@ -77,13 +77,11 @@ test('Vega doctor propagates missing request inventory context', async () => { await assert.rejects( withVegaToolProvider(makeVegaProvider(), async () => vegaToolchainCheck( - contextWith(async () => - Promise.reject( - new AppError('COMMAND_FAILED', 'device inventory context unavailable', { - reason: 'device_inventory_context_unavailable', - }), - ), - ), + contextWith(async () => { + throw new AppError('COMMAND_FAILED', 'device inventory context unavailable', { + reason: 'device_inventory_context_unavailable', + }); + }), ), ), (error: unknown) => @@ -119,7 +117,11 @@ test('Vega doctor keeps version failures diagnostic when neutral inventory fails version: async () => ({ exitCode: 1, stdout: '', stderr: 'version failed' }), }; const check = await withVegaToolProvider(provider, async () => - vegaToolchainCheck(contextWith(async () => Promise.reject(new Error('inventory failed')))), + vegaToolchainCheck( + contextWith(async () => { + throw new Error('inventory failed'); + }), + ), ); assert.deepEqual(check, { diff --git a/src/platforms/web/agent-browser-process-record.ts b/src/platforms/web/agent-browser-process-record.ts index 448ec570b..25298f82c 100644 --- a/src/platforms/web/agent-browser-process-record.ts +++ b/src/platforms/web/agent-browser-process-record.ts @@ -182,5 +182,5 @@ function isCommandUnderPath(processInfo: HostProcessInfo, rootPath: string): boo } function normalizePathSeparators(value: string): string { - return value.replace(/\\/g, '/'); + return value.replaceAll('\\', '/'); } diff --git a/src/platforms/web/agent-browser-snapshot.ts b/src/platforms/web/agent-browser-snapshot.ts index c8502d2a3..4f0211eab 100644 --- a/src/platforms/web/agent-browser-snapshot.ts +++ b/src/platforms/web/agent-browser-snapshot.ts @@ -152,7 +152,9 @@ function extractBrowserRef(line: string): string | null { } function extractNodeAnnotation(line: string): string | undefined { - const structure = line.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''"); + const structure = line + .replaceAll(/"(?:[^"\\]|\\.)*"/g, '""') + .replaceAll(/'(?:[^'\\]|\\.)*'/g, "''"); return structure.match(/\[([^\]]*\bref\s*=\s*['"]?@?e\d+[^\]]*)\]\s*(?=:|$)/i)?.[1]; } @@ -163,9 +165,9 @@ function normalizeBrowserRef(value: string | undefined): string | null { function extractRole(line: string): string | undefined { const cleaned = line - .replace(/\[[^\]]*ref[^\]]*\]/gi, '') - .replace(/@?e\d+\b/gi, '') - .replace(/^[\s|├└─>*-]+/g, '') + .replaceAll(/\[[^\]]*ref[^\]]*\]/gi, '') + .replaceAll(/@?e\d+\b/gi, '') + .replaceAll(/^[\s|├└─>*-]+/g, '') .trim(); return cleaned.match(/^([A-Za-z][\w-]*)\b/)?.[1]; } diff --git a/src/remote/__tests__/daemon-artifacts-save-script.test.ts b/src/remote/__tests__/daemon-artifacts-save-script.test.ts index bfc95b65a..9ecfd13c0 100644 --- a/src/remote/__tests__/daemon-artifacts-save-script.test.ts +++ b/src/remote/__tests__/daemon-artifacts-save-script.test.ts @@ -20,7 +20,7 @@ function replayRequest(saveScript?: boolean | string) { session: 'default', command: 'replay', positionals: ['./flows/login.ad'], - flags: { ...(saveScript === undefined ? {} : { saveScript }) }, + flags: saveScript === undefined ? {} : { saveScript }, meta: { cwd: '/repo' }, }; } diff --git a/src/remote/remote-connection-state.ts b/src/remote/remote-connection-state.ts index f24d6f17d..1b49adbdb 100644 --- a/src/remote/remote-connection-state.ts +++ b/src/remote/remote-connection-state.ts @@ -261,7 +261,7 @@ function sanitizeDaemonBaseUrl(value: string | undefined): string | undefined { const url = new URL(value); url.username = ''; url.password = ''; - for (const key of [...url.searchParams.keys()]) { + for (const key of Array.from(url.searchParams.keys())) { if (/(auth|key|password|secret|token)/i.test(key)) { url.searchParams.delete(key); } diff --git a/src/remote/upload-client-artifact.ts b/src/remote/upload-client-artifact.ts index f0d241ead..2cd6296cb 100644 --- a/src/remote/upload-client-artifact.ts +++ b/src/remote/upload-client-artifact.ts @@ -99,12 +99,12 @@ async function computeFileHash(localPath: string): Promise { callback(); }, }); - await pipeline(fs.createReadStream(localPath), sink).catch((err: unknown) => { + await pipeline(fs.createReadStream(localPath), sink).catch((error: unknown) => { throw new AppError( 'COMMAND_FAILED', 'Failed to read local artifact', {}, - err instanceof Error ? err : undefined, + error instanceof Error ? error : undefined, ); }); return hash.digest('hex'); diff --git a/src/replay/test/__tests__/progress.test.ts b/src/replay/test/__tests__/progress.test.ts index f7fe03e3b..1dd31cf12 100644 --- a/src/replay/test/__tests__/progress.test.ts +++ b/src/replay/test/__tests__/progress.test.ts @@ -85,7 +85,7 @@ test('createReplayTestProgressRenderer renders pass, retry, fail, and skip cases artifactsDir: '/tmp/replay-suite/payment', }, expected: - /^⨯ Payment flow 9\.88s\n file: 03-payment\.ad\n failed at: assertVisible failed\n hint: Stop the owning daemon and retry\n session: maestro-test:test:suite:3:attempt-2\n artifacts: \/tmp\/replay-suite\/payment$/, + /^⨯ Payment flow 9\.88s\n {4}file: 03-payment\.ad\n {4}failed at: assertVisible failed\n {4}hint: Stop the owning daemon and retry\n {4}session: maestro-test:test:suite:3:attempt-2\n {4}artifacts: \/tmp\/replay-suite\/payment$/, }, { event: { @@ -111,7 +111,7 @@ test('createReplayTestProgressRenderer renders pass, retry, fail, and skip cases total: 5, message: 'missing platform metadata for --platform ios', }, - expected: /^- 04-skip\.ad\n missing platform metadata for --platform ios$/, + expected: /^- 04-skip\.ad\n {4}missing platform metadata for --platform ios$/, }, ]; diff --git a/src/replay/test/progress.ts b/src/replay/test/progress.ts index d855ab707..d2d4e75d9 100644 --- a/src/replay/test/progress.ts +++ b/src/replay/test/progress.ts @@ -201,13 +201,13 @@ function replayTestProgressFailureFileLine(event: ReplayTestResult): string | un } function replayTestProgressMessageLine(event: ReplayTestResult): string | undefined { - const message = event.message?.replace(/\s+/g, ' ').trim(); + const message = event.message?.replaceAll(/\s+/g, ' ').trim(); if (!message) return undefined; return ` ${event.status === 'fail' ? `failed at: ${message}` : message}`; } function appendReplayTestProgressHintLine(lines: string[], event: ReplayTestResult): void { - const hint = event.hint?.replace(/\s+/g, ' ').trim(); + const hint = event.hint?.replaceAll(/\s+/g, ' ').trim(); if (event.status === 'fail' && hint) lines.push(` hint: ${hint}`); } @@ -300,9 +300,9 @@ function clearLiveProgressPrefix( const rows = terminalReflowsOnResize ? Math.max(1, Math.ceil(previousWidth / resolveColumns(columns))) : 1; - let output = '\r\x1B[2K'; + let output = '\r\u001B[2K'; for (let row = 1; row < rows; row += 1) { - output += '\x1B[1A\r\x1B[2K'; + output += '\u001B[1A\r\u001B[2K'; } return output; } diff --git a/src/snapshot/snapshot-lines.ts b/src/snapshot/snapshot-lines.ts index fe7677fca..2d2b2d39e 100644 --- a/src/snapshot/snapshot-lines.ts +++ b/src/snapshot/snapshot-lines.ts @@ -72,7 +72,7 @@ export function buildSnapshotDisplayLines( if (type === 'group' && !label && !hasInheritedLabel) { continue; } - while (visibleDepths.length > 0 && depth <= visibleDepths[visibleDepths.length - 1]!) { + while (visibleDepths.length > 0 && depth <= visibleDepths.at(-1)!) { visibleDepths.pop(); } const adjustedDepth = visibleDepths.length; @@ -135,10 +135,10 @@ function collapseControlCharacters(value: string): string { return ( value // Whitespace folds the way text previews already fold it (newlines, tabs). - .replace(/\s+/g, ' ') + .replaceAll(/\s+/g, ' ') // Remaining C0/C1 controls are zero-width but can still corrupt a terminal. // eslint-disable-next-line no-control-regex - .replace(/[\u0000-\u001f\u007f-\u009f]/g, '') + .replaceAll(/[\u0000-\u001f\u007f-\u009f]/g, '') .trim() ); } @@ -169,7 +169,7 @@ export function displayLabel(node: SnapshotNode, type: string): string { export function formatRole(type: string): string { const raw = type; - let normalized = type.replace(/XCUIElementType/gi, '').toLowerCase(); + let normalized = type.replaceAll(/XCUIElementType/gi, '').toLowerCase(); const isAndroidClass = raw.includes('.') && (raw.startsWith('android.') || raw.startsWith('androidx.') || raw.startsWith('com.')); @@ -284,7 +284,7 @@ function looksScrollable(node: SnapshotNode, type: string): boolean { } function escapePreviewText(value: string): string { - return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + return value.replaceAll('\\', String.raw`\\`).replaceAll('"', String.raw`\"`); } function uniqueMetadata(values: string[]): string[] { diff --git a/src/snapshot/snapshot-presentation/ios/action-shelf.ts b/src/snapshot/snapshot-presentation/ios/action-shelf.ts index c55449168..1c62d3bae 100644 --- a/src/snapshot/snapshot-presentation/ios/action-shelf.ts +++ b/src/snapshot/snapshot-presentation/ios/action-shelf.ts @@ -17,9 +17,9 @@ const ACTION_SHELF_EDGE_TOLERANCE = 2; const ACTION_TOGGLE_EXPANSION_RATIO = 1.15; interface ActionShelfPresentation { - rect: Rect; - childActionsPresented: boolean; actionButtons: RawSnapshotNode[]; + childActionsPresented: boolean; + rect: Rect; } export function collectIosReplacedActionShelves( diff --git a/src/snapshot/snapshot-presentation/tree.ts b/src/snapshot/snapshot-presentation/tree.ts index 25350a941..9c8f0592f 100644 --- a/src/snapshot/snapshot-presentation/tree.ts +++ b/src/snapshot/snapshot-presentation/tree.ts @@ -74,7 +74,7 @@ function getDescendantEndPositions(nodes: RawSnapshotNode[]): number[] { const stack: Array<{ depth: number; position: number }> = []; for (const [position, node] of nodes.entries()) { const depth = node?.depth ?? 0; - while (stack.length > 0 && depth <= stack[stack.length - 1]!.depth) { + while (stack.length > 0 && depth <= stack.at(-1)!.depth) { const previous = stack.pop()!; endPositions[previous.position] = position; } diff --git a/src/utils/__tests__/daemon-client.test.ts b/src/utils/__tests__/daemon-client.test.ts index 29de065b3..be0da9b99 100644 --- a/src/utils/__tests__/daemon-client.test.ts +++ b/src/utils/__tests__/daemon-client.test.ts @@ -1187,7 +1187,7 @@ test.each([ try { await withRemoteDaemonEnv(async () => { - let error: unknown; + let thrown: unknown; try { await sendToDaemon({ session: 'default', @@ -1196,18 +1196,18 @@ test.each([ flags: {}, meta: { requestId: 'req-incompatible' }, }); - } catch (caught) { - error = caught; + } catch (error) { + thrown = error; } - assert.ok(error instanceof Error); - assert.equal((error as any).code, 'COMMAND_FAILED'); - assert.match(error.message, /Remote daemon RPC protocol is incompatible/); - assert.equal((error as any).details?.remoteService, 'agent-device-proxy'); - assert.equal((error as any).details?.remoteVersion, '99.0.0'); - assert.equal((error as any).details?.remoteRpcProtocolVersion, remoteProtocolVersion); + assert.ok(thrown instanceof Error); + assert.equal((thrown as any).code, 'COMMAND_FAILED'); + assert.match(thrown.message, /Remote daemon RPC protocol is incompatible/); + assert.equal((thrown as any).details?.remoteService, 'agent-device-proxy'); + assert.equal((thrown as any).details?.remoteVersion, '99.0.0'); + assert.equal((thrown as any).details?.remoteRpcProtocolVersion, remoteProtocolVersion); assert.equal( - (error as any).details?.supportedRpcProtocolVersion, + (thrown as any).details?.supportedRpcProtocolVersion, DAEMON_RPC_PROTOCOL_VERSION, ); }); @@ -1228,7 +1228,7 @@ test('sendToDaemon hints to disconnect when a remote daemon is unavailable', asy try { await withRemoteDaemonEnv(async () => { - let error: unknown; + let thrown: unknown; try { await sendToDaemon({ session: 'default', @@ -1237,14 +1237,14 @@ test('sendToDaemon hints to disconnect when a remote daemon is unavailable', asy flags: {}, meta: { requestId: 'req-remote-unavailable' }, }); - } catch (caught) { - error = caught; + } catch (error) { + thrown = error; } - assert.ok(error instanceof Error); - assert.equal((error as any).code, 'COMMAND_FAILED'); - assert.equal(error.message, 'Remote daemon is unavailable'); - assert.match(String((error as any).details?.hint ?? ''), /agent-device disconnect/); + assert.ok(thrown instanceof Error); + assert.equal((thrown as any).code, 'COMMAND_FAILED'); + assert.equal(thrown.message, 'Remote daemon is unavailable'); + assert.match(String((thrown as any).details?.hint ?? ''), /agent-device disconnect/); }); } finally { restoreHttpRequest(); diff --git a/src/utils/__tests__/device.test.ts b/src/utils/__tests__/device.test.ts index 3eb412668..abf4c0238 100644 --- a/src/utils/__tests__/device.test.ts +++ b/src/utils/__tests__/device.test.ts @@ -181,7 +181,7 @@ test('resolveAppleSimulatorSetPathForSelector ignores simulator scoping for desk test('resolveDevice throws DEVICE_NOT_FOUND with scoped set guidance when simulatorSetPath is set and no devices found', async () => { const setPath = '/path/to/sessions/abc/Simulators'; const err = await resolveDevice([], { platform: 'ios' }, { simulatorSetPath: setPath }).catch( - (e) => e, + (error) => error, ); assert.ok(err instanceof AppError); assert.equal(err.code, 'DEVICE_NOT_FOUND'); @@ -194,7 +194,7 @@ test('resolveDevice throws DEVICE_NOT_FOUND with scoped set guidance when simula }); test('resolveDevice throws generic DEVICE_NOT_FOUND when no simulatorSetPath and no devices found', async () => { - const err = await resolveDevice([], { platform: 'ios' }).catch((e) => e); + const err = await resolveDevice([], { platform: 'ios' }).catch((error) => error); assert.ok(err instanceof AppError); assert.equal(err.code, 'DEVICE_NOT_FOUND'); assert.equal(err.message, 'No devices found'); @@ -204,7 +204,7 @@ test('resolveDevice throws generic DEVICE_NOT_FOUND when no simulatorSetPath and test('resolveDevice does not apply scoped set guidance for non-iOS platform with simulatorSetPath', async () => { const setPath = '/path/to/sessions/abc/Simulators'; const err = await resolveDevice([], { platform: 'android' }, { simulatorSetPath: setPath }).catch( - (e) => e, + (error) => error, ); assert.ok(err instanceof AppError); assert.equal(err.code, 'DEVICE_NOT_FOUND'); @@ -230,7 +230,7 @@ test('resolveDevice treats Android AVD placeholders returned by inventory as sel test('resolveDevice applies scoped set guidance when no platform selector specified and simulatorSetPath is set', async () => { const setPath = '/path/to/sessions/abc/Simulators'; - const err = await resolveDevice([], {}, { simulatorSetPath: setPath }).catch((e) => e); + const err = await resolveDevice([], {}, { simulatorSetPath: setPath }).catch((error) => error); assert.ok(err instanceof AppError); assert.equal(err.code, 'DEVICE_NOT_FOUND'); assert.match(err.message, /scoped simulator set/); diff --git a/src/utils/__tests__/output.test.ts b/src/utils/__tests__/output.test.ts index a578d057e..0204eba2e 100644 --- a/src/utils/__tests__/output.test.ts +++ b/src/utils/__tests__/output.test.ts @@ -54,10 +54,10 @@ test('formatSnapshotDiffText renders plain text when color is disabled', () => { try { const text = formatSnapshotDiffText({ ...DIFF_DATA }); assert.match(text, /^@e2 \[window\]/m); - assert.match(text, /^- @e3 \[text\] "67"$/m); - assert.match(text, /^\+ @e3 \[text\] "134"$/m); + assert.match(text, /^- {2}@e3 \[text\] "67"$/m); + assert.match(text, /^\+ {2}@e3 \[text\] "134"$/m); assert.match(text, /1 additions, 1 removals, 1 unchanged/); - assert.equal(text.includes('\x1b['), false); + assert.equal(text.includes('\u001b['), false); } finally { if (typeof originalForceColor === 'string') process.env.FORCE_COLOR = originalForceColor; else delete process.env.FORCE_COLOR; @@ -76,8 +76,8 @@ test('formatSnapshotDiffText renders ANSI colors when forced', () => { const plainText = stripVTControlCharacters(text); assert.notEqual(text, plainText); assert.match(plainText, /^@e2 \[window\]/m); - assert.match(plainText, /^- @e3 \[text\] "67"$/m); - assert.match(plainText, /^\+ @e3 \[text\] "134"$/m); + assert.match(plainText, /^- {2}@e3 \[text\] "67"$/m); + assert.match(plainText, /^\+ {2}@e3 \[text\] "134"$/m); assert.match(plainText, /1 additions, 1 removals, 1 unchanged/); } finally { if (typeof originalForceColor === 'string') process.env.FORCE_COLOR = originalForceColor; @@ -256,9 +256,9 @@ test('formatSnapshotText omits unlabeled group wrappers while preserving labeled assert.doesNotMatch(text, /@e2 \[group\]/); assert.doesNotMatch(text, /@e3 \[group\]/); assert.match(text, /@e14 \[scroll-area\] \[scrollable\]/); - assert.match(text, / @e17 \[group\] "HomePage"/); - assert.match(text, / @e21 \[group\] "Home"/); - assert.match(text, / @e22 \[button\] "Search"/); + assert.match(text, / {2}@e17 \[group\] "HomePage"/); + assert.match(text, / {4}@e21 \[group\] "Home"/); + assert.match(text, / {4}@e22 \[button\] "Search"/); }); test('formatSnapshotText compresses visible indentation after hidden wrapper chains', () => { @@ -283,8 +283,8 @@ test('formatSnapshotText compresses visible indentation after hidden wrapper cha ); assert.match(text, /^@e2 \[scroll-area\] \[scrollable\]$/m); - assert.match(text, /^ @e3 \[button\] "Back"$/m); - assert.match(text, /^ @e5 \[image\]$/m); + assert.match(text, /^ {2}@e3 \[button\] "Back"$/m); + assert.match(text, /^ {4}@e5 \[image\]$/m); }); test('formatSnapshotText hides off-screen refs and adds compact discovery summaries', () => { @@ -335,7 +335,7 @@ test('formatSnapshotText hides off-screen refs and adds compact discovery summar assert.match(text, /Snapshot: 2 visible nodes \(4 total\)/); assert.match(text, /^@e1 \[window\]$/m); - assert.match(text, /^ @e2 \[button\] "Settings"$/m); + assert.match(text, /^ {2}@e2 \[button\] "Settings"$/m); assert.doesNotMatch(text, /@e3 \[button\] "Privacy"/); assert.doesNotMatch(text, /@e4 \[button\] "Battery"/); assert.match(text, /\[off-screen below\] 2 interactive items: "Privacy", "Battery"/); @@ -385,7 +385,7 @@ test('formatSnapshotText keeps zero-height visible nodes out of off-screen summa }), ); - assert.match(text, /^ @e3 \[button\] "View"$/m); + assert.match(text, /^ {2}@e3 \[button\] "View"$/m); assert.doesNotMatch(text, /\[off-screen above\].*"View"/); assert.match(text, /\[off-screen below\] 1 interactive item: "Later"/); }); @@ -1104,9 +1104,9 @@ test('formatSnapshotText renders explicit hidden scroll-area content hints', () ); assert.match(text, /Snapshot: 3 visible nodes/); - assert.match(text, /^ @e2 \[scroll-area\] "Messages" \[scrollable\]$/m); - assert.match(text, /^ \[content above scroll-area hidden\]$/m); - assert.match(text, /^ \[content below scroll-area hidden\]$/m); + assert.match(text, /^ {2}@e2 \[scroll-area\] "Messages" \[scrollable\]$/m); + assert.match(text, /^ {4}\[content above scroll-area hidden\]$/m); + assert.match(text, /^ {4}\[content below scroll-area hidden\]$/m); assert.ok( text.indexOf('[content above scroll-area hidden]') < text.indexOf('@e3 [button]'), 'above hint should appear before visible scroll-area content', @@ -1154,8 +1154,8 @@ test('formatSnapshotText keeps below scroll hints at the bottom when depth is fl }), ); - assert.match(text, /^ @e2 \[scroll-area\] "Catalog" \[scrollable\]$/m); - assert.match(text, /^ @e3 \[button\] "Visible product"$/m); + assert.match(text, /^ {2}@e2 \[scroll-area\] "Catalog" \[scrollable\]$/m); + assert.match(text, /^ {2}@e3 \[button\] "Visible product"$/m); assert.ok( text.indexOf('[content above scroll-area hidden]') < text.indexOf('@e3 [button]'), 'above hint should stay at the top of the scroll-area', @@ -1232,8 +1232,8 @@ test('formatSnapshotText renders hidden scroll-area content hints in flattened o ); assert.match(text, /^@e2 \[scroll-area\] "Messages" \[scrollable\]$/m); - assert.match(text, /^ \[content above scroll-area hidden\]$/m); - assert.match(text, /^ \[content below scroll-area hidden\]$/m); + assert.match(text, /^ {2}\[content above scroll-area hidden\]$/m); + assert.match(text, /^ {2}\[content below scroll-area hidden\]$/m); }); test('formatSnapshotText normalizes RecyclerView containers to list', () => { @@ -1255,7 +1255,7 @@ test('formatSnapshotText normalizes RecyclerView containers to list', () => { ); assert.match(text, /^@e1 \[list\]$/m); - assert.match(text, /^ \[content below list hidden\]$/m); + assert.match(text, /^ {2}\[content below list hidden\]$/m); }); test('formatSnapshotText renders hidden-below list hints after visible descendants', () => { @@ -1302,9 +1302,9 @@ test('formatSnapshotText renders hidden-below list hints after visible descendan ); assert.match(text, /^@e2 \[list\]$/m); - assert.match(text, /^ @e3 \[text\] "Text view"$/m); - assert.match(text, /^ @e4 \[text\] "loadJSBundleFromAssets"$/m); - assert.match(text, /^ \[content below list hidden\]$/m); + assert.match(text, /^ {2}@e3 \[text\] "Text view"$/m); + assert.match(text, /^ {4}@e4 \[text\] "loadJSBundleFromAssets"$/m); + assert.match(text, /^ {2}\[content below list hidden\]$/m); assert.ok( text.indexOf('@e4 [text] "loadJSBundleFromAssets"') < text.indexOf('[content below list hidden]'), @@ -1366,10 +1366,10 @@ test('formatSnapshotText marks visible scroll areas with hidden content above an }), ); - assert.match(text, /^ @e2 \[scroll-area\] "Messages" \[scrollable\]$/m); - assert.match(text, /^ \[content above scroll-area hidden\]$/m); - assert.match(text, /^ \[content below scroll-area hidden\]$/m); - assert.match(text, /^ @e4 \[button\] "Visible message"$/m); + assert.match(text, /^ {2}@e2 \[scroll-area\] "Messages" \[scrollable\]$/m); + assert.match(text, /^ {4}\[content above scroll-area hidden\]$/m); + assert.match(text, /^ {4}\[content below scroll-area hidden\]$/m); + assert.match(text, /^ {4}@e4 \[button\] "Visible message"$/m); assert.doesNotMatch(text, /\[off-screen above\].*"Earlier message"/); assert.doesNotMatch(text, /\[off-screen below\].*"Later message"/); }); @@ -1400,8 +1400,8 @@ test('formatSnapshotText suppresses noisy system scroll-container labels', () => }), ); - assert.match(text, /^ @e2 \[scroll-area\] \[scrollable\]$/m); - assert.match(text, /^ \[content below scroll-area hidden\]$/m); + assert.match(text, /^ {2}@e2 \[scroll-area\] \[scrollable\]$/m); + assert.match(text, /^ {4}\[content below scroll-area hidden\]$/m); assert.doesNotMatch(text, /Vertical scroll bar, 2 pages/); }); @@ -1589,7 +1589,7 @@ test('formatScreenshotDiffText renders match success without color', () => { }), ); assert.match(text, /✓ Screenshots match\./); - assert.equal(text.includes('\x1b['), false); + assert.equal(text.includes('\u001b['), false); }); test('formatScreenshotDiffText renders mismatch with pixel counts without color', () => { @@ -1638,7 +1638,7 @@ test('formatScreenshotDiffText renders mismatch with pixel counts without color' assert.match(text, /Changed regions:/); assert.match(text, /1\. x=10 y=20 100x40, 70% of diff/); assert.match(text, /overlaps @e1 "Continue", 12% of region/); - assert.equal(text.includes('\x1b['), false); + assert.equal(text.includes('\u001b['), false); }); test('formatScreenshotDiffText renders dimension mismatch', () => { @@ -1689,7 +1689,7 @@ test('formatScreenshotDiffText keeps absolute diff path outside cwd', () => { diffPath, }), ); - assert.match(text, new RegExp(diffPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))); + assert.match(text, new RegExp(diffPath.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`))); assert.equal(text.includes('./'), false); }); @@ -1703,9 +1703,9 @@ test('formatScreenshotDiffText uses ANSI colors when enabled', () => { diffPath: '/tmp/diff.png', }), ); - assert.equal(text.includes('\x1b[31m'), true); - assert.equal(text.includes('\x1b[32m'), true); - assert.equal(text.includes('\x1b[2m'), true); + assert.equal(text.includes('\u001b[31m'), true); + assert.equal(text.includes('\u001b[32m'), true); + assert.equal(text.includes('\u001b[2m'), true); }); test('formatScreenshotDiffText does not show diff path when images match', () => { diff --git a/src/utils/__tests__/snapshot-lines-custom-actions.test.ts b/src/utils/__tests__/snapshot-lines-custom-actions.test.ts index 27bbbb389..aee6426ac 100644 --- a/src/utils/__tests__/snapshot-lines-custom-actions.test.ts +++ b/src/utils/__tests__/snapshot-lines-custom-actions.test.ts @@ -49,7 +49,10 @@ test('formatSnapshotLine omits the actions list when there is nothing to name', test('formatSnapshotLine escapes app-authored names so they cannot corrupt the line', () => { const line = formatSnapshotLine( - { ...mergedCard, actions: ['Say "hi"', 'C:\\path', 'Reply\nto post', 'Bell\u0007ring'] }, + { + ...mergedCard, + actions: ['Say "hi"', String.raw`C:\path`, 'Reply\nto post', 'Bell\u0007ring'], + }, 0, false, undefined, @@ -60,7 +63,7 @@ test('formatSnapshotLine escapes app-authored names so they cannot corrupt the l // character is dropped. The result stays exactly one line. assert.equal( line, - '@e72 [link] "feedItem-by-whiskers.test" actions: ["Say \\"hi\\"", "C:\\\\path", "Reply to post", "Bellring"]', + String.raw`@e72 [link] "feedItem-by-whiskers.test" actions: ["Say \"hi\"", "C:\\path", "Reply to post", "Bellring"]`, ); assert.equal(line.includes('\n'), false); }); diff --git a/src/utils/android-helper-presentation/labels.ts b/src/utils/android-helper-presentation/labels.ts index abe412c16..7fd5dd260 100644 --- a/src/utils/android-helper-presentation/labels.ts +++ b/src/utils/android-helper-presentation/labels.ts @@ -24,7 +24,7 @@ export function visibleNodeLabel(node: SnapshotNode): string { } export function normalizeStructuralNodeLabel(label: string): string | null { - const normalized = label.trim().replace(/\s+/g, ' ').toLowerCase(); + const normalized = label.trim().replaceAll(/\s+/g, ' ').toLowerCase(); if (!normalized) return null; if (/^(true|false|\d+)$/.test(normalized)) return null; return normalized; diff --git a/src/utils/android-helper-presentation/structural-noise.ts b/src/utils/android-helper-presentation/structural-noise.ts index aa3da2528..0aa0fc39f 100644 --- a/src/utils/android-helper-presentation/structural-noise.ts +++ b/src/utils/android-helper-presentation/structural-noise.ts @@ -121,7 +121,7 @@ function collectPromotableRowContent( ) { continue; } - const label = visibleNodeLabel(descendant).trim().replace(/\s+/g, ' '); + const label = visibleNodeLabel(descendant).trim().replaceAll(/\s+/g, ' '); const normalized = normalizeStructuralNodeLabel(label); removableIndexes.push(descendant.index); if (!label || !normalized || seen.has(normalized)) continue; diff --git a/src/utils/install-source-config.ts b/src/utils/install-source-config.ts index cf3270234..67e17437b 100644 --- a/src/utils/install-source-config.ts +++ b/src/utils/install-source-config.ts @@ -99,7 +99,7 @@ function readRequiredText(value: unknown, field: string): string { function readInteger(value: unknown, field: string): number { const parsed = - typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : NaN; + typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : Number.NaN; if (!Number.isInteger(parsed)) { throw new AppError('INVALID_ARGS', `${field} must be an integer.`); } diff --git a/src/utils/output.ts b/src/utils/output.ts index c43e874a2..69b258cd7 100644 --- a/src/utils/output.ts +++ b/src/utils/output.ts @@ -570,11 +570,7 @@ function renderSnapshotDisplayLines(lines: ReturnType 0 && (!nextLine || - isOutsideHiddenContentContainer( - nextLine, - pendingBelow[pendingBelow.length - 1]!, - lineNodesByIndex, - )) + isOutsideHiddenContentContainer(nextLine, pendingBelow.at(-1)!, lineNodesByIndex)) ) { output.push(...readHiddenContentHintLines(pendingBelow.pop()!, 'below')); } diff --git a/src/utils/repeated-nav-subtree.ts b/src/utils/repeated-nav-subtree.ts index 250cf9785..cd3399ad3 100644 --- a/src/utils/repeated-nav-subtree.ts +++ b/src/utils/repeated-nav-subtree.ts @@ -26,7 +26,7 @@ export function detectPossibleRepeatedNavSubtree(nodes: SnapshotNode[]): boolean } function normalizeRepeatedNodeLabel(label: string): string | null { - const normalized = label.trim().replace(/\s+/g, ' ').toLowerCase(); + const normalized = label.trim().replaceAll(/\s+/g, ' ').toLowerCase(); if (!normalized || isEmailLikeLabel(normalized)) return null; return normalized; } diff --git a/src/utils/text-surface.ts b/src/utils/text-surface.ts index c88e80849..418f851fe 100644 --- a/src/utils/text-surface.ts +++ b/src/utils/text-surface.ts @@ -39,7 +39,7 @@ function isLargeTextSurface(node: TextSurfaceNode, displayType?: string): boolea } export function buildTextPreview(text: string): string { - const normalized = text.replace(/\s+/g, ' ').trim(); + const normalized = text.replaceAll(/\s+/g, ' ').trim(); if (normalized.length <= 48) { return normalized; } diff --git a/test/ci/trusted-fixture-artifact.test.mjs b/test/ci/trusted-fixture-artifact.test.mjs index 2e43582ae..322dd7b3e 100644 --- a/test/ci/trusted-fixture-artifact.test.mjs +++ b/test/ci/trusted-fixture-artifact.test.mjs @@ -172,7 +172,7 @@ test('Android APK locator emits an exact APK path and package id, and rejects co '#!/bin/sh', 'if [ "${TEST_AAPT_FAIL:-}" = 1 ]; then exit 23; fi', 'if [ -n "$TEST_AAPT_PACKAGE" ]; then', - ' printf "package: name=\'%s\' versionCode=1 versionName=1\\n" "$TEST_AAPT_PACKAGE"', + String.raw` printf "package: name='%s' versionCode=1 versionName=1\n" "$TEST_AAPT_PACKAGE"`, 'fi', '', ].join('\n'), @@ -230,7 +230,7 @@ test('Android APK repack signs the output and preserves its package id', (t) => path.join(buildTools, 'aapt'), [ '#!/bin/sh', - 'printf "package: name=\'com.example.fixture\' versionCode=1 versionName=1\\n"', + String.raw`printf "package: name='com.example.fixture' versionCode=1 versionName=1\n"`, '', ].join('\n'), ); @@ -242,7 +242,7 @@ test('Android APK repack signs the output and preserves its package id', (t) => 'if [ "${2:-}" = --print-certs ]; then', ' digest="$TEST_SOURCE_DIGEST"', ' if [ "$3" = "$TEST_REPACK_OUTPUT" ]; then digest="$TEST_OUTPUT_DIGEST"; fi', - ' printf "Signer #1 certificate SHA-256 digest: %s\\n" "$digest"', + String.raw` printf "Signer #1 certificate SHA-256 digest: %s\n" "$digest"`, 'fi', '', ].join('\n'), @@ -251,7 +251,7 @@ test('Android APK repack signs the output and preserves its package id', (t) => path.join(binDir, 'pnpm'), [ '#!/bin/sh', - 'printf "%s\\n" "$*" >> "$TEST_COMMAND_LOG"', + String.raw`printf "%s\n" "$*" >> "$TEST_COMMAND_LOG"`, 'while [ "$#" -gt 0 ]; do', ' case "$1" in', ' --source-app) source="$2"; shift 2 ;;', @@ -329,8 +329,8 @@ test('producer maps each platform to its resolved lookup and matrix artifact nam path.join(actionDir, 'resolve-artifact-name.sh'), [ '#!/bin/sh', - 'printf "%s\\n" "$1" >> "$TEST_RESOLVER_LOG"', - 'printf "fingerprint.%s-hash.%s\\n" "$1" "$1"', + String.raw`printf "%s\n" "$1" >> "$TEST_RESOLVER_LOG"`, + String.raw`printf "fingerprint.%s-hash.%s\n" "$1" "$1"`, '', ].join('\n'), ); @@ -341,7 +341,7 @@ test('producer maps each platform to its resolved lookup and matrix artifact nam path.join(binDir, 'node'), [ '#!/bin/sh', - 'printf "%s\\n" "$*" >> "$TEST_NODE_LOG"', + String.raw`printf "%s\n" "$*" >> "$TEST_NODE_LOG"`, 'case "$4" in', ...cachedPlatforms.map((platform) => ` *.${platform}) printf "111" ;;`), ' *) true ;;', @@ -418,12 +418,12 @@ test('artifact name resolver scopes both platforms and rejects invalid output', pnpmStub, [ '#!/bin/sh', - 'printf "%s\\n" "$*" >> "$TEST_CALL_LOG"', + String.raw`printf "%s\n" "$*" >> "$TEST_CALL_LOG"`, 'if [ "$TEST_PNPM_EXIT" -ne 0 ]; then exit "$TEST_PNPM_EXIT"; fi', 'if [ -n "$TEST_RAW_OUTPUT" ]; then', - ' printf "%s\\n" "$TEST_RAW_OUTPUT"', + String.raw` printf "%s\n" "$TEST_RAW_OUTPUT"`, 'else', - ' printf \'{"hash":"%s"}\\n\' "$TEST_HASH"', + String.raw` printf '{"hash":"%s"}\n' "$TEST_HASH"`, 'fi', '', ].join('\n'), @@ -468,7 +468,7 @@ test('artifact name resolver scopes both platforms and rejects invalid output', assert.equal(runResolver('windows', 'unused').status, 2); assert.equal(runResolver('ios', 'null').status, 1); assert.equal(runResolver('ios', 'bad/hash').status, 1); - assert.equal(runResolver('ios', 'unused', 0, '{"hash":"bad\\nhash"}').status, 1); + assert.equal(runResolver('ios', 'unused', 0, String.raw`{"hash":"bad\nhash"}`).status, 1); assert.equal(runResolver('ios', 'unused', 0, '{"hash":"first"}\n{"hash":"second"}').status, 1); assert.equal(runResolver('ios', 'unused', 17).status, 17); }); diff --git a/test/integration/provider-scenarios/android-recording-manifest-fixtures.ts b/test/integration/provider-scenarios/android-recording-manifest-fixtures.ts index c2c30ca95..306d68c4b 100644 --- a/test/integration/provider-scenarios/android-recording-manifest-fixtures.ts +++ b/test/integration/provider-scenarios/android-recording-manifest-fixtures.ts @@ -79,7 +79,7 @@ export function parseManifestWrite( if (!serializedManifest || !temporaryPath || !manifestPath || temporaryPath !== manifestPath) return undefined; try { - const parsed: unknown = JSON.parse(serializedManifest.replace(/'\\''/g, "'")); + const parsed: unknown = JSON.parse(serializedManifest.replaceAll(String.raw`'\''`, "'")); return isAndroidRecordingManifestFixture(parsed) ? { path: manifestPath, manifest: parsed } : undefined; diff --git a/test/integration/provider-scenarios/android-world.ts b/test/integration/provider-scenarios/android-world.ts index 90ee1e471..0b5f0ebd1 100644 --- a/test/integration/provider-scenarios/android-world.ts +++ b/test/integration/provider-scenarios/android-world.ts @@ -277,7 +277,7 @@ function argsStartWith(args: string[], prefix: string[]): boolean { // — the inverse of the quoting in @agent-device/host-kit/command. function unquoteAndroidShellArg(value: string): string { if (!value.startsWith("'") || !value.endsWith("'") || value.length < 2) return value; - return value.slice(1, -1).replaceAll("'\\''", "'"); + return value.slice(1, -1).replaceAll(String.raw`'\''`, "'"); } function androidDeviceAvailabilityAdbResult( @@ -705,8 +705,8 @@ function installFakeHostAdbGuard(): { argsLogPath: string; restore: () => void } adbPath, [ '#!/bin/sh', - 'printf "%s\\n" "$*" >> "$AGENT_DEVICE_TEST_ADB_ARGS_FILE"', - 'printf "host adb must not be used in Provider scenario tests\\n" >&2', + String.raw`printf "%s\n" "$*" >> "$AGENT_DEVICE_TEST_ADB_ARGS_FILE"`, + String.raw`printf "host adb must not be used in Provider scenario tests\n" >&2`, 'exit 99', '', ].join('\n'), @@ -796,7 +796,7 @@ function createScriptedLogcatExecutable(tempRoot: string): string { executable, [ '#!/bin/sh', - 'printf "I/AgentDevice(4242): provider logcat\\n"', + String.raw`printf "I/AgentDevice(4242): provider logcat\n"`, `printf '%s\\n' '04-01 10:00:15.000 D/Network(4242): ${networkEntry}'`, 'trap \'test -n "$child" && kill "$child" 2>/dev/null; exit 0\' INT TERM', 'while :; do sleep 10 & child=$!; wait "$child"; done', diff --git a/test/integration/provider-scenarios/transcript.ts b/test/integration/provider-scenarios/transcript.ts index 38d74bc2b..67c0b8b75 100644 --- a/test/integration/provider-scenarios/transcript.ts +++ b/test/integration/provider-scenarios/transcript.ts @@ -11,9 +11,7 @@ export interface ProviderScenarioProviderEntry< TResult = unknown, > extends ProviderScenarioProviderScope { command: string; - request?: unknown; - /** A factory is invoked per call, so repeated calls can return fresh results. */ - result?: TResult | (() => TResult); + error?: Error | string; /** * Serves every matching call instead of being consumed by the first, and never * counts as unconsumed. Use it where the call COUNT is not the contract — a @@ -27,7 +25,9 @@ export interface ProviderScenarioProviderEntry< * repeat would never advance the queue. */ repeat?: boolean; - error?: Error | string; + request?: unknown; + /** A factory is invoked per call, so repeated calls can return fresh results. */ + result?: TResult | (() => TResult); } export interface ProviderScenarioProviderCall< @@ -39,14 +39,14 @@ export interface ProviderScenarioProviderCall< } export interface ProviderScenarioTranscript { + assertComplete(): void; readonly calls: readonly ProviderScenarioProviderCall[]; - readonly remaining: readonly ProviderScenarioProviderEntry[]; next( command: string, request?: unknown, scope?: ProviderScenarioProviderScope, ): TResult; - assertComplete(): void; + readonly remaining: readonly ProviderScenarioProviderEntry[]; } export function createProviderTranscript( diff --git a/test/integration/recording-overlay.test.ts b/test/integration/recording-overlay.test.ts index 52a749ebf..bc74de3a3 100644 --- a/test/integration/recording-overlay.test.ts +++ b/test/integration/recording-overlay.test.ts @@ -235,6 +235,6 @@ function sanitize(input: string): string { return input .trim() .toLowerCase() - .replace(/[^a-z0-9._-]+/g, '-') - .replace(/-+/g, '-'); + .replaceAll(/[^a-z0-9._-]+/g, '-') + .replaceAll(/-+/g, '-'); } diff --git a/test/integration/smoke-clean-xcuitest-derived.test.ts b/test/integration/smoke-clean-xcuitest-derived.test.ts index 14299151a..fade8b5b5 100644 --- a/test/integration/smoke-clean-xcuitest-derived.test.ts +++ b/test/integration/smoke-clean-xcuitest-derived.test.ts @@ -128,5 +128,5 @@ function runCleanXcuitest(homeDir: string, ...args: Array