diff --git a/.claude/skills/router-design/SKILL.md b/.claude/skills/router-design/SKILL.md new file mode 100644 index 0000000000..d19a53148b --- /dev/null +++ b/.claude/skills/router-design/SKILL.md @@ -0,0 +1,106 @@ +--- +name: router-design +description: 'Stage 2: Analyze reference implementations and produce design decisions from the stage 1 JSON. Reads 01-router-concepts.json and reference code, emits JSON conforming to output.schema.json.' +--- + +# Stage 2: Design Decisions + +## Context + +You are Stage 2 of the router integration pipeline. Your job is to read the structured router concepts from Stage 1, analyze the existing reference implementations, and produce explicit design decisions that will guide code generation in Stage 3. + +The pipeline invokes you with `claude -p --output-format json --json-schema .claude/skills/router-design/output.schema.json`. Your final message must be a single JSON object conforming to that schema; the harness validates it and writes the CLI wrapper to `docs/integrations//02-design-decisions.json` with the payload on `.structured_output`. + +## Input + +You receive a **framework identifier** as skill param (e.g. `angular`, `vue`, `tanstack-react-router`). + +Read: + +1. `docs/integrations//01-router-concepts.json` — stage 1 CLI wrapper. Extract the router-concepts payload with `jq '.structured_output' `. +2. Reference implementations (to understand SDK patterns): + - Plugin files: `packages/rum-vue/src/domain/vuePlugin.ts`, `packages/rum-react/src/domain/reactPlugin.ts`, `packages/rum-nextjs/src/domain/nextjsPlugin.ts` + - Vue router: `packages/rum-vue/src/domain/router/` (all `.ts` files) + - React router: `packages/rum-react/src/domain/reactRouter/` (all `.ts` files) + - Next.js router: `packages/rum-nextjs/src/domain/nextJSRouter/` (all `.ts` files) + - Entry points: `packages/rum-vue/src/entries/main.ts`, `packages/rum-react/src/entries/main.ts`, `packages/rum-nextjs/src/entries/main.ts` + - Package configs: `packages/rum-vue/package.json`, `packages/rum-react/package.json`, `packages/rum-nextjs/package.json` + - Plugin interface: `packages/rum-core/src/domain/plugins.ts` + +## Process + +### 1. Hook Selection (Deterministic) + +Apply these priority rules to the `hooks` array from `01-router-concepts.json`. + +**The integration must be client-side only.** Only consider hooks that fire on the client. Use the `access` field and `ssr` section from `01-router-concepts.json` to determine this. + +**Priority rules (in order):** + +1. `afterCancellation: true` — **required**. Never start a RUM view for a navigation that didn't occur. +2. `afterRedirects: true` — **prefer**. Report the final destination, not intermediate routes. +3. `afterFetch: false` AND `afterRender: false` — **prefer**. Start the view before data loading and DOM mutation so RUM events (fetch resources, long tasks, interactions) are attributed to the new view, not the previous one. + +Apply in order: + +- Filter to `afterCancellation: true`. If no hooks pass, flag as critical issue and stop. +- Among those, prefer `afterRedirects: true`. +- Among those, prefer `afterFetch: false` AND `afterRender: false`. +- If rules conflict (no hook satisfies all), higher-priority rule wins. +- If multiple hooks still tie, prefer the one that fires earliest in the lifecycle. + +Document which hooks were considered, which rules each passed/failed, and why the selected hook won. + +### 2. Wrapping Strategy (LLM Judgment) + +Read the selected hook's `access` field from `01-router-concepts.json`. Determine the most idiomatic way for users to integrate the plugin in this framework. + +Consider: + +- How existing plugins/libraries are typically added in this framework's ecosystem +- Whether the hook needs a router instance (→ wrap the factory that creates it) +- Whether the hook needs component context (→ renderless component or hook) +- Whether the hook needs DI (→ provider registration) + +Reference patterns from existing implementations: + +- Vue: wraps `createRouter()` factory to get router instance for `afterEach` +- React: wraps `createBrowserRouter()` factory OR wraps `useRoutes()` hook +- Angular: provider with `inject(Router)` for `router.events` observable + +### 3. View Name Algorithm (LLM Classification) + +Read the selected hook's `availableApi` from `01-router-concepts.json`. Classify into one of three families (in preference order): + +- **`route-id`** — Framework provides the parameterized route pattern as a string. Minimal post-processing needed (e.g. strip route groups). Example: SvelteKit `route.id`. +- **`matched-records`** — Framework provides matched route records (array or tree). Iterate and concatenate path segments. Handle catch-all substitution. Example: Vue `to.matched[]`, React `state.matches[]`. +- **`param-substitution`** — Framework provides only the evaluated pathname + params object. Must reconstruct the route template by substituting values back with placeholders. Least preferred — heuristic and fragile. Example: Next.js `useParams()` + `usePathname()`. + +### 4. Target Package (LLM Judgment) + +Determine whether this router needs a new package or extends an existing one. + +- **`new-package`** — The router belongs to a framework with no existing SDK package (e.g., SvelteKit, Angular). Create `packages/rum-/`. +- **`extend-existing`** — The router is an alternative router for a framework that already has an SDK package (e.g., TanStack Router is a React router → extends `rum-react`). Add files under a subdirectory within the existing package. + +To decide: check if `packages/rum-*` already has a package for the same UI framework (React, Vue, etc.). If yes, extend it. If no, create new. + +For extend-existing, also determine the subdirectory path for the new router files (e.g., `src/domain/tanstackRouter/`). + +### 5. Reference Implementation + +Select the `packages/rum-*` implementation that is closest across: + +- Hook subscription pattern +- Wrapping strategy +- Algorithm family + +Stage 3 reads this implementation as its primary model for code generation. + +### 6. SSR Handling (LLM Judgment) + +If `ssr.supported: true` in `01-router-concepts.json`, describe how the integration should ensure client-side-only execution. Use the `clientDetection` API from Stage 1 if available. + +## Output Schema + +Return the populated object as your final message. The pipeline invokes you with `--output-format json --json-schema output.schema.json`; the harness validates the object and writes the full CLI wrapper (with the object on `.structured_output`) to `docs/integrations//02-design-decisions.json`. Do not write files yourself. diff --git a/.claude/skills/router-design/output.schema.json b/.claude/skills/router-design/output.schema.json new file mode 100644 index 0000000000..e26b9f127a --- /dev/null +++ b/.claude/skills/router-design/output.schema.json @@ -0,0 +1,111 @@ +{ + "title": "DesignDecisions", + "description": "Explicit design decisions derived from Stage 1 router concepts and reference implementations, used by Stage 3 to generate code.", + "type": "object", + "additionalProperties": false, + "required": ["selectedHook", "wrappingStrategy", "viewNameAlgorithm", "targetPackage", "referenceImplementation", "ssr"], + "properties": { + "selectedHook": { + "type": "object", + "additionalProperties": false, + "required": ["name", "rationale"], + "properties": { + "name": { + "type": "string", + "description": "Hook name from 01-router-concepts.json, selected by the deterministic priority rules." + }, + "rationale": { + "type": "string", + "description": "Which rules each candidate passed/failed and why the selected hook won." + } + } + }, + "wrappingStrategy": { + "type": "object", + "additionalProperties": false, + "required": ["pattern", "target", "rationale"], + "properties": { + "pattern": { + "enum": ["wrap-factory", "renderless-component", "provider", "wrap-hook", "other"], + "description": "wrap-factory: Wrap the router creation function, subscribe to hook inside. renderless-component: Component that calls the hook during lifecycle. provider: DI provider that injects the router and subscribes to events. wrap-hook: Wrap a user-facing hook to intercept route data. other: Escape hatch for unknown patterns." + }, + "target": { + "type": "string", + "description": "What specifically to wrap/provide. E.g. 'createRouter from vue-router', 'ENVIRONMENT_INITIALIZER with inject(Router)'." + }, + "rationale": { + "type": "string", + "description": "Why this is idiomatic for the framework." + } + } + }, + "viewNameAlgorithm": { + "type": "object", + "additionalProperties": false, + "required": ["family", "rationale"], + "properties": { + "family": { + "enum": ["route-id", "matched-records", "param-substitution"], + "description": "route-id: Framework provides parameterized route pattern as string. Minimal post-processing. matched-records: Framework provides matched route records. Iterate and concatenate path segments. param-substitution: Framework provides evaluated pathname + params. Reconstruct route template. Least preferred." + }, + "rationale": { + "type": "string", + "description": "Why this family, based on the hook's availableApi." + } + } + }, + "targetPackage": { + "type": "object", + "additionalProperties": false, + "required": ["mode", "package"], + "properties": { + "mode": { + "enum": ["new-package", "extend-existing"], + "description": "new-package: Create a new packages/rum-/ from scratch. extend-existing: Add router support to an existing package (e.g. adding TanStack Router to rum-react)." + }, + "package": { + "type": "string", + "description": "Target package directory name. For new-package: 'rum-'. For extend-existing: the existing package (e.g. 'rum-react')." + }, + "subpath": { + "type": "string", + "description": "Only for extend-existing. The subdirectory for this router's files within the existing package. E.g. 'src/domain/tanstackRouter/' within packages/rum-react/." + } + } + }, + "referenceImplementation": { + "type": "object", + "additionalProperties": false, + "required": ["primary", "rationale"], + "properties": { + "primary": { + "type": "string", + "description": "Which packages/rum-* to model after (e.g. 'rum-vue'). Stage 3 reads this implementation as its primary source for code patterns." + }, + "rationale": { + "type": "string", + "description": "Why this is the closest match." + } + } + }, + "ssr": { + "type": "object", + "additionalProperties": false, + "required": ["handling"], + "properties": { + "handling": { + "type": "string", + "description": "How to ensure client-side-only execution. 'N/A' if ssr.supported is false in Stage 1." + } + } + }, + "notes": { + "type": "string", + "description": "Free text for additional design context, trade-offs, unmapped concepts, or anything Stage 3 needs to know." + }, + "exitReason": { + "type": "string", + "description": "Only set if Stage 2 cannot proceed (e.g., no hook satisfies afterCancellation: true). When set, all other fields may be empty stubs and the pipeline will stop." + } + } +} diff --git a/.claude/skills/router-experiment/SKILL.md b/.claude/skills/router-experiment/SKILL.md new file mode 100644 index 0000000000..ce0952c66d --- /dev/null +++ b/.claude/skills/router-experiment/SKILL.md @@ -0,0 +1,140 @@ +--- +name: router-experiment +description: Run the router pipeline N times in parallel worktrees for the same framework to measure output consistency. Usage: /router-experiment [runs=3] +--- + +# Router Pipeline Consistency Experiment + +You run the router pipeline multiple times in parallel (each in its own git worktree) for the same npm package, then diff the outputs to measure consistency. Each run creates its own draft PR with a unique branch suffix. + +## Input + +- **Arg 1** (required): npm package URL (e.g. `https://www.npmjs.com/package/vue-router`) +- **Arg 2** (optional): number of parallel runs (default: 3) + +## Step 1: Setup + +```bash +RUNS= # default 3 +EXPERIMENT_DIR="/tmp/router-experiment-$(date +%s)" +mkdir -p "$EXPERIMENT_DIR" +``` + +Create worktrees from main: + +```bash +for i in $(seq 1 $RUNS); do + git worktree add "$EXPERIMENT_DIR/run-$i" main +done +``` + +## Step 2: Launch Parallel Runs + +Each run invokes `/router-pipeline` (stages 1–4) in its own worktree. Branch collisions are handled inside `/router-pr`, which appends a random suffix to the branch name. + +```bash +NPM_URL="" + +for i in $(seq 1 $RUNS); do + ( + cd "$EXPERIMENT_DIR/run-$i" + claude -p "/router-pipeline $NPM_URL" \ + --model opus \ + --allowedTools "Skill,Read,Write,Edit,Glob,Grep,Bash,WebFetch,WebSearch,Agent" + ) > "$EXPERIMENT_DIR/run-$i.log" 2>&1 & +done + +wait +``` + +Run via Bash with a generous timeout (up to 10 minutes). Use `run_in_background: true` so you can monitor progress. + +## Step 3: Compare Outputs + +### 3a. Discover framework name + +```bash +FRAMEWORK=$(basename $(dirname $(ls "$EXPERIMENT_DIR"/run-1/docs/integrations/*/01-router-concepts.json))) +``` + +### 3b. Diff artifacts + +The stage 1/2 artifacts are full `claude -p` wrappers — they include `duration_ms`, `session_id`, `total_cost_usd`, etc. that vary run-to-run. Diff the `structured_output` only, normalized with `jq -S`: + +```bash +for artifact in 01-router-concepts.json 02-design-decisions.json; do + echo "=== $artifact (structured_output) ===" + for i in $(seq 2 $RUNS); do + echo "--- run-1 vs run-$i ---" + diff -u \ + <(jq -S '.structured_output' "$EXPERIMENT_DIR/run-1/docs/integrations/$FRAMEWORK/$artifact") \ + <(jq -S '.structured_output' "$EXPERIMENT_DIR/run-$i/docs/integrations/$FRAMEWORK/$artifact") || true + done +done + +# Cost / duration / turns side-by-side +echo "=== cost & duration ===" +for i in $(seq 1 $RUNS); do + for artifact in 01-router-concepts.json 02-design-decisions.json; do + jq -r --arg run "run-$i" --arg art "$artifact" \ + '[$run, $art, .duration_ms, .num_turns, .total_cost_usd] | @tsv' \ + "$EXPERIMENT_DIR/run-$i/docs/integrations/$FRAMEWORK/$artifact" + done +done | column -t + +echo "=== 03-generation-manifest.md ===" +for i in $(seq 2 $RUNS); do + echo "--- run-1 vs run-$i ---" + diff -u "$EXPERIMENT_DIR/run-1/docs/integrations/$FRAMEWORK/03-generation-manifest.md" \ + "$EXPERIMENT_DIR/run-$i/docs/integrations/$FRAMEWORK/03-generation-manifest.md" || true +done +``` + +### 3c. Diff generated source code + +```bash +echo "=== Source code ===" +for i in $(seq 2 $RUNS); do + echo "--- run-1 vs run-$i ---" + diff -rq "$EXPERIMENT_DIR/run-1/packages/" "$EXPERIMENT_DIR/run-$i/packages/" || true +done +``` + +For files that differ, show the actual diff: + +```bash +for i in $(seq 2 $RUNS); do + diff -ru "$EXPERIMENT_DIR/run-1/packages/" "$EXPERIMENT_DIR/run-$i/packages/" || true +done +``` + +## Step 4: Report + +Present a summary table: + +``` +## Experiment Results: (N=) + +| Artifact | Identical? | Diff lines | +|---------------------------|------------|------------| +| 01-router-concepts.json | ✅ / ❌ | | +| 02-design-decisions.json | ✅ / ❌ | | +| 03-generation-manifest.md | ✅ / ❌ | | +| Generated source code | ✅ / ❌ | | + +### Observations + +``` + +If any diffs exist, show the most interesting ones inline (truncated if large). + +## Step 5: Cleanup + +Remove the worktrees: + +```bash +for i in $(seq 1 $RUNS); do + git worktree remove "$EXPERIMENT_DIR/run-$i" --force +done +rm -rf "$EXPERIMENT_DIR" +``` diff --git a/.claude/skills/router-fetch-docs/SKILL.md b/.claude/skills/router-fetch-docs/SKILL.md new file mode 100644 index 0000000000..7a1a11c44f --- /dev/null +++ b/.claude/skills/router-fetch-docs/SKILL.md @@ -0,0 +1,85 @@ +--- +name: router-fetch-docs +description: 'Stage 1: Fetch framework router documentation and extract structured routing concepts as JSON conforming to output.schema.json from an npm package URL.' +--- + +# Stage 1: Fetch Router Documentation + +## Context + +You are Stage 1 of the router integration pipeline. Your job is to resolve package metadata, fetch framework router documentation, and extract structured routing concepts as **factual data** for later stages. + +The pipeline invokes you with `claude -p --output-format json --json-schema .claude/skills/router-fetch-docs/output.schema.json`. Your final message must be a single JSON object conforming to that schema; the harness validates it and exposes it on `.structured_output` of the CLI wrapper written to `docs/integrations//01-router-concepts.json`. + +Do NOT analyze which hooks the SDK should use, recommend approaches, or compare with existing SDK integrations. Only document what the framework provides. + +## Input + +You receive an **npm package URL** as skill param (e.g. `https://www.npmjs.com/package/vue-router`). + +## Process + +### 1. Resolve Package Metadata + +Use WebFetch on the provided npm URL to extract: + +- **Package name** (e.g. `vue-router`, `@angular/router`) +- **Framework identifier** — derive a lowercase identifier from the package name (e.g. `vue-router` → `vue`, `@angular/router` → `angular`, `@tanstack/react-router` → `tanstack-react-router`) +- Homepage / repository URL +- Keywords and description + +Then **find router documentation URLs** — from the npm page metadata (homepage, repository links), locate the framework's official routing documentation: + +- Check the homepage URL for docs links +- Check the GitHub repository README for documentation links +- Look for `/docs/`, `/guide/`, `/routing` paths on the framework's site +- Collect 1-3 relevant documentation URLs focused on routing + +The pipeline creates the artifact directory for you — do not run `mkdir` yourself. + +### 2. Fetch Documentation + +Before fetching the provided URLs directly, try to find LLM-friendly versions of the docs: + +1. **Check for `llms.txt`** — Fetch `/llms.txt` (e.g. `https://svelte.dev/llms.txt`). This file indexes markdown documentation pages designed for LLM consumption. If it exists, use it to navigate to the relevant routing pages. +2. **Try `.md` suffix** — For each doc URL, try appending `.md` to the path (e.g. `https://svelte.dev/docs/kit/routing.md`). Many doc sites serve a raw markdown version this way, which is much easier to parse accurately. +3. **Fall back to HTML** — If neither LLM-friendly format is available, fetch the original URLs. + +Use the WebFetch tool for all fetches. If a URL fails, note it and continue with remaining URLs. If all URLs fail, stop without emitting a JSON object — the harness will mark the run as an error. + +**Prefer over-fetching to under-fetching.** Fetch every routing-related page you can find — API references, guides, tutorials. It is better to fetch a page and not need it than to miss information that a later stage requires. When in doubt, fetch it. + +### 3. Extract Router Concepts into the JSON Schema + +Analyze the fetched documentation and populate every field in the JSON schema. Use `null` for features the framework does not support. + +#### Sourcing Rules + +Every leaf field has a sibling `source` field. This is **mandatory** — the schema validation fails without it. + +- If extracted from documentation: set `source` to the URL (with anchor if possible) +- If inferred from multiple sources or reasoning: set `source` to `"inferred: "` +- API names, hook names, type names — everything factual must be traceable to a specific doc page + +#### JSON Schema + +Read `output.schema.json` (next to this SKILL.md) for the schema and field descriptions. + +### 4. Compatibility Assessment + +A framework is **incompatible** if it lacks: + +- A client-side route tree or route matching mechanism +- Dynamic segment parameters +- Navigation lifecycle events that can be hooked into + +If incompatible: stop without emitting a JSON object. The harness will mark the run as an error. + +A framework is **compatible** even if: + +- It uses file-based routing (as long as there's a runtime route representation) +- Some hooks fire later than ideal (trade-off is documented, not a blocker) + +## Output + +Return the populated object as your final message. The pipeline invokes you with `--output-format json --json-schema output.schema.json`; the harness validates the object and writes the full CLI wrapper (with the object on `.structured_output`) to `docs/integrations//01-router-concepts.json`. Do not write files yourself. diff --git a/.claude/skills/router-fetch-docs/output.schema.json b/.claude/skills/router-fetch-docs/output.schema.json new file mode 100644 index 0000000000..66403f7167 --- /dev/null +++ b/.claude/skills/router-fetch-docs/output.schema.json @@ -0,0 +1,224 @@ +{ + "title": "RouterConcepts", + "description": "Structured routing concepts extracted from framework documentation. Every factual leaf has a sibling `source` field: a URL (with anchor if possible) or 'inferred: '.", + "type": "object", + "additionalProperties": false, + "required": ["metadata", "routeDefinition", "routeSyntax", "ssr", "hooks", "versions"], + "properties": { + "metadata": { + "type": "object", + "additionalProperties": false, + "required": ["framework", "npmPackage"], + "properties": { + "framework": { + "type": "object", + "additionalProperties": false, + "required": ["value", "source"], + "properties": { + "value": { + "type": "string", + "description": "Lowercase identifier used for directory and package names. Derived from npm package name (e.g. vue-router → vue, @tanstack/react-router → tanstack-react-router)." + }, + "source": { "type": "string" } + } + }, + "npmPackage": { + "type": "object", + "additionalProperties": false, + "required": ["value", "source"], + "properties": { + "value": { + "type": "string", + "description": "npm package name (e.g. vue-router, @angular/router)." + }, + "source": { "type": "string" } + } + } + } + }, + "routeDefinition": { + "type": "object", + "additionalProperties": false, + "required": ["styles", "historyStrategies"], + "properties": { + "styles": { + "type": "array", + "description": "How routes are declared. file-based = filesystem convention (SvelteKit, Next.js). config-object = explicit route configuration array (Vue, React, Angular). other = escape hatch. Array because a framework may support multiple styles.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["value", "source"], + "properties": { + "value": { "enum": ["file-based", "config-object", "other"] }, + "source": { "type": "string" } + } + } + }, + "historyStrategies": { + "type": "array", + "description": "URL strategies the router supports. path = browser History API (pushState). hash = hash-based (#/route). memory = in-memory (SSR, testing).", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["value", "source"], + "properties": { + "value": { "enum": ["path", "hash", "memory"] }, + "source": { "type": "string" } + } + } + } + } + }, + "routeSyntax": { + "type": "object", + "additionalProperties": false, + "required": ["concepts", "examples"], + "properties": { + "concepts": { + "type": "array", + "description": "Which routing concepts the framework supports. Use this enum to flag concepts so later stages never forget to handle them.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["value", "source"], + "properties": { + "value": { "enum": ["static-path", "dynamic-segments", "optional-segments", "catch-all", "nested-routes"] }, + "source": { "type": "string" } + } + } + }, + "examples": { + "type": "array", + "description": "Behavioral examples showing route pattern, actual URL, and expected view name. Must cover every concept listed above. Include framework-specific quirks.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["route", "path", "viewName", "source"], + "properties": { + "route": { "type": "string", "description": "Route pattern as declared." }, + "path": { "type": "string", "description": "Example URL that matches." }, + "viewName": { "type": "string", "description": "Expected view name (pattern with placeholders)." }, + "note": { "type": "string", "description": "Explain non-obvious behavior." }, + "source": { "type": "string" } + } + } + } + } + }, + "ssr": { + "type": "object", + "additionalProperties": false, + "required": ["supported", "clientDetection"], + "properties": { + "supported": { + "type": "object", + "additionalProperties": false, + "required": ["value", "source"], + "properties": { + "value": { "type": "boolean" }, + "source": { "type": "string" } + } + }, + "clientDetection": { + "type": "object", + "additionalProperties": false, + "required": ["value", "source"], + "properties": { + "value": { + "type": ["string", "null"], + "description": "Framework's idiomatic API for detecting client-side execution. Used by Stage 3 to generate guards when needed." + }, + "source": { "type": "string" } + } + } + } + }, + "hooks": { + "type": "array", + "description": "ALL candidate navigation lifecycle hooks (client-side).", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["name", "afterCancellation", "afterRedirects", "afterFetch", "afterRender", "access", "availableApi", "source"], + "properties": { + "name": { + "type": "string", + "description": "Hook/event name as it appears in the framework API." + }, + "afterCancellation": { + "type": "boolean", + "description": "At this hook, is the navigation guaranteed to have survived all cancellation points? Guard redirects (e.g. Angular returning a UrlTree) cancel the current navigation and start a new one — the hook fires only for the final, non-cancelled navigation." + }, + "afterRedirects": { + "type": "boolean", + "description": "At this hook, is the observed route guaranteed to be the final destination? Config redirects resolved before this hook, and programmatic redirects that cancel+restart navigation, both count as 'after' if the cancelled navigation never reaches this hook." + }, + "afterFetch": { + "type": "boolean", + "description": "Fires after data loading (resolvers, load functions)." + }, + "afterRender": { + "type": "boolean", + "description": "Fires after DOM mutation / component rendering." + }, + "access": { + "type": "string", + "description": "What runtime context is needed to call this hook. Examples: 'Return value of createRouter()', 'Must be called during Svelte component init', 'Router service injected via Angular DI'." + }, + "availableApi": { + "type": "array", + "description": "Data accessible at hook invocation time.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["name", "description", "source"], + "properties": { + "name": { "type": "string", "description": "API path (e.g. to.matched, navigation.to.route.id)." }, + "description": { "type": "string" }, + "source": { "type": "string" } + } + } + }, + "source": { + "type": "string", + "description": "URL of the hook's API reference (or 'inferred: ')." + } + } + } + }, + "versions": { + "type": "object", + "additionalProperties": false, + "required": ["entries"], + "properties": { + "entries": { + "type": "array", + "description": "Routing-relevant breaking changes only, max 2 years old.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["version", "date", "routingChanges", "source"], + "properties": { + "version": { "type": "string", "description": "Semver version string." }, + "date": { "type": "string", "description": "Release date (YYYY-MM-DD)." }, + "routingChanges": { + "type": "array", + "description": "Breaking changes affecting routing.", + "items": { "type": "string" } + }, + "source": { "type": "string", "description": "URL of changelog or release notes." } + } + } + } + } + }, + "notes": { + "type": "string", + "description": "Free text for framework-specific context that doesn't fit the schema above." + }, + "incompatibilityReason": { + "type": "string", + "description": "Only set if the framework is incompatible (no client-side route tree, no dynamic segments, or no navigation lifecycle hooks). When set, all other fields may be empty stubs and the pipeline will stop." + } + } +} diff --git a/.claude/skills/router-generate/SKILL.md b/.claude/skills/router-generate/SKILL.md new file mode 100644 index 0000000000..5d7fcf927f --- /dev/null +++ b/.claude/skills/router-generate/SKILL.md @@ -0,0 +1,184 @@ +--- +name: router-generate +description: 'Stage 3: Generate the complete router integration package from design artifacts and reference implementations.' +--- + +# Stage 3: Generate Package + +## Context + +You are Stage 3 of the router integration pipeline. Your job is to generate a complete, working Browser SDK router integration package based on the design decisions from Stage 2 and the patterns from reference implementations. + +## Input + +You receive a **framework identifier** as skill param (e.g. `angular`, `vue`, `tanstack-react-router`). + +Read: + +1. `docs/integrations//01-router-concepts.json` — stage 1 CLI wrapper. Extract the router-concepts payload with `jq '.structured_output' `. +2. `docs/integrations//02-design-decisions.json` — stage 2 CLI wrapper. Extract the design payload with `jq '.structured_output' `. +3. `.claude/skills/router-fetch-docs/output.schema.json` — field definitions for stage 1 output. +4. `.claude/skills/router-design/output.schema.json` — field definitions for stage 2 output. +5. Reference implementation specified by `referenceImplementation.primary` in the stage 2 payload. Read the source files in `packages//`: + - Plugin file: `src/domain/*Plugin.ts` + - Router files: `src/domain/*Router/` or `src/domain/router/` (all `.ts` files) + - Tests: corresponding `.spec.ts` files + - Test helper: `src/test/initialize*Plugin.ts` + - Package config: `package.json`, `tsconfig.json` + - Entry point: `src/entries/main.ts` + +## Process + +Generate files in two phases. Complete each phase before starting the next. + +--- + +### Phase 1: Plugin + Wrapping Strategy + +Generate the plugin file and the framework-specific integration point. This phase reads `targetPackage`, `wrappingStrategy`, and `selectedHook` from `02-design-decisions.json`. + +**Check `targetPackage.mode` first:** + +- **`new-package`**: Create the full package scaffolding (`package.json`, `tsconfig.json`, `README.md`, `src/entries/main.ts`, `src/test/initializePlugin.ts`) by reading from the reference implementation. Then generate plugin + integration files. +- **`extend-existing`**: The package already exists at `packages//`. Do NOT create package scaffolding. Add router files under `targetPackage.subpath` (e.g., `src/domain/tanstackRouter/`). Update `src/entries/main.ts` to add new exports. Update `package.json` to add new peer dependencies if needed. The existing plugin file may need a new configuration option or the new router may reuse the existing plugin's `onRumInit`/`onRumStart` subscribers. + +**Files to generate:** + +For `new-package`: + +- `src/domain/Plugin.ts` — plugin lifecycle, subscriber pattern, configuration +- Integration point file — hook subscription +- `src/domain/Plugin.spec.ts` — plugin structure tests + +For `extend-existing`: + +- Integration point file under `targetPackage.subpath` — hook subscription +- View name and types files under `targetPackage.subpath` +- Tests under `targetPackage.subpath` +- Modify `src/entries/main.ts` — add exports for the new router +- Modify `package.json` — add peer dependencies if needed + +**Plugin file** (only for `new-package`): + +- Follow the exact pattern from the reference: global state, subscriber arrays, `onRumInit`/`onRumStart` exports, `resetPlugin` for tests +- Plugin name must be the framework name in lowercase +- Configuration interface with `router?: boolean` +- `getConfigurationTelemetry` returning `{ router: !!configuration.router }` + +**Integration point file:** + +The wrapping strategy from `02-design-decisions.json` determines what this file looks like: + +- `wrap-factory` → wrap the framework's router creation function, subscribe to hook inside +- `renderless-component` → Svelte/React component that calls the hook during lifecycle +- `provider` → DI provider that injects the router and subscribes to events +- `wrap-hook` → wrap a user-facing hook to intercept route data + +Read the selected hook's `access` and `availableApi` from `01-router-concepts.json` to understand what data is available and how to reach it. + +If `ssr.handling` is not "N/A", add the client-side guard described there. + +--- + +### Phase 2: View Name Algorithm + +Generate the view name computation, types, and tests. This phase reads `viewNameAlgorithm` and `routeSyntax` from the design artifacts. + +**Files to generate:** + +- `src/domain/Router/startView.ts` — `computeViewName()` + `startRouterView()`. Driven by `viewNameAlgorithm.family`. +- `src/domain/Router/types.ts` — minimal local types for route data. Driven by `selectedHook.availableApi`. +- `src/domain/Router/startView.spec.ts` — view name computation tests. Driven by `routeSyntax.examples`. + +**`computeViewName()` implementation by family:** + +- **`route-id`**: Framework provides route pattern as string. Implementation is minimal — mostly cleanup (e.g., strip route groups). Model after the simplest reference. +- **`matched-records`**: Iterate matched route records, concatenate `.path` fields. Handle absolute vs relative paths. Substitute catch-all patterns with actual path segments. Model after `packages/rum-vue/src/domain/router/startVueRouterView.ts` or `packages/rum-react/src/domain/reactRouter/startReactRouterView.ts`. +- **`param-substitution`**: Reverse-engineer route template from pathname + params. Two-pass: catch-all arrays first, then string params. Model after `packages/rum-nextjs/src/domain/nextJSRouter/computeViewNameFromParams.ts`. + +**Test cases:** + +Read the reference implementation's test files first. Match the same coverage and writing style: + +- Same `describe`/`it` structure and naming conventions +- Same test case table format (e.g., `const cases = [...]` with inline comments) +- Same helper patterns (mock setup, cleanup) +- Same level of edge case coverage + +Then adapt test cases for the target framework: + +- Start from `routeSyntax.examples` in `01-router-concepts.json` — each example maps to a test case +- Add edge cases by looking at what the reference test covers and translating to the target framework's syntax +- Every `routeSyntax.concept` must have at least one test case + +**Types file:** + +- Define minimal interfaces matching what the selected hook's `availableApi` exposes +- Avoid runtime framework imports — use local type definitions +- Only type what `computeViewName()` and the integration point actually need + +--- + +### Phase 3: Validate & Fix + +Run the monorepo toolchain against the generated code. Fix failures in a bounded loop. Only modify files you generated in Phase 1 and Phase 2 — never touch reference implementations, core packages, or config files outside the generated package. + +**1. Register the package in the workspace:** + +```bash +yarn +``` + +For `new-package` mode, verify it appears in `yarn workspaces list`. + +**2. Run all checks, fix, repeat — up to 5 iterations:** + +``` +checks = [typecheck, lint, unit tests] + +for iteration in 1..5: + run `yarn format` + run `yarn typecheck` → capture errors + run `yarn lint` → capture errors + run `yarn test:unit --spec packages/rum-/` → capture errors + + if all pass → done, break + + analyze ALL errors together + apply fixes to generated files only + log what was fixed and why + +if iteration 5 still has failures → stop, document in manifest +``` + +**Checks (run all three every iteration):** + +| Command | What it catches | +| ------------------------------------------------- | -------------------------------------------------------- | +| `yarn typecheck` | Wrong imports, missing properties, mismatched interfaces | +| `yarn lint` | Naming, formatting (mostly auto-fixed by `yarn format`) | +| `yarn test:unit --spec packages/rum-/` | Wrong mock shapes, assertion mismatches, missing cleanup | + +Running all three every iteration (instead of gating sequentially) lets you see the full error surface and fix multiple categories at once. + +**Fix strategy:** + +- Read ALL errors before making any changes — fixes often overlap +- Compare with the reference implementation's source and test files to spot pattern mismatches +- Prefer the smallest change that fixes the error + +**Anti-rules — never do these to "fix" a failure:** + +- Add `// @ts-ignore`, `as any`, or type casts to silence type errors +- Delete or skip failing test cases + +## After All Phases: Write Generation Manifest + +Write `docs/integrations//03-generation-manifest.md` using the template at `output.template.md` (next to this SKILL.md). Fill in every placeholder. The validation section documents all fix loop iterations. + +If validation status is `fail`, the pipeline orchestrator must stop before creating a PR. + +## Output + +- Generated package in `packages/rum-/` +- Manifest at `docs/integrations//03-generation-manifest.md` diff --git a/.claude/skills/router-generate/output.template.md b/.claude/skills/router-generate/output.template.md new file mode 100644 index 0000000000..b9a607787b --- /dev/null +++ b/.claude/skills/router-generate/output.template.md @@ -0,0 +1,27 @@ +# Generation Manifest — + +## Generated Files + +### Phase 1: Plugin + Wrapping Strategy + +- `` — + - Reference: `` + - Deviations: + +### Phase 2: View Name Algorithm + +- `` — + - Reference: `` + - Deviations: + +## Validation + +**Status:** pass | fail +**Iterations:** / 5 + +### Iteration , , + +- + + + diff --git a/.claude/skills/router-pipeline/SKILL.md b/.claude/skills/router-pipeline/SKILL.md new file mode 100644 index 0000000000..e44c80426c --- /dev/null +++ b/.claude/skills/router-pipeline/SKILL.md @@ -0,0 +1,78 @@ +--- +name: router-pipeline +description: Fully automated pipeline that generates a draft Browser SDK router integration PR from an npm package URL. Usage: /router-pipeline +--- + +# Router Integration Pipeline + +You are an orchestrator that dispatches `claude -p` processes for each stage to generate a complete Browser SDK router integration package and draft PR. + +Each stage runs as a dedicated `claude -p` process with its own context window. Stages 1 and 2 use `--output-format json --json-schema `. The full CLI wrapper JSON (including `structured_output`, `is_error`, `duration_ms`, cost, etc.) is persisted as the reviewable artifact under `docs/integrations//`. Downstream stages extract `.structured_output` with `jq` when they read the data. Stages 3 and 4 produce code and markdown via normal tool calls. + +## Input + +The single argument is an npm package URL (e.g. `https://www.npmjs.com/package/@angular/router`). + +Example: `/router-pipeline https://www.npmjs.com/package/vue-router` + +## Step 1: Stage 1 — Fetch Docs (structured JSON) + +The skill instructs the model to emit a JSON object conforming to `.claude/skills/router-fetch-docs/output.schema.json`. The harness validates it and exposes it on `.structured_output`. + +The framework name isn't known yet, so write the artifact to a real temp file first, then move it under `docs/integrations//` once stage 1 resolves it. + +```bash +SCHEMA_1=$(cat .claude/skills/router-fetch-docs/output.schema.json) + +STAGE1_OUT=/tmp/router-stage1.json + +claude -p "/router-fetch-docs " \ + --model opus \ + --output-format json \ + --json-schema "$SCHEMA_1" \ + --permission-mode auto \ + > "$STAGE1_OUT" + +FRAMEWORK=$(jq -r '.structured_output.metadata.framework.value' "$STAGE1_OUT") +mkdir -p "docs/integrations/$FRAMEWORK" +mv "$STAGE1_OUT" "docs/integrations/$FRAMEWORK/01-router-concepts.json" +``` + +The file committed under `docs/integrations/$FRAMEWORK/01-router-concepts.json` is the full CLI wrapper: `{type, subtype, is_error, duration_ms, result, structured_output, usage, total_cost_usd, ...}`. Downstream stages use `jq '.structured_output' ` to read the schema-shaped payload. + +## Step 2: Stage 2 — Design Decisions (structured JSON) + +```bash +SCHEMA_2=$(cat .claude/skills/router-design/output.schema.json) + +claude -p "/router-design $FRAMEWORK" \ + --model opus \ + --output-format json \ + --json-schema "$SCHEMA_2" \ + --permission-mode auto \ + > "docs/integrations/$FRAMEWORK/02-design-decisions.json" +``` + +## Step 3: Stage 3 — Generate Code + +Stage 3 produces source code and a markdown manifest via normal tool calls — no structured output. + +```bash +claude -p "/router-generate $FRAMEWORK" \ + --model opus \ + --permission-mode auto +``` + +Verify `docs/integrations/$FRAMEWORK/03-generation-manifest.md` + +Check the **Validation** section in the manifest. If `**Status:** fail`, stop the pipeline and report the failures to the user. Do not proceed to Stage 4. + +## Step 4: Stage 4 — Create PR + +```bash +claude -p "/router-pr $FRAMEWORK" \ + --model opus \ + --permission-mode auto +``` + +Report the PR URL to the user when done. diff --git a/.claude/skills/router-pr/SKILL.md b/.claude/skills/router-pr/SKILL.md new file mode 100644 index 0000000000..8bd597bc57 --- /dev/null +++ b/.claude/skills/router-pr/SKILL.md @@ -0,0 +1,116 @@ +--- +name: router-pr +description: 'Stage 4: Create a git branch, commit artifacts and generated code, and open a draft PR on GitHub.' +--- + +# Stage 4: Create Draft PR + +## Context + +You are Stage 4 of the router integration pipeline. Your job is to create a branch, commit all generated artifacts and code, and open a draft PR on GitHub. + +## Input + +You receive a **framework identifier** as skill param (e.g. `angular`, `vue`, `tanstack-react-router`). + +Read: + +1. `docs/integrations//02-design-decisions.json` — stage 2 CLI wrapper. Extract the design payload (used for PR body content) with `jq '.structured_output' `. +2. `docs/integrations//03-generation-manifest.md` — list of generated files. +3. `git config user.name` — current git user name (for branch naming). + +## Process + +### 1. Create Branch + +```bash +# Get the current user's branch prefix +USER=$(git config user.name | tr ' ' '.' | tr '[:upper:]' '[:lower:]') +# Random suffix to avoid branch collisions across repeated runs +BRANCH="${USER}/-router-integration-${RANDOM}" + +git checkout -b "$BRANCH" +``` + +### 2. Commit Artifacts + +First commit: design documentation only. + +```bash +git add docs/integrations// +git commit -m "📝 Add router integration design docs + +Generated by the router integration pipeline. +Artifacts: router concepts, design decisions, generation manifest." +``` + +### 3. Commit Generated Code + +Second commit: the generated code. Read `targetPackage.mode` from `02-design-decisions.json`. + +For `new-package`: + +```bash +git add packages/rum-/ +git commit -m "✨ Add router integration package + +Auto-generated from framework documentation using the router integration pipeline. +See docs/integrations// for design artifacts and decision rationale." +``` + +For `extend-existing`: + +```bash +git add packages// +git commit -m "✨ Add router support to + +Auto-generated from framework documentation using the router integration pipeline. +See docs/integrations// for design artifacts and decision rationale." +``` + +### 4. Push and Create Draft PR + +```bash +git push -u origin "$BRANCH" +``` + +Then use `gh pr create` with this structure: + +```bash +gh pr create --draft --title "✨ Add router integration" --body "$(cat <<'PREOF' +## Summary + +Auto-generated router integration for using the router integration pipeline. + +- Router view tracking via `startView()` on route changes +- Unit tests for view name computation and navigation filtering + +## Design Artifacts + +Full design trail at `docs/integrations//`: +- `01-router-concepts.json` — structured routing concepts extracted from framework docs +- `02-design-decisions.json` — hook selection, wrapping strategy, algorithm family decisions +- `03-generation-manifest.md` — list of generated files with lineage + +## Key Decisions + + + +## Test plan + +- [ ] Review design artifacts in `docs/integrations//` +- [ ] Review generated code against reference implementations +- [ ] Run `yarn typecheck` to verify type correctness +- [ ] Run `yarn test:unit --spec packages/rum-/` to verify tests pass +- [ ] Manual review of `computeViewName()` edge cases + +🤖 Generated with the router integration pipeline +PREOF +)" +``` + +Replace `` with the actual framework name. Replace the key decisions placeholder with actual content from `02-design-decisions.json`. + +## Output + +Report the PR URL to the user. diff --git a/.gitignore b/.gitignore index af4e781f89..0bf48db32e 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ playwright-report/ # Claude Code local files *.local.md .claude/settings.local.json +.worktrees/ diff --git a/docs/integrations/angular/01-router-concepts.json b/docs/integrations/angular/01-router-concepts.json new file mode 100644 index 0000000000..ba2be0305b --- /dev/null +++ b/docs/integrations/angular/01-router-concepts.json @@ -0,0 +1 @@ +{"type":"result","subtype":"success","is_error":false,"api_error_status":null,"duration_ms":172092,"duration_api_ms":171024,"num_turns":14,"result":"Emitted structured output. Note: WebFetch was denied, so all sources are marked `inferred:` from training knowledge — Stage 2 should verify API shapes (event properties, NavigationCancellationCode/NavigationSkippedCode enums) against live angular.dev before generating code.","stop_reason":"end_turn","session_id":"f031446b-08f3-4f62-9106-51302fc67cdd","total_cost_usd":1.1494074999999997,"usage":{"input_tokens":29,"cache_creation_input_tokens":93296,"cache_read_input_tokens":493375,"output_tokens":12779,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":93296},"inference_geo":"","iterations":[{"input_tokens":1,"output_tokens":98,"cache_read_input_tokens":42778,"cache_creation_input_tokens":10708,"cache_creation":{"ephemeral_5m_input_tokens":10708,"ephemeral_1h_input_tokens":0},"type":"message"}],"speed":"standard"},"modelUsage":{"claude-opus-4-7":{"inputTokens":29,"outputTokens":12779,"cacheReadInputTokens":493375,"cacheCreationInputTokens":93296,"webSearchRequests":0,"costUSD":1.1494074999999997,"contextWindow":200000,"maxOutputTokens":64000}},"permission_denials":[{"tool_name":"WebFetch","tool_use_id":"toolu_01MZbPVuHH4CKqcFLMFbKvXx","tool_input":{"url":"https://www.npmjs.com/package/@angular/router","prompt":"Extract: package name, version, description, homepage URL, repository URL, and all documentation links. Also look for keywords. Provide URLs exactly as they appear."}},{"tool_name":"WebFetch","tool_use_id":"toolu_017YkWNbmwMboqW3Gn2veQuR","tool_input":{"url":"https://www.npmjs.com/package/@angular/router","prompt":"Extract: package name, version, description, homepage URL, repository URL, and all documentation links. Also look for keywords. Provide URLs exactly as they appear."}},{"tool_name":"WebFetch","tool_use_id":"toolu_01Y2rRsxFbNB3uaZj1WuGacJ","tool_input":{"url":"https://www.npmjs.com/package/@angular/router","prompt":"Extract: package name, version, description, homepage URL, repository URL, and all documentation links. Also look for keywords. Provide URLs exactly as they appear."}},{"tool_name":"WebFetch","tool_use_id":"toolu_011LKMAEBmkZMZDEiGoSeAdv","tool_input":{"url":"https://www.npmjs.com/package/@angular/router","prompt":"Extract: package name, version, description, homepage URL, repository URL, and all documentation links. Also look for keywords. Provide URLs exactly as they appear."}},{"tool_name":"WebFetch","tool_use_id":"toolu_01BgYFY8J2yzgx23VYRrquwG","tool_input":{"url":"https://angular.dev/guide/routing","prompt":"List all the subpages and sections of this routing guide, and any links to API references. Also summarize what this page covers about client-side routing."}},{"tool_name":"WebFetch","tool_use_id":"toolu_0165R1zZJofcnYQ3ufzkJwqH","tool_input":{"url":"https://angular.dev/llms.txt","prompt":"Extract the full content of this llms.txt file and list all routing-related pages."}},{"tool_name":"WebFetch","tool_use_id":"toolu_01U3MWx2hWzkfwTKU3nzs4gD","tool_input":{"url":"https://angular.dev/llms.txt","prompt":"Extract the full content and list all routing-related pages."}},{"tool_name":"WebFetch","tool_use_id":"toolu_0151rzZUi5RTKiRXpiZVoiHX","tool_input":{"url":"https://www.npmjs.com/package/@angular/router","prompt":"Extract package name, version, description, homepage URL, repository URL, and keywords."}},{"tool_name":"WebFetch","tool_use_id":"toolu_01JnGc7iZZyH4H7eh4Vt5uRE","tool_input":{"url":"https://www.npmjs.com/package/@angular/router","prompt":"Extract package name, version, description, homepage URL, repository URL, and keywords."}}],"structured_output":{"metadata":{"framework":{"value":"angular","source":"inferred: derived from npm package @angular/router; WebFetch permission not granted"},"npmPackage":{"value":"@angular/router","source":"inferred: URL https://www.npmjs.com/package/@angular/router provided as skill argument; fetch denied"}},"routeDefinition":{"styles":[{"value":"config-object","source":"inferred: Angular Router uses Routes arrays with path/component/children/loadChildren; Routes API at https://angular.dev/api/router/Routes"}],"historyStrategies":[{"value":"path","source":"inferred: PathLocationStrategy is default (History API); https://angular.dev/api/common/PathLocationStrategy"},{"value":"hash","source":"inferred: HashLocationStrategy opt-in via withHashLocation() or {useHash:true}; https://angular.dev/api/common/HashLocationStrategy"}]},"routeSyntax":{"concepts":[{"value":"static-path","source":"inferred: Routes support literal paths like {path: 'users'}; Angular Router core API"},{"value":"dynamic-segments","source":"inferred: colon-prefixed segments like :id; https://angular.dev/guide/routing/common-router-tasks"},{"value":"catch-all","source":"inferred: wildcard ** matches any unmatched URL; https://angular.dev/guide/routing/common-router-tasks"},{"value":"nested-routes","source":"inferred: children property on Route produces nested routes rendered in child router-outlet"}],"examples":[{"route":"users","path":"/users","viewName":"UsersComponent","note":"Static path; matches exact segment.","source":"inferred: Angular Router standard usage"},{"route":"users/:id","path":"/users/42","viewName":"UserComponent (params.id='42')","note":"Dynamic segment; accessed via ActivatedRoute.paramMap or @Input() when withComponentInputBinding() enabled.","source":"inferred: Angular Router standard usage; https://angular.dev/guide/routing/common-router-tasks"},{"route":"**","path":"/anything/unmatched","viewName":"PageNotFoundComponent","note":"Wildcard route; must be declared last in Routes array.","source":"inferred: https://angular.dev/guide/routing/common-router-tasks"},{"route":"admin with children [{path:'settings', component: SettingsComponent}]","path":"/admin/settings","viewName":"AdminComponent > SettingsComponent","note":"Nested routes require a inside the parent component.","source":"inferred: https://angular.dev/guide/routing/common-router-tasks"},{"route":"products with children [{path:':id', component: ProductComponent}]","path":"/products/10","viewName":"ProductsComponent > ProductComponent","note":"Dynamic nested segment.","source":"inferred: Angular Router standard usage"}]},"ssr":{"supported":{"value":true,"source":"inferred: Angular SSR (formerly Universal) via @angular/ssr; https://angular.dev/guide/ssr"},"clientDetection":{"value":"isPlatformBrowser(inject(PLATFORM_ID))","source":"inferred: idiomatic SSR guard using @angular/common PLATFORM_ID token; https://angular.dev/api/common/isPlatformBrowser"}},"hooks":[{"name":"NavigationStart","afterCancellation":false,"afterRedirects":false,"afterFetch":false,"afterRender":false,"access":"Subscribe to Router.events (Router service injected via Angular DI); filter by instanceof NavigationStart","availableApi":[{"name":"event.id","description":"Unique navigation identifier (number).","source":"inferred: https://angular.dev/api/router/NavigationStart"},{"name":"event.url","description":"Target URL string (pre-redirect).","source":"inferred: https://angular.dev/api/router/NavigationStart"},{"name":"event.navigationTrigger","description":"'imperative' | 'popstate' | 'hashchange'.","source":"inferred: https://angular.dev/api/router/NavigationStart"},{"name":"event.restoredState","description":"Popstate state snapshot when navigation triggered by back/forward.","source":"inferred: https://angular.dev/api/router/NavigationStart"}],"source":"inferred: https://angular.dev/api/router/NavigationStart"},{"name":"RouteConfigLoadStart","afterCancellation":false,"afterRedirects":false,"afterFetch":false,"afterRender":false,"access":"Subscribe to Router.events; filter by instanceof RouteConfigLoadStart","availableApi":[{"name":"event.route","description":"The Route config about to be lazy-loaded.","source":"inferred: https://angular.dev/api/router/RouteConfigLoadStart"}],"source":"inferred: https://angular.dev/api/router/RouteConfigLoadStart"},{"name":"RouteConfigLoadEnd","afterCancellation":false,"afterRedirects":false,"afterFetch":false,"afterRender":false,"access":"Subscribe to Router.events; filter by instanceof RouteConfigLoadEnd","availableApi":[{"name":"event.route","description":"The Route config just lazy-loaded.","source":"inferred: https://angular.dev/api/router/RouteConfigLoadEnd"}],"source":"inferred: https://angular.dev/api/router/RouteConfigLoadEnd"},{"name":"RoutesRecognized","afterCancellation":false,"afterRedirects":true,"afterFetch":false,"afterRender":false,"access":"Subscribe to Router.events; filter by instanceof RoutesRecognized","availableApi":[{"name":"event.id","description":"Navigation id.","source":"inferred: https://angular.dev/api/router/RoutesRecognized"},{"name":"event.url","description":"Original URL.","source":"inferred: https://angular.dev/api/router/RoutesRecognized"},{"name":"event.urlAfterRedirects","description":"Final URL after configured redirects applied.","source":"inferred: https://angular.dev/api/router/RoutesRecognized"},{"name":"event.state","description":"RouterStateSnapshot with the matched route tree; walk state.root to access ActivatedRouteSnapshot nodes including routeConfig.path, params, data.","source":"inferred: https://angular.dev/api/router/RouterStateSnapshot"}],"source":"inferred: https://angular.dev/api/router/RoutesRecognized"},{"name":"GuardsCheckStart","afterCancellation":false,"afterRedirects":true,"afterFetch":false,"afterRender":false,"access":"Subscribe to Router.events; filter by instanceof GuardsCheckStart","availableApi":[{"name":"event.state","description":"RouterStateSnapshot prior to guards running.","source":"inferred: https://angular.dev/api/router/GuardsCheckStart"},{"name":"event.urlAfterRedirects","description":"Final URL after config redirects.","source":"inferred: https://angular.dev/api/router/GuardsCheckStart"}],"source":"inferred: https://angular.dev/api/router/GuardsCheckStart"},{"name":"GuardsCheckEnd","afterCancellation":false,"afterRedirects":true,"afterFetch":false,"afterRender":false,"access":"Subscribe to Router.events; filter by instanceof GuardsCheckEnd","availableApi":[{"name":"event.shouldActivate","description":"Boolean: whether guards permitted activation; false triggers NavigationCancel afterwards.","source":"inferred: https://angular.dev/api/router/GuardsCheckEnd"},{"name":"event.state","description":"RouterStateSnapshot.","source":"inferred: https://angular.dev/api/router/GuardsCheckEnd"}],"source":"inferred: https://angular.dev/api/router/GuardsCheckEnd"},{"name":"ResolveStart","afterCancellation":true,"afterRedirects":true,"afterFetch":false,"afterRender":false,"access":"Subscribe to Router.events; filter by instanceof ResolveStart","availableApi":[{"name":"event.state","description":"RouterStateSnapshot entering resolve phase.","source":"inferred: https://angular.dev/api/router/ResolveStart"}],"source":"inferred: https://angular.dev/api/router/ResolveStart"},{"name":"ResolveEnd","afterCancellation":true,"afterRedirects":true,"afterFetch":true,"afterRender":false,"access":"Subscribe to Router.events; filter by instanceof ResolveEnd","availableApi":[{"name":"event.state","description":"RouterStateSnapshot with resolved data on ActivatedRouteSnapshot.data.","source":"inferred: https://angular.dev/api/router/ResolveEnd"},{"name":"event.urlAfterRedirects","description":"Final URL.","source":"inferred: https://angular.dev/api/router/ResolveEnd"}],"source":"inferred: https://angular.dev/api/router/ResolveEnd"},{"name":"ActivationStart","afterCancellation":true,"afterRedirects":true,"afterFetch":true,"afterRender":false,"access":"Subscribe to Router.events; filter by instanceof ActivationStart","availableApi":[{"name":"event.snapshot","description":"ActivatedRouteSnapshot of the route being activated (one per activation).","source":"inferred: https://angular.dev/api/router/ActivationStart"}],"source":"inferred: https://angular.dev/api/router/ActivationStart"},{"name":"ActivationEnd","afterCancellation":true,"afterRedirects":true,"afterFetch":true,"afterRender":false,"access":"Subscribe to Router.events; filter by instanceof ActivationEnd","availableApi":[{"name":"event.snapshot","description":"ActivatedRouteSnapshot of activated route.","source":"inferred: https://angular.dev/api/router/ActivationEnd"}],"source":"inferred: https://angular.dev/api/router/ActivationEnd"},{"name":"ChildActivationStart","afterCancellation":true,"afterRedirects":true,"afterFetch":true,"afterRender":false,"access":"Subscribe to Router.events; filter by instanceof ChildActivationStart","availableApi":[{"name":"event.snapshot","description":"ActivatedRouteSnapshot of the parent whose children are activating.","source":"inferred: https://angular.dev/api/router/ChildActivationStart"}],"source":"inferred: https://angular.dev/api/router/ChildActivationStart"},{"name":"ChildActivationEnd","afterCancellation":true,"afterRedirects":true,"afterFetch":true,"afterRender":false,"access":"Subscribe to Router.events; filter by instanceof ChildActivationEnd","availableApi":[{"name":"event.snapshot","description":"ActivatedRouteSnapshot of the parent whose children activated.","source":"inferred: https://angular.dev/api/router/ChildActivationEnd"}],"source":"inferred: https://angular.dev/api/router/ChildActivationEnd"},{"name":"NavigationEnd","afterCancellation":true,"afterRedirects":true,"afterFetch":true,"afterRender":false,"access":"Subscribe to Router.events; filter by instanceof NavigationEnd (or use router.events.pipe(filter(e => e instanceof NavigationEnd)))","availableApi":[{"name":"event.id","description":"Navigation id.","source":"inferred: https://angular.dev/api/router/NavigationEnd"},{"name":"event.url","description":"Initial URL.","source":"inferred: https://angular.dev/api/router/NavigationEnd"},{"name":"event.urlAfterRedirects","description":"Final URL committed to the browser.","source":"inferred: https://angular.dev/api/router/NavigationEnd"},{"name":"router.routerState.snapshot","description":"RouterStateSnapshot of the activated route tree; accessible via Router service.","source":"inferred: https://angular.dev/api/router/Router"},{"name":"router.routerState.root","description":"ActivatedRoute root; traverse firstChild chain to reach leaf; each node exposes routeConfig.path and snapshot.params.","source":"inferred: https://angular.dev/api/router/ActivatedRoute"}],"source":"inferred: https://angular.dev/api/router/NavigationEnd"},{"name":"NavigationCancel","afterCancellation":false,"afterRedirects":false,"afterFetch":false,"afterRender":false,"access":"Subscribe to Router.events; filter by instanceof NavigationCancel","availableApi":[{"name":"event.reason","description":"String explanation of cancellation.","source":"inferred: https://angular.dev/api/router/NavigationCancel"},{"name":"event.code","description":"NavigationCancellationCode enum (Redirect, SupersededByNewNavigation, NoDataFromResolver, GuardRejected).","source":"inferred: https://angular.dev/api/router/NavigationCancellationCode"}],"source":"inferred: https://angular.dev/api/router/NavigationCancel"},{"name":"NavigationError","afterCancellation":false,"afterRedirects":false,"afterFetch":false,"afterRender":false,"access":"Subscribe to Router.events; filter by instanceof NavigationError","availableApi":[{"name":"event.error","description":"Error thrown during navigation.","source":"inferred: https://angular.dev/api/router/NavigationError"},{"name":"event.url","description":"URL that failed.","source":"inferred: https://angular.dev/api/router/NavigationError"}],"source":"inferred: https://angular.dev/api/router/NavigationError"},{"name":"NavigationSkipped","afterCancellation":true,"afterRedirects":false,"afterFetch":false,"afterRender":false,"access":"Subscribe to Router.events; filter by instanceof NavigationSkipped","availableApi":[{"name":"event.reason","description":"NavigationSkippedCode enum (IgnoredSameUrlNavigation, IgnoredByUrlHandlingStrategy).","source":"inferred: https://angular.dev/api/router/NavigationSkipped"},{"name":"event.url","description":"URL that was skipped.","source":"inferred: https://angular.dev/api/router/NavigationSkipped"}],"source":"inferred: https://angular.dev/api/router/NavigationSkipped"},{"name":"Scroll","afterCancellation":true,"afterRedirects":true,"afterFetch":true,"afterRender":true,"access":"Subscribe to Router.events; filter by instanceof Scroll (only fired when withInMemoryScrolling() feature enabled)","availableApi":[{"name":"event.routerEvent","description":"The originating NavigationEnd event.","source":"inferred: https://angular.dev/api/router/Scroll"},{"name":"event.position","description":"[x, y] scroll restore position, or null.","source":"inferred: https://angular.dev/api/router/Scroll"},{"name":"event.anchor","description":"URL fragment identifier, or null.","source":"inferred: https://angular.dev/api/router/Scroll"}],"source":"inferred: https://angular.dev/api/router/Scroll"}],"versions":{"entries":[{"version":"17.0.0","date":"2023-11-08","routingChanges":["Deprecated CanLoad in favor of CanMatch guard.","Added withComponentInputBinding() feature to bind route params/data/queryParams as component @Input.","redirectTo may be a function receiving ActivatedRouteSnapshot."],"source":"inferred: Angular v17 release notes (https://github.com/angular/angular/blob/main/CHANGELOG.md)"},{"version":"18.0.0","date":"2024-05-22","routingChanges":["Router events surface NavigationSkipped with refined NavigationSkippedCode enum.","withNavigationErrorHandler() router feature added for structured error handling."],"source":"inferred: Angular v18 release notes"},{"version":"19.0.0","date":"2024-11-19","routingChanges":["Standalone APIs by default (affects provideRouter usage patterns).","Event replay hydration integrated with router for SSR."],"source":"inferred: Angular v19 release notes"},{"version":"20.0.0","date":"2025-05-28","routingChanges":["Further tightening of typed route data/params; continued stabilization of signal-based inputs with router input binding."],"source":"inferred: Angular v20 release notes; date approximate from Angular's 6-month cadence"}]},"notes":"Documentation could not be fetched (WebFetch permission denied); all fields were populated from training knowledge of Angular Router and marked 'inferred'. Stage 2 should independently verify any API names (especially event property shapes and NavigationCancellationCode/NavigationSkippedCode enum members) against live angular.dev docs before generating code.\n\nKey Angular-specific nuances for downstream stages:\n- Router.events emits *all* event types on one Observable; SDK must filter by instanceof.\n- Route tree reconstruction: after NavigationEnd, walk router.routerState.root.firstChild recursively; each ActivatedRouteSnapshot has routeConfig?.path (literal pattern, may be '', ':id', '**') and params. Concatenate non-empty path segments to build the view name.\n- Lazy-loaded routes: RouteConfigLoadEnd fires before RoutesRecognized; no special handling usually needed for view-name extraction since final state is captured at NavigationEnd.\n- Guard-triggered redirect (returning UrlTree): fires NavigationCancel with code=Redirect, then a new NavigationStart for the redirect target — SDK should treat only the final NavigationEnd as the committed view.\n- Config redirects ({path:'a', redirectTo:'b'}) are resolved in-flight and surfaced via urlAfterRedirects on RoutesRecognized/NavigationEnd — no cancel/restart.\n- Angular does not have a first-class 'optional segment' syntax; optional data is typically expressed via matrix params or multiple Route entries.\n- withHashLocation() is the modern way to enable hash strategy (replaces {useHash:true}).\n- SSR via @angular/ssr: the Router runs server-side; use isPlatformBrowser(PLATFORM_ID) before touching window/document in any SDK subscriber."},"terminal_reason":"completed","fast_mode_state":"off","uuid":"f0d4b1f5-6f0e-413d-ad75-9d14ae1b1463"} diff --git a/docs/integrations/angular/02-design-decisions.json b/docs/integrations/angular/02-design-decisions.json new file mode 100644 index 0000000000..4bc02af06d --- /dev/null +++ b/docs/integrations/angular/02-design-decisions.json @@ -0,0 +1 @@ +{"type":"result","subtype":"success","is_error":false,"api_error_status":null,"duration_ms":175570,"duration_api_ms":171769,"num_turns":17,"result":"Design decisions complete for Angular:\n\n- **Hook**: `ResolveStart` — only hook passing all 3 priority rules (afterCancellation + afterRedirects + afterFetch:false & afterRender:false)\n- **Wrapping**: `provider` — `provideDatadogRouter()` returning `ENVIRONMENT_INITIALIZER` that uses `inject(Router).events.subscribe(...)`\n- **View name**: `matched-records` — walk `event.state.root.firstChild` concatenating `routeConfig.path`, substitute `**` with actual URL remainder\n- **Package**: `new-package` → `packages/rum-angular/`\n- **Reference**: `rum-vue` (matched-records + router-instance event subscription + dual-entry package shape)\n- **SSR**: guard with `isPlatformBrowser(inject(PLATFORM_ID))` inside the initializer","stop_reason":"end_turn","session_id":"1f4a1065-b359-4465-96a6-fda1b35e11c2","total_cost_usd":1.06183225,"usage":{"input_tokens":22,"cache_creation_input_tokens":69915,"cache_read_input_tokens":724057,"output_tokens":10509,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":69915},"inference_geo":"","iterations":[{"input_tokens":1,"output_tokens":305,"cache_read_input_tokens":56322,"cache_creation_input_tokens":4145,"cache_creation":{"ephemeral_5m_input_tokens":4145,"ephemeral_1h_input_tokens":0},"type":"message"}],"speed":"standard"},"modelUsage":{"claude-opus-4-7":{"inputTokens":22,"outputTokens":10509,"cacheReadInputTokens":724057,"cacheCreationInputTokens":69915,"webSearchRequests":0,"costUSD":1.06183225,"contextWindow":200000,"maxOutputTokens":64000}},"permission_denials":[],"structured_output":{"selectedHook":{"name":"ResolveStart","rationale":"Candidates with afterCancellation:true — ResolveStart, ResolveEnd, ActivationStart, ActivationEnd, ChildActivationStart, ChildActivationEnd, NavigationEnd, NavigationSkipped, Scroll. Eliminated NavigationStart/RouteConfigLoadStart/RouteConfigLoadEnd/RoutesRecognized/GuardsCheckStart/GuardsCheckEnd/NavigationCancel/NavigationError (afterCancellation:false). Step 2 (prefer afterRedirects:true) eliminates NavigationSkipped (afterRedirects:false). Step 3 (prefer afterFetch:false AND afterRender:false) is passed only by ResolveStart (ResolveEnd, ActivationStart/End, ChildActivationStart/End, NavigationEnd all have afterFetch:true; Scroll has both afterFetch:true and afterRender:true). ResolveStart is the unique survivor: guards have already passed, redirects are applied (event.urlAfterRedirects available), RouterStateSnapshot (event.state) is populated with the final matched route tree, resolvers have not yet executed so their fetches are attributed to the new view, and component activation/render happens after so long tasks and interactions land on the new view. Note: supersession can still cancel a navigation after ResolveStart, but per the deterministic priority rules afterFetch:false wins over that edge case."},"wrappingStrategy":{"pattern":"provider","target":"A provideDatadogRouter() function returning Provider[] that registers an ENVIRONMENT_INITIALIZER (multi: true) which uses inject(Router) to subscribe to router.events, filtered by `instanceof ResolveStart`.","rationale":"Angular has no factory-wrapping convention analogous to vue-router's createRouter; the idiomatic integration surface is DI. Users already call provideRouter(routes) in bootstrapApplication; adding a sibling provideDatadogRouter() is the canonical Angular pattern (mirrors provideHttpClient, provideAnimations, etc.). ENVIRONMENT_INITIALIZER executes once per application environment at bootstrap, which is the correct lifetime for a singleton Router subscription. Using inject(Router) inside the initializer avoids circular provider ordering and works with standalone APIs (Angular 15+). A renderless component would require users to place it in their template, which is non-idiomatic for cross-cutting services. A wrap-factory over provideRouter is possible but provideRouter is not a router instance factory — the Router is instantiated lazily by DI — so wrapping the providers array adds complexity without benefit over a direct ENVIRONMENT_INITIALIZER provider."},"viewNameAlgorithm":{"family":"matched-records","rationale":"ResolveStart exposes event.state: RouterStateSnapshot. The route tree is reached via state.root (ActivatedRouteSnapshot) and traversed by walking the firstChild chain. Each ActivatedRouteSnapshot node exposes routeConfig?.path holding the literal Route pattern ('', 'users', ':id', '**', etc.). The view name is built by concatenating non-empty path segments with '/' separators — the same matched-records approach used by rum-vue (to.matched[].path) and rum-react (state.matches[].route.path). Catch-all handling: when a node has routeConfig?.path === '**', substitute the remaining actual URL segments (from event.urlAfterRedirects) for the wildcard, mirroring rum-vue's substituteCatchAll and rum-react's splat substitution. This is strictly preferred over route-id (Angular has no single parameterized-pattern string) and over param-substitution (which is lossy and heuristic)."},"targetPackage":{"mode":"new-package","package":"rum-angular"},"referenceImplementation":{"primary":"rum-vue","rationale":"Closest on all three axes: (1) subscription pattern — rum-vue subscribes to a router-instance hook (router.afterEach) analogous to Angular's router.events.subscribe; rum-react wraps a router *factory* (createBrowserRouter) which has no Angular equivalent. (2) view-name algorithm — rum-vue uses matched-records (to.matched[] path concatenation + catch-all substitution); rum-angular does the same against ActivatedRouteSnapshot.routeConfig.path. rum-react also uses matched-records but the splat syntax and matches shape differ more from Angular's than Vue's do. (3) package shape — rum-vue ships a main entry (vuePlugin) plus a router subpath entry (vue-router/createRouter); rum-angular will ship angularPlugin + a router subpath (provideDatadogRouter), the same two-entry layout. rum-nextjs is param-substitution and React-component-driven — not a match. Stage 3 should model vuePlugin.ts / startVueRouterView.ts / vueRouter.ts and adapt: swap createRouter-wrapping for an ENVIRONMENT_INITIALIZER provider, swap vue-router's RouteLocationMatched[] for walking ActivatedRouteSnapshot.firstChild, and swap Vue's '/:pathMatch(.*)*' for Angular's '**' catch-all marker."},"ssr":{"handling":"Angular supports SSR via @angular/ssr and the Router runs server-side. The ENVIRONMENT_INITIALIZER must guard its subscription so it only subscribes on the client: inside the initializer factory, call isPlatformBrowser(inject(PLATFORM_ID)) (both from @angular/common) and return early if false. This prevents server-rendered navigations from calling rumPublicApi.startView() (which would also fail because window is unavailable) and avoids duplicate view starts during client hydration — the subsequent client-side router initialization will fire its own ResolveStart for the initial route."},"notes":"\"Key implementation details for Stage 3:\\n\\n1. Event import: Angular's Router event classes live in @angular/router. Use `import { Router, ResolveStart } from '@angular/router'` and `event instanceof ResolveStart` for type narrowing.\\n\\n2. Route tree walk: At ResolveStart, event.state is a RouterStateSnapshot. Start at event.state.root (ActivatedRouteSnapshot), then walk node.firstChild recursively until null. Collect each node's routeConfig?.path, skipping empty strings and undefined. Concatenate with '/' separators, ensuring a leading '/'. This matches rum-vue/startVueRouterView.ts:computeViewName shape.\\n\\n3. Catch-all: Angular uses '**' as the wildcard path (not '*' like React Router or ':pathMatch(.*)*' like Vue). When a walked node has routeConfig?.path === '**', replace that segment with the remaining portion of event.urlAfterRedirects. Use event.urlAfterRedirects (not event.url) so redirects are already applied.\\n\\n4. Trust-but-verify: Stage 1 marked ResolveStart as afterCancellation:true because it fires after guards pass. Supersession (NavigationCancel with code=SupersededByNewNavigation) can still occur after ResolveStart — the SDK accepts this trade-off because the priority rules weight afterFetch:false higher (resource attribution correctness > occasional spurious view starts that get superseded by the next ResolveStart).\\n\\n5. Plugin surface: Mirror vuePlugin — export angularPlugin({ router?: boolean }) that sets initConfiguration.trackViewsManually = true when router:true, plus a separate entry `@datadog/browser-rum-angular/router` exporting provideDatadogRouter(). Two entry points in package.json exports map (main + router subpath) — same as rum-vue's main.ts + vueRouter.ts entries and vue-router-v4 files entry.\\n\\n6. Peer deps: `@angular/core` (for inject, PLATFORM_ID, ENVIRONMENT_INITIALIZER, Provider) and `@angular/router` (for Router, ResolveStart, RouterStateSnapshot, ActivatedRouteSnapshot) and `@angular/common` (for isPlatformBrowser) — all marked optional in peerDependenciesMeta. Support Angular 17+ (standalone-first, withComponentInputBinding era) per Stage 1 versions table.\\n\\n7. Hash routing: withHashLocation() users still trigger the same Router events, so no special handling required — urlAfterRedirects includes the hash-strategy URL in normalized form.\\n\\n8. RxJS subscription: router.events is a standard RxJS Subject. No need to import operators — use `.subscribe((event) => { if (event instanceof ResolveStart) { ... } })` to avoid pulling rxjs/operators as a dep surface. The subscription lifetime is the application environment (single-page lifetime), so no manual unsubscribe is needed — matching rum-vue's approach of leaving the afterEach registered.\\n\\n9. Unmapped Stage 1 concepts: NavigationSkipped, NavigationError, Scroll, and the lazy-load events (RouteConfigLoadStart/End) are intentionally not used — they don't add information beyond what ResolveStart provides for view tracking. Document these as 'not used' in the generated code comments if relevant, but do not add special-case handling.\""},"terminal_reason":"completed","fast_mode_state":"off","uuid":"a4aacdf6-00ad-4720-9e4e-16f6466f8453"} diff --git a/docs/integrations/angular/03-generation-manifest.md b/docs/integrations/angular/03-generation-manifest.md new file mode 100644 index 0000000000..8ce839955b --- /dev/null +++ b/docs/integrations/angular/03-generation-manifest.md @@ -0,0 +1,87 @@ +# Generation Manifest — angular + +## Generated Files + +### Phase 1: Plugin + Wrapping Strategy + +- `packages/rum-angular/package.json` — package manifest with main + `angular-router` subpath entries, optional `@angular/*` peer deps + - Reference: `packages/rum-vue/package.json` + - Deviations: peer deps are `@angular/common`, `@angular/core`, `@angular/router`, `rxjs` (all optional) instead of `vue`/`vue-router`; added `zone.js`/`tslib` to devDependencies per Angular runtime requirements. + +- `packages/rum-angular/angular-router/package.json` — subpath package entry pointer + - Reference: `packages/rum-vue/vue-router-v4/package.json` + - Deviations: none (rename-only). + +- `packages/rum-angular/angular-router/typedoc.json` — docs entry config + - Reference: `packages/rum-vue/vue-router-v4/typedoc.json` + - Deviations: none. + +- `packages/rum-angular/typedoc.json` — docs entry for main + - Reference: `packages/rum-vue/typedoc.json` + - Deviations: none. + +- `packages/rum-angular/README.md` — package overview, setup, router tracking docs + - Reference: n/a (written against design + routeSyntax.examples) + - Deviations: n/a. + +- `packages/rum-angular/src/entries/main.ts` — public main entry exporting `angularPlugin` and config types + - Reference: `packages/rum-vue/src/entries/main.ts` + - Deviations: none (rename-only). + +- `packages/rum-angular/src/entries/angularRouter.ts` — router subpath entry exporting `provideDatadogRouter` + - Reference: `packages/rum-vue/src/entries/vueRouter.ts` + - Deviations: exports `provideDatadogRouter` (DI provider array) instead of `createRouter` (factory wrapper), per `wrappingStrategy.pattern = "provider"`. + +- `packages/rum-angular/src/domain/angularPlugin.ts` — plugin lifecycle, subscriber pattern, configuration + - Reference: `packages/rum-vue/src/domain/vuePlugin.ts` + - Deviations: identifier renames only (`vue`→`angular`). + +- `packages/rum-angular/src/domain/angularPlugin.spec.ts` — plugin structure tests + - Reference: `packages/rum-vue/src/domain/vuePlugin.spec.ts` + - Deviations: identifier renames only. + +- `packages/rum-angular/src/domain/angularRouter/provideDatadogRouter.ts` — integration point: `ENVIRONMENT_INITIALIZER` provider subscribing to `Router.events` and filtering by `ResolveStart` + - Reference: `packages/rum-vue/src/domain/router/vueRouter.ts` + - Deviations: per `wrappingStrategy.pattern = "provider"` — DI provider array instead of factory wrap. Uses `inject(Router)` and `isPlatformBrowser(inject(PLATFORM_ID))` for SSR guard (design `ssr.handling`). Filter uses a `isResolveStart` type predicate over `EventType.ResolveStart` (instead of `instanceof ResolveStart`) so `startAngularRouterTracking` is testable with plain-object fakes, preserving design intent (same runtime semantics). + +- `packages/rum-angular/src/domain/angularRouter/provideDatadogRouter.spec.ts` — provider + subscription tests + - Reference: `packages/rum-vue/src/domain/router/vueRouter.spec.ts` + - Deviations: test fake is a minimal `events.subscribe` object (vue spec uses a real `createRouter` + `createMemoryHistory`). Reason: Angular's `Router` requires a full DI bootstrap; the provider shape is covered with a dedicated `provideDatadogRouter` suite. First line imports `@angular/compiler` to satisfy JIT requirement when `@angular/router` modules load under Karma. + +- `packages/rum-angular/test/initializeAngularPlugin.ts` — shared test helper + - Reference: `packages/rum-vue/test/initializeVuePlugin.ts` + - Deviations: identifier renames only. + +### Phase 2: View Name Algorithm + +- `packages/rum-angular/src/domain/angularRouter/startAngularView.ts` — `computeViewName()` + `startAngularRouterView()` walking `ActivatedRouteSnapshot.firstChild` chain and substituting `**` catch-all + - Reference: `packages/rum-vue/src/domain/router/startVueRouterView.ts` + - Deviations: input is a root `ActivatedRouteSnapshot` with `firstChild` chain (walked recursively) instead of Vue's flat `matched[]` array. Catch-all marker is `/**` (Angular) instead of `/:pathMatch(.*)*` (Vue Router). `substituteCatchAll` additionally strips query/fragment from the URL before splitting, matching Angular's `urlAfterRedirects` format. + +- `packages/rum-angular/src/domain/angularRouter/types.ts` — minimal `AngularActivatedRouteSnapshot` local type + - Reference: n/a (no Vue analogue — Vue imports `RouteLocationMatched` at runtime since `vue-router` is allowlisted for side-effects) + - Deviations: local type mirrors only `routeConfig.path` and `firstChild` to avoid runtime coupling to `@angular/router` in the main entry and keep tests simple. + +- `packages/rum-angular/src/domain/angularRouter/startAngularView.spec.ts` — view name tests + - Reference: `packages/rum-vue/src/domain/router/startVueRouterView.spec.ts` + - Deviations: test helper builds a snapshot tree via nested `firstChild` links (instead of a `matched[]` array). Test cases translated from `routeSyntax.examples`: static paths, dynamic `:id`, nested routes, `**` catch-all (with + without prefix params), and URL query/fragment stripping. Every `routeSyntax.concept` has coverage. + +## Validation + +**Status:** pass +**Iterations:** 2 / 5 + +### Iteration 1 — typecheck fail, lint fail, unit tests not reached + +- Typecheck: `RouterEventsEmitter.events.subscribe` typed its observer parameter as `Event` (from `@angular/router`), incompatible with the test fake's `(event: unknown) => void` subscriber under `strictFunctionTypes`. Fixed by widening the callback parameter to `unknown` and replacing the inline type assertion with an `isResolveStart` type-predicate function. +- Lint `local-rules/disallow-side-effects`: `@angular/core`, `@angular/common`, `@angular/router` are not in the repo's allowlist of side-effect-free packages. Added all three alongside the existing `vue`/`vue-router`/`react` entries in `eslint-local-rules/disallowSideEffects.js` — the canonical precedent for adding a new framework integration. +- Lint `@typescript-eslint/no-unnecessary-type-assertion`: `event as ResolveStart` became redundant once the type predicate was in place — removed. +- Lint `@typescript-eslint/no-empty-function`: fake router's `unsubscribe() {}` method replaced with `unsubscribe: noop` (imported from `@datadog/browser-core`). +- Lint `prefer-const`: changed `let root` to `const root` in the fake-event builder (only the reference is reassigned; the object literal itself is not). + +### Iteration 2 — typecheck fail, lint pass, unit tests fail + +- Unit tests: Karma load failed with `JIT compilation failed for injectable PlatformNavigation` because importing `@angular/router` in the spec file transitively pulls `@angular/common`'s partially-compiled decorators that need the JIT compiler. Added `import '@angular/compiler'` as the first import of the spec file. `@angular/compiler` was already a devDependency of `rum-angular`. + +### Iteration 3 — typecheck pass, lint pass, unit tests pass (37 / 37) + diff --git a/docs/superpowers/plans/2026-04-08-router-integration-pipeline.md b/docs/superpowers/plans/2026-04-08-router-integration-pipeline.md new file mode 100644 index 0000000000..91ded7b66f --- /dev/null +++ b/docs/superpowers/plans/2026-04-08-router-integration-pipeline.md @@ -0,0 +1,802 @@ +# Router Integration Pipeline — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build a fully automated Claude Code skill pipeline that generates draft Browser SDK router integration PRs from framework public documentation. + +**Architecture:** Six Claude Code skills (one orchestrator + five stage skills), each in its own subdirectory under `.claude/skills/` with a `SKILL.md` entry point. Each stage reads/writes markdown artifacts in `docs/integrations//`. The orchestrator chains stages sequentially with early-exit conditions. + +**Tech Stack:** Claude Code skills (markdown), Bash (git/gh CLI), WebFetch (doc fetching), existing SDK reference implementations as examples. + +**Spec:** `docs/superpowers/specs/2026-04-08-router-integration-pipeline-design.md` + +--- + +## File Structure + +```text +.claude/skills/ +├── router-pipeline/SKILL.md # Orchestrator — /router:pipeline +├── router-fetch-docs/SKILL.md # Stage 1 — /router:fetch-docs +├── router-analyze/SKILL.md # Stage 2 — /router:analyze +├── router-design/SKILL.md # Stage 3 — /router:design +├── router-generate/SKILL.md # Stage 4 — /router:generate +└── router-pr/SKILL.md # Stage 5 — /router:pr +``` + +Runtime artifacts (created per invocation): + +```text +docs/integrations// +├── 00-pipeline-input.md +├── 01-router-concepts.md +├── 02-sdk-mapping.md +├── 03-design-decisions.md +├── 04-generation-manifest.md +└── EXIT.md # Only on early exit +``` + +--- + +### Task 1: Create the orchestrator skill + +**Files:** + +- Create: `.claude/skills/router-pipeline/SKILL.md` + +- [ ] **Step 1: Create the skill directory** + +```bash +mkdir -p .claude/skills/router-pipeline +``` + +- [ ] **Step 2: Write the orchestrator skill** + +Create `.claude/skills/router-pipeline/SKILL.md`: + +```markdown +--- +name: router:pipeline +description: Fully automated pipeline that generates a draft Browser SDK router integration PR from framework public docs. Usage: /router:pipeline [...] +--- + +# Router Integration Pipeline + +You are an orchestrator that chains five stage skills to generate a complete Browser SDK router integration package and draft PR. + +## Input + +Parse the arguments: first argument is the framework name (lowercase), remaining arguments are documentation URLs. + +Example: `/router:pipeline svelte https://svelte.dev/docs/kit/routing` + +## Step 1: Initialize + +Create the artifact directory and write the input file: + +```bash +mkdir -p docs/integrations/ +``` + +Write `docs/integrations//00-pipeline-input.md`: + +```markdown +# Pipeline Input + +**Framework:** +**Documentation URLs:** +- +- +**Initiated:** +``` + +## Step 2: Invoke /router:fetch-docs + +Use the Skill tool to invoke `router:fetch-docs`. + +After completion, read `docs/integrations//01-router-concepts.md` and check the `compatible` field. + +If `compatible: false`, write `docs/integrations//EXIT.md` with: +- Stage: fetch-docs +- Reason: the incompatibility reason from 01-router-concepts.md +- Artifacts produced: 00-pipeline-input.md, 01-router-concepts.md + +Then stop and report the exit to the user. + +## Step 3: Invoke /router:analyze + +Use the Skill tool to invoke `router:analyze`. + +After completion, read `docs/integrations//02-sdk-mapping.md` and check for any concept marked `unmapped` with severity `critical`. + +If critical unmapped concepts exist, write `EXIT.md` with: +- Stage: analyze +- Reason: which critical concepts could not be mapped and why +- Artifacts produced: 00-pipeline-input.md, 01-router-concepts.md, 02-sdk-mapping.md + +Then stop and report the exit to the user. + +## Step 4: Invoke /router:design + +Use the Skill tool to invoke `router:design`. + +## Step 5: Invoke /router:generate + +Use the Skill tool to invoke `router:generate`. + +## Step 6: Invoke /router:pr + +Use the Skill tool to invoke `router:pr`. + +## Error Handling + +If any stage fails (fetch timeout, parse error, tool failure), write `EXIT.md` with: +- Stage: which stage failed +- Reason: error details +- Artifacts produced: list of files written before failure + +All artifacts written before the failure are preserved. Stop and report the failure to the user. +``` + +- [ ] **Step 3: Verify the skill is discoverable** + +```bash +head -5 .claude/skills/router-pipeline/SKILL.md +``` + +Expected: the frontmatter with `name: router:pipeline`. + +- [ ] **Step 4: Commit** + +```bash +git add .claude/skills/router-pipeline/SKILL.md +git commit -m "✨ Add router:pipeline orchestrator skill" +``` + +--- + +### Task 2: Create the fetch-docs skill (Stage 1) + +**Files:** + +- Create: `.claude/skills/router-fetch-docs/SKILL.md` + +- [ ] **Step 1: Create the skill directory and write the skill** + +```bash +mkdir -p .claude/skills/router-fetch-docs +``` + +Create `.claude/skills/router-fetch-docs/SKILL.md`: + +```markdown +--- +name: router:fetch-docs +description: "Stage 1: Fetch framework router documentation and extract structured routing concepts. Reads input from docs/integrations//00-pipeline-input.md." +--- + +# Stage 1: Fetch Router Documentation + +## Context + +You are Stage 1 of the router integration pipeline. Your job is to fetch framework router documentation, extract structured routing concepts, and assess compatibility with the Browser SDK router integration model. + +## Input + +Read `docs/integrations//00-pipeline-input.md` to get the framework name and documentation URLs. Find the `` directory by listing `docs/integrations/` and finding the most recently created subdirectory. + +## Process + +### 1. Fetch Documentation + +Use the WebFetch tool to fetch each documentation URL. If a URL fails, note it and continue with remaining URLs. If all URLs fail, write EXIT.md and stop. + +### 2. Extract Router Concepts + +Analyze the fetched documentation and produce a structured summary covering these sections. Every factual claim MUST be inline-linked to the source URL and section where you found it. + +Unsourced claims must be marked as *inferred: *. + +#### Required Sections + +**Route Definition Format** +How routes are declared: config object, file-based routing, decorators, or other. Include code examples from the docs. + +**Dynamic Segment Syntax** +What syntax the framework uses for parameterized route segments (e.g. `:id`, `[id]`, `{id}`). Include examples. + +**Catch-All / Wildcard Syntax** +What syntax the framework uses for catch-all or wildcard routes (e.g. `*`, `**`, `[...slug]`, `/:pathMatch(.*)*`). Include examples. If the framework has no catch-all syntax, state that explicitly. + +**Navigation Lifecycle Hooks** +List every navigation lifecycle event/hook the framework exposes, in the order they fire. For each, describe: +- When it fires relative to other hooks +- What data is available at that point +- Whether it can cancel/redirect navigation + +**Navigation Lifecycle Timing** +This is the most critical section. Determine: +- Where in the lifecycle do **redirects** resolve? +- Where do **data fetches** (loaders, resolvers) execute? +- Where does **component rendering** begin? + +The SDK wants to start a view **as early as possible** but: +1. **After redirects** — so the view name reflects the final destination +2. **Before data fetches** — so loader/resolver network requests are attributed to the correct view +3. **Before component rendering** — so rendering work is attributed to the correct view + +Identify the ideal hook point. If no single hook satisfies all three constraints, document the trade-off and rank the available options from best to worst, explaining what is lost with each. + +Reference how existing integrations solve this: +- Angular uses [`GuardsCheckEnd`](packages/rum-angular/src/domain/angularRouter/provideDatadogRouter.ts) (after guards/redirects, before resolvers) +- Vue uses [`afterEach`](packages/rum-vue/src/domain/router/vueRouter.ts) (after everything including render) +- React uses [`subscribe`](packages/rum-react/src/domain/reactRouter/createRouter.ts) (after state change) + +**Route Matching Model** +How the framework matches URLs to routes: nested vs flat, layout routes, named outlets/slots, parallel routes. + +**Programmatic Navigation API** +How the router exposes current route state and navigation methods. What objects/hooks are available to read the current route, its params, and matched route records. + +### 3. Compatibility Assessment + +At the end of the document, include a `## Compatibility` section with: + +- `compatible: true` or `compatible: false` +- If false, a `reason:` field explaining why + +A framework is **incompatible** if it lacks: +- A client-side route tree or route matching mechanism +- Dynamic segment parameters +- Navigation lifecycle events that can be hooked into +- Examples: Shopify Hydrogen (server-only loaders), Salesforce Lightning (proprietary component model) + +A framework is **compatible** even if: +- It uses file-based routing (as long as there's a runtime route representation) +- Some hooks fire later than ideal (trade-off is documented, not a blocker) + +## Output + +Write the result to `docs/integrations//01-router-concepts.md`. +``` + +- [ ] **Step 2: Verify the skill file** + +```bash +head -5 .claude/skills/router-fetch-docs/SKILL.md +``` + +Expected: frontmatter with `name: router:fetch-docs`. + +- [ ] **Step 3: Commit** + +```bash +git add .claude/skills/router-fetch-docs/SKILL.md +git commit -m "✨ Add router:fetch-docs skill (Stage 1)" +``` + +--- + +### Task 3: Create the analyze skill (Stage 2) + +**Files:** + +- Create: `.claude/skills/router-analyze/SKILL.md` + +- [ ] **Step 1: Create the skill directory and write the skill** + +```bash +mkdir -p .claude/skills/router-analyze +``` + +Create `.claude/skills/router-analyze/SKILL.md`: + +```markdown +--- +name: router:analyze +description: "Stage 2: Map framework router concepts to existing SDK patterns. Reads 01-router-concepts.md and reference implementations." +--- + +# Stage 2: Analyze and Map to SDK Patterns + +## Context + +You are Stage 2 of the router integration pipeline. Your job is to read the router concepts extracted in Stage 1 and map each one to the equivalent pattern in the existing Browser SDK framework integrations. + +## Input + +1. Read `docs/integrations//01-router-concepts.md` for the framework's router concepts +2. Read the reference implementations to understand the SDK contract: + - Plugin interface: `packages/rum-core/src/domain/plugins.ts` + - Public API: `packages/rum-core/src/boot/rumPublicApi.ts` + - Angular router: `packages/rum-angular/src/domain/angularRouter/` (all files) + - React router: `packages/rum-react/src/domain/reactRouter/` (all files) + - Vue router: `packages/rum-vue/src/domain/router/` (all files) + +Find the `` directory by listing `docs/integrations/`. + +## Process + +For each concept in `01-router-concepts.md`, find the closest equivalent in the reference implementations. Every mapping MUST include inline links to both: +- The framework doc source (from 01-router-concepts.md links) +- The specific file and line range in the reference implementation + +### Required Mappings + +**Navigation Event Mapping** +Which framework hook/event to subscribe to, and which reference implementation it's most similar to. Justify the choice based on the lifecycle timing analysis from Stage 1 (after redirects, before data fetches, before render). If the chosen hook involves trade-offs, document them here with links to the Stage 1 analysis. + +**View Name Computation** +How to build the `computeViewName()` function: +- How to access the matched route records after navigation +- How dynamic segments appear in the route definition (and whether they need normalization) +- How catch-all/wildcard routes should be substituted with actual path segments +- Link to the most similar existing `computeViewName` implementation and note differences + +**Wrapping Strategy** +How the integration hooks into the framework: +- Angular: [`ENVIRONMENT_INITIALIZER` provider](packages/rum-angular/src/domain/angularRouter/provideDatadogRouter.ts) +- React: [wrapper around `createRouter`](packages/rum-react/src/domain/reactRouter/createRouter.ts) and [`useRoutes` hook](packages/rum-react/src/domain/reactRouter/useRoutes.ts) +- Vue: [wrapper around `createRouter`](packages/rum-vue/src/domain/router/vueRouter.ts) + +Determine which pattern fits this framework and why. + +**Type Strategy** +Whether to define minimal local types (like Angular's [`RouteSnapshot`](packages/rum-angular/src/domain/angularRouter/types.ts)) to avoid runtime framework imports, or import types directly from the framework package. + +**Plugin Configuration** +How the plugin will be configured. All reference implementations use the same pattern: +- [`VuePluginConfiguration`](packages/rum-vue/src/domain/vuePlugin.ts) with `router?: boolean` +- `onInit` sets `trackViewsManually = true` when `router: true` + +**Peer Dependencies** +Which framework packages are required as peer dependencies, with version ranges. + +**Navigation Filtering** +How to handle: +- Failed navigations (guards blocking, cancellations) +- Duplicate navigations (same path) +- Query-only changes +- Initial navigation + +Reference the filtering logic in existing implementations: +- Vue: [lines 15-22 of vueRouter.ts](packages/rum-vue/src/domain/router/vueRouter.ts) +- React: subscribe callback in [createRouter.ts](packages/rum-react/src/domain/reactRouter/createRouter.ts) + +### Unmapped Concepts + +For any framework concept that has no SDK equivalent, create a section: + +```markdown +### Unmapped: + +**Severity:** critical | minor +**Reason:** +**Impact:** +``` + +- `critical`: the integration cannot work without this (e.g. no way to get route matches) +- `minor`: the integration works but this feature isn't supported (e.g. named outlets not tracked) + +## Output + +Write the result to `docs/integrations//02-sdk-mapping.md`. +``` + +- [ ] **Step 2: Commit** + +```bash +git add .claude/skills/router-analyze/SKILL.md +git commit -m "✨ Add router:analyze skill (Stage 2)" +``` + +--- + +### Task 4: Create the design skill (Stage 3) + +**Files:** + +- Create: `.claude/skills/router-design/SKILL.md` + +- [ ] **Step 1: Create the skill directory and write the skill** + +```bash +mkdir -p .claude/skills/router-design +``` + +Create `.claude/skills/router-design/SKILL.md`: + +```markdown +--- +name: router:design +description: "Stage 3: Produce design decisions document from router concepts and SDK mapping. Reads 01 and 02 artifacts." +--- + +# Stage 3: Design Decisions + +## Context + +You are Stage 3 of the router integration pipeline. Your job is to synthesize the router concepts (Stage 1) and SDK mapping (Stage 2) into a concrete design document that will guide code generation. + +## Input + +1. Read `docs/integrations//01-router-concepts.md` +2. Read `docs/integrations//02-sdk-mapping.md` +3. Read reference implementations for structural patterns: + - Angular entry point: `packages/rum-angular/src/entries/main.ts` + - Vue entry point: `packages/rum-vue/src/entries/main.ts` + - React entry point: `packages/rum-react/src/entries/main.ts` + - Vue package.json: `packages/rum-vue/package.json` + - Angular package.json: `packages/rum-angular/package.json` + +Find the `` directory by listing `docs/integrations/`. + +## Process + +Produce a design document with these sections. Every decision MUST link back to the concept or mapping that informed it. + +### Required Sections + +**Architecture Overview** +2-3 sentences describing the overall approach. Which reference implementation is closest and why. + +**Public API** +Exactly what the user imports and calls. Show the complete setup code example: +```typescript +// What the user writes in their app +import { ... } from '@datadog/browser-rum-' +``` + +**File Structure** +The exact file tree for `packages/rum-/` with one-line descriptions per file. Follow the convention from reference packages: +- `src/entries/main.ts` — public exports +- `src/domain/Plugin.ts` — plugin + subscriber pattern +- `src/domain/Router/` — router integration files +- `src/domain/error/` — error handling files +- `src/test/` — test helpers +- `package.json`, `tsconfig.json`, `README.md` + +**Navigation Hook Decision** +Which hook to use, with the full trade-off analysis from Stage 1 and the mapping from Stage 2. This is the most important architectural decision — make it explicit. + +**View Name Algorithm** +Pseudocode or step-by-step description of how `computeViewName()` will work for this framework. Cover: +- Normal routes +- Dynamic segments +- Nested routes +- Catch-all/wildcard routes +- Edge cases specific to this framework + +**Navigation Filtering** +How failed, duplicate, query-only, and initial navigations are handled. + +**Error Handler Integration** +How the framework's error handling mechanism will be integrated, following the pattern from reference implementations: +- Angular: [`provideDatadogErrorHandler`](packages/rum-angular/src/domain/error/provideDatadogErrorHandler.ts) +- Vue: [`addVueError`](packages/rum-vue/src/domain/error/addVueError.ts) +- React: [`ErrorBoundary`](packages/rum-react/src/domain/error/errorBoundary.ts) + +**Test Strategy** +List every test case to implement, grouped by file: +- `Plugin.spec.ts`: plugin structure, subscriber callbacks, telemetry, trackViewsManually +- `startView.spec.ts`: all view name computation cases (static, dynamic, nested, catch-all, edge cases) +- Router integration spec: navigation event handling, filtering, deduplication +- Error handler spec: error reporting, context merging + +**Trade-offs and Alternatives** +Document any decisions where multiple valid approaches existed. For each, state what was chosen, what was rejected, and why. + +## Output + +Write the result to `docs/integrations//03-design-decisions.md`. +``` + +- [ ] **Step 2: Commit** + +```bash +git add .claude/skills/router-design/SKILL.md +git commit -m "✨ Add router:design skill (Stage 3)" +``` + +--- + +### Task 5: Create the generate skill (Stage 4) + +**Files:** + +- Create: `.claude/skills/router-generate/SKILL.md` + +- [ ] **Step 1: Create the skill directory and write the skill** + +```bash +mkdir -p .claude/skills/router-generate +``` + +Create `.claude/skills/router-generate/SKILL.md`: + +```markdown +--- +name: router:generate +description: "Stage 4: Generate the complete router integration package from design artifacts and reference implementations." +--- + +# Stage 4: Generate Package + +## Context + +You are Stage 4 of the router integration pipeline. Your job is to generate a complete, working Browser SDK router integration package based on the design decisions from Stage 3 and the patterns from reference implementations. + +## Input + +1. Read `docs/integrations//01-router-concepts.md` +2. Read `docs/integrations//02-sdk-mapping.md` +3. Read `docs/integrations//03-design-decisions.md` — this is your primary guide +4. Read reference implementations for code patterns (read the actual source files, not summaries): + - The reference implementation identified as "closest" in the design doc + - Plugin: e.g. `packages/rum-vue/src/domain/vuePlugin.ts` + - Router: e.g. `packages/rum-vue/src/domain/router/` + - Error: e.g. `packages/rum-vue/src/domain/error/` + - Tests: corresponding `.spec.ts` files + - Test helper: e.g. `packages/rum-vue/src/test/initializeVuePlugin.ts` + - Package config: e.g. `packages/rum-vue/package.json`, `packages/rum-vue/tsconfig.json` + - Entry point: e.g. `packages/rum-vue/src/entries/main.ts` + +Find the `` directory by listing `docs/integrations/`. + +## Process + +### 1. Generate Each File + +Follow the file structure defined in `03-design-decisions.md`. For each file: + +1. Read the corresponding reference implementation file +2. Adapt it to the target framework using the mappings from `02-sdk-mapping.md` and decisions from `03-design-decisions.md` +3. Write the file using the Write tool + +**Code conventions** (from `AGENTS.md`): +- Use **camelCase** for all internal variables and object properties +- Conversion to snake_case happens at serialization boundary only +- Use TypeScript type narrowing over runtime assertions +- Follow existing patterns exactly — match import style, export style, comment style + +**Plugin file** (`Plugin.ts`): +- Follow the exact pattern from the reference: global state, subscriber arrays, `onRumInit`/`onRumStart` exports, `resetPlugin` for tests +- Plugin name must be the framework name in lowercase +- Configuration interface with `router?: boolean` +- `getConfigurationTelemetry` returning `{ router: !!configuration.router }` + +**Router integration files**: +- `types.ts`: minimal interface for route-related types, avoiding runtime framework imports where possible +- `startView.ts`: `computeViewName()` + `startRouterView()` calling `onRumInit` +- Integration point file: the framework-specific wrapper/provider/hook + +**Error files**: +- Follow the reference pattern: capture error + stack + timing, defer via `onRumStart` +- Include `dd_context` merging from original error + +**Test files**: +- Follow Jasmine conventions: `describe`/`it` blocks +- Use `registerCleanupTask()` for cleanup, NOT `afterEach()` +- Use the test helper for plugin initialization +- Cover every test case listed in `03-design-decisions.md` + +**package.json**: +- Follow the reference `package.json` structure exactly +- Set version to match current monorepo version (read from a reference package) +- Set correct peer dependencies from `02-sdk-mapping.md` +- Include both ESM and CJS entry points + +**tsconfig.json**: +- Copy from nearest reference package, adjust paths + +**README.md**: +- Follow the reference README structure +- Include setup instructions matching the public API from `03-design-decisions.md` + +### 2. Write Generation Manifest + +After all files are generated, write `docs/integrations//04-generation-manifest.md`: + +```markdown +# Generation Manifest + +## Generated Files + +| File | Purpose | Modeled After | Deviations | +|------|---------|---------------|------------| +| `packages/rum-/src/domain/Plugin.ts` | Plugin + subscriber pattern | [`vuePlugin.ts`](packages/rum-vue/src/domain/vuePlugin.ts) | None | +| ... | ... | ... | ... | +``` + +For each file, link to the reference file it was modeled after. If there are deviations from the reference pattern, explain why. + +## Output + +- Generated package in `packages/rum-/` +- Manifest at `docs/integrations//04-generation-manifest.md` +``` + +- [ ] **Step 2: Commit** + +```bash +git add .claude/skills/router-generate/SKILL.md +git commit -m "✨ Add router:generate skill (Stage 4)" +``` + +--- + +### Task 6: Create the PR skill (Stage 5) + +**Files:** + +- Create: `.claude/skills/router-pr/SKILL.md` + +- [ ] **Step 1: Create the skill directory and write the skill** + +```bash +mkdir -p .claude/skills/router-pr +``` + +Create `.claude/skills/router-pr/SKILL.md`: + +```markdown +--- +name: router:pr +description: "Stage 5: Create a git branch, commit artifacts and generated code, and open a draft PR on GitHub." +--- + +# Stage 5: Create Draft PR + +## Context + +You are Stage 5 of the router integration pipeline. Your job is to create a branch, commit all generated artifacts and code, and open a draft PR on GitHub. + +## Input + +1. Read `docs/integrations//03-design-decisions.md` for PR body content +2. Read `docs/integrations//04-generation-manifest.md` for the list of generated files +3. Determine the current git user name from `git config user.name` (for branch naming) + +Find the `` directory by listing `docs/integrations/`. + +## Process + +### 1. Create Branch + +```bash +# Get the current user's branch prefix +USER=$(git config user.name | tr ' ' '.' | tr '[:upper:]' '[:lower:]') +BRANCH="${USER}/-router-integration" + +git checkout -b "$BRANCH" +``` + +### 2. Commit Artifacts + +First commit: design documentation only. + +```bash +git add docs/integrations// +git commit -m "📝 Add router integration design docs + +Generated by the router integration pipeline. +Artifacts: pipeline input, router concepts, SDK mapping, design decisions, generation manifest." +``` + +### 3. Commit Generated Package + +Second commit: the generated package. + +```bash +git add packages/rum-/ +git commit -m "✨ Add router integration package + +Auto-generated from framework documentation using the router integration pipeline. +See docs/integrations// for design artifacts and decision rationale." +``` + +### 4. Push and Create Draft PR + +```bash +git push -u origin "$BRANCH" +``` + +Then use `gh pr create` with this structure: + +```bash +gh pr create --draft --title "✨ Add router integration" --body "$(cat <<'PREOF' +## Summary + +Auto-generated router integration for using the router integration pipeline. + +- Router view tracking via `startView()` on route changes +- Error handler integration +- Unit tests for view name computation and navigation filtering + +## Design Artifacts + +Full design trail at `docs/integrations//`: +- `01-router-concepts.md` — extracted routing concepts from framework docs +- `02-sdk-mapping.md` — mapping to existing SDK patterns +- `03-design-decisions.md` — architecture and API decisions +- `04-generation-manifest.md` — list of generated files with lineage + +## Key Decisions + + + +## Test plan + +- [ ] Review design artifacts in `docs/integrations//` +- [ ] Review generated code against reference implementations +- [ ] Run `yarn typecheck` to verify type correctness +- [ ] Run `yarn test:unit --spec packages/rum-/` to verify tests pass +- [ ] Manual review of `computeViewName()` edge cases + +🤖 Generated with the router integration pipeline +PREOF +)" +``` + +Replace `` with the actual framework name. Replace the key decisions placeholder with actual content from `03-design-decisions.md`. + +## Output + +Report the PR URL to the user. +``` + +- [ ] **Step 2: Commit** + +```bash +git add .claude/skills/router-pr/SKILL.md +git commit -m "✨ Add router:pr skill (Stage 5)" +``` + +--- + +### Task 7: End-to-end validation + +**Files:** + +- No new files — validation only + +- [ ] **Step 1: Verify all six skill directories exist** + +```bash +ls -d .claude/skills/router-*/ +``` + +Expected: `router-pipeline/`, `router-fetch-docs/`, `router-analyze/`, `router-design/`, `router-generate/`, `router-pr/` + +- [ ] **Step 2: Verify each skill has correct frontmatter** + +```bash +for f in .claude/skills/router-*/SKILL.md; do + echo "=== $f ===" + head -4 "$f" + echo +done +``` + +Expected: each file has `---` delimiters with `name:` and `description:` fields. Names should be: `router:pipeline`, `router:fetch-docs`, `router:analyze`, `router:design`, `router:generate`, `router:pr`. + +- [ ] **Step 3: Verify the orchestrator references all stage skills** + +```bash +grep -n "router:" .claude/skills/router-pipeline/SKILL.md +``` + +Expected: references to all five stage skill names (`router:fetch-docs`, `router:analyze`, `router:design`, `router:generate`, `router:pr`). + +- [ ] **Step 4: Commit if any uncommitted changes remain** + +```bash +git status +``` + +If there are uncommitted changes: + +```bash +git add .claude/skills/router-*/SKILL.md +git commit -m "🔧 Final adjustments to router integration pipeline skills" +``` diff --git a/docs/superpowers/specs/2026-04-08-router-integration-pipeline-design.md b/docs/superpowers/specs/2026-04-08-router-integration-pipeline-design.md new file mode 100644 index 0000000000..4f8405c689 --- /dev/null +++ b/docs/superpowers/specs/2026-04-08-router-integration-pipeline-design.md @@ -0,0 +1,227 @@ +# Router Integration Pipeline — Design Spec + +## Overview + +A fully automated Claude Code skill pipeline that generates draft Browser SDK router integration PRs from framework public documentation. The pipeline fetches framework router docs, analyzes concepts, maps them to existing SDK patterns, generates a complete package with tests, and opens a draft PR — with no human intervention until the PR is ready for review. + +## Invocation + +``` +/router:pipeline [...] +``` + +Example: +``` +/router:pipeline svelte https://svelte.dev/docs/kit/routing +``` + +## Architecture + +### Approach: Hybrid — Orchestrator + Stage Skills + +Six skills total: one orchestrator that chains five stage skills sequentially. Each stage produces a durable markdown artifact in `docs/integrations//`. Individual stage skills can be re-run independently. + +### Pipeline Flow + +``` +/router:pipeline + │ + ├─ /router:fetch-docs → 01-router-concepts.md + │ └─ EXIT if incompatible framework + │ + ├─ /router:analyze → 02-sdk-mapping.md + │ └─ EXIT if critical concepts unmapped + │ + ├─ /router:design → 03-design-decisions.md + │ + ├─ /router:generate → 04-generation-manifest.md + packages/rum-/ + │ + └─ /router:pr → Draft PR on GitHub +``` + +No human gates. Early exits produce an `EXIT.md` alongside whatever artifacts were written. + +## Artifacts + +All artifacts live in `docs/integrations//`. + +### Source Reference Convention + +Every factual claim in every artifact is inline-linked to its source — either an external doc URL or an internal file path with line range. Unsourced claims are explicitly marked as *inferred: \*. + +Example: +```markdown +Routes use [`:param` syntax](https://svelte.dev/docs/kit/routing#dynamic-parameters) +for dynamic segments, equivalent to Angular's +[`:id` in route config path](packages/rum-angular/src/domain/angularRouter/startAngularView.ts#L15-L28). +``` + +### `01-router-concepts.md` (Stage 1 output) + +Structured extraction from framework docs: + +- **Route definition format** — config object, file-based, decorators +- **Dynamic segment syntax** — `:id`, `[id]`, `{id}`, etc. +- **Catch-all/wildcard syntax** — `*`, `**`, `[...slug]`, etc. +- **Navigation lifecycle hooks** — which events fire and when +- **Navigation lifecycle timing** — specifically: where in the lifecycle do redirects resolve, and where do data fetches (loaders/resolvers) execute? The SDK wants to start a view as early as possible but after redirects and before both data fetches (loaders/resolvers) and component rendering, so that all resource loading and rendering work is attributed to the correct view. If no single hook satisfies all three constraints (after redirects, before data fetches, before render), document the trade-off and rank the options. Reference how existing integrations solve this: Angular uses [`GuardsCheckEnd`](packages/rum-angular/src/domain/angularRouter/provideDatadogRouter.ts) (after guards/redirects, before resolvers), Vue uses [`afterEach`](packages/rum-vue/src/domain/router/vueRouter.ts) (after everything), React uses [`subscribe`](packages/rum-react/src/domain/reactRouter/createRouter.ts) (after state change). +- **Route matching model** — nested vs flat, outlets, layouts +- **Programmatic navigation API** — how router exposes state +- **`compatible`** — boolean flag. `false` if the framework lacks a client-side route tree, dynamic segments, or navigation lifecycle events + +### `02-sdk-mapping.md` (Stage 2 output) + +Maps each framework concept to its SDK equivalent by reading reference implementations ([rum-angular](packages/rum-angular/), [rum-react](packages/rum-react/), [rum-vue](packages/rum-vue/)): + +- Navigation event → equivalent of Angular's [`GuardsCheckEnd`](packages/rum-angular/src/domain/angularRouter/provideDatadogRouter.ts), Vue's [`afterEach`](packages/rum-vue/src/domain/router/vueRouter.ts), React's [`subscribe`](packages/rum-react/src/domain/reactRouter/createRouter.ts) +- Dynamic segment syntax → `computeViewName` normalization strategy +- Catch-all handling → substitution approach +- Route tree shape → traversal algorithm +- Framework DI/plugin model → wrapping strategy (provider, hook, wrapper, plugin install) +- Peer dependencies needed +- Concepts marked `unmapped` if no SDK equivalent exists (with severity: `critical` or `minor`) + +### `03-design-decisions.md` (Stage 3 output) + +Design document covering: + +- Architecture decisions with rationale +- Public API surface (what the user imports and calls) +- File structure plan +- Test strategy and edge cases to cover +- Trade-offs and alternatives considered + +### `04-generation-manifest.md` (Stage 4 output) + +Listing of every generated file: + +- File path +- Purpose (one line) +- Which reference file it was modeled after (linked) +- Deviations from the reference pattern and why + +### `EXIT.md` (on early exit only) + +Written when the pipeline exits before completion: + +- Which stage failed or exited +- Reason with source links supporting the exit decision +- List of artifacts produced before exit + +## Stage Details + +### Stage 1: `/router:fetch-docs` + +**Input:** Framework name + doc URL(s) +**Output:** `01-router-concepts.md` + +Fetches and parses the provided URLs. Extracts the structured summary described above. Performs compatibility check — flags frameworks that lack standard routing concepts (no client-side route tree, no dynamic segments, no navigation events). Examples of incompatible frameworks: Shopify Hydrogen (server-only loaders), Salesforce Lightning (proprietary component model). + +### Stage 2: `/router:analyze` + +**Input:** `01-router-concepts.md` + reference implementations in `packages/rum-angular/`, `packages/rum-react/`, `packages/rum-vue/` +**Output:** `02-sdk-mapping.md` + +Reads reference implementations to understand the common contract: +- [Plugin interface](packages/rum-core/src/domain/plugins.ts) — `RumPlugin` with `onInit`/`onRumStart` +- [Public API](packages/rum-core/src/boot/rumPublicApi.ts) — `startView()` method +- `computeViewName()` implementations across all three reference packages +- Navigation event subscription patterns + +Maps each concept from Stage 1 to an SDK equivalent. Exits if critical concepts (navigation event, route tree access) have no mapping. + +### Stage 3: `/router:design` + +**Input:** `01-router-concepts.md` + `02-sdk-mapping.md` +**Output:** `03-design-decisions.md` + +Produces the design document. Synthesizes the mapping into concrete decisions: which files to create, what the public API looks like, how tests are structured. References both the framework docs and the SDK patterns that informed each decision. + +### Stage 4: `/router:generate` + +**Input:** All previous artifacts + reference implementations +**Output:** `packages/rum-/` + `04-generation-manifest.md` + +Generates the package structure: + +``` +packages/rum-/ +├── src/ +│ ├── entries/ +│ │ └── main.ts +│ ├── domain/ +│ │ ├── Plugin.ts +│ │ ├── Plugin.spec.ts +│ │ ├── Router/ +│ │ │ ├── startView.ts +│ │ │ ├── startView.spec.ts +│ │ │ ├── types.ts +│ │ │ └── .ts +│ │ └── error/ +│ │ ├── addError.ts +│ │ └── addError.spec.ts +│ └── test/ +│ └── initializePlugin.ts +├── package.json +├── tsconfig.json +└── README.md +``` + +**Code generation approach:** The agent reads reference implementations as examples (not templates). It uses `02-sdk-mapping.md` and `03-design-decisions.md` to determine the specific logic for: +- `.ts` — shaped by which navigation hook to subscribe to +- `computeViewName()` — shaped by dynamic segment and catch-all syntax +- Plugin exposure — shaped by framework DI/plugin model + +**Test generation:** Derived from two sources: +1. Common edge cases shared across all routers (from reference spec files): static paths, single/nested dynamic segments, empty/layout routes, catch-all/wildcard, duplicate navigation filtering, initial navigation +2. Framework-specific cases from `01-router-concepts.md` + +### Stage 5: `/router:pr` + +**Input:** Generated package + all artifacts +**Output:** Draft PR on GitHub + +- Creates branch: `/-router-integration` +- Two commits: + 1. `📝 Add router integration design docs` + 2. `✨ Add router integration package` +- Opens draft PR with body structured from `03-design-decisions.md`: + - Summary of what was generated + - Key design decisions + - Link to `docs/integrations//` for full artifact trail + +## Scope Boundaries + +### In scope +- Conventional SPA/SSR frontend frameworks with standard routing: route tree, dynamic params, navigation lifecycle (e.g. Svelte, Remix, Solid, Ember) +- Router integration (view tracking via `startView`) +- Error handler integration (following reference pattern) +- Unit tests for view name computation and navigation filtering + +### Out of scope +- Non-conventional frameworks (Shopify Hydrogen, Salesforce Lightning, etc.) — pipeline exits early +- E2E tests — not generated, left for human follow-up +- Publishing/release automation +- Modifications to existing packages or rum-core + +## Skill Location + +Each skill lives in its own subdirectory under `.claude/skills/` with a `SKILL.md` entry point (Claude Code requires one `SKILL.md` per subdirectory): + +```text +.claude/skills/ +├── router-pipeline/SKILL.md # /router:pipeline — orchestrator +├── router-fetch-docs/SKILL.md # /router:fetch-docs +├── router-analyze/SKILL.md # /router:analyze +├── router-design/SKILL.md # /router:design +├── router-generate/SKILL.md # /router:generate +└── router-pr/SKILL.md # /router:pr +``` + +## Stage Input Convention + +Each stage skill reads its inputs from the filesystem — no arguments are passed between skills except through the orchestrator's context: + +- **Orchestrator** receives `` and `` as arguments, writes them to `docs/integrations//00-pipeline-input.md` +- **Stage skills** read from `docs/integrations//` for previous artifacts and from `packages/rum-angular/`, `packages/rum-react/`, `packages/rum-vue/` for reference implementations +- **Framework name** is derived from the directory name diff --git a/eslint-local-rules/disallowSideEffects.js b/eslint-local-rules/disallowSideEffects.js index 890c5852df..36debfb995 100644 --- a/eslint-local-rules/disallowSideEffects.js +++ b/eslint-local-rules/disallowSideEffects.js @@ -40,9 +40,9 @@ const packagesWithoutSideEffect = new Set([ 'react-router-dom', 'vue', 'vue-router', + '@angular/common', '@angular/core', '@angular/router', - 'rxjs', ]) /** diff --git a/eslint.config.mjs b/eslint.config.mjs index 595675bdc7..57551de418 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -32,7 +32,6 @@ export default tseslint.config( 'test/apps/react-shopist-like', 'test/apps/microfrontend', 'test/apps/nextjs', - 'test/apps/angular-app', 'test/apps/vue-router-app', 'sandbox', 'coverage', diff --git a/packages/rum-angular/README.md b/packages/rum-angular/README.md index d6f53dd529..a33290dda1 100644 --- a/packages/rum-angular/README.md +++ b/packages/rum-angular/README.md @@ -1,31 +1,30 @@ # RUM Browser Monitoring - Angular integration -> **Note**: This integration is in beta. Features and configuration may change. +**Note**: This integration is in Preview. Features and configuration are subject to change. ## Overview -With the Datadog RUM Angular integration, resolve performance issues quickly in Angular applications by: +The Datadog RUM Angular integration provides framework-specific instrumentation to help you monitor and debug Angular applications. This integration adds: -- Debugging the root cause of performance bottlenecks, such as a slow server response time, render-blocking resource, or an error inside a component -- Automatically correlating web performance data with user journeys, HTTP calls, and logs -- Alerting your engineering teams when crucial web performance metrics (such as Core Web Vitals) fall below a threshold that results in a poor user experience +- **Automatic route change detection** using Angular Router +- **View name normalization** that maps dynamic route segments to their parameterized definitions (e.g. `/users/123` becomes `/users/:id`) +- **Full-stack visibility** by correlating frontend performance with backend traces and logs -Monitor your Angular applications from end-to-end by: - -- Tracking and visualizing user journeys across your entire stack -- Debugging the root cause of slow load times, which may be an issue with your Angular code, network performance, or underlying infrastructure -- Analyzing and contextualizing every user session with attributes such as user ID, email, name, and more -- Unifying full-stack monitoring in one platform for frontend and backend development teams +Combined with Datadog RUM's core capabilities, you can debug performance bottlenecks, track user journeys, monitor Core Web Vitals, and analyze every user session with context. ## Setup -Start by setting up [Datadog RUM][1] in your Angular application. If you're creating a new RUM application in the Datadog App, select Angular as the application type. If you already have an existing RUM application, you can update its type to Angular instead. Once configured, the Datadog App will provide instructions for integrating the [RUM-Angular plugin][2] with the Browser SDK. If Angular is not available as an option, follow the steps below to integrate the plugin manually. +Start by setting up [Datadog RUM][1] in your Angular application. + +This integration requires **Angular 17+**. -## Usage +## Basic usage ### 1. Initialize the Datadog RUM SDK with the Angular plugin -```typescript +In your `main.ts`: + +```ts import { datadogRum } from '@datadog/browser-rum' import { angularPlugin } from '@datadog/browser-rum-angular' @@ -37,107 +36,52 @@ datadogRum.init({ }) ``` -## Error Tracking - -To track errors that occur inside Angular components, you can either use the built-in provider or report errors manually from your own error handler. - -### `provideDatadogErrorHandler` usage - -`provideDatadogErrorHandler()` replaces Angular's default `ErrorHandler` with one that reports errors to Datadog RUM. It preserves the default `console.error` behavior. - -**Standalone setup:** - -```typescript -import { bootstrapApplication } from '@angular/platform-browser' -import { angularPlugin, provideDatadogErrorHandler } from '@datadog/browser-rum-angular' -import { datadogRum } from '@datadog/browser-rum' - -datadogRum.init({ - ... - plugins: [angularPlugin()], -}) +## Router view tracking -bootstrapApplication(AppComponent, { - providers: [provideDatadogErrorHandler()], -}) -``` +To automatically track route changes as RUM views, enable the `router` option in the plugin and register the Datadog router providers alongside `provideRouter()`. -**NgModule setup:** +### 1. Initialize the RUM SDK with router tracking enabled -```typescript -import { angularPlugin, provideDatadogErrorHandler } from '@datadog/browser-rum-angular' +```ts import { datadogRum } from '@datadog/browser-rum' +import { angularPlugin } from '@datadog/browser-rum-angular' datadogRum.init({ - ... - plugins: [angularPlugin()], -}) - -@NgModule({ - providers: [provideDatadogErrorHandler()], + applicationId: '', + clientToken: '', + site: 'datadoghq.com', + plugins: [angularPlugin({ router: true })], }) -export class AppModule {} ``` -### Reporting Angular errors from your own `ErrorHandler` +### 2. Register the Datadog router providers -If you already have a custom `ErrorHandler`, use `addAngularError` to report errors to Datadog without replacing your handler: - -```typescript -import { ErrorHandler } from '@angular/core' -import { addAngularError } from '@datadog/browser-rum-angular' - -class MyCustomErrorHandler implements ErrorHandler { - handleError(error: unknown): void { - addAngularError(error) - // ... custom logic (show toast, log to service, etc.) - } -} -``` +Add `provideDatadogRouter()` to your `bootstrapApplication` providers, next to `provideRouter()`: -## Angular Router Integration - -To track route changes with Angular's built-in router, initialize the `angularPlugin` with the `router: true` option and add `provideDatadogRouter()` to your providers. - -**Standalone setup:** - -```typescript +```ts import { bootstrapApplication } from '@angular/platform-browser' import { provideRouter } from '@angular/router' -import { angularPlugin, provideDatadogRouter } from '@datadog/browser-rum-angular' -import { datadogRum } from '@datadog/browser-rum' - -datadogRum.init({ - ... - plugins: [angularPlugin({ router: true })], -}) +import { provideDatadogRouter } from '@datadog/browser-rum-angular/angular-router' +import { AppComponent } from './app/app.component' +import { routes } from './app/routes' bootstrapApplication(AppComponent, { providers: [provideRouter(routes), provideDatadogRouter()], }) ``` -**NgModule setup:** +## Route tracking -```typescript -import { angularPlugin, provideDatadogRouter } from '@datadog/browser-rum-angular' -import { datadogRum } from '@datadog/browser-rum' - -datadogRum.init({ - ... - plugins: [angularPlugin({ router: true })], -}) - -@NgModule({ - imports: [RouterModule.forRoot(routes)], - providers: [provideDatadogRouter()], -}) -export class AppModule {} -``` +Router tracking listens for the `ResolveStart` router event (after guards and redirects, before resolvers execute), and normalizes dynamic segments into parameterized view names: -When enabled, the integration uses route patterns as view names instead of resolved URLs. For example, navigating to `/article/2` generates a view named `/article/:articleId` instead. +| Actual URL | View name | +| ---------------------- | -------------------------- | +| `/about` | `/about` | +| `/users/123` | `/users/:id` | +| `/users/123/posts/456` | `/users/:id/posts/:postId` | +| `/docs/a/b/c` | `/docs/a/b/c` | -## Go Further with Datadog Angular Integration +## Go further with Datadog Angular integration ### Traces @@ -145,7 +89,7 @@ Connect your RUM and trace data to get a complete view of your application's per ### Logs -To start forwarding your Angular application's logs to Datadog, see [JavaScript Log Collection][4]. +To forward your Angular application's logs to Datadog, see [JavaScript Logs Collection][4]. ### Metrics @@ -156,7 +100,6 @@ To generate custom metrics from your RUM application, see [Generate Metrics][5]. Need help? Contact [Datadog Support][6]. [1]: https://docs.datadoghq.com/real_user_monitoring/browser/setup/client -[2]: https://www.npmjs.com/package/@datadog/browser-rum-angular [3]: https://docs.datadoghq.com/real_user_monitoring/platform/connect_rum_and_traces/?tab=browserrum [4]: https://docs.datadoghq.com/logs/log_collection/javascript/ [5]: https://docs.datadoghq.com/real_user_monitoring/generate_metrics diff --git a/packages/rum-angular/angular-router/package.json b/packages/rum-angular/angular-router/package.json new file mode 100644 index 0000000000..15c9eb1fa7 --- /dev/null +++ b/packages/rum-angular/angular-router/package.json @@ -0,0 +1,7 @@ +{ + "name": "@datadog/browser-rum-angular/angular-router", + "private": true, + "main": "../cjs/entries/angularRouter.js", + "module": "../esm/entries/angularRouter.js", + "types": "../cjs/entries/angularRouter.d.ts" +} diff --git a/packages/rum-angular/angular-router/typedoc.json b/packages/rum-angular/angular-router/typedoc.json new file mode 100644 index 0000000000..4781794a37 --- /dev/null +++ b/packages/rum-angular/angular-router/typedoc.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "entryPoints": ["../src/entries/angularRouter.ts"] +} diff --git a/packages/rum-angular/package.json b/packages/rum-angular/package.json index c9ccd840ee..09375de42e 100644 --- a/packages/rum-angular/package.json +++ b/packages/rum-angular/package.json @@ -9,21 +9,53 @@ "cjs", "esm", "src", + "angular-router", "!src/**/*.spec.ts", "!src/**/*.specHelper.ts" ], "scripts": { "build": "node ../../scripts/build/build-package.ts --modules", - "prepack": "npm run build" + "prepack": "yarn build" }, "dependencies": { "@datadog/browser-core": "6.32.0", "@datadog/browser-rum-core": "6.32.0" }, "peerDependencies": { - "@angular/core": ">=15 <=21", - "@angular/router": ">=15 <=21", - "rxjs": ">=7" + "@angular/common": ">=17.0.0", + "@angular/core": ">=17.0.0", + "@angular/router": ">=17.0.0", + "rxjs": ">=7.0.0" + }, + "peerDependenciesMeta": { + "@angular/common": { + "optional": true + }, + "@angular/core": { + "optional": true + }, + "@angular/router": { + "optional": true + }, + "@datadog/browser-rum": { + "optional": true + }, + "@datadog/browser-rum-slim": { + "optional": true + }, + "rxjs": { + "optional": true + } + }, + "devDependencies": { + "@angular/common": "17.3.12", + "@angular/compiler": "17.3.12", + "@angular/core": "17.3.12", + "@angular/platform-browser": "17.3.12", + "@angular/router": "17.3.12", + "rxjs": "7.8.1", + "tslib": "2.8.1", + "zone.js": "0.14.10" }, "repository": { "type": "git", @@ -35,12 +67,5 @@ }, "publishConfig": { "access": "public" - }, - "devDependencies": { - "@angular/common": "19.2.5", - "@angular/compiler": "19.2.5", - "@angular/core": "19.2.5", - "@angular/router": "19.2.5", - "rxjs": "7.8.2" } } diff --git a/packages/rum-angular/src/domain/angularPlugin.spec.ts b/packages/rum-angular/src/domain/angularPlugin.spec.ts index 2f948bffa2..3becb5c3d5 100644 --- a/packages/rum-angular/src/domain/angularPlugin.spec.ts +++ b/packages/rum-angular/src/domain/angularPlugin.spec.ts @@ -1,98 +1,48 @@ import type { RumInitConfiguration, RumPublicApi } from '@datadog/browser-rum-core' import { registerCleanupTask } from '../../../core/test' -import { angularPlugin, onRumInit, onRumStart, resetAngularPlugin } from './angularPlugin' +import { onRumInit, angularPlugin, resetAngularPlugin } from './angularPlugin' const PUBLIC_API = {} as RumPublicApi const INIT_CONFIGURATION = {} as RumInitConfiguration describe('angularPlugin', () => { beforeEach(() => { - registerCleanupTask(() => { - resetAngularPlugin() - }) + registerCleanupTask(() => resetAngularPlugin()) }) - it('returns a plugin object', () => { - const plugin = angularPlugin() - expect(plugin).toEqual( - jasmine.objectContaining({ - name: 'angular', - onInit: jasmine.any(Function), - onRumStart: jasmine.any(Function), - }) - ) + it('returns a plugin object with name "angular"', () => { + expect(angularPlugin()).toEqual(jasmine.objectContaining({ name: 'angular' })) }) it('calls callbacks registered with onRumInit during onInit', () => { - const callbackSpy = jasmine.createSpy() - const pluginConfiguration = {} - onRumInit(callbackSpy) - - expect(callbackSpy).not.toHaveBeenCalled() - - angularPlugin(pluginConfiguration).onInit!({ - publicApi: PUBLIC_API, - initConfiguration: INIT_CONFIGURATION, - }) - - expect(callbackSpy).toHaveBeenCalledTimes(1) - expect(callbackSpy.calls.mostRecent().args[0]).toBe(pluginConfiguration) - expect(callbackSpy.calls.mostRecent().args[1]).toBe(PUBLIC_API) + const spy = jasmine.createSpy() + const config = {} + onRumInit(spy) + angularPlugin(config).onInit({ publicApi: PUBLIC_API, initConfiguration: INIT_CONFIGURATION }) + expect(spy).toHaveBeenCalledOnceWith(config, PUBLIC_API) }) it('calls callbacks immediately if onInit was already invoked', () => { - const callbackSpy = jasmine.createSpy() - const pluginConfiguration = {} - angularPlugin(pluginConfiguration).onInit!({ - publicApi: PUBLIC_API, - initConfiguration: INIT_CONFIGURATION, - }) - - onRumInit(callbackSpy) - - expect(callbackSpy).toHaveBeenCalledTimes(1) - expect(callbackSpy.calls.mostRecent().args[0]).toBe(pluginConfiguration) - expect(callbackSpy.calls.mostRecent().args[1]).toBe(PUBLIC_API) + const spy = jasmine.createSpy() + const config = {} + angularPlugin(config).onInit({ publicApi: PUBLIC_API, initConfiguration: INIT_CONFIGURATION }) + onRumInit(spy) + expect(spy).toHaveBeenCalledOnceWith(config, PUBLIC_API) }) - it('enforce manual view tracking when router is enabled', () => { + it('sets trackViewsManually when router is true', () => { const initConfiguration = { ...INIT_CONFIGURATION } - angularPlugin({ router: true }).onInit!({ publicApi: PUBLIC_API, initConfiguration }) - + angularPlugin({ router: true }).onInit({ publicApi: PUBLIC_API, initConfiguration }) expect(initConfiguration.trackViewsManually).toBe(true) }) - it('does not enforce manual view tracking when router is disabled', () => { + it('does not set trackViewsManually when router is false', () => { const initConfiguration = { ...INIT_CONFIGURATION } - angularPlugin({ router: false }).onInit!({ publicApi: PUBLIC_API, initConfiguration }) - + angularPlugin({ router: false }).onInit({ publicApi: PUBLIC_API, initConfiguration }) expect(initConfiguration.trackViewsManually).toBeUndefined() }) - it('returns the configuration telemetry', () => { - const pluginConfiguration = { router: true } - const plugin = angularPlugin(pluginConfiguration) - - expect(plugin.getConfigurationTelemetry!()).toEqual({ router: true }) - }) - - it('calls onRumStart subscribers during onRumStart', () => { - const callbackSpy = jasmine.createSpy() - const addErrorSpy = jasmine.createSpy() - onRumStart(callbackSpy) - - angularPlugin().onRumStart!({ addError: addErrorSpy }) - - expect(callbackSpy).toHaveBeenCalledWith(addErrorSpy) - }) - - it('calls onRumStart subscribers immediately if already started', () => { - const addErrorSpy = jasmine.createSpy() - angularPlugin().onRumStart!({ addError: addErrorSpy }) - - const callbackSpy = jasmine.createSpy() - onRumStart(callbackSpy) - - expect(callbackSpy).toHaveBeenCalledWith(addErrorSpy) + it('returns configuration telemetry', () => { + expect(angularPlugin({ router: true }).getConfigurationTelemetry()).toEqual({ router: true }) }) }) diff --git a/packages/rum-angular/src/domain/angularPlugin.ts b/packages/rum-angular/src/domain/angularPlugin.ts index 2a8163f71f..b5677dbbb7 100644 --- a/packages/rum-angular/src/domain/angularPlugin.ts +++ b/packages/rum-angular/src/domain/angularPlugin.ts @@ -1,47 +1,22 @@ import type { RumPlugin, RumPublicApi, StartRumResult } from '@datadog/browser-rum-core' -type InitSubscriber = (configuration: AngularPluginConfiguration, rumPublicApi: RumPublicApi) => void -type StartSubscriber = (addError: StartRumResult['addError']) => void - let globalPublicApi: RumPublicApi | undefined let globalConfiguration: AngularPluginConfiguration | undefined let globalAddError: StartRumResult['addError'] | undefined +type InitSubscriber = (configuration: AngularPluginConfiguration, rumPublicApi: RumPublicApi) => void +type StartSubscriber = (addError: StartRumResult['addError']) => void + const onRumInitSubscribers: InitSubscriber[] = [] const onRumStartSubscribers: StartSubscriber[] = [] -/** - * Angular plugin configuration. - * - * @category Main - */ export interface AngularPluginConfiguration { - /** - * Enable Angular Router integration. Make sure to use `provideDatadogRouter()` in your - * application providers. - */ router?: boolean } -/** - * Angular plugin constructor. - * - * @category Main - * @example - * ```ts - * import { datadogRum } from '@datadog/browser-rum' - * import { angularPlugin } from '@datadog/browser-rum-angular' - * - * datadogRum.init({ - * applicationId: '', - * clientToken: '', - * site: '', - * plugins: [angularPlugin({ router: true })], - * // ... - * }) - * ``` - */ -export function angularPlugin(configuration: AngularPluginConfiguration = {}): RumPlugin { +export type AngularPlugin = Required + +export function angularPlugin(configuration: AngularPluginConfiguration = {}): AngularPlugin { return { name: 'angular', onInit({ publicApi, initConfiguration }) { diff --git a/packages/rum-angular/src/domain/angularRouter/provideDatadogRouter.spec.ts b/packages/rum-angular/src/domain/angularRouter/provideDatadogRouter.spec.ts new file mode 100644 index 0000000000..86603a61b0 --- /dev/null +++ b/packages/rum-angular/src/domain/angularRouter/provideDatadogRouter.spec.ts @@ -0,0 +1,132 @@ +// Importing `@angular/router` at runtime pulls transitive providers (e.g. PlatformNavigation) +// that rely on Angular's JIT compiler when not AOT-linked. Load the compiler first so the +// partial-compiled decorators can be instantiated inside Karma. +import '@angular/compiler' +import { ENVIRONMENT_INITIALIZER } from '@angular/core' +import { EventType } from '@angular/router' +import { noop } from '@datadog/browser-core' +import { initializeAngularPlugin } from '../../../test/initializeAngularPlugin' +import { provideDatadogRouter, startAngularRouterTracking } from './provideDatadogRouter' + +type EventListener = (event: unknown) => void + +function createFakeRouter() { + const listeners: EventListener[] = [] + return { + events: { + subscribe(next: EventListener) { + listeners.push(next) + return { unsubscribe: noop } + }, + }, + emit(event: unknown) { + for (const listener of listeners) { + listener(event) + } + }, + } +} + +function buildResolveStart(paths: string[], urlAfterRedirects: string) { + const root: { routeConfig: { path: string } | null; firstChild: unknown } = { + routeConfig: null, + firstChild: null, + } + let current = root + for (const path of paths) { + const next = { routeConfig: { path }, firstChild: null as unknown } + current.firstChild = next + current = next as typeof root + } + return { + type: EventType.ResolveStart, + state: { root }, + urlAfterRedirects, + } +} + +describe('provideDatadogRouter', () => { + it('returns a single ENVIRONMENT_INITIALIZER multi-provider', () => { + const providers = provideDatadogRouter() as Array<{ provide: unknown; multi?: boolean; useValue?: unknown }> + expect(providers.length).toBe(1) + expect(providers[0].provide).toBe(ENVIRONMENT_INITIALIZER) + expect(providers[0].multi).toBe(true) + expect(typeof providers[0].useValue).toBe('function') + }) +}) + +describe('startAngularRouterTracking', () => { + it('calls startView on ResolveStart with the computed view name', () => { + const startViewSpy = jasmine.createSpy() + initializeAngularPlugin({ + configuration: { router: true }, + publicApi: { startView: startViewSpy }, + }) + + const router = createFakeRouter() + startAngularRouterTracking(router) + router.emit(buildResolveStart(['users', ':id'], '/users/42')) + + expect(startViewSpy).toHaveBeenCalledOnceWith('/users/:id') + }) + + it('ignores non-ResolveStart events', () => { + const startViewSpy = jasmine.createSpy() + initializeAngularPlugin({ + configuration: { router: true }, + publicApi: { startView: startViewSpy }, + }) + + const router = createFakeRouter() + startAngularRouterTracking(router) + router.emit({ type: EventType.NavigationStart, id: 1, url: '/users/42' }) + router.emit({ type: EventType.NavigationEnd, id: 1, url: '/users/42', urlAfterRedirects: '/users/42' }) + + expect(startViewSpy).not.toHaveBeenCalled() + }) + + it('tracks each navigation independently', () => { + const startViewSpy = jasmine.createSpy() + initializeAngularPlugin({ + configuration: { router: true }, + publicApi: { startView: startViewSpy }, + }) + + const router = createFakeRouter() + startAngularRouterTracking(router) + router.emit(buildResolveStart([''], '/')) + router.emit(buildResolveStart(['about'], '/about')) + + expect(startViewSpy).toHaveBeenCalledTimes(2) + expect(startViewSpy.calls.allArgs()).toEqual([['/'], ['/about']]) + }) + + it('substitutes catch-all pattern with the actual path', () => { + const startViewSpy = jasmine.createSpy() + initializeAngularPlugin({ + configuration: { router: true }, + publicApi: { startView: startViewSpy }, + }) + + const router = createFakeRouter() + startAngularRouterTracking(router) + router.emit(buildResolveStart(['**'], '/unknown/page')) + + expect(startViewSpy).toHaveBeenCalledOnceWith('/unknown/page') + }) + + it('uses urlAfterRedirects for view name after a redirect', () => { + const startViewSpy = jasmine.createSpy() + initializeAngularPlugin({ + configuration: { router: true }, + publicApi: { startView: startViewSpy }, + }) + + const router = createFakeRouter() + startAngularRouterTracking(router) + // Initial URL '/old', redirected to '/new' — the matched tree belongs to the redirect target. + router.emit(buildResolveStart(['new'], '/new')) + + expect(startViewSpy).toHaveBeenCalledOnceWith('/new') + }) +}) diff --git a/packages/rum-angular/src/domain/angularRouter/provideDatadogRouter.ts b/packages/rum-angular/src/domain/angularRouter/provideDatadogRouter.ts index 034c2b2755..a594ced21a 100644 --- a/packages/rum-angular/src/domain/angularRouter/provideDatadogRouter.ts +++ b/packages/rum-angular/src/domain/angularRouter/provideDatadogRouter.ts @@ -1,64 +1,69 @@ -import type { EnvironmentProviders } from '@angular/core' -import { ENVIRONMENT_INITIALIZER, inject, makeEnvironmentProviders } from '@angular/core' -import { GuardsCheckEnd, Router } from '@angular/router' -import { filter } from 'rxjs' -import { startAngularView } from './startAngularView' +import { ENVIRONMENT_INITIALIZER, PLATFORM_ID, inject } from '@angular/core' +import type { Provider } from '@angular/core' +import { isPlatformBrowser } from '@angular/common' +import { EventType, Router } from '@angular/router' +import type { ResolveStart } from '@angular/router' +import { startAngularRouterView } from './startAngularView' +import type { AngularActivatedRouteSnapshot } from './types' /** - * Angular provider that subscribes to Router events and starts a new RUM view - * on each GuardsCheckEnd, using the matched route template as the view name. - * - * GuardsCheckEnd fires after guards pass but before resolvers run, so data - * fetches from resolvers are correctly attributed to the new view. + * Minimal shape of the Angular `Router` service that the tracking function consumes. + * Using this internal type lets us test `startAngularRouterTracking` without spinning + * up an Angular DI container. + */ +interface RouterEventsEmitter { + events: { + subscribe: (observer: (event: unknown) => void) => unknown + } +} + +/** + * Returns the providers needed to enable Datadog RUM view tracking for Angular Router. * - * @category Main - * @example - * ```ts - * import { bootstrapApplication } from '@angular/platform-browser' - * import { provideRouter } from '@angular/router' - * import { provideDatadogRouter } from '@datadog/browser-rum-angular' + * Add this alongside your `provideRouter` call in `bootstrapApplication` (or in a module's + * providers list). It installs an `ENVIRONMENT_INITIALIZER` that subscribes to the Router's + * `ResolveStart` events and calls `startView` on the RUM public API for each committed + * navigation. * - * bootstrapApplication(AppComponent, { - * providers: [ - * provideRouter(routes), - * provideDatadogRouter(), - * ], - * }) - * ``` + * The subscription is skipped during server-side rendering (no browser platform). */ -export function provideDatadogRouter(): EnvironmentProviders { - return makeEnvironmentProviders([ +export function provideDatadogRouter(): Provider[] { + return [ { - // Needed for Angular v15 support (provideEnvironmentInitializer requires v16+) provide: ENVIRONMENT_INITIALIZER, multi: true, - useFactory: () => { - const router = inject(Router) - - return () => { - let currentPath: string | undefined - - // No unsubscribe needed as its for the full app lifecycle and because DestroyRef requires v16+ - router.events - .pipe(filter((event): event is GuardsCheckEnd => event instanceof GuardsCheckEnd)) - .subscribe((event) => { - if (!event.shouldActivate) { - return - } + useValue: initializeDatadogRouterTracking, + }, + ] +} - const url = event.urlAfterRedirects - const path = url.replace(/[?#].*$/, '') +function initializeDatadogRouterTracking() { + // On the server (@angular/ssr), window is unavailable and a fresh ResolveStart + // will fire again during client hydration — avoid subscribing twice. + if (!isPlatformBrowser(inject(PLATFORM_ID))) { + return + } + startAngularRouterTracking(inject(Router)) +} - if (path === currentPath) { - return - } - currentPath = path +/** + * Subscribes to a Router's events and triggers a RUM view start for each committed + * `ResolveStart`. Exported for testing — production code should use `provideDatadogRouter()`. + * + * ResolveStart is chosen over NavigationEnd so that resolver fetches and initial + * component render work are attributed to the new view rather than the previous one. + * Supersession can still cancel a navigation after this point; the next navigation's + * ResolveStart supersedes it. + */ +export function startAngularRouterTracking(router: RouterEventsEmitter) { + router.events.subscribe((event) => { + if (isResolveStart(event)) { + const root = event.state.root as unknown as AngularActivatedRouteSnapshot + startAngularRouterView(root, event.urlAfterRedirects) + } + }) +} - const root = event.state.root - startAngularView(root, url) - }) - } - }, - }, - ]) +function isResolveStart(event: unknown): event is ResolveStart { + return typeof event === 'object' && event !== null && (event as { type?: unknown }).type === EventType.ResolveStart } diff --git a/packages/rum-angular/src/domain/angularRouter/startAngularView.spec.ts b/packages/rum-angular/src/domain/angularRouter/startAngularView.spec.ts index 3610a25d89..7538d2b0dc 100644 --- a/packages/rum-angular/src/domain/angularRouter/startAngularView.spec.ts +++ b/packages/rum-angular/src/domain/angularRouter/startAngularView.spec.ts @@ -1,101 +1,101 @@ -import { computeViewName } from './startAngularView' -import type { RouteSnapshot } from './types' +import { display } from '@datadog/browser-core' +import { initializeAngularPlugin } from '../../../test/initializeAngularPlugin' +import { startAngularRouterView, computeViewName } from './startAngularView' +import type { AngularActivatedRouteSnapshot } from './types' -function createSnapshot( - path: string | undefined, - children: RouteSnapshot[] = [], - outlet: string = 'primary', - url: Array<{ path: string }> = [] -): RouteSnapshot { - return { - routeConfig: path !== undefined ? { path } : null, - children, - outlet, - url, +/** + * Build an ActivatedRouteSnapshot-like tree from a flat list of route paths. The first entry + * corresponds to the root's firstChild (Angular's root snapshot has no routeConfig), subsequent + * entries nest as firstChild chains. Passing `undefined` produces a node with no routeConfig. + */ +function buildSnapshot(paths: Array): AngularActivatedRouteSnapshot { + const nodes: AngularActivatedRouteSnapshot[] = paths.map((path) => ({ + routeConfig: path === undefined ? null : { path }, + firstChild: null, + })) + for (let i = 0; i < nodes.length - 1; i++) { + nodes[i].firstChild = nodes[i + 1] } + const root: AngularActivatedRouteSnapshot = { + routeConfig: null, + firstChild: nodes[0] ?? null, + } + return root } -/** - * Build a RouteSnapshot tree from a compact route string and an actual path. - * - * Route string format mirrors the React Router test convention: - * 'foo > bar > :id' represents nested routes { path: 'foo', children: [{ path: 'bar', children: [{ path: ':id' }] }] } - * - * The actualPath is used to compute the URL segments matched by '**' wildcards, - * since Angular stores the matched segments on the wildcard node's `url` property. - */ -function buildSnapshot(routePaths: string, actualPath: string): RouteSnapshot { - const paths = routePaths.split(' > ') +describe('startAngularRouterView', () => { + it('starts a new view with the computed view name', () => { + const startViewSpy = jasmine.createSpy() + initializeAngularPlugin({ + configuration: { router: true }, + publicApi: { startView: startViewSpy }, + }) - // Compute prefix consumed before the wildcard to derive matched URL segments - const wildcardIndex = paths.indexOf('**') - let wildcardUrl: Array<{ path: string }> = [] - if (wildcardIndex !== -1) { - const prefixSegments = paths.slice(0, wildcardIndex).filter((p) => p !== '') - let remaining = actualPath.startsWith('/') ? actualPath.slice(1) : actualPath - for (const prefix of prefixSegments) { - if (remaining.startsWith(prefix)) { - remaining = remaining.slice(prefix.length) - if (remaining.startsWith('/')) { - remaining = remaining.slice(1) - } - } - } - wildcardUrl = remaining ? remaining.split('/').map((s) => ({ path: s })) : [] - } + startAngularRouterView(buildSnapshot(['user', ':id']), '/user/1') - // Build snapshot chain from right to left - let current: RouteSnapshot | undefined - for (let i = paths.length - 1; i >= 0; i--) { - const path = paths[i] - const url = path === '**' ? wildcardUrl : [] - current = createSnapshot(path, current ? [current] : [], 'primary', url) - } + expect(startViewSpy).toHaveBeenCalledOnceWith('/user/:id') + }) - // Wrap in root node (null routeConfig), matching Angular's ActivatedRouteSnapshot tree - return createSnapshot(undefined, current ? [current] : []) -} + it('warns if router: true is missing from plugin config', () => { + const warnSpy = spyOn(display, 'warn') + initializeAngularPlugin({ configuration: {} }) + startAngularRouterView(buildSnapshot([]), '/') + expect(warnSpy).toHaveBeenCalledOnceWith( + '`router: true` is missing from the angular plugin configuration, the view will not be tracked.' + ) + }) +}) describe('computeViewName', () => { - it('returns / when root has no children', () => { - expect(computeViewName(createSnapshot(undefined))).toBe('/') + it('returns an empty string if the root is null', () => { + expect(computeViewName(null, '/')).toBe('') }) - it('follows primary outlet only, ignoring named outlets', () => { - const primaryChild = createSnapshot('users', [], 'primary') - const namedChild = createSnapshot('sidebar', [], 'sidebar') - const root = createSnapshot(undefined, [namedChild, primaryChild]) - expect(computeViewName(root)).toBe('/users') + it('returns "/" if the tree has no routeConfig.path segments', () => { + expect(computeViewName(buildSnapshot([undefined, '']), '/')).toBe('/') + }) + + it('ignores routes with an empty path (Angular componentless/group routes)', () => { + expect(computeViewName(buildSnapshot(['foo', '', ':id']), '/foo/1')).toBe('/foo/:id') }) // prettier-ignore - const cases = [ - // route paths, actual path, expected view name + // Angular's ActivatedRouteSnapshot exposes routeConfig.path which holds the literal Route + // pattern ('', 'users', ':id', '**', etc.). Paths are concatenated with '/' separators. + // Catch-all uses '**' (not bare '*' like React Router or '/:pathMatch(.*)*' like Vue). + const cases: Array<[string, Array, string, string]> = [ + // description, segments, path, expected // Simple paths - ['', '/', '/'], - ['users', '/users', '/users'], - ['users > :id', '/users/42', '/users/:id'], - ['users > :id > posts > :postId','/users/1/posts/2', '/users/:id/posts/:postId'], - ['users/list', '/users/list', '/users/list'], - - // Empty-path wrappers (layout routes) - [' > users > :id', '/users/42', '/users/:id'], - [' > > users', '/users', '/users'], + ['single static segment', ['foo'], '/foo', '/foo'], + ['nested static segments', ['foo', 'bar'], '/foo/bar', '/foo/bar'], + ['nested with param', ['foo', 'bar', ':p'], '/foo/bar/1', '/foo/bar/:p'], + ['root param', [':p'], '/foo', '/:p'], + ['param in single segment', ['foo/:p'], '/foo/bar', '/foo/:p'], + ['nested param', ['foo', ':p'], '/foo/bar', '/foo/:p'], + ['multiple params', [':a/:b'], '/foo/bar', '/:a/:b'], + ['nested multiple params', [':a', ':b'], '/foo/bar', '/:a/:b'], + ['param with prefix', ['foo-:a'], '/foo-1', '/foo-:a'], + ['empty root with child', ['', 'users'], '/users', '/users'], + ['empty root with param child', ['', ':id'], '/42', '/:id'], - // Lazy-loaded routes (same shape post-resolution) - ['admin > settings', '/admin/settings', '/admin/settings'], + // Catch-all routes (Angular uses '**') + ['catch-all at root', ['**'], '/foo/1', '/foo/1'], + ['catch-all at root (index)', ['**'], '/', '/'], + ['nested catch-all', ['foo', '**'], '/foo/1', '/foo/1'], + ['deeply nested catch-all', ['foo', 'bar', '**'], '/foo/bar/baz', '/foo/bar/baz'], + ['static sibling before catch-all', ['foo', '**'], '/foo/bar', '/foo/bar'], + ['param before catch-all', [':p', '**'], '/foo/baz', '/:p/baz'], + ['multiple params before catch-all', ['org', ':orgId', '**'], '/org/123/some/page', '/org/:orgId/some/page'], - // Wildcards - ['**', '/foo/bar', '/foo/bar'], - ['**', '/', '/'], - ['admin > **', '/admin/foo', '/admin/foo'], - ] as const + // URL variants + ['strips query string in catch-all', ['**'], '/foo/bar?x=1', '/foo/bar'], + ['strips fragment in catch-all', ['**'], '/foo/bar#anchor', '/foo/bar'], + ] - cases.forEach(([routePaths, path, expectedViewName]) => { - it(`returns "${expectedViewName}" for path "${path}" with routes "${routePaths}"`, () => { - const root = buildSnapshot(routePaths, path) - expect(computeViewName(root)).toBe(expectedViewName) + cases.forEach(([description, segments, path, expected]) => { + it(`returns "${expected}" for ${description}`, () => { + expect(computeViewName(buildSnapshot(segments), path)).toBe(expected) }) }) }) diff --git a/packages/rum-angular/src/domain/angularRouter/startAngularView.ts b/packages/rum-angular/src/domain/angularRouter/startAngularView.ts index 902841a346..ccb99380ef 100644 --- a/packages/rum-angular/src/domain/angularRouter/startAngularView.ts +++ b/packages/rum-angular/src/domain/angularRouter/startAngularView.ts @@ -1,43 +1,65 @@ import { display } from '@datadog/browser-core' import { onRumInit } from '../angularPlugin' -import type { RouteSnapshot } from './types' +import type { AngularActivatedRouteSnapshot } from './types' -const PRIMARY_OUTLET = 'primary' - -export function startAngularView(root: RouteSnapshot, url: string) { +export function startAngularRouterView(root: AngularActivatedRouteSnapshot, urlAfterRedirects: string) { onRumInit((configuration, rumPublicApi) => { if (!configuration.router) { - display.warn('`router: true` is missing from the react plugin configuration, the view will not be tracked.') + display.warn('`router: true` is missing from the angular plugin configuration, the view will not be tracked.') return } - - const viewName = computeViewName(root) - - rumPublicApi.startView({ name: viewName, url }) + rumPublicApi.startView(computeViewName(root, urlAfterRedirects)) }) } -export function computeViewName(root: RouteSnapshot): string { - const segments: string[] = [] - let current: RouteSnapshot | undefined = root +export function computeViewName(root: AngularActivatedRouteSnapshot | null, urlAfterRedirects: string): string { + if (!root) { + return '' + } - while (current) { - const path = current.routeConfig?.path + let viewName = '/' + let node: AngularActivatedRouteSnapshot | null = root.firstChild - if (path === '**') { - for (const segment of current.url) { - segments.push(segment.path) + while (node) { + const routePath = node.routeConfig?.path + if (routePath) { + if (!viewName.endsWith('/')) { + viewName += '/' } - break + viewName += routePath } + node = node.firstChild + } - if (path) { - segments.push(path) - } + return substituteCatchAll(viewName, urlAfterRedirects) +} - // Follow primary outlet only — named outlets (e.g. sidebar) are excluded - current = current.children.find((child) => child.outlet === PRIMARY_OUTLET) +/** + * Angular uses `**` as the catch-all wildcard path. Keeping the raw pattern as the view + * name isn't useful, it hides which path was actually visited. This function replaces + * only the catch-all segment with the corresponding portion of the actual URL, preserving + * any parameterized segments that precede it. This aligns with how the Vue and React + * integrations substitute their catch-all patterns. + * + * @example + * substituteCatchAll('/**', '/unknown/page') // => '/unknown/page' + * substituteCatchAll('/org/:orgId/**', '/org/123/some/page') // => '/org/:orgId/some/page' + */ +function substituteCatchAll(viewName: string, path: string): string { + const catchAllIndex = viewName.indexOf('/**') + if (catchAllIndex === -1) { + return viewName } - return `/${segments.join('/')}` + const prefix = viewName.substring(0, catchAllIndex) + const prefixSegmentCount = prefix === '' ? 0 : prefix.split('/').length - 1 + const pathSegments = stripQueryAndFragment(path).split('/') + const suffix = pathSegments.slice(prefixSegmentCount + 1).join('/') + + return prefix + (suffix ? `/${suffix}` : '') || '/' +} + +function stripQueryAndFragment(path: string): string { + const queryIndex = path.search(/[?#]/) + return queryIndex === -1 ? path : path.substring(0, queryIndex) } diff --git a/packages/rum-angular/src/domain/angularRouter/types.ts b/packages/rum-angular/src/domain/angularRouter/types.ts index 91834045e8..3ffdee8e4c 100644 --- a/packages/rum-angular/src/domain/angularRouter/types.ts +++ b/packages/rum-angular/src/domain/angularRouter/types.ts @@ -1,10 +1,8 @@ -/** - * Minimal interface matching the shape of Angular's ActivatedRouteSnapshot - * that we need for view name computation. Avoids importing @angular/router at runtime. - */ -export interface RouteSnapshot { +// Minimal local types mirroring the subset of @angular/router we actually consume. +// Defined here to avoid a runtime dependency on @angular/router for consumers that +// only use the main plugin entry. + +export interface AngularActivatedRouteSnapshot { routeConfig: { path?: string } | null - children: RouteSnapshot[] - outlet: string - url: Array<{ path: string }> + firstChild: AngularActivatedRouteSnapshot | null } diff --git a/packages/rum-angular/src/domain/error/addAngularError.spec.ts b/packages/rum-angular/src/domain/error/addAngularError.spec.ts deleted file mode 100644 index a5a5a3879c..0000000000 --- a/packages/rum-angular/src/domain/error/addAngularError.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { initializeAngularPlugin } from '../../../test/initializeAngularPlugin' -import { addAngularError } from './addAngularError' - -describe('addAngularError', () => { - it('delegates the error to addError', () => { - const addErrorSpy = jasmine.createSpy() - initializeAngularPlugin({ - addError: addErrorSpy, - }) - const originalError = new Error('error message') - - addAngularError(originalError) - - expect(addErrorSpy).toHaveBeenCalledOnceWith({ - error: originalError, - handlingStack: jasmine.any(String), - startClocks: jasmine.any(Object), - context: { - framework: 'angular', - }, - }) - }) - - it('should merge dd_context from the original error with angular error context', () => { - const addErrorSpy = jasmine.createSpy() - initializeAngularPlugin({ - addError: addErrorSpy, - }) - const originalError = new Error('error message') - ;(originalError as any).dd_context = { component: 'UserList', param: 42 } - - addAngularError(originalError) - - expect(addErrorSpy).toHaveBeenCalledWith( - jasmine.objectContaining({ - error: originalError, - context: { - framework: 'angular', - component: 'UserList', - param: 42, - }, - }) - ) - }) - - it('handles non-Error values', () => { - const addErrorSpy = jasmine.createSpy() - initializeAngularPlugin({ - addError: addErrorSpy, - }) - - addAngularError('string error') - - expect(addErrorSpy).toHaveBeenCalledOnceWith( - jasmine.objectContaining({ - error: 'string error', - handlingStack: jasmine.any(String), - startClocks: jasmine.any(Object), - context: { framework: 'angular' }, - }) - ) - }) -}) diff --git a/packages/rum-angular/src/domain/error/addAngularError.ts b/packages/rum-angular/src/domain/error/addAngularError.ts deleted file mode 100644 index 17c610c6db..0000000000 --- a/packages/rum-angular/src/domain/error/addAngularError.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { Context } from '@datadog/browser-core' -import { callMonitored, clocksNow, createHandlingStack } from '@datadog/browser-core' -import { onRumStart } from '../angularPlugin' - -/** - * Add an Angular error to the RUM session. - * - * This function is used internally by `provideDatadogErrorHandler()`, but can also be called - * directly to report errors caught by custom error handling logic. - * - * @category Error - * @example - * ```ts - * import { addAngularError } from '@datadog/browser-rum-angular' - * - * // In a custom ErrorHandler - * handleError(error: any) { - * addAngularError(error) - * // your own error handling... - * } - * ``` - */ -export function addAngularError(error: unknown) { - const handlingStack = createHandlingStack('angular error') - const startClocks = clocksNow() - onRumStart((addError) => { - callMonitored(() => { - addError({ - error, - handlingStack, - startClocks, - context: { - ...(typeof error === 'object' && error !== null ? (error as { dd_context?: Context }).dd_context : undefined), - framework: 'angular', - }, - }) - }) - }) -} diff --git a/packages/rum-angular/src/domain/error/index.ts b/packages/rum-angular/src/domain/error/index.ts deleted file mode 100644 index e20b7ee212..0000000000 --- a/packages/rum-angular/src/domain/error/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { addAngularError } from './addAngularError' -export { provideDatadogErrorHandler } from './provideDatadogErrorHandler' diff --git a/packages/rum-angular/src/domain/error/provideDatadogErrorHandler.spec.ts b/packages/rum-angular/src/domain/error/provideDatadogErrorHandler.spec.ts deleted file mode 100644 index 7118728b34..0000000000 --- a/packages/rum-angular/src/domain/error/provideDatadogErrorHandler.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { EnvironmentInjector } from '@angular/core' -import { ErrorHandler, Injector, createEnvironmentInjector } from '@angular/core' -import { initializeAngularPlugin } from '../../../test/initializeAngularPlugin' -import { provideDatadogErrorHandler } from './provideDatadogErrorHandler' - -function createErrorHandler(): ErrorHandler { - const injector = createEnvironmentInjector([provideDatadogErrorHandler()], Injector.NULL as EnvironmentInjector) - return injector.get(ErrorHandler) -} - -describe('provideDatadogErrorHandler', () => { - it('provides an ErrorHandler that reports errors to Datadog', () => { - const addErrorSpy = jasmine.createSpy() - initializeAngularPlugin({ addError: addErrorSpy }) - - spyOn(console, 'error') // Mute console.errors - const handler = createErrorHandler() - handler.handleError(new Error('test error')) - - expect(addErrorSpy).toHaveBeenCalled() - }) - - it('still logs the error to the console via default ErrorHandler', () => { - initializeAngularPlugin() - - const consoleErrorSpy = spyOn(console, 'error') - const handler = createErrorHandler() - handler.handleError(new Error('test error')) - - expect(consoleErrorSpy).toHaveBeenCalled() - }) -}) diff --git a/packages/rum-angular/src/domain/error/provideDatadogErrorHandler.ts b/packages/rum-angular/src/domain/error/provideDatadogErrorHandler.ts deleted file mode 100644 index a1de66c28a..0000000000 --- a/packages/rum-angular/src/domain/error/provideDatadogErrorHandler.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { EnvironmentProviders } from '@angular/core' -import { ErrorHandler, makeEnvironmentProviders } from '@angular/core' -import { addAngularError } from './addAngularError' - -// eslint-disable-next-line no-restricted-syntax -class DatadogErrorHandler extends ErrorHandler { - override handleError(error: unknown): void { - addAngularError(error) - super.handleError(error) - } -} - -/** - * Provides a Datadog-instrumented Angular ErrorHandler that reports errors to RUM. - * - * @category Error - * @example - * ```ts - * import { provideDatadogErrorHandler } from '@datadog/browser-rum-angular' - * - * bootstrapApplication(AppComponent, { - * providers: [provideDatadogErrorHandler()], - * }) - * ``` - */ -export function provideDatadogErrorHandler(): EnvironmentProviders { - return makeEnvironmentProviders([{ provide: ErrorHandler, useClass: DatadogErrorHandler }]) -} diff --git a/packages/rum-angular/src/entries/angularRouter.ts b/packages/rum-angular/src/entries/angularRouter.ts new file mode 100644 index 0000000000..98284f3a9d --- /dev/null +++ b/packages/rum-angular/src/entries/angularRouter.ts @@ -0,0 +1 @@ +export { provideDatadogRouter } from '../domain/angularRouter/provideDatadogRouter' diff --git a/packages/rum-angular/src/entries/main.ts b/packages/rum-angular/src/entries/main.ts index 2fd1b9dd44..d1f80401ca 100644 --- a/packages/rum-angular/src/entries/main.ts +++ b/packages/rum-angular/src/entries/main.ts @@ -1,4 +1,2 @@ +export type { AngularPluginConfiguration, AngularPlugin } from '../domain/angularPlugin' export { angularPlugin } from '../domain/angularPlugin' -export type { AngularPluginConfiguration } from '../domain/angularPlugin' -export { provideDatadogRouter } from '../domain/angularRouter/provideDatadogRouter' -export { addAngularError, provideDatadogErrorHandler } from '../domain/error' diff --git a/packages/rum-angular/test/initializeAngularPlugin.ts b/packages/rum-angular/test/initializeAngularPlugin.ts index 47522d437b..0edefd47fc 100644 --- a/packages/rum-angular/test/initializeAngularPlugin.ts +++ b/packages/rum-angular/test/initializeAngularPlugin.ts @@ -1,23 +1,26 @@ import type { RumInitConfiguration, RumPublicApi, StartRumResult } from '@datadog/browser-rum-core' import { noop } from '@datadog/browser-core' +import type { AngularPluginConfiguration } from '../src/domain/angularPlugin' import { angularPlugin, resetAngularPlugin } from '../src/domain/angularPlugin' import { registerCleanupTask } from '../../core/test' export function initializeAngularPlugin({ + configuration = {}, + initConfiguration = {}, + publicApi = {}, addError = noop, }: { + configuration?: AngularPluginConfiguration + initConfiguration?: Partial + publicApi?: Partial addError?: StartRumResult['addError'] } = {}) { resetAngularPlugin() - const plugin = angularPlugin() - - plugin.onInit!({ - publicApi: {} as RumPublicApi, - initConfiguration: {} as RumInitConfiguration, - }) - plugin.onRumStart!({ addError }) - - registerCleanupTask(() => { - resetAngularPlugin() + const plugin = angularPlugin(configuration) + plugin.onInit({ + publicApi: publicApi as RumPublicApi, + initConfiguration: initConfiguration as RumInitConfiguration, }) + plugin.onRumStart({ addError }) + registerCleanupTask(() => resetAngularPlugin()) } diff --git a/packages/rum-angular/tsconfig.json b/packages/rum-angular/tsconfig.json deleted file mode 100644 index c27e50b613..0000000000 --- a/packages/rum-angular/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "types": ["jasmine"], - "allowImportingTsExtensions": true, - "noEmit": true - }, - "include": ["src", "test"] -} diff --git a/packages/rum-angular/typedoc.json b/packages/rum-angular/typedoc.json new file mode 100644 index 0000000000..002b26a53c --- /dev/null +++ b/packages/rum-angular/typedoc.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "entryPoints": ["src/entries/main.ts"] +} diff --git a/scripts/build/build-test-apps.ts b/scripts/build/build-test-apps.ts index 1135abc88a..0fecce130c 100644 --- a/scripts/build/build-test-apps.ts +++ b/scripts/build/build-test-apps.ts @@ -26,7 +26,6 @@ const APPS: AppConfig[] = [ { name: 'react-shopist-like' }, { name: 'microfrontend' }, { name: 'nextjs' }, - { name: 'angular-app' }, { name: 'vue-router-app' }, // React Router apps diff --git a/test/apps/angular-app/.gitignore b/test/apps/angular-app/.gitignore deleted file mode 100644 index e20e3cfac7..0000000000 --- a/test/apps/angular-app/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -dist -node_modules -.yarn/* diff --git a/test/apps/angular-app/main.ts b/test/apps/angular-app/main.ts deleted file mode 100644 index 9e74db2d7c..0000000000 --- a/test/apps/angular-app/main.ts +++ /dev/null @@ -1,100 +0,0 @@ -import 'zone.js' -import { Component } from '@angular/core' -import { bootstrapApplication } from '@angular/platform-browser' -import { provideRouter, RouterOutlet, RouterLink, type Routes } from '@angular/router' -import { datadogRum } from '@datadog/browser-rum' -import { angularPlugin, provideDatadogRouter, provideDatadogErrorHandler } from '@datadog/browser-rum-angular' - -declare global { - interface Window { - RUM_CONFIGURATION?: any - RUM_CONTEXT?: any - } -} - -// Initialize RUM before bootstrap -datadogRum.init({ ...window.RUM_CONFIGURATION, plugins: [angularPlugin({ router: true })] }) -if (window.RUM_CONTEXT) { - datadogRum.setGlobalContext(window.RUM_CONTEXT) -} - -// Components - -@Component({ - selector: 'app-initial-route', - standalone: true, - imports: [RouterLink], - template: ` -

Initial Route

- Go to Parameterized Route
- Go to Nested Route
- Go to Wildcard Route
- Change Query Param
- - - `, -}) -class InitialRouteComponent { - throwError() { - throw new Error('angular error from component') - } - - throwErrorWithContext() { - const error = new Error('angular error with dd_context') - ;(error as any).dd_context = { component: 'InitialRoute', userId: 42 } - throw error - } -} - -@Component({ - selector: 'app-parameterized-route', - standalone: true, - template: '

Parameterized Route

', -}) -class ParameterizedRouteComponent {} - -@Component({ - selector: 'app-nested-route', - standalone: true, - template: '

Nested Route

', -}) -class NestedRouteComponent {} - -@Component({ - selector: 'app-wildcard-route', - standalone: true, - template: '

Wildcard Route

', -}) -class WildcardRouteComponent {} - -@Component({ - selector: 'app-root', - standalone: true, - imports: [RouterOutlet, RouterLink], - template: ` - - - `, -}) -class AppComponent {} - -// Routes -const nestedRoutes: Routes = [{ path: 'nested', component: NestedRouteComponent }] - -const routes: Routes = [ - { path: '', component: InitialRouteComponent }, - { path: 'parameterized/:id', component: ParameterizedRouteComponent }, - { path: 'parent', loadChildren: () => Promise.resolve(nestedRoutes) }, - { path: '**', component: WildcardRouteComponent }, -] - -// Bootstrap - dynamically create root element (E2E framework serves bare HTML) -const rootElement = document.createElement('app-root') -document.body.appendChild(rootElement) - -void bootstrapApplication(AppComponent, { - providers: [provideRouter(routes), provideDatadogRouter(), provideDatadogErrorHandler()], -}) diff --git a/test/apps/angular-app/package.json b/test/apps/angular-app/package.json deleted file mode 100644 index 7906767055..0000000000 --- a/test/apps/angular-app/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "angular-app", - "private": true, - "scripts": { - "build": "webpack" - }, - "dependencies": { - "@angular/common": "15.2.10", - "@angular/compiler": "15.2.10", - "@angular/compiler-cli": "15.2.10", - "@angular/core": "15.2.10", - "@angular/platform-browser": "15.2.10", - "@angular/router": "15.2.10", - "rxjs": "7.8.2", - "zone.js": "0.12.0" - }, - "peerDependencies": { - "@datadog/browser-rum": "*", - "@datadog/browser-rum-angular": "*" - }, - "peerDependenciesMeta": { - "@datadog/browser-rum": { - "optional": true - }, - "@datadog/browser-rum-angular": { - "optional": true - } - }, - "resolutions": { - "@datadog/browser-rum-core": "file:../../../packages/rum-core/package.tgz", - "@datadog/browser-core": "file:../../../packages/core/package.tgz", - "@datadog/browser-rum": "file:../../../packages/rum/package.tgz", - "@datadog/browser-rum-angular": "file:../../../packages/rum-angular/package.tgz", - "@datadog/browser-rum-slim": "file:../../../packages/rum-slim/package.tgz", - "@datadog/browser-worker": "file:../../../packages/worker/package.tgz" - }, - "devDependencies": { - "@ngtools/webpack": "15.2.11", - "babel-loader": "9.2.1", - "typescript": "4.9.5", - "webpack": "5.105.2", - "webpack-cli": "6.0.1" - }, - "volta": { - "extends": "../../../package.json" - } -} diff --git a/test/apps/angular-app/tsconfig.json b/test/apps/angular-app/tsconfig.json deleted file mode 100644 index be972b534e..0000000000 --- a/test/apps/angular-app/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "outDir": "./dist/", - "strict": true, - "module": "es2022", - "moduleResolution": "node", - "esModuleInterop": true, - "target": "es2022", - "lib": ["ES2022", "DOM"], - "types": [], - "experimentalDecorators": true, - "useDefineForClassFields": false - }, - "angularCompilerOptions": { - "enableI18nLegacyMessageIdFormat": false, - "strictInjectionParameters": true, - "strictInputAccessModifiers": true, - "strictTemplates": true - } -} diff --git a/test/apps/angular-app/webpack.config.js b/test/apps/angular-app/webpack.config.js deleted file mode 100644 index 165136d566..0000000000 --- a/test/apps/angular-app/webpack.config.js +++ /dev/null @@ -1,43 +0,0 @@ -const path = require('node:path') -// eslint-disable-next-line import/no-unresolved -const { AngularWebpackPlugin } = require('@ngtools/webpack') - -module.exports = { - mode: 'production', - entry: './main.ts', - target: ['web', 'es2022'], - module: { - rules: [ - { test: /\.ts$/, use: '@ngtools/webpack' }, - { - test: /\.[cm]?js$/, - include: /node_modules/, - use: { - loader: 'babel-loader', - options: { - plugins: ['@angular/compiler-cli/linker/babel'], - compact: false, - cacheDirectory: true, - }, - }, - }, - ], - }, - resolve: { - extensions: ['.ts', '.js'], - }, - plugins: [ - new AngularWebpackPlugin({ - tsconfig: path.resolve(__dirname, 'tsconfig.json'), - jitMode: false, - }), - ], - optimization: { - chunkIds: 'named', - }, - output: { - path: path.resolve(__dirname, 'dist'), - filename: 'angular-app.js', - chunkFilename: 'chunks/[name]-[contenthash]-angular-app.js', - }, -} diff --git a/test/apps/angular-app/yarn.lock b/test/apps/angular-app/yarn.lock deleted file mode 100644 index 7070b9c168..0000000000 --- a/test/apps/angular-app/yarn.lock +++ /dev/null @@ -1,2491 +0,0 @@ -# This file is generated by running "yarn install" inside your project. -# Manual changes might be lost - proceed with caution! - -__metadata: - version: 8 - cacheKey: 10c0 - -"@ampproject/remapping@npm:^2.1.0": - version: 2.3.0 - resolution: "@ampproject/remapping@npm:2.3.0" - dependencies: - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed - languageName: node - linkType: hard - -"@angular/common@npm:15.2.10": - version: 15.2.10 - resolution: "@angular/common@npm:15.2.10" - dependencies: - tslib: "npm:^2.3.0" - peerDependencies: - "@angular/core": 15.2.10 - rxjs: ^6.5.3 || ^7.4.0 - checksum: 10c0/3c473cf2d4124c1f8cb6119bc88ef891e6cbf8a00058b09c80f506742285da579480172b7b82e33909aca27ba2bd14adfcf42bdb92cbd864629fa1b40fd9b099 - languageName: node - linkType: hard - -"@angular/compiler-cli@npm:15.2.10": - version: 15.2.10 - resolution: "@angular/compiler-cli@npm:15.2.10" - dependencies: - "@babel/core": "npm:7.19.3" - "@jridgewell/sourcemap-codec": "npm:^1.4.14" - chokidar: "npm:^3.0.0" - convert-source-map: "npm:^1.5.1" - dependency-graph: "npm:^0.11.0" - magic-string: "npm:^0.27.0" - reflect-metadata: "npm:^0.1.2" - semver: "npm:^7.0.0" - tslib: "npm:^2.3.0" - yargs: "npm:^17.2.1" - peerDependencies: - "@angular/compiler": 15.2.10 - typescript: ">=4.8.2 <5.0" - bin: - ng-xi18n: bundles/src/bin/ng_xi18n.js - ngc: bundles/src/bin/ngc.js - ngcc: bundles/ngcc/main-ngcc.js - checksum: 10c0/b9bce00c178ba75edb590e34965a0b0fd1def03cd83c167a98dbb48e6b6380f053951335bd60f67fc6668bed3c7dce2da4a912bfbc96a2030e626cc992268bfd - languageName: node - linkType: hard - -"@angular/compiler@npm:15.2.10": - version: 15.2.10 - resolution: "@angular/compiler@npm:15.2.10" - dependencies: - tslib: "npm:^2.3.0" - peerDependencies: - "@angular/core": 15.2.10 - peerDependenciesMeta: - "@angular/core": - optional: true - checksum: 10c0/9b9c8d7263d647197e44c57a1852f4900e9c3c8f52e69f8bd002c63569f99580e9ec3c5fcaa0ceb3b3fd666661d129c45bcf769b32c28d07ae1ba6bd435a1038 - languageName: node - linkType: hard - -"@angular/core@npm:15.2.10": - version: 15.2.10 - resolution: "@angular/core@npm:15.2.10" - dependencies: - tslib: "npm:^2.3.0" - peerDependencies: - rxjs: ^6.5.3 || ^7.4.0 - zone.js: ~0.11.4 || ~0.12.0 || ~0.13.0 - checksum: 10c0/d0c3c4e97ac56867b4459f9299571442a4a928c2b7a2bec6018ed242dee323b7f470abf9272edb16b6e60dd3c8de11ee2596662bcd08cd87e852ecb0abbee52e - languageName: node - linkType: hard - -"@angular/platform-browser@npm:15.2.10": - version: 15.2.10 - resolution: "@angular/platform-browser@npm:15.2.10" - dependencies: - tslib: "npm:^2.3.0" - peerDependencies: - "@angular/animations": 15.2.10 - "@angular/common": 15.2.10 - "@angular/core": 15.2.10 - peerDependenciesMeta: - "@angular/animations": - optional: true - checksum: 10c0/1a24ebd9bd1685f6b518269146f0f232b863e4ad17aa0f05d5d83e8b3cd7b86e3eb780104c3f2ba4108035ac8731608348dc6a458d59542cb9ce0fec5d248728 - languageName: node - linkType: hard - -"@angular/router@npm:15.2.10": - version: 15.2.10 - resolution: "@angular/router@npm:15.2.10" - dependencies: - tslib: "npm:^2.3.0" - peerDependencies: - "@angular/common": 15.2.10 - "@angular/core": 15.2.10 - "@angular/platform-browser": 15.2.10 - rxjs: ^6.5.3 || ^7.4.0 - checksum: 10c0/8f36f3da6870725123f05ae8060d9d8c650a494555a707a460fecf9913f10692f87f12a5b458cd0ca2189887c6a643b8b87983db8b936914672246a3b55732e1 - languageName: node - linkType: hard - -"@babel/code-frame@npm:^7.18.6, @babel/code-frame@npm:^7.28.6, @babel/code-frame@npm:^7.29.0": - version: 7.29.0 - resolution: "@babel/code-frame@npm:7.29.0" - dependencies: - "@babel/helper-validator-identifier": "npm:^7.28.5" - js-tokens: "npm:^4.0.0" - picocolors: "npm:^1.1.1" - checksum: 10c0/d34cc504e7765dfb576a663d97067afb614525806b5cad1a5cc1a7183b916fec8ff57fa233585e3926fd5a9e6b31aae6df91aa81ae9775fb7a28f658d3346f0d - languageName: node - linkType: hard - -"@babel/compat-data@npm:^7.28.6": - version: 7.29.0 - resolution: "@babel/compat-data@npm:7.29.0" - checksum: 10c0/08f348554989d23aa801bf1405aa34b15e841c0d52d79da7e524285c77a5f9d298e70e11d91cc578d8e2c9542efc586d50c5f5cf8e1915b254a9dcf786913a94 - languageName: node - linkType: hard - -"@babel/core@npm:7.19.3": - version: 7.19.3 - resolution: "@babel/core@npm:7.19.3" - dependencies: - "@ampproject/remapping": "npm:^2.1.0" - "@babel/code-frame": "npm:^7.18.6" - "@babel/generator": "npm:^7.19.3" - "@babel/helper-compilation-targets": "npm:^7.19.3" - "@babel/helper-module-transforms": "npm:^7.19.0" - "@babel/helpers": "npm:^7.19.0" - "@babel/parser": "npm:^7.19.3" - "@babel/template": "npm:^7.18.10" - "@babel/traverse": "npm:^7.19.3" - "@babel/types": "npm:^7.19.3" - convert-source-map: "npm:^1.7.0" - debug: "npm:^4.1.0" - gensync: "npm:^1.0.0-beta.2" - json5: "npm:^2.2.1" - semver: "npm:^6.3.0" - checksum: 10c0/2ef6bc3c407f5aa868a3fdc5ec58bcaf98d073de5fff65c1b16b1133cd232f43b5a413a1356c4cdd37f477fb006ac9fc0d5fce8a0f2f4f5d881de0dd1f6b0b06 - languageName: node - linkType: hard - -"@babel/generator@npm:^7.19.3, @babel/generator@npm:^7.29.0": - version: 7.29.1 - resolution: "@babel/generator@npm:7.29.1" - dependencies: - "@babel/parser": "npm:^7.29.0" - "@babel/types": "npm:^7.29.0" - "@jridgewell/gen-mapping": "npm:^0.3.12" - "@jridgewell/trace-mapping": "npm:^0.3.28" - jsesc: "npm:^3.0.2" - checksum: 10c0/349086e6876258ef3fb2823030fee0f6c0eb9c3ebe35fc572e16997f8c030d765f636ddc6299edae63e760ea6658f8ee9a2edfa6d6b24c9a80c917916b973551 - languageName: node - linkType: hard - -"@babel/helper-compilation-targets@npm:^7.19.3": - version: 7.28.6 - resolution: "@babel/helper-compilation-targets@npm:7.28.6" - dependencies: - "@babel/compat-data": "npm:^7.28.6" - "@babel/helper-validator-option": "npm:^7.27.1" - browserslist: "npm:^4.24.0" - lru-cache: "npm:^5.1.1" - semver: "npm:^6.3.1" - checksum: 10c0/3fcdf3b1b857a1578e99d20508859dbd3f22f3c87b8a0f3dc540627b4be539bae7f6e61e49d931542fe5b557545347272bbdacd7f58a5c77025a18b745593a50 - languageName: node - linkType: hard - -"@babel/helper-globals@npm:^7.28.0": - version: 7.28.0 - resolution: "@babel/helper-globals@npm:7.28.0" - checksum: 10c0/5a0cd0c0e8c764b5f27f2095e4243e8af6fa145daea2b41b53c0c1414fe6ff139e3640f4e2207ae2b3d2153a1abd346f901c26c290ee7cb3881dd922d4ee9232 - languageName: node - linkType: hard - -"@babel/helper-module-imports@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/helper-module-imports@npm:7.28.6" - dependencies: - "@babel/traverse": "npm:^7.28.6" - "@babel/types": "npm:^7.28.6" - checksum: 10c0/b49d8d8f204d9dbfd5ac70c54e533e5269afb3cea966a9d976722b13e9922cc773a653405f53c89acb247d5aebdae4681d631a3ae3df77ec046b58da76eda2ac - languageName: node - linkType: hard - -"@babel/helper-module-transforms@npm:^7.19.0": - version: 7.28.6 - resolution: "@babel/helper-module-transforms@npm:7.28.6" - dependencies: - "@babel/helper-module-imports": "npm:^7.28.6" - "@babel/helper-validator-identifier": "npm:^7.28.5" - "@babel/traverse": "npm:^7.28.6" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/6f03e14fc30b287ce0b839474b5f271e72837d0cafe6b172d759184d998fbee3903a035e81e07c2c596449e504f453463d58baa65b6f40a37ded5bec74620b2b - languageName: node - linkType: hard - -"@babel/helper-string-parser@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-string-parser@npm:7.27.1" - checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 - languageName: node - linkType: hard - -"@babel/helper-validator-identifier@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/helper-validator-identifier@npm:7.28.5" - checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 - languageName: node - linkType: hard - -"@babel/helper-validator-option@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-validator-option@npm:7.27.1" - checksum: 10c0/6fec5f006eba40001a20f26b1ef5dbbda377b7b68c8ad518c05baa9af3f396e780bdfded24c4eef95d14bb7b8fd56192a6ed38d5d439b97d10efc5f1a191d148 - languageName: node - linkType: hard - -"@babel/helpers@npm:^7.19.0": - version: 7.29.2 - resolution: "@babel/helpers@npm:7.29.2" - dependencies: - "@babel/template": "npm:^7.28.6" - "@babel/types": "npm:^7.29.0" - checksum: 10c0/dab0e65b9318b2502a62c58bc0913572318595eec0482c31f0ad416b72636e6698a1d7c57cd2791d4528eb8c548bca88d338dc4d2a55a108dc1f6702f9bc5512 - languageName: node - linkType: hard - -"@babel/parser@npm:^7.19.3": - version: 7.29.2 - resolution: "@babel/parser@npm:7.29.2" - dependencies: - "@babel/types": "npm:^7.29.0" - bin: - parser: ./bin/babel-parser.js - checksum: 10c0/e5a4e69e3ac7acdde995f37cf299a68458cfe7009dff66bd0962fd04920bef287201169006af365af479c08ff216bfefbb595e331f87f6ae7283858aebbc3317 - languageName: node - linkType: hard - -"@babel/parser@npm:^7.28.6, @babel/parser@npm:^7.29.0": - version: 7.29.0 - resolution: "@babel/parser@npm:7.29.0" - dependencies: - "@babel/types": "npm:^7.29.0" - bin: - parser: ./bin/babel-parser.js - checksum: 10c0/333b2aa761264b91577a74bee86141ef733f9f9f6d4fc52548e4847dc35dfbf821f58c46832c637bfa761a6d9909d6a68f7d1ed59e17e4ffbb958dc510c17b62 - languageName: node - linkType: hard - -"@babel/template@npm:^7.18.10, @babel/template@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/template@npm:7.28.6" - dependencies: - "@babel/code-frame": "npm:^7.28.6" - "@babel/parser": "npm:^7.28.6" - "@babel/types": "npm:^7.28.6" - checksum: 10c0/66d87225ed0bc77f888181ae2d97845021838c619944877f7c4398c6748bcf611f216dfd6be74d39016af502bca876e6ce6873db3c49e4ac354c56d34d57e9f5 - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.19.3, @babel/traverse@npm:^7.28.6": - version: 7.29.0 - resolution: "@babel/traverse@npm:7.29.0" - dependencies: - "@babel/code-frame": "npm:^7.29.0" - "@babel/generator": "npm:^7.29.0" - "@babel/helper-globals": "npm:^7.28.0" - "@babel/parser": "npm:^7.29.0" - "@babel/template": "npm:^7.28.6" - "@babel/types": "npm:^7.29.0" - debug: "npm:^4.3.1" - checksum: 10c0/f63ef6e58d02a9fbf3c0e2e5f1c877da3e0bc57f91a19d2223d53e356a76859cbaf51171c9211c71816d94a0e69efa2732fd27ffc0e1bbc84b636e60932333eb - languageName: node - linkType: hard - -"@babel/types@npm:^7.19.3, @babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0": - version: 7.29.0 - resolution: "@babel/types@npm:7.29.0" - dependencies: - "@babel/helper-string-parser": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.28.5" - checksum: 10c0/23cc3466e83bcbfab8b9bd0edaafdb5d4efdb88b82b3be6728bbade5ba2f0996f84f63b1c5f7a8c0d67efded28300898a5f930b171bb40b311bca2029c4e9b4f - languageName: node - linkType: hard - -"@datadog/browser-core@file:../../../packages/core/package.tgz::locator=angular-app%40workspace%3A.": - version: 6.31.0 - resolution: "@datadog/browser-core@file:../../../packages/core/package.tgz#../../../packages/core/package.tgz::hash=6201e3&locator=angular-app%40workspace%3A." - checksum: 10c0/a019713374971a2dd35c63318cecd76e38d08348a24ca5b88abb8fca8501d17589e187bd4e68c7358f3523dd3fd08d110b307647854a620b0f3371f2c7db14ca - languageName: node - linkType: hard - -"@datadog/browser-rum-angular@file:../../../packages/rum-angular/package.tgz::locator=angular-app%40workspace%3A.": - version: 0.0.0 - resolution: "@datadog/browser-rum-angular@file:../../../packages/rum-angular/package.tgz#../../../packages/rum-angular/package.tgz::hash=004229&locator=angular-app%40workspace%3A." - dependencies: - "@datadog/browser-core": "npm:6.31.0" - "@datadog/browser-rum-core": "npm:6.31.0" - peerDependencies: - "@angular/core": ">=15 <=21" - "@angular/router": ">=15 <=21" - rxjs: ">=7" - checksum: 10c0/ec9054d51805f45cb4618bcbc6fa00049ba5d1597048aa3c354aea14a15a06275ee0ba06b3de7d77fbd0d2f009f74159a88e10f2f6b9e629eeed0a5ca30a27ae - languageName: node - linkType: hard - -"@datadog/browser-rum-core@file:../../../packages/rum-core/package.tgz::locator=angular-app%40workspace%3A.": - version: 6.31.0 - resolution: "@datadog/browser-rum-core@file:../../../packages/rum-core/package.tgz#../../../packages/rum-core/package.tgz::hash=0334b6&locator=angular-app%40workspace%3A." - dependencies: - "@datadog/browser-core": "npm:6.31.0" - checksum: 10c0/3ece55e0ca0bc0691fd91c557433c806e2871b56e3a0008c01b6e369809c9130fa384952b22ee6464d49d182d3e4e5bd66f51b6b629d4024d6e482f7e64d13c8 - languageName: node - linkType: hard - -"@datadog/browser-rum@file:../../../packages/rum/package.tgz::locator=angular-app%40workspace%3A.": - version: 6.31.0 - resolution: "@datadog/browser-rum@file:../../../packages/rum/package.tgz#../../../packages/rum/package.tgz::hash=033900&locator=angular-app%40workspace%3A." - dependencies: - "@datadog/browser-core": "npm:6.31.0" - "@datadog/browser-rum-core": "npm:6.31.0" - peerDependencies: - "@datadog/browser-logs": 6.31.0 - peerDependenciesMeta: - "@datadog/browser-logs": - optional: true - checksum: 10c0/ac0080efb7f664a5664162a64e618b885d2c4d2d06426db0ad076c855dd02cbc84f1ba6fc30913db8db74d50a511d0a0a62ba1ad7bb9e8de5c3ef0c03e3f9c56 - languageName: node - linkType: hard - -"@discoveryjs/json-ext@npm:^0.6.1": - version: 0.6.3 - resolution: "@discoveryjs/json-ext@npm:0.6.3" - checksum: 10c0/778a9f9d5c3696da3c1f9fa4186613db95a1090abbfb6c2601430645c0d0158cd5e4ba4f32c05904e2dd2747d57710f6aab22bd2f8aa3c4e8feab9b247c65d85 - languageName: node - linkType: hard - -"@gar/promise-retry@npm:^1.0.0": - version: 1.0.2 - resolution: "@gar/promise-retry@npm:1.0.2" - dependencies: - retry: "npm:^0.13.1" - checksum: 10c0/748a84fb0ab962f7867966f21dc24d1872c53c1656dd3352320fe69ad3b2043f2dfdb3be024c7636ce4904c5ba1da22d0f3558e489c3de578f5bb520f062d0fd - languageName: node - linkType: hard - -"@isaacs/fs-minipass@npm:^4.0.0": - version: 4.0.1 - resolution: "@isaacs/fs-minipass@npm:4.0.1" - dependencies: - minipass: "npm:^7.0.4" - checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 - languageName: node - linkType: hard - -"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.5": - version: 0.3.13 - resolution: "@jridgewell/gen-mapping@npm:0.3.13" - dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.0" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b - languageName: node - linkType: hard - -"@jridgewell/resolve-uri@npm:^3.1.0": - version: 3.1.2 - resolution: "@jridgewell/resolve-uri@npm:3.1.2" - checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e - languageName: node - linkType: hard - -"@jridgewell/source-map@npm:^0.3.3": - version: 0.3.11 - resolution: "@jridgewell/source-map@npm:0.3.11" - dependencies: - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.25" - checksum: 10c0/50a4fdafe0b8f655cb2877e59fe81320272eaa4ccdbe6b9b87f10614b2220399ae3e05c16137a59db1f189523b42c7f88bd097ee991dbd7bc0e01113c583e844 - languageName: node - linkType: hard - -"@jridgewell/sourcemap-codec@npm:^1.4.13, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": - version: 1.5.5 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" - checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 - languageName: node - linkType: hard - -"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28": - version: 0.3.31 - resolution: "@jridgewell/trace-mapping@npm:0.3.31" - dependencies: - "@jridgewell/resolve-uri": "npm:^3.1.0" - "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 - languageName: node - linkType: hard - -"@ngtools/webpack@npm:15.2.11": - version: 15.2.11 - resolution: "@ngtools/webpack@npm:15.2.11" - peerDependencies: - "@angular/compiler-cli": ^15.0.0 - typescript: ">=4.8.2 <5.0" - webpack: ^5.54.0 - checksum: 10c0/2f3d4f87a10f399f0de459369e7d24d5543200cb96b61ef4c98a589d5cd323a6adca293118a9dc68312428ec8dbe49f5d46e08f65ce41e42f118e1636fb96367 - languageName: node - linkType: hard - -"@npmcli/agent@npm:^4.0.0": - version: 4.0.0 - resolution: "@npmcli/agent@npm:4.0.0" - dependencies: - agent-base: "npm:^7.1.0" - http-proxy-agent: "npm:^7.0.0" - https-proxy-agent: "npm:^7.0.1" - lru-cache: "npm:^11.2.1" - socks-proxy-agent: "npm:^8.0.3" - checksum: 10c0/f7b5ce0f3dd42c3f8c6546e8433573d8049f67ef11ec22aa4704bc41483122f68bf97752e06302c455ead667af5cb753e6a09bff06632bc465c1cfd4c4b75a53 - languageName: node - linkType: hard - -"@npmcli/fs@npm:^5.0.0": - version: 5.0.0 - resolution: "@npmcli/fs@npm:5.0.0" - dependencies: - semver: "npm:^7.3.5" - checksum: 10c0/26e376d780f60ff16e874a0ac9bc3399186846baae0b6e1352286385ac134d900cc5dafaded77f38d77f86898fc923ae1cee9d7399f0275b1aa24878915d722b - languageName: node - linkType: hard - -"@types/eslint-scope@npm:^3.7.7": - version: 3.7.7 - resolution: "@types/eslint-scope@npm:3.7.7" - dependencies: - "@types/eslint": "npm:*" - "@types/estree": "npm:*" - checksum: 10c0/a0ecbdf2f03912679440550817ff77ef39a30fa8bfdacaf6372b88b1f931828aec392f52283240f0d648cf3055c5ddc564544a626bcf245f3d09fcb099ebe3cc - languageName: node - linkType: hard - -"@types/eslint@npm:*": - version: 9.6.1 - resolution: "@types/eslint@npm:9.6.1" - dependencies: - "@types/estree": "npm:*" - "@types/json-schema": "npm:*" - checksum: 10c0/69ba24fee600d1e4c5abe0df086c1a4d798abf13792d8cfab912d76817fe1a894359a1518557d21237fbaf6eda93c5ab9309143dee4c59ef54336d1b3570420e - languageName: node - linkType: hard - -"@types/estree@npm:*, @types/estree@npm:^1.0.8": - version: 1.0.8 - resolution: "@types/estree@npm:1.0.8" - checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 - languageName: node - linkType: hard - -"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.9": - version: 7.0.15 - resolution: "@types/json-schema@npm:7.0.15" - checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db - languageName: node - linkType: hard - -"@types/node@npm:*": - version: 25.4.0 - resolution: "@types/node@npm:25.4.0" - dependencies: - undici-types: "npm:~7.18.0" - checksum: 10c0/da81e8b0a3a57964b1b5f85d134bfefc1b923fd67ed41756842348a049d7915b72e8773f5598d6929b9cb8119c2427993c55d364fd93bd572a3450e58b98a60e - languageName: node - linkType: hard - -"@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.14.1": - version: 1.14.1 - resolution: "@webassemblyjs/ast@npm:1.14.1" - dependencies: - "@webassemblyjs/helper-numbers": "npm:1.13.2" - "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" - checksum: 10c0/67a59be8ed50ddd33fbb2e09daa5193ac215bf7f40a9371be9a0d9797a114d0d1196316d2f3943efdb923a3d809175e1563a3cb80c814fb8edccd1e77494972b - languageName: node - linkType: hard - -"@webassemblyjs/floating-point-hex-parser@npm:1.13.2": - version: 1.13.2 - resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.13.2" - checksum: 10c0/0e88bdb8b50507d9938be64df0867f00396b55eba9df7d3546eb5dc0ca64d62e06f8d881ec4a6153f2127d0f4c11d102b6e7d17aec2f26bb5ff95a5e60652412 - languageName: node - linkType: hard - -"@webassemblyjs/helper-api-error@npm:1.13.2": - version: 1.13.2 - resolution: "@webassemblyjs/helper-api-error@npm:1.13.2" - checksum: 10c0/31be497f996ed30aae4c08cac3cce50c8dcd5b29660383c0155fce1753804fc55d47fcba74e10141c7dd2899033164e117b3bcfcda23a6b043e4ded4f1003dfb - languageName: node - linkType: hard - -"@webassemblyjs/helper-buffer@npm:1.14.1": - version: 1.14.1 - resolution: "@webassemblyjs/helper-buffer@npm:1.14.1" - checksum: 10c0/0d54105dc373c0fe6287f1091e41e3a02e36cdc05e8cf8533cdc16c59ff05a646355415893449d3768cda588af451c274f13263300a251dc11a575bc4c9bd210 - languageName: node - linkType: hard - -"@webassemblyjs/helper-numbers@npm:1.13.2": - version: 1.13.2 - resolution: "@webassemblyjs/helper-numbers@npm:1.13.2" - dependencies: - "@webassemblyjs/floating-point-hex-parser": "npm:1.13.2" - "@webassemblyjs/helper-api-error": "npm:1.13.2" - "@xtuc/long": "npm:4.2.2" - checksum: 10c0/9c46852f31b234a8fb5a5a9d3f027bc542392a0d4de32f1a9c0075d5e8684aa073cb5929b56df565500b3f9cc0a2ab983b650314295b9bf208d1a1651bfc825a - languageName: node - linkType: hard - -"@webassemblyjs/helper-wasm-bytecode@npm:1.13.2": - version: 1.13.2 - resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.13.2" - checksum: 10c0/c4355d14f369b30cf3cbdd3acfafc7d0488e086be6d578e3c9780bd1b512932352246be96e034e2a7fcfba4f540ec813352f312bfcbbfe5bcfbf694f82ccc682 - languageName: node - linkType: hard - -"@webassemblyjs/helper-wasm-section@npm:1.14.1": - version: 1.14.1 - resolution: "@webassemblyjs/helper-wasm-section@npm:1.14.1" - dependencies: - "@webassemblyjs/ast": "npm:1.14.1" - "@webassemblyjs/helper-buffer": "npm:1.14.1" - "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" - "@webassemblyjs/wasm-gen": "npm:1.14.1" - checksum: 10c0/1f9b33731c3c6dbac3a9c483269562fa00d1b6a4e7133217f40e83e975e636fd0f8736e53abd9a47b06b66082ecc976c7384391ab0a68e12d509ea4e4b948d64 - languageName: node - linkType: hard - -"@webassemblyjs/ieee754@npm:1.13.2": - version: 1.13.2 - resolution: "@webassemblyjs/ieee754@npm:1.13.2" - dependencies: - "@xtuc/ieee754": "npm:^1.2.0" - checksum: 10c0/2e732ca78c6fbae3c9b112f4915d85caecdab285c0b337954b180460290ccd0fb00d2b1dc4bb69df3504abead5191e0d28d0d17dfd6c9d2f30acac8c4961c8a7 - languageName: node - linkType: hard - -"@webassemblyjs/leb128@npm:1.13.2": - version: 1.13.2 - resolution: "@webassemblyjs/leb128@npm:1.13.2" - dependencies: - "@xtuc/long": "npm:4.2.2" - checksum: 10c0/dad5ef9e383c8ab523ce432dfd80098384bf01c45f70eb179d594f85ce5db2f80fa8c9cba03adafd85684e6d6310f0d3969a882538975989919329ac4c984659 - languageName: node - linkType: hard - -"@webassemblyjs/utf8@npm:1.13.2": - version: 1.13.2 - resolution: "@webassemblyjs/utf8@npm:1.13.2" - checksum: 10c0/d3fac9130b0e3e5a1a7f2886124a278e9323827c87a2b971e6d0da22a2ba1278ac9f66a4f2e363ecd9fac8da42e6941b22df061a119e5c0335f81006de9ee799 - languageName: node - linkType: hard - -"@webassemblyjs/wasm-edit@npm:^1.14.1": - version: 1.14.1 - resolution: "@webassemblyjs/wasm-edit@npm:1.14.1" - dependencies: - "@webassemblyjs/ast": "npm:1.14.1" - "@webassemblyjs/helper-buffer": "npm:1.14.1" - "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" - "@webassemblyjs/helper-wasm-section": "npm:1.14.1" - "@webassemblyjs/wasm-gen": "npm:1.14.1" - "@webassemblyjs/wasm-opt": "npm:1.14.1" - "@webassemblyjs/wasm-parser": "npm:1.14.1" - "@webassemblyjs/wast-printer": "npm:1.14.1" - checksum: 10c0/5ac4781086a2ca4b320bdbfd965a209655fe8a208ca38d89197148f8597e587c9a2c94fb6bd6f1a7dbd4527c49c6844fcdc2af981f8d793a97bf63a016aa86d2 - languageName: node - linkType: hard - -"@webassemblyjs/wasm-gen@npm:1.14.1": - version: 1.14.1 - resolution: "@webassemblyjs/wasm-gen@npm:1.14.1" - dependencies: - "@webassemblyjs/ast": "npm:1.14.1" - "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" - "@webassemblyjs/ieee754": "npm:1.13.2" - "@webassemblyjs/leb128": "npm:1.13.2" - "@webassemblyjs/utf8": "npm:1.13.2" - checksum: 10c0/d678810d7f3f8fecb2e2bdadfb9afad2ec1d2bc79f59e4711ab49c81cec578371e22732d4966f59067abe5fba8e9c54923b57060a729d28d408e608beef67b10 - languageName: node - linkType: hard - -"@webassemblyjs/wasm-opt@npm:1.14.1": - version: 1.14.1 - resolution: "@webassemblyjs/wasm-opt@npm:1.14.1" - dependencies: - "@webassemblyjs/ast": "npm:1.14.1" - "@webassemblyjs/helper-buffer": "npm:1.14.1" - "@webassemblyjs/wasm-gen": "npm:1.14.1" - "@webassemblyjs/wasm-parser": "npm:1.14.1" - checksum: 10c0/515bfb15277ee99ba6b11d2232ddbf22aed32aad6d0956fe8a0a0a004a1b5a3a277a71d9a3a38365d0538ac40d1b7b7243b1a244ad6cd6dece1c1bb2eb5de7ee - languageName: node - linkType: hard - -"@webassemblyjs/wasm-parser@npm:1.14.1, @webassemblyjs/wasm-parser@npm:^1.14.1": - version: 1.14.1 - resolution: "@webassemblyjs/wasm-parser@npm:1.14.1" - dependencies: - "@webassemblyjs/ast": "npm:1.14.1" - "@webassemblyjs/helper-api-error": "npm:1.13.2" - "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" - "@webassemblyjs/ieee754": "npm:1.13.2" - "@webassemblyjs/leb128": "npm:1.13.2" - "@webassemblyjs/utf8": "npm:1.13.2" - checksum: 10c0/95427b9e5addbd0f647939bd28e3e06b8deefdbdadcf892385b5edc70091bf9b92fa5faac3fce8333554437c5d85835afef8c8a7d9d27ab6ba01ffab954db8c6 - languageName: node - linkType: hard - -"@webassemblyjs/wast-printer@npm:1.14.1": - version: 1.14.1 - resolution: "@webassemblyjs/wast-printer@npm:1.14.1" - dependencies: - "@webassemblyjs/ast": "npm:1.14.1" - "@xtuc/long": "npm:4.2.2" - checksum: 10c0/8d7768608996a052545251e896eac079c98e0401842af8dd4de78fba8d90bd505efb6c537e909cd6dae96e09db3fa2e765a6f26492553a675da56e2db51f9d24 - languageName: node - linkType: hard - -"@webpack-cli/configtest@npm:^3.0.1": - version: 3.0.1 - resolution: "@webpack-cli/configtest@npm:3.0.1" - peerDependencies: - webpack: ^5.82.0 - webpack-cli: 6.x.x - checksum: 10c0/edd24ecfc429298fe86446f7d7daedfe82d72e7f6236c81420605484fdadade5d59c6bcef3d76bd724e11d9727f74e75de183223ae62d3a568b2d54199688cbe - languageName: node - linkType: hard - -"@webpack-cli/info@npm:^3.0.1": - version: 3.0.1 - resolution: "@webpack-cli/info@npm:3.0.1" - peerDependencies: - webpack: ^5.82.0 - webpack-cli: 6.x.x - checksum: 10c0/b23b94e7dc8c93e79248f20d5f1bd0fbb7b9ba4b012803e2fdc5440b8f2ee1f3eca7f4933bbca346c8168673bf572b1858169a3cb2c17d9b8bcd833d480c2170 - languageName: node - linkType: hard - -"@webpack-cli/serve@npm:^3.0.1": - version: 3.0.1 - resolution: "@webpack-cli/serve@npm:3.0.1" - peerDependencies: - webpack: ^5.82.0 - webpack-cli: 6.x.x - peerDependenciesMeta: - webpack-dev-server: - optional: true - checksum: 10c0/65245e45bfa35e11a5b30631b99cfed0c1b39b2cc8320fa2d2a4185264535618827d349ec032c58af4201d6236cbc43bec894fcb840fdd06314611537a80e210 - languageName: node - linkType: hard - -"@xtuc/ieee754@npm:^1.2.0": - version: 1.2.0 - resolution: "@xtuc/ieee754@npm:1.2.0" - checksum: 10c0/a8565d29d135039bd99ae4b2220d3e167d22cf53f867e491ed479b3f84f895742d0097f935b19aab90265a23d5d46711e4204f14c479ae3637fbf06c4666882f - languageName: node - linkType: hard - -"@xtuc/long@npm:4.2.2": - version: 4.2.2 - resolution: "@xtuc/long@npm:4.2.2" - checksum: 10c0/8582cbc69c79ad2d31568c412129bf23d2b1210a1dfb60c82d5a1df93334da4ee51f3057051658569e2c196d8dc33bc05ae6b974a711d0d16e801e1d0647ccd1 - languageName: node - linkType: hard - -"abbrev@npm:^4.0.0": - version: 4.0.0 - resolution: "abbrev@npm:4.0.0" - checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5 - languageName: node - linkType: hard - -"acorn-import-phases@npm:^1.0.3": - version: 1.0.4 - resolution: "acorn-import-phases@npm:1.0.4" - peerDependencies: - acorn: ^8.14.0 - checksum: 10c0/338eb46fc1aed5544f628344cb9af189450b401d152ceadbf1f5746901a5d923016cd0e7740d5606062d374fdf6941c29bb515d2bd133c4f4242d5d4cd73a3c7 - languageName: node - linkType: hard - -"acorn@npm:^8.15.0": - version: 8.16.0 - resolution: "acorn@npm:8.16.0" - bin: - acorn: bin/acorn - checksum: 10c0/c9c52697227661b68d0debaf972222d4f622aa06b185824164e153438afa7b08273432ca43ea792cadb24dada1d46f6f6bb1ef8de9956979288cc1b96bf9914e - languageName: node - linkType: hard - -"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": - version: 7.1.4 - resolution: "agent-base@npm:7.1.4" - checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe - languageName: node - linkType: hard - -"ajv-formats@npm:^2.1.1": - version: 2.1.1 - resolution: "ajv-formats@npm:2.1.1" - dependencies: - ajv: "npm:^8.0.0" - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - checksum: 10c0/e43ba22e91b6a48d96224b83d260d3a3a561b42d391f8d3c6d2c1559f9aa5b253bfb306bc94bbeca1d967c014e15a6efe9a207309e95b3eaae07fcbcdc2af662 - languageName: node - linkType: hard - -"ajv-keywords@npm:^5.1.0": - version: 5.1.0 - resolution: "ajv-keywords@npm:5.1.0" - dependencies: - fast-deep-equal: "npm:^3.1.3" - peerDependencies: - ajv: ^8.8.2 - checksum: 10c0/18bec51f0171b83123ba1d8883c126e60c6f420cef885250898bf77a8d3e65e3bfb9e8564f497e30bdbe762a83e0d144a36931328616a973ee669dc74d4a9590 - languageName: node - linkType: hard - -"ajv@npm:^8.0.0, ajv@npm:^8.9.0": - version: 8.18.0 - resolution: "ajv@npm:8.18.0" - dependencies: - fast-deep-equal: "npm:^3.1.3" - fast-uri: "npm:^3.0.1" - json-schema-traverse: "npm:^1.0.0" - require-from-string: "npm:^2.0.2" - checksum: 10c0/e7517c426173513a07391be951879932bdf3348feaebd2199f5b901c20f99d60db8cd1591502d4d551dc82f594e82a05c4fe1c70139b15b8937f7afeaed9532f - languageName: node - linkType: hard - -"angular-app@workspace:.": - version: 0.0.0-use.local - resolution: "angular-app@workspace:." - dependencies: - "@angular/common": "npm:15.2.10" - "@angular/compiler": "npm:15.2.10" - "@angular/compiler-cli": "npm:15.2.10" - "@angular/core": "npm:15.2.10" - "@angular/platform-browser": "npm:15.2.10" - "@angular/router": "npm:15.2.10" - "@datadog/browser-rum": "file:../../../packages/rum/package.tgz" - "@datadog/browser-rum-angular": "file:../../../packages/rum-angular/package.tgz" - "@ngtools/webpack": "npm:15.2.11" - babel-loader: "npm:9.2.1" - rxjs: "npm:7.8.2" - typescript: "npm:4.9.5" - webpack: "npm:5.105.2" - webpack-cli: "npm:6.0.1" - zone.js: "npm:0.12.0" - languageName: unknown - linkType: soft - -"ansi-regex@npm:^5.0.1": - version: 5.0.1 - resolution: "ansi-regex@npm:5.0.1" - checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 - languageName: node - linkType: hard - -"ansi-styles@npm:^4.0.0": - version: 4.3.0 - resolution: "ansi-styles@npm:4.3.0" - dependencies: - color-convert: "npm:^2.0.1" - checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 - languageName: node - linkType: hard - -"anymatch@npm:~3.1.2": - version: 3.1.3 - resolution: "anymatch@npm:3.1.3" - dependencies: - normalize-path: "npm:^3.0.0" - picomatch: "npm:^2.0.4" - checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac - languageName: node - linkType: hard - -"babel-loader@npm:9.2.1": - version: 9.2.1 - resolution: "babel-loader@npm:9.2.1" - dependencies: - find-cache-dir: "npm:^4.0.0" - schema-utils: "npm:^4.0.0" - peerDependencies: - "@babel/core": ^7.12.0 - webpack: ">=5" - checksum: 10c0/efb82faff4c7c27e9c15bb28bf11c73200e61cf365118a9514e8d74dd489d0afc2a0d5aaa62cb4254eefc2ab631579224d95a03fd245410f28ea75e24de54ba4 - languageName: node - linkType: hard - -"balanced-match@npm:^4.0.2": - version: 4.0.4 - resolution: "balanced-match@npm:4.0.4" - checksum: 10c0/07e86102a3eb2ee2a6a1a89164f29d0dbaebd28f2ca3f5ca786f36b8b23d9e417eb3be45a4acf754f837be5ac0a2317de90d3fcb7f4f4dc95720a1f36b26a17b - languageName: node - linkType: hard - -"baseline-browser-mapping@npm:^2.9.0": - version: 2.10.0 - resolution: "baseline-browser-mapping@npm:2.10.0" - bin: - baseline-browser-mapping: dist/cli.cjs - checksum: 10c0/da9c3ec0fcd7f325226a47d2142794d41706b6e0a405718a2c15410bbdb72aacadd65738bedef558c6f1b106ed19458cb25b06f63b66df2c284799905dbbd003 - languageName: node - linkType: hard - -"binary-extensions@npm:^2.0.0": - version: 2.3.0 - resolution: "binary-extensions@npm:2.3.0" - checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5 - languageName: node - linkType: hard - -"brace-expansion@npm:^5.0.2": - version: 5.0.4 - resolution: "brace-expansion@npm:5.0.4" - dependencies: - balanced-match: "npm:^4.0.2" - checksum: 10c0/359cbcfa80b2eb914ca1f3440e92313fbfe7919ee6b274c35db55bec555aded69dac5ee78f102cec90c35f98c20fa43d10936d0cd9978158823c249257e1643a - languageName: node - linkType: hard - -"braces@npm:~3.0.2": - version: 3.0.3 - resolution: "braces@npm:3.0.3" - dependencies: - fill-range: "npm:^7.1.1" - checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 - languageName: node - linkType: hard - -"browserslist@npm:^4.24.0, browserslist@npm:^4.28.1": - version: 4.28.1 - resolution: "browserslist@npm:4.28.1" - dependencies: - baseline-browser-mapping: "npm:^2.9.0" - caniuse-lite: "npm:^1.0.30001759" - electron-to-chromium: "npm:^1.5.263" - node-releases: "npm:^2.0.27" - update-browserslist-db: "npm:^1.2.0" - bin: - browserslist: cli.js - checksum: 10c0/545a5fa9d7234e3777a7177ec1e9134bb2ba60a69e6b95683f6982b1473aad347c77c1264ccf2ac5dea609a9731fbfbda6b85782bdca70f80f86e28a402504bd - languageName: node - linkType: hard - -"buffer-from@npm:^1.0.0": - version: 1.1.2 - resolution: "buffer-from@npm:1.1.2" - checksum: 10c0/124fff9d66d691a86d3b062eff4663fe437a9d9ee4b47b1b9e97f5a5d14f6d5399345db80f796827be7c95e70a8e765dd404b7c3ff3b3324f98e9b0c8826cc34 - languageName: node - linkType: hard - -"cacache@npm:^20.0.1": - version: 20.0.3 - resolution: "cacache@npm:20.0.3" - dependencies: - "@npmcli/fs": "npm:^5.0.0" - fs-minipass: "npm:^3.0.0" - glob: "npm:^13.0.0" - lru-cache: "npm:^11.1.0" - minipass: "npm:^7.0.3" - minipass-collect: "npm:^2.0.1" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - p-map: "npm:^7.0.2" - ssri: "npm:^13.0.0" - unique-filename: "npm:^5.0.0" - checksum: 10c0/c7da1ca694d20e8f8aedabd21dc11518f809a7d2b59aa76a1fc655db5a9e62379e465c157ddd2afe34b19230808882288effa6911b2de26a088a6d5645123462 - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001759": - version: 1.0.30001777 - resolution: "caniuse-lite@npm:1.0.30001777" - checksum: 10c0/e35443fa7c470edc06e315297cca706790840e96983fff12dfe502a4b123d6e4a64b9b4e8e35fb2f5bb60c31b24fbda93d76b2f700ce183df474671236fa7a4a - languageName: node - linkType: hard - -"chokidar@npm:^3.0.0": - version: 3.6.0 - resolution: "chokidar@npm:3.6.0" - dependencies: - anymatch: "npm:~3.1.2" - braces: "npm:~3.0.2" - fsevents: "npm:~2.3.2" - glob-parent: "npm:~5.1.2" - is-binary-path: "npm:~2.1.0" - is-glob: "npm:~4.0.1" - normalize-path: "npm:~3.0.0" - readdirp: "npm:~3.6.0" - dependenciesMeta: - fsevents: - optional: true - checksum: 10c0/8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462 - languageName: node - linkType: hard - -"chownr@npm:^3.0.0": - version: 3.0.0 - resolution: "chownr@npm:3.0.0" - checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 - languageName: node - linkType: hard - -"chrome-trace-event@npm:^1.0.2": - version: 1.0.4 - resolution: "chrome-trace-event@npm:1.0.4" - checksum: 10c0/3058da7a5f4934b87cf6a90ef5fb68ebc5f7d06f143ed5a4650208e5d7acae47bc03ec844b29fbf5ba7e46e8daa6acecc878f7983a4f4bb7271593da91e61ff5 - languageName: node - linkType: hard - -"cliui@npm:^8.0.1": - version: 8.0.1 - resolution: "cliui@npm:8.0.1" - dependencies: - string-width: "npm:^4.2.0" - strip-ansi: "npm:^6.0.1" - wrap-ansi: "npm:^7.0.0" - checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 - languageName: node - linkType: hard - -"clone-deep@npm:^4.0.1": - version: 4.0.1 - resolution: "clone-deep@npm:4.0.1" - dependencies: - is-plain-object: "npm:^2.0.4" - kind-of: "npm:^6.0.2" - shallow-clone: "npm:^3.0.0" - checksum: 10c0/637753615aa24adf0f2d505947a1bb75e63964309034a1cf56ba4b1f30af155201edd38d26ffe26911adaae267a3c138b344a4947d39f5fc1b6d6108125aa758 - languageName: node - linkType: hard - -"color-convert@npm:^2.0.1": - version: 2.0.1 - resolution: "color-convert@npm:2.0.1" - dependencies: - color-name: "npm:~1.1.4" - checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 - languageName: node - linkType: hard - -"color-name@npm:~1.1.4": - version: 1.1.4 - resolution: "color-name@npm:1.1.4" - checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 - languageName: node - linkType: hard - -"colorette@npm:^2.0.14": - version: 2.0.20 - resolution: "colorette@npm:2.0.20" - checksum: 10c0/e94116ff33b0ff56f3b83b9ace895e5bf87c2a7a47b3401b8c3f3226e050d5ef76cf4072fb3325f9dc24d1698f9b730baf4e05eeaf861d74a1883073f4c98a40 - languageName: node - linkType: hard - -"commander@npm:^12.1.0": - version: 12.1.0 - resolution: "commander@npm:12.1.0" - checksum: 10c0/6e1996680c083b3b897bfc1cfe1c58dfbcd9842fd43e1aaf8a795fbc237f65efcc860a3ef457b318e73f29a4f4a28f6403c3d653d021d960e4632dd45bde54a9 - languageName: node - linkType: hard - -"commander@npm:^2.20.0": - version: 2.20.3 - resolution: "commander@npm:2.20.3" - checksum: 10c0/74c781a5248c2402a0a3e966a0a2bba3c054aad144f5c023364be83265e796b20565aa9feff624132ff629aa64e16999fa40a743c10c12f7c61e96a794b99288 - languageName: node - linkType: hard - -"common-path-prefix@npm:^3.0.0": - version: 3.0.0 - resolution: "common-path-prefix@npm:3.0.0" - checksum: 10c0/c4a74294e1b1570f4a8ab435285d185a03976c323caa16359053e749db4fde44e3e6586c29cd051100335e11895767cbbd27ea389108e327d62f38daf4548fdb - languageName: node - linkType: hard - -"convert-source-map@npm:^1.5.1, convert-source-map@npm:^1.7.0": - version: 1.9.0 - resolution: "convert-source-map@npm:1.9.0" - checksum: 10c0/281da55454bf8126cbc6625385928c43479f2060984180c42f3a86c8b8c12720a24eac260624a7d1e090004028d2dee78602330578ceec1a08e27cb8bb0a8a5b - languageName: node - linkType: hard - -"cross-spawn@npm:^7.0.3": - version: 7.0.6 - resolution: "cross-spawn@npm:7.0.6" - dependencies: - path-key: "npm:^3.1.0" - shebang-command: "npm:^2.0.0" - which: "npm:^2.0.1" - checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 - languageName: node - linkType: hard - -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.4": - version: 4.4.3 - resolution: "debug@npm:4.4.3" - dependencies: - ms: "npm:^2.1.3" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 - languageName: node - linkType: hard - -"dependency-graph@npm:^0.11.0": - version: 0.11.0 - resolution: "dependency-graph@npm:0.11.0" - checksum: 10c0/9e6968d1534fdb502f7f3a25a3819b499f9d60f8389193950ed0b4d1618f1341b36b5d039f2cee256cfe10c9e8198ace16b271e370df06a93fac206e81602e7c - languageName: node - linkType: hard - -"electron-to-chromium@npm:^1.5.263": - version: 1.5.307 - resolution: "electron-to-chromium@npm:1.5.307" - checksum: 10c0/eb773a28af0dd7b3717b9bc2b31f332bcb42b43019866e039276db75c8c14063f96e29d19bea47231b4335a319d8518997b2d577dec6b5b237b768c7afdc5588 - languageName: node - linkType: hard - -"emoji-regex@npm:^8.0.0": - version: 8.0.0 - resolution: "emoji-regex@npm:8.0.0" - checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 - languageName: node - linkType: hard - -"enhanced-resolve@npm:^5.19.0": - version: 5.20.0 - resolution: "enhanced-resolve@npm:5.20.0" - dependencies: - graceful-fs: "npm:^4.2.4" - tapable: "npm:^2.3.0" - checksum: 10c0/4ed5f38406fc9ad74c58a3d63b8215862243ab0ed6b0efc51ccdb72cdcedd3ac8638abe298680b279d7a83c3cb140e5eea7a5f8bd99696c74588f07ad89a95a7 - languageName: node - linkType: hard - -"env-paths@npm:^2.2.0": - version: 2.2.1 - resolution: "env-paths@npm:2.2.1" - checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 - languageName: node - linkType: hard - -"envinfo@npm:^7.14.0": - version: 7.21.0 - resolution: "envinfo@npm:7.21.0" - bin: - envinfo: dist/cli.js - checksum: 10c0/4170127ca72dbf85be2c114f85558bd08178e8a43b394951ba9fd72d067c6fea3374df45a7b040e39e4e7b30bdd268e5bdf8661d99ae28302c2a88dedb41b5e6 - languageName: node - linkType: hard - -"es-module-lexer@npm:^2.0.0": - version: 2.0.0 - resolution: "es-module-lexer@npm:2.0.0" - checksum: 10c0/ae78dbbd43035a4b972c46cfb6877e374ea290adfc62bc2f5a083fea242c0b2baaab25c5886af86be55f092f4a326741cb94334cd3c478c383fdc8a9ec5ff817 - languageName: node - linkType: hard - -"escalade@npm:^3.1.1, escalade@npm:^3.2.0": - version: 3.2.0 - resolution: "escalade@npm:3.2.0" - checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 - languageName: node - linkType: hard - -"eslint-scope@npm:5.1.1": - version: 5.1.1 - resolution: "eslint-scope@npm:5.1.1" - dependencies: - esrecurse: "npm:^4.3.0" - estraverse: "npm:^4.1.1" - checksum: 10c0/d30ef9dc1c1cbdece34db1539a4933fe3f9b14e1ffb27ecc85987902ee663ad7c9473bbd49a9a03195a373741e62e2f807c4938992e019b511993d163450e70a - languageName: node - linkType: hard - -"esrecurse@npm:^4.3.0": - version: 4.3.0 - resolution: "esrecurse@npm:4.3.0" - dependencies: - estraverse: "npm:^5.2.0" - checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 - languageName: node - linkType: hard - -"estraverse@npm:^4.1.1": - version: 4.3.0 - resolution: "estraverse@npm:4.3.0" - checksum: 10c0/9cb46463ef8a8a4905d3708a652d60122a0c20bb58dec7e0e12ab0e7235123d74214fc0141d743c381813e1b992767e2708194f6f6e0f9fd00c1b4e0887b8b6d - languageName: node - linkType: hard - -"estraverse@npm:^5.2.0": - version: 5.3.0 - resolution: "estraverse@npm:5.3.0" - checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 - languageName: node - linkType: hard - -"events@npm:^3.2.0": - version: 3.3.0 - resolution: "events@npm:3.3.0" - checksum: 10c0/d6b6f2adbccbcda74ddbab52ed07db727ef52e31a61ed26db9feb7dc62af7fc8e060defa65e5f8af9449b86b52cc1a1f6a79f2eafcf4e62add2b7a1fa4a432f6 - languageName: node - linkType: hard - -"exponential-backoff@npm:^3.1.1": - version: 3.1.3 - resolution: "exponential-backoff@npm:3.1.3" - checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 - languageName: node - linkType: hard - -"fast-deep-equal@npm:^3.1.3": - version: 3.1.3 - resolution: "fast-deep-equal@npm:3.1.3" - checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 - languageName: node - linkType: hard - -"fast-uri@npm:^3.0.1": - version: 3.1.0 - resolution: "fast-uri@npm:3.1.0" - checksum: 10c0/44364adca566f70f40d1e9b772c923138d47efeac2ae9732a872baafd77061f26b097ba2f68f0892885ad177becd065520412b8ffeec34b16c99433c5b9e2de7 - languageName: node - linkType: hard - -"fastest-levenshtein@npm:^1.0.12": - version: 1.0.16 - resolution: "fastest-levenshtein@npm:1.0.16" - checksum: 10c0/7e3d8ae812a7f4fdf8cad18e9cde436a39addf266a5986f653ea0d81e0de0900f50c0f27c6d5aff3f686bcb48acbd45be115ae2216f36a6a13a7dbbf5cad878b - languageName: node - linkType: hard - -"fdir@npm:^6.5.0": - version: 6.5.0 - resolution: "fdir@npm:6.5.0" - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f - languageName: node - linkType: hard - -"fill-range@npm:^7.1.1": - version: 7.1.1 - resolution: "fill-range@npm:7.1.1" - dependencies: - to-regex-range: "npm:^5.0.1" - checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 - languageName: node - linkType: hard - -"find-cache-dir@npm:^4.0.0": - version: 4.0.0 - resolution: "find-cache-dir@npm:4.0.0" - dependencies: - common-path-prefix: "npm:^3.0.0" - pkg-dir: "npm:^7.0.0" - checksum: 10c0/0faa7956974726c8769671de696d24c643ca1e5b8f7a2401283caa9e07a5da093293e0a0f4bd18c920ec981d2ef945c7f5b946cde268dfc9077d833ad0293cff - languageName: node - linkType: hard - -"find-up@npm:^4.0.0": - version: 4.1.0 - resolution: "find-up@npm:4.1.0" - dependencies: - locate-path: "npm:^5.0.0" - path-exists: "npm:^4.0.0" - checksum: 10c0/0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1 - languageName: node - linkType: hard - -"find-up@npm:^6.3.0": - version: 6.3.0 - resolution: "find-up@npm:6.3.0" - dependencies: - locate-path: "npm:^7.1.0" - path-exists: "npm:^5.0.0" - checksum: 10c0/07e0314362d316b2b13f7f11ea4692d5191e718ca3f7264110127520f3347996349bf9e16805abae3e196805814bc66ef4bff2b8904dc4a6476085fc9b0eba07 - languageName: node - linkType: hard - -"flat@npm:^5.0.2": - version: 5.0.2 - resolution: "flat@npm:5.0.2" - bin: - flat: cli.js - checksum: 10c0/f178b13482f0cd80c7fede05f4d10585b1f2fdebf26e12edc138e32d3150c6ea6482b7f12813a1091143bad52bb6d3596bca51a162257a21163c0ff438baa5fe - languageName: node - linkType: hard - -"fs-minipass@npm:^3.0.0": - version: 3.0.3 - resolution: "fs-minipass@npm:3.0.3" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 - languageName: node - linkType: hard - -"fsevents@npm:~2.3.2": - version: 2.3.3 - resolution: "fsevents@npm:2.3.3" - dependencies: - node-gyp: "npm:latest" - checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 - conditions: os=darwin - languageName: node - linkType: hard - -"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": - version: 2.3.3 - resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" - dependencies: - node-gyp: "npm:latest" - conditions: os=darwin - languageName: node - linkType: hard - -"function-bind@npm:^1.1.2": - version: 1.1.2 - resolution: "function-bind@npm:1.1.2" - checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 - languageName: node - linkType: hard - -"gensync@npm:^1.0.0-beta.2": - version: 1.0.0-beta.2 - resolution: "gensync@npm:1.0.0-beta.2" - checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 - languageName: node - linkType: hard - -"get-caller-file@npm:^2.0.5": - version: 2.0.5 - resolution: "get-caller-file@npm:2.0.5" - checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde - languageName: node - linkType: hard - -"glob-parent@npm:~5.1.2": - version: 5.1.2 - resolution: "glob-parent@npm:5.1.2" - dependencies: - is-glob: "npm:^4.0.1" - checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee - languageName: node - linkType: hard - -"glob-to-regexp@npm:^0.4.1": - version: 0.4.1 - resolution: "glob-to-regexp@npm:0.4.1" - checksum: 10c0/0486925072d7a916f052842772b61c3e86247f0a80cc0deb9b5a3e8a1a9faad5b04fb6f58986a09f34d3e96cd2a22a24b7e9882fb1cf904c31e9a310de96c429 - languageName: node - linkType: hard - -"glob@npm:^13.0.0": - version: 13.0.6 - resolution: "glob@npm:13.0.6" - dependencies: - minimatch: "npm:^10.2.2" - minipass: "npm:^7.1.3" - path-scurry: "npm:^2.0.2" - checksum: 10c0/269c236f11a9b50357fe7a8c6aadac667e01deb5242b19c84975628f05f4438d8ee1354bb62c5d6c10f37fd59911b54d7799730633a2786660d8c69f1d18120a - languageName: node - linkType: hard - -"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": - version: 4.2.11 - resolution: "graceful-fs@npm:4.2.11" - checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 - languageName: node - linkType: hard - -"has-flag@npm:^4.0.0": - version: 4.0.0 - resolution: "has-flag@npm:4.0.0" - checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 - languageName: node - linkType: hard - -"hasown@npm:^2.0.2": - version: 2.0.2 - resolution: "hasown@npm:2.0.2" - dependencies: - function-bind: "npm:^1.1.2" - checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 - languageName: node - linkType: hard - -"http-cache-semantics@npm:^4.1.1": - version: 4.2.0 - resolution: "http-cache-semantics@npm:4.2.0" - checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 - languageName: node - linkType: hard - -"http-proxy-agent@npm:^7.0.0": - version: 7.0.2 - resolution: "http-proxy-agent@npm:7.0.2" - dependencies: - agent-base: "npm:^7.1.0" - debug: "npm:^4.3.4" - checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 - languageName: node - linkType: hard - -"https-proxy-agent@npm:^7.0.1": - version: 7.0.6 - resolution: "https-proxy-agent@npm:7.0.6" - dependencies: - agent-base: "npm:^7.1.2" - debug: "npm:4" - checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac - languageName: node - linkType: hard - -"iconv-lite@npm:^0.7.2": - version: 0.7.2 - resolution: "iconv-lite@npm:0.7.2" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10c0/3c228920f3bd307f56bf8363706a776f4a060eb042f131cd23855ceca962951b264d0997ab38a1ad340e1c5df8499ed26e1f4f0db6b2a2ad9befaff22f14b722 - languageName: node - linkType: hard - -"import-local@npm:^3.0.2": - version: 3.2.0 - resolution: "import-local@npm:3.2.0" - dependencies: - pkg-dir: "npm:^4.2.0" - resolve-cwd: "npm:^3.0.0" - bin: - import-local-fixture: fixtures/cli.js - checksum: 10c0/94cd6367a672b7e0cb026970c85b76902d2710a64896fa6de93bd5c571dd03b228c5759308959de205083e3b1c61e799f019c9e36ee8e9c523b993e1057f0433 - languageName: node - linkType: hard - -"imurmurhash@npm:^0.1.4": - version: 0.1.4 - resolution: "imurmurhash@npm:0.1.4" - checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 - languageName: node - linkType: hard - -"interpret@npm:^3.1.1": - version: 3.1.1 - resolution: "interpret@npm:3.1.1" - checksum: 10c0/6f3c4d0aa6ec1b43a8862375588a249e3c917739895cbe67fe12f0a76260ea632af51e8e2431b50fbcd0145356dc28ca147be08dbe6a523739fd55c0f91dc2a5 - languageName: node - linkType: hard - -"ip-address@npm:^10.0.1": - version: 10.1.0 - resolution: "ip-address@npm:10.1.0" - checksum: 10c0/0103516cfa93f6433b3bd7333fa876eb21263912329bfa47010af5e16934eeeff86f3d2ae700a3744a137839ddfad62b900c7a445607884a49b5d1e32a3d7566 - languageName: node - linkType: hard - -"is-binary-path@npm:~2.1.0": - version: 2.1.0 - resolution: "is-binary-path@npm:2.1.0" - dependencies: - binary-extensions: "npm:^2.0.0" - checksum: 10c0/a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38 - languageName: node - linkType: hard - -"is-core-module@npm:^2.16.1": - version: 2.16.1 - resolution: "is-core-module@npm:2.16.1" - dependencies: - hasown: "npm:^2.0.2" - checksum: 10c0/898443c14780a577e807618aaae2b6f745c8538eca5c7bc11388a3f2dc6de82b9902bcc7eb74f07be672b11bbe82dd6a6edded44a00cb3d8f933d0459905eedd - languageName: node - linkType: hard - -"is-extglob@npm:^2.1.1": - version: 2.1.1 - resolution: "is-extglob@npm:2.1.1" - checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 - languageName: node - linkType: hard - -"is-fullwidth-code-point@npm:^3.0.0": - version: 3.0.0 - resolution: "is-fullwidth-code-point@npm:3.0.0" - checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc - languageName: node - linkType: hard - -"is-glob@npm:^4.0.1, is-glob@npm:~4.0.1": - version: 4.0.3 - resolution: "is-glob@npm:4.0.3" - dependencies: - is-extglob: "npm:^2.1.1" - checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a - languageName: node - linkType: hard - -"is-number@npm:^7.0.0": - version: 7.0.0 - resolution: "is-number@npm:7.0.0" - checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 - languageName: node - linkType: hard - -"is-plain-object@npm:^2.0.4": - version: 2.0.4 - resolution: "is-plain-object@npm:2.0.4" - dependencies: - isobject: "npm:^3.0.1" - checksum: 10c0/f050fdd5203d9c81e8c4df1b3ff461c4bc64e8b5ca383bcdde46131361d0a678e80bcf00b5257646f6c636197629644d53bd8e2375aea633de09a82d57e942f4 - languageName: node - linkType: hard - -"isexe@npm:^2.0.0": - version: 2.0.0 - resolution: "isexe@npm:2.0.0" - checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d - languageName: node - linkType: hard - -"isexe@npm:^4.0.0": - version: 4.0.0 - resolution: "isexe@npm:4.0.0" - checksum: 10c0/5884815115bceac452877659a9c7726382531592f43dc29e5d48b7c4100661aed54018cb90bd36cb2eaeba521092570769167acbb95c18d39afdccbcca06c5ce - languageName: node - linkType: hard - -"isobject@npm:^3.0.1": - version: 3.0.1 - resolution: "isobject@npm:3.0.1" - checksum: 10c0/03344f5064a82f099a0cd1a8a407f4c0d20b7b8485e8e816c39f249e9416b06c322e8dec5b842b6bb8a06de0af9cb48e7bc1b5352f0fadc2f0abac033db3d4db - languageName: node - linkType: hard - -"jest-worker@npm:^27.4.5": - version: 27.5.1 - resolution: "jest-worker@npm:27.5.1" - dependencies: - "@types/node": "npm:*" - merge-stream: "npm:^2.0.0" - supports-color: "npm:^8.0.0" - checksum: 10c0/8c4737ffd03887b3c6768e4cc3ca0269c0336c1e4b1b120943958ddb035ed2a0fc6acab6dc99631720a3720af4e708ff84fb45382ad1e83c27946adf3623969b - languageName: node - linkType: hard - -"js-tokens@npm:^4.0.0": - version: 4.0.0 - resolution: "js-tokens@npm:4.0.0" - checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed - languageName: node - linkType: hard - -"jsesc@npm:^3.0.2": - version: 3.1.0 - resolution: "jsesc@npm:3.1.0" - bin: - jsesc: bin/jsesc - checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 - languageName: node - linkType: hard - -"json-parse-even-better-errors@npm:^2.3.1": - version: 2.3.1 - resolution: "json-parse-even-better-errors@npm:2.3.1" - checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 - languageName: node - linkType: hard - -"json-schema-traverse@npm:^1.0.0": - version: 1.0.0 - resolution: "json-schema-traverse@npm:1.0.0" - checksum: 10c0/71e30015d7f3d6dc1c316d6298047c8ef98a06d31ad064919976583eb61e1018a60a0067338f0f79cabc00d84af3fcc489bd48ce8a46ea165d9541ba17fb30c6 - languageName: node - linkType: hard - -"json5@npm:^2.2.1": - version: 2.2.3 - resolution: "json5@npm:2.2.3" - bin: - json5: lib/cli.js - checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c - languageName: node - linkType: hard - -"kind-of@npm:^6.0.2": - version: 6.0.3 - resolution: "kind-of@npm:6.0.3" - checksum: 10c0/61cdff9623dabf3568b6445e93e31376bee1cdb93f8ba7033d86022c2a9b1791a1d9510e026e6465ebd701a6dd2f7b0808483ad8838341ac52f003f512e0b4c4 - languageName: node - linkType: hard - -"loader-runner@npm:^4.3.1": - version: 4.3.1 - resolution: "loader-runner@npm:4.3.1" - checksum: 10c0/a523b6329f114e0a98317158e30a7dfce044b731521be5399464010472a93a15ece44757d1eaed1d8845019869c5390218bc1c7c3110f4eeaef5157394486eac - languageName: node - linkType: hard - -"locate-path@npm:^5.0.0": - version: 5.0.0 - resolution: "locate-path@npm:5.0.0" - dependencies: - p-locate: "npm:^4.1.0" - checksum: 10c0/33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59 - languageName: node - linkType: hard - -"locate-path@npm:^7.1.0": - version: 7.2.0 - resolution: "locate-path@npm:7.2.0" - dependencies: - p-locate: "npm:^6.0.0" - checksum: 10c0/139e8a7fe11cfbd7f20db03923cacfa5db9e14fa14887ea121345597472b4a63c1a42a8a5187defeeff6acf98fd568da7382aa39682d38f0af27433953a97751 - languageName: node - linkType: hard - -"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": - version: 11.2.6 - resolution: "lru-cache@npm:11.2.6" - checksum: 10c0/73bbffb298760e71b2bfe8ebc16a311c6a60ceddbba919cfedfd8635c2d125fbfb5a39b71818200e67973b11f8d59c5a9e31d6f90722e340e90393663a66e5cd - languageName: node - linkType: hard - -"lru-cache@npm:^5.1.1": - version: 5.1.1 - resolution: "lru-cache@npm:5.1.1" - dependencies: - yallist: "npm:^3.0.2" - checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 - languageName: node - linkType: hard - -"magic-string@npm:^0.27.0": - version: 0.27.0 - resolution: "magic-string@npm:0.27.0" - dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.4.13" - checksum: 10c0/cddacfea14441ca57ae8a307bc3cf90bac69efaa4138dd9a80804cffc2759bf06f32da3a293fb13eaa96334b7d45b7768a34f1d226afae25d2f05b05a3bb37d8 - languageName: node - linkType: hard - -"make-fetch-happen@npm:^15.0.0": - version: 15.0.4 - resolution: "make-fetch-happen@npm:15.0.4" - dependencies: - "@gar/promise-retry": "npm:^1.0.0" - "@npmcli/agent": "npm:^4.0.0" - cacache: "npm:^20.0.1" - http-cache-semantics: "npm:^4.1.1" - minipass: "npm:^7.0.2" - minipass-fetch: "npm:^5.0.0" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - negotiator: "npm:^1.0.0" - proc-log: "npm:^6.0.0" - ssri: "npm:^13.0.0" - checksum: 10c0/b874bf6879fc0b8ef3a3cafdddadea4d956acf94790f8dede1a9d3c74c7886b6cd3eb992616b8e5935e6fd550016a465f10ba51bf6723a0c6f4d98883ae2926b - languageName: node - linkType: hard - -"merge-stream@npm:^2.0.0": - version: 2.0.0 - resolution: "merge-stream@npm:2.0.0" - checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 - languageName: node - linkType: hard - -"mime-db@npm:1.52.0": - version: 1.52.0 - resolution: "mime-db@npm:1.52.0" - checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa - languageName: node - linkType: hard - -"mime-types@npm:^2.1.27": - version: 2.1.35 - resolution: "mime-types@npm:2.1.35" - dependencies: - mime-db: "npm:1.52.0" - checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 - languageName: node - linkType: hard - -"minimatch@npm:^10.2.2": - version: 10.2.4 - resolution: "minimatch@npm:10.2.4" - dependencies: - brace-expansion: "npm:^5.0.2" - checksum: 10c0/35f3dfb7b99b51efd46afd378486889f590e7efb10e0f6a10ba6800428cf65c9a8dedb74427d0570b318d749b543dc4e85f06d46d2858bc8cac7e1eb49a95945 - languageName: node - linkType: hard - -"minipass-collect@npm:^2.0.1": - version: 2.0.1 - resolution: "minipass-collect@npm:2.0.1" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e - languageName: node - linkType: hard - -"minipass-fetch@npm:^5.0.0": - version: 5.0.2 - resolution: "minipass-fetch@npm:5.0.2" - dependencies: - iconv-lite: "npm:^0.7.2" - minipass: "npm:^7.0.3" - minipass-sized: "npm:^2.0.0" - minizlib: "npm:^3.0.1" - dependenciesMeta: - iconv-lite: - optional: true - checksum: 10c0/ce4ab9f21cfabaead2097d95dd33f485af8072fbc6b19611bce694965393453a1639d641c2bcf1c48f2ea7d41ea7fab8278373f1d0bee4e63b0a5b2cdd0ef649 - languageName: node - linkType: hard - -"minipass-flush@npm:^1.0.5": - version: 1.0.5 - resolution: "minipass-flush@npm:1.0.5" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd - languageName: node - linkType: hard - -"minipass-pipeline@npm:^1.2.4": - version: 1.2.4 - resolution: "minipass-pipeline@npm:1.2.4" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 - languageName: node - linkType: hard - -"minipass-sized@npm:^2.0.0": - version: 2.0.0 - resolution: "minipass-sized@npm:2.0.0" - dependencies: - minipass: "npm:^7.1.2" - checksum: 10c0/f9201696a6f6d68610d04c9c83e3d2e5cb9c026aae1c8cbf7e17f386105cb79c1bb088dbc21bf0b1eb4f3fb5df384fd1e7aa3bf1f33868c416ae8c8a92679db8 - languageName: node - linkType: hard - -"minipass@npm:^3.0.0": - version: 3.3.6 - resolution: "minipass@npm:3.3.6" - dependencies: - yallist: "npm:^4.0.0" - checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c - languageName: node - linkType: hard - -"minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2, minipass@npm:^7.1.3": - version: 7.1.3 - resolution: "minipass@npm:7.1.3" - checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb - languageName: node - linkType: hard - -"minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": - version: 3.1.0 - resolution: "minizlib@npm:3.1.0" - dependencies: - minipass: "npm:^7.1.2" - checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec - languageName: node - linkType: hard - -"ms@npm:^2.1.3": - version: 2.1.3 - resolution: "ms@npm:2.1.3" - checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 - languageName: node - linkType: hard - -"negotiator@npm:^1.0.0": - version: 1.0.0 - resolution: "negotiator@npm:1.0.0" - checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b - languageName: node - linkType: hard - -"neo-async@npm:^2.6.2": - version: 2.6.2 - resolution: "neo-async@npm:2.6.2" - checksum: 10c0/c2f5a604a54a8ec5438a342e1f356dff4bc33ccccdb6dc668d94fe8e5eccfc9d2c2eea6064b0967a767ba63b33763f51ccf2cd2441b461a7322656c1f06b3f5d - languageName: node - linkType: hard - -"node-gyp@npm:latest": - version: 12.2.0 - resolution: "node-gyp@npm:12.2.0" - dependencies: - env-paths: "npm:^2.2.0" - exponential-backoff: "npm:^3.1.1" - graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^15.0.0" - nopt: "npm:^9.0.0" - proc-log: "npm:^6.0.0" - semver: "npm:^7.3.5" - tar: "npm:^7.5.4" - tinyglobby: "npm:^0.2.12" - which: "npm:^6.0.0" - bin: - node-gyp: bin/node-gyp.js - checksum: 10c0/3ed046746a5a7d90950cd8b0547332b06598443f31fe213ef4332a7174c7b7d259e1704835feda79b87d3f02e59d7791842aac60642ede4396ab25fdf0f8f759 - languageName: node - linkType: hard - -"node-releases@npm:^2.0.27": - version: 2.0.36 - resolution: "node-releases@npm:2.0.36" - checksum: 10c0/85d8d7f4b6248c8372831cbcc3829ce634cb2b01dbd85e55705cefc8a9eda4ce8121bd218b9629cf2579aef8a360541bad409f3925a35675c825b9471a49d7e9 - languageName: node - linkType: hard - -"nopt@npm:^9.0.0": - version: 9.0.0 - resolution: "nopt@npm:9.0.0" - dependencies: - abbrev: "npm:^4.0.0" - bin: - nopt: bin/nopt.js - checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd - languageName: node - linkType: hard - -"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": - version: 3.0.0 - resolution: "normalize-path@npm:3.0.0" - checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 - languageName: node - linkType: hard - -"p-limit@npm:^2.2.0": - version: 2.3.0 - resolution: "p-limit@npm:2.3.0" - dependencies: - p-try: "npm:^2.0.0" - checksum: 10c0/8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 - languageName: node - linkType: hard - -"p-limit@npm:^4.0.0": - version: 4.0.0 - resolution: "p-limit@npm:4.0.0" - dependencies: - yocto-queue: "npm:^1.0.0" - checksum: 10c0/a56af34a77f8df2ff61ddfb29431044557fcbcb7642d5a3233143ebba805fc7306ac1d448de724352861cb99de934bc9ab74f0d16fe6a5460bdbdf938de875ad - languageName: node - linkType: hard - -"p-locate@npm:^4.1.0": - version: 4.1.0 - resolution: "p-locate@npm:4.1.0" - dependencies: - p-limit: "npm:^2.2.0" - checksum: 10c0/1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9 - languageName: node - linkType: hard - -"p-locate@npm:^6.0.0": - version: 6.0.0 - resolution: "p-locate@npm:6.0.0" - dependencies: - p-limit: "npm:^4.0.0" - checksum: 10c0/d72fa2f41adce59c198270aa4d3c832536c87a1806e0f69dffb7c1a7ca998fb053915ca833d90f166a8c082d3859eabfed95f01698a3214c20df6bb8de046312 - languageName: node - linkType: hard - -"p-map@npm:^7.0.2": - version: 7.0.4 - resolution: "p-map@npm:7.0.4" - checksum: 10c0/a5030935d3cb2919d7e89454d1ce82141e6f9955413658b8c9403cfe379283770ed3048146b44cde168aa9e8c716505f196d5689db0ae3ce9a71521a2fef3abd - languageName: node - linkType: hard - -"p-try@npm:^2.0.0": - version: 2.2.0 - resolution: "p-try@npm:2.2.0" - checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f - languageName: node - linkType: hard - -"path-exists@npm:^4.0.0": - version: 4.0.0 - resolution: "path-exists@npm:4.0.0" - checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b - languageName: node - linkType: hard - -"path-exists@npm:^5.0.0": - version: 5.0.0 - resolution: "path-exists@npm:5.0.0" - checksum: 10c0/b170f3060b31604cde93eefdb7392b89d832dfbc1bed717c9718cbe0f230c1669b7e75f87e19901da2250b84d092989a0f9e44d2ef41deb09aa3ad28e691a40a - languageName: node - linkType: hard - -"path-key@npm:^3.1.0": - version: 3.1.1 - resolution: "path-key@npm:3.1.1" - checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c - languageName: node - linkType: hard - -"path-parse@npm:^1.0.7": - version: 1.0.7 - resolution: "path-parse@npm:1.0.7" - checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 - languageName: node - linkType: hard - -"path-scurry@npm:^2.0.2": - version: 2.0.2 - resolution: "path-scurry@npm:2.0.2" - dependencies: - lru-cache: "npm:^11.0.0" - minipass: "npm:^7.1.2" - checksum: 10c0/b35ad37cf6557a87fd057121ce2be7695380c9138d93e87ae928609da259ea0a170fac6f3ef1eb3ece8a068e8b7f2f3adf5bb2374cf4d4a57fe484954fcc9482 - languageName: node - linkType: hard - -"picocolors@npm:^1.1.1": - version: 1.1.1 - resolution: "picocolors@npm:1.1.1" - checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 - languageName: node - linkType: hard - -"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1": - version: 2.3.1 - resolution: "picomatch@npm:2.3.1" - checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be - languageName: node - linkType: hard - -"picomatch@npm:^4.0.3": - version: 4.0.3 - resolution: "picomatch@npm:4.0.3" - checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 - languageName: node - linkType: hard - -"pkg-dir@npm:^4.2.0": - version: 4.2.0 - resolution: "pkg-dir@npm:4.2.0" - dependencies: - find-up: "npm:^4.0.0" - checksum: 10c0/c56bda7769e04907a88423feb320babaed0711af8c436ce3e56763ab1021ba107c7b0cafb11cde7529f669cfc22bffcaebffb573645cbd63842ea9fb17cd7728 - languageName: node - linkType: hard - -"pkg-dir@npm:^7.0.0": - version: 7.0.0 - resolution: "pkg-dir@npm:7.0.0" - dependencies: - find-up: "npm:^6.3.0" - checksum: 10c0/1afb23d2efb1ec9d8b2c4a0c37bf146822ad2774f074cb05b853be5dca1b40815c5960dd126df30ab8908349262a266f31b771e877235870a3b8fd313beebec5 - languageName: node - linkType: hard - -"proc-log@npm:^6.0.0": - version: 6.1.0 - resolution: "proc-log@npm:6.1.0" - checksum: 10c0/4f178d4062733ead9d71a9b1ab24ebcecdfe2250916a5b1555f04fe2eda972a0ec76fbaa8df1ad9c02707add6749219d118a4fc46dc56bdfe4dde4b47d80bb82 - languageName: node - linkType: hard - -"readdirp@npm:~3.6.0": - version: 3.6.0 - resolution: "readdirp@npm:3.6.0" - dependencies: - picomatch: "npm:^2.2.1" - checksum: 10c0/6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b - languageName: node - linkType: hard - -"rechoir@npm:^0.8.0": - version: 0.8.0 - resolution: "rechoir@npm:0.8.0" - dependencies: - resolve: "npm:^1.20.0" - checksum: 10c0/1a30074124a22abbd5d44d802dac26407fa72a0a95f162aa5504ba8246bc5452f8b1a027b154d9bdbabcd8764920ff9333d934c46a8f17479c8912e92332f3ff - languageName: node - linkType: hard - -"reflect-metadata@npm:^0.1.2": - version: 0.1.14 - resolution: "reflect-metadata@npm:0.1.14" - checksum: 10c0/3a6190c7f6cb224f26a012d11f9e329360c01c1945e2cbefea23976a8bacf9db6b794aeb5bf18adcb673c448a234fbc06fc41853c00a6c206b30f0777ecf019e - languageName: node - linkType: hard - -"require-directory@npm:^2.1.1": - version: 2.1.1 - resolution: "require-directory@npm:2.1.1" - checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 - languageName: node - linkType: hard - -"require-from-string@npm:^2.0.2": - version: 2.0.2 - resolution: "require-from-string@npm:2.0.2" - checksum: 10c0/aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2 - languageName: node - linkType: hard - -"resolve-cwd@npm:^3.0.0": - version: 3.0.0 - resolution: "resolve-cwd@npm:3.0.0" - dependencies: - resolve-from: "npm:^5.0.0" - checksum: 10c0/e608a3ebd15356264653c32d7ecbc8fd702f94c6703ea4ac2fb81d9c359180cba0ae2e6b71faa446631ed6145454d5a56b227efc33a2d40638ac13f8beb20ee4 - languageName: node - linkType: hard - -"resolve-from@npm:^5.0.0": - version: 5.0.0 - resolution: "resolve-from@npm:5.0.0" - checksum: 10c0/b21cb7f1fb746de8107b9febab60095187781137fd803e6a59a76d421444b1531b641bba5857f5dc011974d8a5c635d61cec49e6bd3b7fc20e01f0fafc4efbf2 - languageName: node - linkType: hard - -"resolve@npm:^1.20.0": - version: 1.22.11 - resolution: "resolve@npm:1.22.11" - dependencies: - is-core-module: "npm:^2.16.1" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/f657191507530f2cbecb5815b1ee99b20741ea6ee02a59c57028e9ec4c2c8d7681afcc35febbd554ac0ded459db6f2d8153382c53a2f266cee2575e512674409 - languageName: node - linkType: hard - -"resolve@patch:resolve@npm%3A^1.20.0#optional!builtin": - version: 1.22.11 - resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin::version=1.22.11&hash=c3c19d" - dependencies: - is-core-module: "npm:^2.16.1" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/ee5b182f2e37cb1165465e58c6abc797fec0a80b5ba3231607beb4677db0c9291ac010c47cf092b6daa2b7f518d69a0e21888e7e2b633f68d501a874212a8c63 - languageName: node - linkType: hard - -"retry@npm:^0.13.1": - version: 0.13.1 - resolution: "retry@npm:0.13.1" - checksum: 10c0/9ae822ee19db2163497e074ea919780b1efa00431d197c7afdb950e42bf109196774b92a49fc9821f0b8b328a98eea6017410bfc5e8a0fc19c85c6d11adb3772 - languageName: node - linkType: hard - -"rxjs@npm:7.8.2": - version: 7.8.2 - resolution: "rxjs@npm:7.8.2" - dependencies: - tslib: "npm:^2.1.0" - checksum: 10c0/1fcd33d2066ada98ba8f21fcbbcaee9f0b271de1d38dc7f4e256bfbc6ffcdde68c8bfb69093de7eeb46f24b1fb820620bf0223706cff26b4ab99a7ff7b2e2c45 - languageName: node - linkType: hard - -"safer-buffer@npm:>= 2.1.2 < 3.0.0": - version: 2.1.2 - resolution: "safer-buffer@npm:2.1.2" - checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 - languageName: node - linkType: hard - -"schema-utils@npm:^4.0.0, schema-utils@npm:^4.3.0, schema-utils@npm:^4.3.3": - version: 4.3.3 - resolution: "schema-utils@npm:4.3.3" - dependencies: - "@types/json-schema": "npm:^7.0.9" - ajv: "npm:^8.9.0" - ajv-formats: "npm:^2.1.1" - ajv-keywords: "npm:^5.1.0" - checksum: 10c0/1c8d2c480a026d7c02ab2ecbe5919133a096d6a721a3f201fa50663e4f30f6d6ba020dfddd93cb828b66b922e76b342e103edd19a62c95c8f60e9079cc403202 - languageName: node - linkType: hard - -"semver@npm:^6.3.0, semver@npm:^6.3.1": - version: 6.3.1 - resolution: "semver@npm:6.3.1" - bin: - semver: bin/semver.js - checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d - languageName: node - linkType: hard - -"semver@npm:^7.0.0, semver@npm:^7.3.5": - version: 7.7.4 - resolution: "semver@npm:7.7.4" - bin: - semver: bin/semver.js - checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2 - languageName: node - linkType: hard - -"shallow-clone@npm:^3.0.0": - version: 3.0.1 - resolution: "shallow-clone@npm:3.0.1" - dependencies: - kind-of: "npm:^6.0.2" - checksum: 10c0/7bab09613a1b9f480c85a9823aebec533015579fa055ba6634aa56ba1f984380670eaf33b8217502931872aa1401c9fcadaa15f9f604d631536df475b05bcf1e - languageName: node - linkType: hard - -"shebang-command@npm:^2.0.0": - version: 2.0.0 - resolution: "shebang-command@npm:2.0.0" - dependencies: - shebang-regex: "npm:^3.0.0" - checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e - languageName: node - linkType: hard - -"shebang-regex@npm:^3.0.0": - version: 3.0.0 - resolution: "shebang-regex@npm:3.0.0" - checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 - languageName: node - linkType: hard - -"smart-buffer@npm:^4.2.0": - version: 4.2.0 - resolution: "smart-buffer@npm:4.2.0" - checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 - languageName: node - linkType: hard - -"socks-proxy-agent@npm:^8.0.3": - version: 8.0.5 - resolution: "socks-proxy-agent@npm:8.0.5" - dependencies: - agent-base: "npm:^7.1.2" - debug: "npm:^4.3.4" - socks: "npm:^2.8.3" - checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 - languageName: node - linkType: hard - -"socks@npm:^2.8.3": - version: 2.8.7 - resolution: "socks@npm:2.8.7" - dependencies: - ip-address: "npm:^10.0.1" - smart-buffer: "npm:^4.2.0" - checksum: 10c0/2805a43a1c4bcf9ebf6e018268d87b32b32b06fbbc1f9282573583acc155860dc361500f89c73bfbb157caa1b4ac78059eac0ef15d1811eb0ca75e0bdadbc9d2 - languageName: node - linkType: hard - -"source-map-support@npm:~0.5.20": - version: 0.5.21 - resolution: "source-map-support@npm:0.5.21" - dependencies: - buffer-from: "npm:^1.0.0" - source-map: "npm:^0.6.0" - checksum: 10c0/9ee09942f415e0f721d6daad3917ec1516af746a8120bba7bb56278707a37f1eb8642bde456e98454b8a885023af81a16e646869975f06afc1a711fb90484e7d - languageName: node - linkType: hard - -"source-map@npm:^0.6.0": - version: 0.6.1 - resolution: "source-map@npm:0.6.1" - checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 - languageName: node - linkType: hard - -"ssri@npm:^13.0.0": - version: 13.0.1 - resolution: "ssri@npm:13.0.1" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/cf6408a18676c57ff2ed06b8a20dc64bb3e748e5c7e095332e6aecaa2b8422b1e94a739a8453bf65156a8a47afe23757ba4ab52d3ea3b62322dc40875763e17a - languageName: node - linkType: hard - -"string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": - version: 4.2.3 - resolution: "string-width@npm:4.2.3" - dependencies: - emoji-regex: "npm:^8.0.0" - is-fullwidth-code-point: "npm:^3.0.0" - strip-ansi: "npm:^6.0.1" - checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b - languageName: node - linkType: hard - -"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": - version: 6.0.1 - resolution: "strip-ansi@npm:6.0.1" - dependencies: - ansi-regex: "npm:^5.0.1" - checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 - languageName: node - linkType: hard - -"supports-color@npm:^8.0.0": - version: 8.1.1 - resolution: "supports-color@npm:8.1.1" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 - languageName: node - linkType: hard - -"supports-preserve-symlinks-flag@npm:^1.0.0": - version: 1.0.0 - resolution: "supports-preserve-symlinks-flag@npm:1.0.0" - checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 - languageName: node - linkType: hard - -"tapable@npm:^2.3.0": - version: 2.3.0 - resolution: "tapable@npm:2.3.0" - checksum: 10c0/cb9d67cc2c6a74dedc812ef3085d9d681edd2c1fa18e4aef57a3c0605fdbe44e6b8ea00bd9ef21bc74dd45314e39d31227aa031ebf2f5e38164df514136f2681 - languageName: node - linkType: hard - -"tar@npm:^7.5.4": - version: 7.5.11 - resolution: "tar@npm:7.5.11" - dependencies: - "@isaacs/fs-minipass": "npm:^4.0.0" - chownr: "npm:^3.0.0" - minipass: "npm:^7.1.2" - minizlib: "npm:^3.1.0" - yallist: "npm:^5.0.0" - checksum: 10c0/b6bb420550ef50ef23356018155e956cd83282c97b6128d8d5cfe5740c57582d806a244b2ef0bf686a74ce526babe8b8b9061527623e935e850008d86d838929 - languageName: node - linkType: hard - -"terser-webpack-plugin@npm:^5.3.16": - version: 5.4.0 - resolution: "terser-webpack-plugin@npm:5.4.0" - dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.25" - jest-worker: "npm:^27.4.5" - schema-utils: "npm:^4.3.0" - terser: "npm:^5.31.1" - peerDependencies: - webpack: ^5.1.0 - peerDependenciesMeta: - "@swc/core": - optional: true - esbuild: - optional: true - uglify-js: - optional: true - checksum: 10c0/1feed4b9575af795dae6af0c8f0d76d6e1fb7b357b8628d90e834c23a651b918a58cdc48d0ae6c1f0581f74bc8169b33c3b8d049f2d2190bac4e310964e59fde - languageName: node - linkType: hard - -"terser@npm:^5.31.1": - version: 5.46.0 - resolution: "terser@npm:5.46.0" - dependencies: - "@jridgewell/source-map": "npm:^0.3.3" - acorn: "npm:^8.15.0" - commander: "npm:^2.20.0" - source-map-support: "npm:~0.5.20" - bin: - terser: bin/terser - checksum: 10c0/93ad468f13187c4f66b609bbfc00a6aee752007779ca3157f2c1ee063697815748d6010fd449a16c30be33213748431d5f54cc0224ba6a3fbbf5acd3582a4356 - languageName: node - linkType: hard - -"tinyglobby@npm:^0.2.12": - version: 0.2.15 - resolution: "tinyglobby@npm:0.2.15" - dependencies: - fdir: "npm:^6.5.0" - picomatch: "npm:^4.0.3" - checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 - languageName: node - linkType: hard - -"to-regex-range@npm:^5.0.1": - version: 5.0.1 - resolution: "to-regex-range@npm:5.0.1" - dependencies: - is-number: "npm:^7.0.0" - checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 - languageName: node - linkType: hard - -"tslib@npm:^2.1.0, tslib@npm:^2.3.0": - version: 2.8.1 - resolution: "tslib@npm:2.8.1" - checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 - languageName: node - linkType: hard - -"typescript@npm:4.9.5": - version: 4.9.5 - resolution: "typescript@npm:4.9.5" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/5f6cad2e728a8a063521328e612d7876e12f0d8a8390d3b3aaa452a6a65e24e9ac8ea22beb72a924fd96ea0a49ea63bb4e251fb922b12eedfb7f7a26475e5c56 - languageName: node - linkType: hard - -"typescript@patch:typescript@npm%3A4.9.5#optional!builtin": - version: 4.9.5 - resolution: "typescript@patch:typescript@npm%3A4.9.5#optional!builtin::version=4.9.5&hash=289587" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/e3333f887c6829dfe0ab6c1dbe0dd1e3e2aeb56c66460cb85c5440c566f900c833d370ca34eb47558c0c69e78ced4bfe09b8f4f98b6de7afed9b84b8d1dd06a1 - languageName: node - linkType: hard - -"undici-types@npm:~7.18.0": - version: 7.18.2 - resolution: "undici-types@npm:7.18.2" - checksum: 10c0/85a79189113a238959d7a647368e4f7c5559c3a404ebdb8fc4488145ce9426fcd82252a844a302798dfc0e37e6fb178ff481ed03bc4caf634c5757d9ef43521d - languageName: node - linkType: hard - -"unique-filename@npm:^5.0.0": - version: 5.0.0 - resolution: "unique-filename@npm:5.0.0" - dependencies: - unique-slug: "npm:^6.0.0" - checksum: 10c0/afb897e9cf4c2fb622ea716f7c2bb462001928fc5f437972213afdf1cc32101a230c0f1e9d96fc91ee5185eca0f2feb34127145874975f347be52eb91d6ccc2c - languageName: node - linkType: hard - -"unique-slug@npm:^6.0.0": - version: 6.0.0 - resolution: "unique-slug@npm:6.0.0" - dependencies: - imurmurhash: "npm:^0.1.4" - checksum: 10c0/da7ade4cb04eb33ad0499861f82fe95ce9c7c878b7139dc54d140ecfb6a6541c18a5c8dac16188b8b379fe62c0c1f1b710814baac910cde5f4fec06212126c6a - languageName: node - linkType: hard - -"update-browserslist-db@npm:^1.2.0": - version: 1.2.3 - resolution: "update-browserslist-db@npm:1.2.3" - dependencies: - escalade: "npm:^3.2.0" - picocolors: "npm:^1.1.1" - peerDependencies: - browserslist: ">= 4.21.0" - bin: - update-browserslist-db: cli.js - checksum: 10c0/13a00355ea822388f68af57410ce3255941d5fb9b7c49342c4709a07c9f230bbef7f7499ae0ca7e0de532e79a82cc0c4edbd125f1a323a1845bf914efddf8bec - languageName: node - linkType: hard - -"watchpack@npm:^2.5.1": - version: 2.5.1 - resolution: "watchpack@npm:2.5.1" - dependencies: - glob-to-regexp: "npm:^0.4.1" - graceful-fs: "npm:^4.1.2" - checksum: 10c0/dffbb483d1f61be90dc570630a1eb308581e2227d507d783b1d94a57ac7b705ecd9a1a4b73d73c15eab596d39874e5276a3d9cb88bbb698bafc3f8d08c34cf17 - languageName: node - linkType: hard - -"webpack-cli@npm:6.0.1": - version: 6.0.1 - resolution: "webpack-cli@npm:6.0.1" - dependencies: - "@discoveryjs/json-ext": "npm:^0.6.1" - "@webpack-cli/configtest": "npm:^3.0.1" - "@webpack-cli/info": "npm:^3.0.1" - "@webpack-cli/serve": "npm:^3.0.1" - colorette: "npm:^2.0.14" - commander: "npm:^12.1.0" - cross-spawn: "npm:^7.0.3" - envinfo: "npm:^7.14.0" - fastest-levenshtein: "npm:^1.0.12" - import-local: "npm:^3.0.2" - interpret: "npm:^3.1.1" - rechoir: "npm:^0.8.0" - webpack-merge: "npm:^6.0.1" - peerDependencies: - webpack: ^5.82.0 - peerDependenciesMeta: - webpack-bundle-analyzer: - optional: true - webpack-dev-server: - optional: true - bin: - webpack-cli: ./bin/cli.js - checksum: 10c0/2aaca78e277427f03f528602abd707d224696048fb46286ea636c7975592409c4381ca94d68bbbb3900f195ca97f256e619583e8feb34a80da531461323bf3e2 - languageName: node - linkType: hard - -"webpack-merge@npm:^6.0.1": - version: 6.0.1 - resolution: "webpack-merge@npm:6.0.1" - dependencies: - clone-deep: "npm:^4.0.1" - flat: "npm:^5.0.2" - wildcard: "npm:^2.0.1" - checksum: 10c0/bf1429567858b353641801b8a2696ca0aac270fc8c55d4de8a7b586fe07d27fdcfc83099a98ab47e6162383db8dd63bb8cc25b1beb2ec82150422eec843b0dc0 - languageName: node - linkType: hard - -"webpack-sources@npm:^3.3.3": - version: 3.3.4 - resolution: "webpack-sources@npm:3.3.4" - checksum: 10c0/94a42508531338eb41939cf1d48a4a8a6db97f3a47e5453cff2133a68d3169ca779d4bcbe9dfed072ce16611959eba1e16f085bc2dc56714e1a1c1783fd661a3 - languageName: node - linkType: hard - -"webpack@npm:5.105.2": - version: 5.105.2 - resolution: "webpack@npm:5.105.2" - dependencies: - "@types/eslint-scope": "npm:^3.7.7" - "@types/estree": "npm:^1.0.8" - "@types/json-schema": "npm:^7.0.15" - "@webassemblyjs/ast": "npm:^1.14.1" - "@webassemblyjs/wasm-edit": "npm:^1.14.1" - "@webassemblyjs/wasm-parser": "npm:^1.14.1" - acorn: "npm:^8.15.0" - acorn-import-phases: "npm:^1.0.3" - browserslist: "npm:^4.28.1" - chrome-trace-event: "npm:^1.0.2" - enhanced-resolve: "npm:^5.19.0" - es-module-lexer: "npm:^2.0.0" - eslint-scope: "npm:5.1.1" - events: "npm:^3.2.0" - glob-to-regexp: "npm:^0.4.1" - graceful-fs: "npm:^4.2.11" - json-parse-even-better-errors: "npm:^2.3.1" - loader-runner: "npm:^4.3.1" - mime-types: "npm:^2.1.27" - neo-async: "npm:^2.6.2" - schema-utils: "npm:^4.3.3" - tapable: "npm:^2.3.0" - terser-webpack-plugin: "npm:^5.3.16" - watchpack: "npm:^2.5.1" - webpack-sources: "npm:^3.3.3" - peerDependenciesMeta: - webpack-cli: - optional: true - bin: - webpack: bin/webpack.js - checksum: 10c0/565df8072c00d72e0a22e136971862b7eac7beb8b8d39a2ae4ab00838941ea58acc5b49dd7ea268e3d839810756cb86ba5c272b3a25904f6db7807cfa8ed0b29 - languageName: node - linkType: hard - -"which@npm:^2.0.1": - version: 2.0.2 - resolution: "which@npm:2.0.2" - dependencies: - isexe: "npm:^2.0.0" - bin: - node-which: ./bin/node-which - checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f - languageName: node - linkType: hard - -"which@npm:^6.0.0": - version: 6.0.1 - resolution: "which@npm:6.0.1" - dependencies: - isexe: "npm:^4.0.0" - bin: - node-which: bin/which.js - checksum: 10c0/7e710e54ea36d2d6183bee2f9caa27a3b47b9baf8dee55a199b736fcf85eab3b9df7556fca3d02b50af7f3dfba5ea3a45644189836df06267df457e354da66d5 - languageName: node - linkType: hard - -"wildcard@npm:^2.0.1": - version: 2.0.1 - resolution: "wildcard@npm:2.0.1" - checksum: 10c0/08f70cd97dd9a20aea280847a1fe8148e17cae7d231640e41eb26d2388697cbe65b67fd9e68715251c39b080c5ae4f76d71a9a69fa101d897273efdfb1b58bf7 - languageName: node - linkType: hard - -"wrap-ansi@npm:^7.0.0": - version: 7.0.0 - resolution: "wrap-ansi@npm:7.0.0" - dependencies: - ansi-styles: "npm:^4.0.0" - string-width: "npm:^4.1.0" - strip-ansi: "npm:^6.0.0" - checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da - languageName: node - linkType: hard - -"y18n@npm:^5.0.5": - version: 5.0.8 - resolution: "y18n@npm:5.0.8" - checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 - languageName: node - linkType: hard - -"yallist@npm:^3.0.2": - version: 3.1.1 - resolution: "yallist@npm:3.1.1" - checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 - languageName: node - linkType: hard - -"yallist@npm:^4.0.0": - version: 4.0.0 - resolution: "yallist@npm:4.0.0" - checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a - languageName: node - linkType: hard - -"yallist@npm:^5.0.0": - version: 5.0.0 - resolution: "yallist@npm:5.0.0" - checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 - languageName: node - linkType: hard - -"yargs-parser@npm:^21.1.1": - version: 21.1.1 - resolution: "yargs-parser@npm:21.1.1" - checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 - languageName: node - linkType: hard - -"yargs@npm:^17.2.1": - version: 17.7.2 - resolution: "yargs@npm:17.7.2" - dependencies: - cliui: "npm:^8.0.1" - escalade: "npm:^3.1.1" - get-caller-file: "npm:^2.0.5" - require-directory: "npm:^2.1.1" - string-width: "npm:^4.2.3" - y18n: "npm:^5.0.5" - yargs-parser: "npm:^21.1.1" - checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 - languageName: node - linkType: hard - -"yocto-queue@npm:^1.0.0": - version: 1.2.2 - resolution: "yocto-queue@npm:1.2.2" - checksum: 10c0/36d4793e9cf7060f9da543baf67c55e354f4862c8d3d34de1a1b1d7c382d44171315cc54abf84d8900b8113d742b830108a1434f4898fb244f9b7e8426d4b8f5 - languageName: node - linkType: hard - -"zone.js@npm:0.12.0": - version: 0.12.0 - resolution: "zone.js@npm:0.12.0" - dependencies: - tslib: "npm:^2.3.0" - checksum: 10c0/827efca5e0139644882aef6fa2c26c38beba5fa488613fc0a98d8bdd103d35b73b89d8a4b9d9de4ebab1f9cde32b17b9e19aff0824883af427988e5043621351 - languageName: node - linkType: hard diff --git a/test/e2e/lib/framework/sdkBuilds.ts b/test/e2e/lib/framework/sdkBuilds.ts index 58652b5140..a4baf98ebd 100644 --- a/test/e2e/lib/framework/sdkBuilds.ts +++ b/test/e2e/lib/framework/sdkBuilds.ts @@ -11,7 +11,6 @@ export function getTestAppBundlePath(appName: string, originalUrl: string) { app: 'apps/vanilla', 'react-router-v6-app': 'apps/react-router-v6-app', 'react-router-v7-app': 'apps/react-router-v7-app', - 'angular-app': 'apps/angular-app', microfrontend: 'apps/microfrontend', } diff --git a/test/e2e/lib/framework/serverApps/mock.ts b/test/e2e/lib/framework/serverApps/mock.ts index dbeaaa2279..e0cd2a217f 100644 --- a/test/e2e/lib/framework/serverApps/mock.ts +++ b/test/e2e/lib/framework/serverApps/mock.ts @@ -203,7 +203,7 @@ export function createMockServerApp(servers: Servers, setup: string, setupOption } }) - app.get(/(?app|react-[\w-]+|angular-[\w-]+).js$/, (req, res) => { + app.get(/(?app|react-[\w-]+).js$/, (req, res) => { const { originalUrl, params } = req res.sendFile(getTestAppBundlePath(params.appName, originalUrl)) }) diff --git a/test/e2e/scenario/angularPlugin.scenario.ts b/test/e2e/scenario/angularPlugin.scenario.ts deleted file mode 100644 index 3a7f4b5b8e..0000000000 --- a/test/e2e/scenario/angularPlugin.scenario.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { test, expect } from '@playwright/test' -import { createTest } from '../lib/framework' - -test.describe('angular plugin', () => { - createTest('should define a view name based on the route') - .withRum() - .withApp('angular-app') - .run(async ({ page, flushEvents, intakeRegistry }) => { - await page.click('text=Go to Parameterized Route') - await flushEvents() - const viewEvents = intakeRegistry.rumViewEvents - expect(viewEvents.length).toBeGreaterThan(0) - const lastView = viewEvents[viewEvents.length - 1] - expect(lastView.view.name).toBe('/parameterized/:id') - expect(lastView.view.url).toContain('/parameterized/42') - }) - - createTest('should define a view name for nested routes') - .withRum() - .withApp('angular-app') - .run(async ({ page, flushEvents, intakeRegistry }) => { - await page.click('text=Go to Nested Route') - await flushEvents() - const viewEvents = intakeRegistry.rumViewEvents - expect(viewEvents.length).toBeGreaterThan(0) - const lastView = viewEvents[viewEvents.length - 1] - expect(lastView.view.name).toBe('/parent/nested') - expect(lastView.view.url).toContain('/parent/nested') - }) - - createTest('should define a view name with the actual path for wildcard routes') - .withRum() - .withApp('angular-app') - .run(async ({ page, flushEvents, intakeRegistry }) => { - await page.click('text=Go to Wildcard Route') - await flushEvents() - const viewEvents = intakeRegistry.rumViewEvents - expect(viewEvents.length).toBeGreaterThan(0) - const lastView = viewEvents[viewEvents.length - 1] - expect(lastView.view.name).toBe('/unknown/page') - expect(lastView.view.url).toContain('/unknown/page') - }) - - createTest('should define a view name for the initial route') - .withRum() - .withApp('angular-app') - .run(async ({ flushEvents, intakeRegistry }) => { - await flushEvents() - const viewEvents = intakeRegistry.rumViewEvents - expect(viewEvents.length).toBeGreaterThan(0) - const firstView = viewEvents[0] - expect(firstView.view.name).toBe('/') - }) - - createTest('should not create a new view on query param changes') - .withRum() - .withApp('angular-app') - .run(async ({ page, flushEvents, intakeRegistry }) => { - await page.click('#query-param-link') - await flushEvents() - - const viewEvents = intakeRegistry.rumViewEvents - // Only the initial view should exist — the query param change should not create a new one - expect(viewEvents).toHaveLength(1) - }) - - createTest('should report errors caught by provideDatadogErrorHandler') - .withRum() - .withApp('angular-app') - .run(async ({ page, flushEvents, intakeRegistry, withBrowserLogs }) => { - await page.click('#throw-error') - await flushEvents() - - const angularErrors = intakeRegistry.rumErrorEvents.filter((event) => event.context?.framework === 'angular') - expect(angularErrors).toHaveLength(1) - expect(angularErrors[0].error.message).toBe('angular error from component') - expect(angularErrors[0].error.handling).toBe('handled') - expect(angularErrors[0].error.source).toBe('custom') - expect(angularErrors[0].error.handling_stack).toEqual(expect.stringContaining('angular error')) - - withBrowserLogs((browserLogs) => { - expect(browserLogs.filter((log) => log.level === 'error').length).toBeGreaterThan(0) - }) - }) - - createTest('should merge dd_context from the error object into the event context') - .withRum() - .withApp('angular-app') - .run(async ({ page, flushEvents, intakeRegistry, withBrowserLogs }) => { - await page.click('#throw-error-with-context') - await flushEvents() - - const angularErrors = intakeRegistry.rumErrorEvents.filter((event) => event.context?.framework === 'angular') - expect(angularErrors).toHaveLength(1) - expect(angularErrors[0].error.message).toBe('angular error with dd_context') - expect(angularErrors[0].context).toEqual( - expect.objectContaining({ - framework: 'angular', - component: 'InitialRoute', - userId: 42, - }) - ) - - withBrowserLogs((browserLogs) => { - expect(browserLogs.filter((log) => log.level === 'error').length).toBeGreaterThan(0) - }) - }) -}) diff --git a/test/unit/globalThisPolyfill.js b/test/unit/globalThisPolyfill.js deleted file mode 100644 index cd39cadf99..0000000000 --- a/test/unit/globalThisPolyfill.js +++ /dev/null @@ -1,6 +0,0 @@ -// Polyfill globalThis for browsers that don't support it (e.g. Chrome < 71) -// Required because @angular/core uses globalThis internally. -/* eslint-disable no-undef */ -if (typeof globalThis === 'undefined') { - window.globalThis = window -} diff --git a/test/unit/karma.base.conf.js b/test/unit/karma.base.conf.js index f5cc511058..1e0970f7d3 100644 --- a/test/unit/karma.base.conf.js +++ b/test/unit/karma.base.conf.js @@ -21,9 +21,6 @@ if (testReportDirectory) { } const FILES = [ - // Polyfill globalThis for older browsers (e.g. Chrome 63) that don't support it. - // Required because @angular/core uses globalThis internally. - { pattern: 'test/unit/globalThisPolyfill.js', watched: false }, // Make sure 'forEach.spec' is the first file to be loaded, so its `beforeEach` hook is executed // before all other `beforeEach` hooks, and its `afterEach` hook is executed after all other // `afterEach` hooks. @@ -130,7 +127,7 @@ function overrideTsLoaderRule(module) { module.rules.push({ test: /\.m?js$/, include: - /node_modules\/(@angular\/core|react|react-router-dom|react-dom|react-router|turbo-stream|vue-router|@vue\/test-utils)/, + /node_modules\/(react|react-router-dom|react-dom|react-router|turbo-stream|vue-router|@vue\/test-utils)/, use: { loader: 'swc-loader', options: { diff --git a/tsconfig.base.json b/tsconfig.base.json index fb97cd8dd0..d92c4f645d 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -37,6 +37,9 @@ "@datadog/browser-rum-vue": ["./packages/rum-vue/src/entries/main"], "@datadog/browser-rum-vue/vue-router-v4": ["./packages/rum-vue/src/entries/vueRouter"], + "@datadog/browser-rum-angular": ["./packages/rum-angular/src/entries/main"], + "@datadog/browser-rum-angular/angular-router": ["./packages/rum-angular/src/entries/angularRouter"], + "@datadog/browser-rum-nextjs": ["./packages/rum-nextjs/src/entries/main"], "@datadog/browser-worker": ["./packages/worker/src/entries/main"] diff --git a/yarn.lock b/yarn.lock index 64169689d4..cce5ee9db2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,50 +34,71 @@ __metadata: languageName: node linkType: hard -"@angular/common@npm:19.2.5": - version: 19.2.5 - resolution: "@angular/common@npm:19.2.5" +"@angular/common@npm:17.3.12": + version: 17.3.12 + resolution: "@angular/common@npm:17.3.12" dependencies: tslib: "npm:^2.3.0" peerDependencies: - "@angular/core": 19.2.5 + "@angular/core": 17.3.12 rxjs: ^6.5.3 || ^7.4.0 - checksum: 10c0/0aed8a84a6655215948a2d739f7c8059aa1e9f2e83eac9220a76f776b47401538b26d1c35695926a262c7d838a96accf56a72a0611c77e0335cfed56eef132bf + checksum: 10c0/f40d76081b04bacb3efd4389b0aa4dcfa742a4954e38a76a434dab1ae226b3192241077be5d0032cb011cc1625cb04043449cd5956f8ba44da99f8f7a3bbe83a languageName: node linkType: hard -"@angular/compiler@npm:19.2.5": - version: 19.2.5 - resolution: "@angular/compiler@npm:19.2.5" +"@angular/compiler@npm:17.3.12": + version: 17.3.12 + resolution: "@angular/compiler@npm:17.3.12" dependencies: tslib: "npm:^2.3.0" - checksum: 10c0/e65f84ef2d53eb1250d3085074eb1b3fbd8d3f8f9603e32be67c82194ae4f8a4a98f250d954f830a67e68c4d2f3b3d01da5c0f49157cc1746064a37d14b74a76 + peerDependencies: + "@angular/core": 17.3.12 + peerDependenciesMeta: + "@angular/core": + optional: true + checksum: 10c0/e044b2cdd30deb43eda84b49ec0c9261d1745517927b0a7eacf0f1390ba9c8463a78945e10d888f209344085cde6d99495431f4c5401af412876e0e2141797b3 languageName: node linkType: hard -"@angular/core@npm:19.2.5": - version: 19.2.5 - resolution: "@angular/core@npm:19.2.5" +"@angular/core@npm:17.3.12": + version: 17.3.12 + resolution: "@angular/core@npm:17.3.12" dependencies: tslib: "npm:^2.3.0" peerDependencies: rxjs: ^6.5.3 || ^7.4.0 - zone.js: ~0.15.0 - checksum: 10c0/102efc14698c9adea29af9ddc81297e8d41d061e3148a640713c2cdff0f91cf43b161b02790ab21f1bb551a0685d023b5a63e30740a62dcfde8a4c6e6c41d496 + zone.js: ~0.14.0 + checksum: 10c0/0a2119daf7efdb81660c81dee1476b14e1f5456aee94e3f504cfbc1d98107dd7716374f9594837a5789d004b5c4b49aa7c553ef0dc87797cd581628bbfcd6858 + languageName: node + linkType: hard + +"@angular/platform-browser@npm:17.3.12": + version: 17.3.12 + resolution: "@angular/platform-browser@npm:17.3.12" + dependencies: + tslib: "npm:^2.3.0" + peerDependencies: + "@angular/animations": 17.3.12 + "@angular/common": 17.3.12 + "@angular/core": 17.3.12 + peerDependenciesMeta: + "@angular/animations": + optional: true + checksum: 10c0/ef0066f481920b4c778f7b342145790a6486f59faba4b88ad3216edb9e207f9a25d607d90e5b911654d092bf772261ab31de3c918a7b73fe8a507bdb6bfe54e1 languageName: node linkType: hard -"@angular/router@npm:19.2.5": - version: 19.2.5 - resolution: "@angular/router@npm:19.2.5" +"@angular/router@npm:17.3.12": + version: 17.3.12 + resolution: "@angular/router@npm:17.3.12" dependencies: tslib: "npm:^2.3.0" peerDependencies: - "@angular/common": 19.2.5 - "@angular/core": 19.2.5 - "@angular/platform-browser": 19.2.5 + "@angular/common": 17.3.12 + "@angular/core": 17.3.12 + "@angular/platform-browser": 17.3.12 rxjs: ^6.5.3 || ^7.4.0 - checksum: 10c0/5eecadb52552633603fce5e5415c37275eb44451d2591fb78f9dd85fc5015a1d65014989b89e5fbdf9eacfc39420804b11c9d4774c28f3508bce4fee129c9f26 + checksum: 10c0/b7d832804daa6c0777a2a09b4a53ae8c797e92e4224cc089bf8c05fb767b714c64e9feecaa5f0d4e3f72e86c487eddb23ebc0dc7a1a960b24e0ce96db99b4610 languageName: node linkType: hard @@ -343,17 +364,34 @@ __metadata: version: 0.0.0-use.local resolution: "@datadog/browser-rum-angular@workspace:packages/rum-angular" dependencies: - "@angular/common": "npm:19.2.5" - "@angular/compiler": "npm:19.2.5" - "@angular/core": "npm:19.2.5" - "@angular/router": "npm:19.2.5" + "@angular/common": "npm:17.3.12" + "@angular/compiler": "npm:17.3.12" + "@angular/core": "npm:17.3.12" + "@angular/platform-browser": "npm:17.3.12" + "@angular/router": "npm:17.3.12" "@datadog/browser-core": "npm:6.32.0" "@datadog/browser-rum-core": "npm:6.32.0" - rxjs: "npm:7.8.2" + rxjs: "npm:7.8.1" + tslib: "npm:2.8.1" + zone.js: "npm:0.14.10" peerDependencies: - "@angular/core": ">=15 <=21" - "@angular/router": ">=15 <=21" - rxjs: ">=7" + "@angular/common": ">=17.0.0" + "@angular/core": ">=17.0.0" + "@angular/router": ">=17.0.0" + rxjs: ">=7.0.0" + peerDependenciesMeta: + "@angular/common": + optional: true + "@angular/core": + optional: true + "@angular/router": + optional: true + "@datadog/browser-rum": + optional: true + "@datadog/browser-rum-slim": + optional: true + rxjs: + optional: true languageName: unknown linkType: soft @@ -10552,12 +10590,12 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:7.8.2": - version: 7.8.2 - resolution: "rxjs@npm:7.8.2" +"rxjs@npm:7.8.1": + version: 7.8.1 + resolution: "rxjs@npm:7.8.1" dependencies: tslib: "npm:^2.1.0" - checksum: 10c0/1fcd33d2066ada98ba8f21fcbbcaee9f0b271de1d38dc7f4e256bfbc6ffcdde68c8bfb69093de7eeb46f24b1fb820620bf0223706cff26b4ab99a7ff7b2e2c45 + checksum: 10c0/3c49c1ecd66170b175c9cacf5cef67f8914dcbc7cd0162855538d365c83fea631167cacb644b3ce533b2ea0e9a4d0b12175186985f89d75abe73dbd8f7f06f68 languageName: node linkType: hard @@ -11758,7 +11796,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.4.0, tslib@npm:^2.8.0": +"tslib@npm:2.8.1, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.4.0, tslib@npm:^2.8.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 @@ -13065,3 +13103,10 @@ __metadata: checksum: 10c0/5718ec35e3c40b600316c5b4c5e4976f7fee68151bc8f8d90ec18a469be9571f072e1bbaace10f1e85cf8892ea12d90821b200e980ab46916a6166a4260a983c languageName: node linkType: hard + +"zone.js@npm:0.14.10": + version: 0.14.10 + resolution: "zone.js@npm:0.14.10" + checksum: 10c0/61283d152cb1eff899bae61621dccd572aa9f47e0c60c04b249bf86b43e3e4ba627bf6dba371b725023a4f302f39e554d7bf2d25bbf40c869c6c52f774b17e8b + languageName: node + linkType: hard