diff --git a/.claude/commands/spec-drift-changed.md b/.claude/commands/spec-drift-changed.md new file mode 100644 index 000000000..be63b7b6c --- /dev/null +++ b/.claude/commands/spec-drift-changed.md @@ -0,0 +1,164 @@ +# Spec Drift Detection — Changed Files Only + +Validate ai-docs affected by any staged/unstaged changes — whether the changes are to ai-docs themselves OR to source code that has corresponding ai-docs. Lightweight mode for pre-commit validation. + +## Step 1: Find All Changed Files + +Run these commands to find all changed files (staged and unstaged): + +```bash +# Get both staged and unstaged changed files +(git diff --name-only HEAD 2>/dev/null; git diff --name-only --cached 2>/dev/null) | sort -u +``` + +## Step 2: Identify ai-docs That Need Validation + +From the changed files, build TWO lists: + +### List A: Changed ai-docs files +Filter for files matching `ai-docs/*.md`. These docs changed directly and need validation. + +### List B: Source code files with corresponding ai-docs +For each changed source file under `packages/contact-center/` (excluding ai-docs files themselves): +1. Walk up the file's directory path +2. Check if any ancestor directory contains an `ai-docs/` folder +3. If yes, that `ai-docs/` folder needs validation against the updated source code + +Use this to discover ai-docs folders: +```bash +find packages/contact-center -type d -name "ai-docs" +``` + +Build a mapping like: +``` +Changed source file → ai-docs to validate +packages/contact-center/store/src/store.ts → packages/contact-center/store/ai-docs/ +packages/contact-center/task/src/widgets/CallControl/CallControl.tsx → packages/contact-center/task/ai-docs/widgets/CallControl/ +packages/contact-center/cc-components/src/SomeComponent.tsx → packages/contact-center/cc-components/ai-docs/ +``` + +For task widget source files, map to the widget-specific ai-docs dynamically: +- `packages/contact-center/task/src/widgets/{WidgetName}/*` → `packages/contact-center/task/ai-docs/widgets/{WidgetName}/` (if the ai-docs folder exists) + +This covers all current and future widgets (e.g., CallControl, IncomingTask, OutdialCall, TaskList, and any new widgets added later). + +**Deduplicate**: If multiple source files map to the same ai-docs folder, validate that folder only once. + +### Combine Lists A and B +The final set of ai-docs folders to validate is the union of: +- Folders containing files from List A +- Folders identified from List B + +If NEITHER list has entries, report: **"No ai-docs affected by current changes — nothing to check."** and stop. + +## Step 3: Validate Affected ai-docs + +For each ai-docs folder that needs validation, spawn an Explore agent with this prompt: + +``` +You are validating SDD documentation accuracy against source code. + +SOURCE OF TRUTH (actual code): {source_code_directory} +DOCS TO VALIDATE: {ai_docs_folder} (all .md files in this folder) + +CHANGED SOURCE FILES (if any): {list of changed source files in this package} +CHANGED DOC FILES (if any): {list of changed ai-docs files} + +Read every markdown file in the ai-docs folder. For each document, check these 7 categories: + +1. FILE TREE: Read any documented file/directory trees. Glob the actual directory. Report missing/extra files. + +2. METHOD/API SIGNATURES: For every method documented, read the actual source and verify: name, params, param types, return type, modifiers. Flag any mismatch. Pay special attention to methods in changed source files — new methods may be missing from docs, or changed signatures may not be reflected. + +3. TYPE DEFINITIONS: For every type/enum/interface documented, find the actual definition in source. Compare name, fields, field types, enum values. Check if changed source files introduced new types not yet documented. + +4. EVENT NAMES: For every event constant or observable referenced, verify it exists in source with the exact name. For MobX: verify @observable/@computed/@action decorators. For React: verify prop callback names. Check if changed source files added new events not yet documented. + +5. ARCHITECTURE PATTERNS: Verify claims about MobX store patterns, React component patterns, singleton vs factory, component hierarchy, store injection. + +6. LINK VALIDATION: For every relative link [text](path), verify the target exists on disk. + +7. CODE EXAMPLES: For every code block, verify API names, method names, parameter names, import paths, MobX patterns are correct. + +For each finding, report: +- File: (path) +- Line/Section: (approximate line or section heading) +- Category: (1-7) +- Severity: Blocking / Important / Medium / Minor + - Blocking = wrong API that would cause runtime errors if an AI agent follows the docs + - Important = wrong params/types that would cause compilation errors + - Medium = incomplete or stale info (e.g., new methods/types/events missing from docs) + - Minor = broken links, cosmetic issues +- What doc says: (quoted) +- What code actually has: (evidence with file:line) +- Suggested fix: (exact replacement text) +``` + +Run all agents in parallel if multiple ai-docs folders are affected. + +## Step 4: Consolidate and Report + +Present findings in this format: + +```markdown +## Spec Drift Report — Changed Files +Generated: {date} +Trigger: {source code changes / ai-docs changes / both} +ai-docs folders checked: {list} + +### Changed Source Files +{list of changed source files and their mapped ai-docs folder} + +### Changed ai-docs Files +{list of changed ai-docs files, or "None"} + +### Summary + +| ai-docs Folder | Findings | Blocking | Important | Medium | Minor | +|----------------|----------|----------|-----------|--------|-------| +| ... | | | | | | + +### Blocking Findings +... + +### Important Findings +... + +### Medium Findings +... + +### Minor Findings +... + +### Actionable Fixes by File +(grouped by file path, each with exact old text -> new text) +``` + +## Step 5: Create Verification Marker + +After presenting the validation report (regardless of findings), create a verification marker so the pre-commit hook allows the commit: + +```bash +# Hash staged content (not just paths) — must match the hook's hash logic exactly +CC_PKG="packages/contact-center" +STAGED_CC=$(git diff --cached --name-only 2>/dev/null | grep "^${CC_PKG}/") +if [ -n "$STAGED_CC" ]; then + HASH=$(git diff --cached -- "$CC_PKG" | (shasum 2>/dev/null || sha256sum) | cut -d' ' -f1) + touch "/tmp/.spec-drift-verified-${HASH}" + echo "Verification marker created: /tmp/.spec-drift-verified-${HASH}" +fi +``` + +> **Note:** The verification marker covers only currently **staged** content. If you modify and re-stage files after verification, the content hash changes and you will need to re-run `/spec-drift-changed`. + +Report to the user: "Verification marker created. The pre-commit hook will allow the next commit for these staged files." + +## Rules + +- Do NOT auto-fix anything — report findings only +- Always read actual source code to verify — never assume +- Use the Agent tool with `subagent_type: "Explore"` for checker agents +- Run agents in parallel when multiple folders are affected +- If an agent does not return within a reasonable time, note it as "Timed out — manual review needed" in the report and continue with available results +- Always create the verification marker at the end, even if there are findings — this tool is **advisory**: the developer decides whether to fix or commit as-is +- The marker hash MUST match the hook's hash computation — both use `git diff --cached` content (not just file paths) diff --git a/.claude/commands/spec-drift.md b/.claude/commands/spec-drift.md new file mode 100644 index 000000000..1d5a834c1 --- /dev/null +++ b/.claude/commands/spec-drift.md @@ -0,0 +1,200 @@ +# Spec Drift Detection — Full Scan + +Run a comprehensive validation of all SDD ai-docs against actual source code. Deploys a parallel agent team to catch documentation drift across 7 categories. + +## Step 1: Auto-Discovery + +Detect repo type and discover all ai-docs: + +1. **Repo detection**: This is ccWidgets — `packages/contact-center/` exists (no `@webex` scope) +2. **Root AGENTS.md**: `AGENTS.md` (repo root) +3. **Framework docs**: `ai-docs/` (README, RULES, patterns/*, templates/*) +4. **Package-level ai-docs**: Glob for `packages/contact-center/**/ai-docs/` to find all ai-docs folders +5. **Samples ai-docs**: Check `widgets-samples/**/ai-docs/` as well + +For each ai-docs folder found, identify its corresponding source code directory (the parent directory of `ai-docs/`). + +Build an inventory (example — actual results will vary based on current branch): +``` +ai-docs folder → source directory +packages/contact-center/store/ai-docs/ → packages/contact-center/store/src/ +packages/contact-center/cc-components/ai-docs/ → packages/contact-center/cc-components/src/ +packages/contact-center/cc-widgets/ai-docs/ → packages/contact-center/cc-widgets/src/ +packages/contact-center/station-login/ai-docs/ → packages/contact-center/station-login/src/ +packages/contact-center/task/ai-docs/widgets/CallControl/ → packages/contact-center/task/src/widgets/CallControl/ +packages/contact-center/task/ai-docs/widgets/IncomingTask/ → packages/contact-center/task/src/widgets/IncomingTask/ +packages/contact-center/task/ai-docs/widgets/OutdialCall/ → packages/contact-center/task/src/widgets/OutdialCall/ +packages/contact-center/task/ai-docs/widgets/TaskList/ → packages/contact-center/task/src/widgets/TaskList/ +packages/contact-center/user-state/ai-docs/ → packages/contact-center/user-state/src/ +packages/contact-center/ui-logging/ai-docs/ → packages/contact-center/ui-logging/src/ +packages/contact-center/test-fixtures/ai-docs/ → packages/contact-center/test-fixtures/src/ +widgets-samples/cc/samples-cc-react-app/ai-docs/ → widgets-samples/cc/samples-cc-react-app/src/ +... (discover all that exist on the current branch) +``` + +## Step 2: Spawn Checker Agents in Parallel + +Use the Agent tool to spawn agents. **All agents run in parallel.** + +### Per-Package Checker Agents (one per ai-docs folder) + +For EACH ai-docs folder discovered, spawn one Explore agent with this prompt: + +``` +You are validating SDD documentation accuracy. + +SOURCE OF TRUTH (actual code): {source_code_directory} +DOCS TO VALIDATE: {ai_docs_folder} + +Read every markdown file in the ai-docs folder. For each document, check these 7 categories: + +### Category 1: FILE TREE +Read any documented file/directory trees in the docs. Glob the actual directory. Report: +- Files listed in docs but missing on disk +- Files on disk but missing from docs +- Wrong nesting or directory structure + +### Category 2: METHOD/API SIGNATURES +For every method, function, or API endpoint documented: +- Read the actual source file +- Verify: method name, parameter names, parameter types, return type, access modifiers (public/private/static) +- Check if method actually exists in the documented file +- Flag any param that is documented but doesn't exist, or exists but isn't documented + +### Category 3: TYPE DEFINITIONS +For every type, enum, interface, or constant documented: +- Find the actual definition in source (check package-level types files and shared types) +- Compare: name, fields/members, field types, enum values +- Flag missing fields, wrong types, renamed types + +### Category 4: EVENT NAMES +For every event constant or observable referenced: +- Find the actual definition in source +- Verify the exact name matches +- For MobX observables: verify @observable, @computed, @action decorators match docs +- For React events: verify prop callback names match + +### Category 5: ARCHITECTURE PATTERNS +For claims about architectural patterns, verify: +- MobX store patterns: Are @observable, @action, @computed correctly documented? +- React component patterns: Are props, state, lifecycle methods correct? +- Singleton vs factory: Is the instantiation pattern correct? +- Component hierarchy: Are parent-child relationships correct? +- Store injection patterns: Are MobX store injections accurately described? + +### Category 6: LINK VALIDATION +For every relative markdown link [text](path): +- Resolve the path relative to the document's location +- Verify the target file exists on disk +- For anchor links (#section), verify the heading exists in the target + +### Category 7: CODE EXAMPLES +For every inline code block or code snippet: +- Verify API names, method names, parameter names are correct +- Verify import paths are valid +- Check that documented usage patterns match actual API signatures +- Verify MobX patterns use correct decorators and patterns + +## Output Format + +For each finding, report: +- **File**: (path to the ai-docs file with the issue) +- **Line/Section**: (approximate line number or section heading) +- **Category**: (1-7 from above) +- **Severity**: + - Blocking = wrong API that would cause runtime errors if AI agent follows the docs + - Important = wrong params/types that would cause compilation errors + - Medium = incomplete or stale info that would cause confusion + - Minor = broken links, cosmetic issues +- **What doc says**: (quoted text from the doc) +- **What code actually has**: (evidence from source, with file path and line) +- **Suggested fix**: (exact replacement text) + +If no issues found in a category, state "No issues found" for that category. +``` + +### Framework Agent + +Spawn one additional Explore agent for root-level framework validation: + +``` +Validate the root-level SDD framework documents for ccWidgets: + +1. **Root AGENTS.md** (repo root AGENTS.md): + - Package Routing Table: Every package listed must exist on disk at the documented path + - Every actual package directory under packages/contact-center/ should be listed + - Task classification types must be consistent with template directories that exist + - Quick Start Workflow steps must reference files that exist + +2. **ai-docs/RULES.md**: + - Test commands: Verify documented commands are correct + - Naming conventions: Verify claims against actual code + - Pattern references: All referenced patterns should exist + +3. **ai-docs/README.md**: + - File tree must match actual ai-docs directory structure + - All referenced documents must exist + +4. **ai-docs/patterns/*.md** (mobx-patterns, react-patterns, testing-patterns, typescript-patterns): + - Each pattern file's code examples must match actual source conventions + - MobX patterns must match actual decorator usage in stores + - React patterns must match actual component patterns + - Test patterns must reference correct commands and configs + +5. **ai-docs/templates/**: + - Cross-references to AGENTS.md sections must be valid + - Referenced file paths in templates must exist + - Workflow steps must be internally consistent + +For each finding, report: +- **File**: (path) +- **Line/Section**: (section heading or line) +- **Category**: (1-7: File Tree, Method/API, Type Definition, Event Name, Architecture Pattern, Link Validation, Code Example) +- **Severity**: Blocking / Important / Medium / Minor +- **What doc says**: (quoted) +- **What code actually has**: (evidence with file:line) +- **Suggested fix**: (replacement text) +``` + +## Step 3: Consolidate Results + +After ALL agents complete, consolidate into this report format: + +```markdown +## Spec Drift Report — ccWidgets +Generated: {date} +Scanned: {N} ai-docs folders, {M} documents + +### Summary + +| ai-docs Folder | Findings | Blocking | Important | Medium | Minor | +|----------------|----------|----------|-----------|--------|-------| +| (each folder) | | | | | | +| framework | | | | | | +| **Total** | **N** | | | | | + +### Blocking Findings +(must fix — wrong APIs that would cause runtime errors if AI agent follows the docs) + +### Important Findings +(wrong params, signatures, types — would cause compilation errors) + +### Medium Findings +(incomplete info, stale file trees — would cause confusion) + +### Minor Findings +(broken links, cosmetic issues) + +### Actionable Fixes by File +(grouped by file path, each with exact old text -> new text) +``` + +## Rules + +- Do NOT auto-fix anything — report findings only +- Always read actual source code to verify — never assume +- Use the Agent tool with `subagent_type: "Explore"` for all checker agents +- Run all agents in parallel for speed +- If an agent does not return within a reasonable time, note it as "Timed out — manual review needed" in the report and continue with available results +- If an ai-docs folder has no corresponding source directory, flag it as a Category 1 (File Tree) finding +- Count findings by severity in the summary table diff --git a/.claude/commands/sync-sdk.md b/.claude/commands/sync-sdk.md new file mode 100644 index 000000000..573242fb0 --- /dev/null +++ b/.claude/commands/sync-sdk.md @@ -0,0 +1,52 @@ +Sync SDK dependencies and generate an impact report. + +## What this does + +1. Reads the SDK manifest from the local SDK checkout (via node_modules symlink or fallback local path) +2. Compares it against the cached previous manifest (if any) in `.sdk-cache/` +3. Regenerates `sdk-dependencies.yaml` by scanning all consumer source files +4. Cross-references manifest changes with the dependency map +5. Outputs a precise impact report showing which files need updating + +## Steps + +1. Run: `npx ts-node --project scripts/tsconfig.json scripts/generate-sdk-deps.ts` +2. Check if `.sdk-cache/@webex/contact-center.manifest.yaml` exists (previous cached manifest) +3. Read the current manifest from the SDK (via `node_modules/@webex/contact-center/sdk-manifest.yaml` or the local ccSDK path) +4. If a cached version exists, diff the two manifests: + - Find added/removed/changed methods + - Find added/removed/changed events + - Find added/removed/changed types +5. For each change, look up affected files in `sdk-dependencies.yaml` +6. Output the impact report in a structured format +7. Cache the current manifest to `.sdk-cache/@webex/contact-center.manifest.yaml` for future diffing + +## Impact Report Format + +``` +SDK Sync Report — @webex/contact-center +Previous: v -> Current: v + +=== BREAKING CHANGES === +1. Method: + Change: + Affected files: + -> : () + Action: + +=== ADDITIONS (non-breaking) === +2. Method: added + No current consumers. + +=== SUMMARY === +Breaking changes: N (affects M files) +Additions: N +Removals: N +``` + +## When to run + +- After bumping the SDK version in package.json +- After the SDK team notifies of API changes +- Before starting work that depends on SDK APIs +- Periodically to check for drift diff --git a/.claude/hooks/check-ai-docs-drift.sh b/.claude/hooks/check-ai-docs-drift.sh new file mode 100755 index 000000000..1c667d691 --- /dev/null +++ b/.claude/hooks/check-ai-docs-drift.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# PreToolUse hook: Block git commit if contact-center code/docs changed without spec-drift verification +# +# Flow: +# 1. Read stdin to get the Bash command (JSON at tool_input.command) +# 2. If python3 unavailable or JSON parse fails -> exit 2 (fail-closed) +# 3. If command is NOT git commit -> exit 0 (allow immediately) +# 4. Check if ANY staged files are under packages/contact-center/ +# 5. If none -> exit 0 (allow) +# 6. Check for verification marker (created by /spec-drift-changed) +# 7. If marker exists -> exit 0 (allow — marker stays until content changes) +# 8. If no marker -> exit 2 (BLOCK, instruct to run /spec-drift-changed) + +CC_PKG="packages/contact-center" + +# Ensure python3 is available +if ! command -v python3 >/dev/null 2>&1; then + echo "ERROR: python3 is required for the spec-drift pre-commit hook but was not found." + echo "Install python3 or remove this hook from .claude/settings.json to proceed." + exit 2 +fi + +# Read tool input from stdin (JSON with tool_input.command) +INPUT=$(cat) +COMMAND=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))" 2>/dev/null) + +# Fail-closed: if we couldn't parse the command, block rather than silently allow +if [ -z "$COMMAND" ]; then + echo "ERROR: Could not parse tool input. Blocking commit as a safety measure." + exit 2 +fi + +# Only gate git commit commands (precise match to avoid catching git commit-tree, etc.) +case "$COMMAND" in + "git commit"|git\ commit\ *) ;; # Continue to check + *) exit 0 ;; # Not a commit, allow immediately +esac + +# Get staged files under the contact-center package +STAGED_CC=$(git diff --cached --name-only 2>/dev/null | grep "^${CC_PKG}/") + +if [ -z "$STAGED_CC" ]; then + exit 0 # No contact-center files staged, allow commit +fi + +# Compute hash from staged content (not just paths) to detect re-staged changes +HASH=$(git diff --cached -- "$CC_PKG" | (shasum 2>/dev/null || sha256sum) | cut -d' ' -f1) +MARKER="/tmp/.spec-drift-verified-${HASH}" + +if [ -f "$MARKER" ]; then + exit 0 # Verified, allow commit (marker stays — invalidated naturally when content changes) +fi + +# Block the commit +echo "BLOCKED: contact-center files are staged but ai-docs have not been verified for spec drift." +echo "" +echo "Staged contact-center files:" +echo "$STAGED_CC" | sed 's/^/ - /' +echo "" +echo "Run /spec-drift-changed to validate ai-docs against source code before committing." +echo "The command will check documentation accuracy and create a verification marker." +exit 2 # Exit code 2 = blocking error in Claude Code hooks diff --git a/.claude/hooks/check-sdk-version-change.sh b/.claude/hooks/check-sdk-version-change.sh new file mode 100755 index 000000000..49185e45c --- /dev/null +++ b/.claude/hooks/check-sdk-version-change.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# PostToolUse hook: Detect SDK version changes in package.json files +# When a Write/Edit modifies a package.json that contains @webex/contact-center, +# remind the developer to run /sync-sdk. + +# Read tool input from stdin +INPUT=$(cat) + +# Extract the file path from the tool input +FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_response.filePath // empty' 2>/dev/null) + +# Only care about package.json files +if [[ "$FILE_PATH" != *"package.json" ]]; then + exit 0 +fi + +# Check if this package.json has an SDK dependency +if grep -q "@webex/contact-center" "$FILE_PATH" 2>/dev/null; then + # Check if the SDK version line was part of the change + # Use git diff to see if the version actually changed + SDK_CHANGED=$(git diff --no-index /dev/null "$FILE_PATH" 2>/dev/null | grep -c "@webex/contact-center" || true) + + if [ "$SDK_CHANGED" -gt 0 ]; then + echo '{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"SDK dependency @webex/contact-center detected in modified package.json. Remind the developer: Run /sync-sdk to check for breaking changes and regenerate the dependency map."}}' + exit 0 + fi +fi + +exit 0 diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 000000000..1a0518cfd --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,10 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [".claude/hooks/check-ai-docs-drift.sh"] + } + ] + } +} diff --git a/.gitignore b/.gitignore index 5f35c58cb..452996168 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,9 @@ node_modules/ .yarn/* !.yarn/releases +# SDK sync cache (local dev only) +.sdk-cache/ + # Playwright reports **/test-results/ **/playwright-report diff --git a/AGENTS.md b/AGENTS.md index 70963b493..8ebb32378 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -609,6 +609,34 @@ ccWidgets/ --- +## SDK Sync (Cross-Repo Dependency Tracking) + +This repo tracks all `@webex/contact-center` SDK API usages via `sdk-dependencies.yaml`. +When the SDK changes, run `/sync-sdk` to see which files are affected. + +### When to run +- After bumping SDK version in `package.json` +- After the SDK team notifies of API changes +- Before starting work that depends on SDK APIs + +### How to run +```bash +npm run generate:sdk-deps +``` +Or use the Claude Code skill: `/sync-sdk` + +### What it produces +`sdk-dependencies.yaml` — maps each SDK import to exact file + line number. + +### SDK API Verification (MANDATORY for AI agents) +Before generating code that calls `@webex/contact-center`: +1. Read the SDK manifest: `node_modules/@webex/contact-center/sdk-manifest.yaml` +2. Verify the method signature matches what you're about to write +3. Check `sdk-dependencies.yaml` for existing usage patterns +4. NEVER assume SDK method signatures from memory — always verify + +--- + ## Related Documentation - **Repository Rules:** [RULES.md](./RULES.md) diff --git a/package.json b/package.json index 78c839418..0f2e63809 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^11.0.1", "@testing-library/react-hooks": "^8.0.1", + "@types/js-yaml": "^4.0.9", + "@types/node": "^25.5.0", "@webex/cli-tools": "0.0.0-next.2", "@webex/package-tools": "0.0.0-next.6", "babel-jest": "^29.7.0", @@ -28,6 +30,7 @@ "husky": "^9.1.7", "jest": "29.7.0", "jest-canvas-mock": "^2.5.2", + "js-yaml": "^4.1.1", "node-gyp": "^10.2.0", "nodemailer": "^7.0.3", "os-browserify": "^0.3.0", @@ -38,6 +41,8 @@ "semantic-release": "^24.2.0", "stream-browserify": "^3.0.0", "style-loader": "^4.0.0", + "ts-morph": "^27.0.2", + "ts-node": "^10.9.2", "typescript": "^5.6.3", "vm-browserify": "^1.1.2", "webpack": "^5.96.1", @@ -67,6 +72,7 @@ "release:widgets": "semantic-release", "postinstall": "husky install", "prepare": "husky", - "package-tools": "webex-package-tools" + "package-tools": "webex-package-tools", + "generate:sdk-deps": "ts-node --project scripts/tsconfig.json scripts/generate-sdk-deps.ts" } } diff --git a/scripts/generate-sdk-deps.ts b/scripts/generate-sdk-deps.ts new file mode 100644 index 000000000..a0babf596 --- /dev/null +++ b/scripts/generate-sdk-deps.ts @@ -0,0 +1,437 @@ +/** + * Consumer SDK Dependency Map Generator + * + * Scans the ccWidgets monorepo for all imports from tracked SDK packages + * and produces sdk-dependencies.yaml mapping each SDK API to the exact + * consumer files and line numbers that use it. + * + * Usage: npx ts-node scripts/generate-sdk-deps.ts + */ + +import {Project, Node, SyntaxKind, SourceFile} from 'ts-morph'; +import * as yaml from 'js-yaml'; +import * as fs from 'fs'; +import * as path from 'path'; + +const REPO_ROOT = path.resolve(__dirname, '..'); +const OUTPUT_FILE = path.join(REPO_ROOT, 'sdk-dependencies.yaml'); + +// SDK dependency configuration — single source of truth. +// To track a new SDK, add one entry here. No other changes needed. +interface SDKConfigEntry { + package: string; + scanDirs: string[]; + localManifestPath: string; +} + +const SDK_CONFIG: SDKConfigEntry[] = [ + { + package: '@webex/contact-center', + scanDirs: ['packages/contact-center'], + localManifestPath: '../ccSDK/webex-js-sdk/packages/@webex/contact-center/sdk-manifest.yaml', + }, + // To add a new SDK: + // { + // package: '@webex/calling', + // scanDirs: ['packages/calling'], + // localManifestPath: '../ccSDK/webex-js-sdk/packages/calling/sdk-manifest.yaml', + // }, +]; + +// Skip test files, fixtures, ai-docs, and dist/build output +const SKIP_PATTERNS = [ + '/tests/', + '/test/', + '/__tests__/', + '/test-fixtures/', + '/ai-docs/', + '/dist/', + '/build/', + '/node_modules/', + '.test.ts', + '.test.tsx', + '.spec.ts', + '.spec.tsx', + '.d.ts', +]; + +interface UsageEntry { + file: string; + line: number; + context: string; +} + +interface MethodUsages { + usages: UsageEntry[]; +} + +interface EventUsages { + listeners: UsageEntry[]; +} + +interface TypeUsages { + imports: UsageEntry[]; +} + +interface SDKDependency { + version: string; + manifest_hash: string; + methods: Record; + events: Record; + types: Record; +} + +interface DependencyMap { + generated_at: string; + generator: string; + dependencies: Record; +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function shouldSkip(filePath: string): boolean { + return SKIP_PATTERNS.some((p) => filePath.includes(p)); +} + +function relativePath(absPath: string): string { + return path.relative(REPO_ROOT, absPath); +} + +function getLineContext(sourceFile: SourceFile, line: number): string { + const fullText = sourceFile.getFullText(); + const lines = fullText.split('\n'); + if (line > 0 && line <= lines.length) { + return lines[line - 1].trim().substring(0, 100); + } + return ''; +} + +/** + * Find the SDK manifest — checks node_modules first, then local path from config. + */ +function findManifestPath(config: SDKConfigEntry): string | null { + // 1. Check node_modules (works for both symlinked and npm-installed) + const nmPath = path.join(REPO_ROOT, 'node_modules', config.package, 'sdk-manifest.yaml'); + if (fs.existsSync(nmPath)) return nmPath; + + // 2. Check local path from config (for development when symlink is broken) + const localPath = path.resolve(REPO_ROOT, config.localManifestPath); + if (fs.existsSync(localPath)) return localPath; + + return null; +} + +/** + * Read the SDK manifest to get the version and compute a simple hash for staleness detection. + */ +function getManifestInfo(config: SDKConfigEntry): {version: string; hash: string} { + const manifestPath = findManifestPath(config); + if (manifestPath) { + const content = fs.readFileSync(manifestPath, 'utf8'); + const manifest = yaml.load(content) as Record; + // Simple hash: first 8 chars of content hash + const crypto = require('crypto'); + const hash = crypto.createHash('md5').update(content).digest('hex').substring(0, 8); + return { + version: (manifest.version as string) || 'unknown', + hash, + }; + } + return {version: 'unknown', hash: 'no-manifest'}; +} + +/** + * Load the SDK manifest to know which exports are methods vs types vs events. + */ +function loadManifest(config: SDKConfigEntry): {methods: Set; types: Set; events: Set} | null { + const manifestPath = findManifestPath(config); + if (!manifestPath) return null; + + const content = fs.readFileSync(manifestPath, 'utf8'); + const manifest = yaml.load(content) as Record; + + const methods = new Set(); + const types = new Set(); + const events = new Set(); + + // Extract class method names + const classes = (manifest.classes || {}) as Record}>; + for (const [className, cls] of Object.entries(classes)) { + for (const methodName of Object.keys(cls.methods || {})) { + methods.add(`${className}.${methodName}`); + methods.add(methodName); // Also track bare method name + } + } + + // Extract type names + const typeEntries = (manifest.types || {}) as Record; + for (const typeName of Object.keys(typeEntries)) { + types.add(typeName); + } + + // Extract event string values + const eventEntries = (manifest.events || {}) as Record>; + for (const enumGroup of Object.values(eventEntries)) { + for (const eventValue of Object.values(enumGroup)) { + events.add(eventValue); + } + } + + return {methods, types, events}; +} + +// --------------------------------------------------------------------------- +// Main scanner +// --------------------------------------------------------------------------- + +function generate(): void { + console.log('Loading TypeScript project...'); + + const project = new Project({ + tsConfigFilePath: path.join(REPO_ROOT, 'tsconfig.json'), + skipAddingFilesFromTsConfig: true, + }); + + // Add source files from all configured scan directories + const allScanDirs = new Set(); + for (const config of SDK_CONFIG) { + for (const dir of config.scanDirs) { + allScanDirs.add(dir); + } + } + for (const dir of allScanDirs) { + const absDir = path.join(REPO_ROOT, dir); + project.addSourceFilesAtPaths([ + `${absDir}/**/*.ts`, + `${absDir}/**/*.tsx`, + ]); + } + + const sourceFiles = project.getSourceFiles().filter((sf) => !shouldSkip(sf.getFilePath())); + console.log(`Scanning ${sourceFiles.length} source files...`); + + const depMap: DependencyMap = { + generated_at: new Date().toISOString(), + generator: 'generate-sdk-deps.ts v1.0', + dependencies: {}, + }; + + // Initialize dependency entries for each tracked SDK + for (const config of SDK_CONFIG) { + const manifestInfo = getManifestInfo(config); + depMap.dependencies[config.package] = { + version: manifestInfo.version, + manifest_hash: manifestInfo.hash, + methods: {}, + events: {}, + types: {}, + }; + } + + // Load manifest for classification + const manifests: Record> = {}; + for (const config of SDK_CONFIG) { + manifests[config.package] = loadManifest(config); + } + + // Build lookup: package name -> set of all tracked packages + const trackedPackages = new Set(SDK_CONFIG.map(c => c.package)); + + for (const sourceFile of sourceFiles) { + const filePath = sourceFile.getFilePath(); + const relPath = relativePath(filePath); + + // Find all import declarations from tracked SDKs + const imports = sourceFile.getImportDeclarations(); + + for (const imp of imports) { + const moduleSpecifier = imp.getModuleSpecifierValue(); + + // Check if this import is from a tracked SDK + const matchedSdk = [...trackedPackages].find((sdk) => + moduleSpecifier === sdk || moduleSpecifier.startsWith(sdk + '/') + ); + if (!matchedSdk) continue; + + const sdkDep = depMap.dependencies[matchedSdk]; + const manifest = manifests[matchedSdk]; + + // Process named imports + const namedImports = imp.getNamedImports(); + for (const named of namedImports) { + const importName = named.getName(); + const alias = named.getAliasNode()?.getText() || importName; + const line = named.getStartLineNumber(); + + // Classify: is this a type import or value import? + const isTypeOnly = imp.isTypeOnly() || named.isTypeOnly(); + + if (isTypeOnly || (manifest && manifest.types.has(importName) && !manifest.methods.has(importName))) { + // Type import + if (!sdkDep.types[importName]) { + sdkDep.types[importName] = {imports: []}; + } + sdkDep.types[importName].imports.push({ + file: relPath, + line, + context: getLineContext(sourceFile, line), + }); + } else { + // Value import — could be a class, enum, or constant + // Record the import itself + if (!sdkDep.types[importName]) { + sdkDep.types[importName] = {imports: []}; + } + sdkDep.types[importName].imports.push({ + file: relPath, + line, + context: getLineContext(sourceFile, line), + }); + + // Now find all usages of this imported symbol in the file + const localName = alias; + + // Find method calls: localName.methodName() or localName() + const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression); + for (const call of callExpressions) { + const callText = call.getExpression().getText(); + + // Match: instance.method() patterns + if (callText.startsWith(localName + '.')) { + const methodName = callText.replace(localName + '.', '').split('(')[0]; + const qualifiedName = `${importName}.${methodName}`; + const callLine = call.getStartLineNumber(); + + if (!sdkDep.methods[qualifiedName]) { + sdkDep.methods[qualifiedName] = {usages: []}; + } + sdkDep.methods[qualifiedName].usages.push({ + file: relPath, + line: callLine, + context: getLineContext(sourceFile, callLine), + }); + } + } + + // Find event listener registrations: .on('eventName', ...) or .on(ENUM.VALUE, ...) + for (const call of callExpressions) { + const expr = call.getExpression(); + if (!Node.isPropertyAccessExpression(expr)) continue; + const methodName = expr.getName(); + if (methodName !== 'on' && methodName !== 'off' && methodName !== 'once') continue; + + const obj = expr.getExpression().getText(); + if (obj !== localName && !obj.endsWith('.' + localName)) continue; + + const args = call.getArguments(); + if (args.length === 0) continue; + + const firstArg = args[0]; + let eventName: string | null = null; + + if (Node.isStringLiteral(firstArg)) { + eventName = firstArg.getLiteralValue(); + } else { + eventName = firstArg.getText(); + } + + if (eventName) { + const callLine = call.getStartLineNumber(); + if (!sdkDep.events[eventName]) { + sdkDep.events[eventName] = {listeners: []}; + } + sdkDep.events[eventName].listeners.push({ + file: relPath, + line: callLine, + context: getLineContext(sourceFile, callLine), + }); + } + } + } + } + + // Process default import (e.g., import Webex from '@webex/contact-center') + const defaultImport = imp.getDefaultImport(); + if (defaultImport) { + const importName = defaultImport.getText(); + const line = defaultImport.getStartLineNumber(); + + if (!sdkDep.types[importName]) { + sdkDep.types[importName] = {imports: []}; + } + sdkDep.types[importName].imports.push({ + file: relPath, + line, + context: getLineContext(sourceFile, line), + }); + + // Track method calls on the default import + const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression); + for (const call of callExpressions) { + const callText = call.getExpression().getText(); + if (callText.startsWith(importName + '.')) { + const methodName = callText.replace(importName + '.', '').split('(')[0]; + const callLine = call.getStartLineNumber(); + const qualifiedName = `default.${methodName}`; + + if (!sdkDep.methods[qualifiedName]) { + sdkDep.methods[qualifiedName] = {usages: []}; + } + sdkDep.methods[qualifiedName].usages.push({ + file: relPath, + line: callLine, + context: getLineContext(sourceFile, callLine), + }); + } + } + } + } + } + + // Clean up empty entries + for (const config of SDK_CONFIG) { + const dep = depMap.dependencies[config.package]; + for (const [key, val] of Object.entries(dep.methods)) { + if (val.usages.length === 0) delete dep.methods[key]; + } + for (const [key, val] of Object.entries(dep.events)) { + if (val.listeners.length === 0) delete dep.events[key]; + } + for (const [key, val] of Object.entries(dep.types)) { + if (val.imports.length === 0) delete dep.types[key]; + } + } + + // Write YAML + const yamlContent = yaml.dump(depMap, { + lineWidth: 120, + noRefs: true, + sortKeys: false, + quotingType: '"', + forceQuotes: false, + }); + + fs.writeFileSync(OUTPUT_FILE, yamlContent, 'utf8'); + + // Summary + for (const [sdk, dep] of Object.entries(depMap.dependencies)) { + const methodCount = Object.keys(dep.methods).length; + const eventCount = Object.keys(dep.events).length; + const typeCount = Object.keys(dep.types).length; + const totalUsages = Object.values(dep.methods).reduce((s, m) => s + m.usages.length, 0) + + Object.values(dep.events).reduce((s, e) => s + e.listeners.length, 0) + + Object.values(dep.types).reduce((s, t) => s + t.imports.length, 0); + + console.log(`\n${sdk} (v${dep.version}):`); + console.log(` Methods: ${methodCount} (${Object.values(dep.methods).reduce((s, m) => s + m.usages.length, 0)} call sites)`); + console.log(` Events: ${eventCount} (${Object.values(dep.events).reduce((s, e) => s + e.listeners.length, 0)} listeners)`); + console.log(` Types: ${typeCount} (${Object.values(dep.types).reduce((s, t) => s + t.imports.length, 0)} imports)`); + console.log(` Total usages: ${totalUsages}`); + } + + console.log(`\nDependency map written: ${OUTPUT_FILE}`); +} + +generate(); diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 000000000..524229d59 --- /dev/null +++ b/scripts/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["*.ts"] +} diff --git a/sdk-dependencies.yaml b/sdk-dependencies.yaml new file mode 100644 index 000000000..658cf8657 --- /dev/null +++ b/sdk-dependencies.yaml @@ -0,0 +1,168 @@ +generated_at: "2026-03-26T09:05:57.957Z" +generator: generate-sdk-deps.ts v1.0 +dependencies: + "@webex/contact-center": + version: workspace + manifest_hash: 977fc0b8 + methods: + default.init: + usages: + - file: packages/contact-center/store/src/store.ts + line: 141 + context: const webex = Webex.init({ + events: {} + types: + LogoutSuccess: + imports: + - file: packages/contact-center/station-login/src/helper.ts + line: 2 + context: import {LogoutSuccess, AgentProfileUpdate, LoginOption, StationLoginSuccessResponse} from '@webex/co + - file: packages/contact-center/cc-components/src/components/StationLogin/station-login.types.ts + line: 1 + context: import {StationLoginSuccessResponse, LogoutSuccess} from '@webex/contact-center'; + AgentProfileUpdate: + imports: + - file: packages/contact-center/station-login/src/helper.ts + line: 2 + context: import {LogoutSuccess, AgentProfileUpdate, LoginOption, StationLoginSuccessResponse} from '@webex/co + LoginOption: + imports: + - file: packages/contact-center/station-login/src/helper.ts + line: 2 + context: import {LogoutSuccess, AgentProfileUpdate, LoginOption, StationLoginSuccessResponse} from '@webex/co + StationLoginSuccessResponse: + imports: + - file: packages/contact-center/station-login/src/helper.ts + line: 2 + context: import {LogoutSuccess, AgentProfileUpdate, LoginOption, StationLoginSuccessResponse} from '@webex/co + - file: packages/contact-center/cc-components/src/components/StationLogin/station-login.types.ts + line: 1 + context: import {StationLoginSuccessResponse, LogoutSuccess} from '@webex/contact-center'; + ITask: + imports: + - file: packages/contact-center/store/src/store.ts + line: 2 + context: import Webex, {ITask} from '@webex/contact-center'; + - file: packages/contact-center/store/src/store.types.ts + line: 6 + context: ITask, + - file: packages/contact-center/task/src/helper.ts + line: 2 + context: import {AddressBookEntriesResponse, AddressBookEntrySearchParams, ITask} from '@webex/contact-center + - file: packages/contact-center/task/src/Utils/task-util.ts + line: 13 + context: import {ITask, Interaction} from '@webex/contact-center'; + Webex: + imports: + - file: packages/contact-center/store/src/store.ts + line: 2 + context: import Webex, {ITask} from '@webex/contact-center'; + AgentLogin: + imports: + - file: packages/contact-center/store/src/store.types.ts + line: 2 + context: AgentLogin, + Profile: + imports: + - file: packages/contact-center/store/src/store.types.ts + line: 3 + context: Profile, + BuddyDetails: + imports: + - file: packages/contact-center/store/src/store.types.ts + line: 4 + context: BuddyDetails, + ContactServiceQueue: + imports: + - file: packages/contact-center/store/src/store.types.ts + line: 5 + context: ContactServiceQueue, + BuddyAgents: + imports: + - file: packages/contact-center/store/src/store.types.ts + line: 7 + context: BuddyAgents, + BuddyAgentsResponse: + imports: + - file: packages/contact-center/store/src/store.types.ts + line: 8 + context: BuddyAgentsResponse, + StateChange: + imports: + - file: packages/contact-center/store/src/store.types.ts + line: 9 + context: StateChange, + Logout: + imports: + - file: packages/contact-center/store/src/store.types.ts + line: 10 + context: Logout, + EntryPointRecord: + imports: + - file: packages/contact-center/store/src/store.types.ts + line: 11 + context: EntryPointRecord, + EntryPointListResponse: + imports: + - file: packages/contact-center/store/src/store.types.ts + line: 12 + context: EntryPointListResponse, + EntryPointSearchParams: + imports: + - file: packages/contact-center/store/src/store.types.ts + line: 13 + context: EntryPointSearchParams, + AddressBookEntry: + imports: + - file: packages/contact-center/store/src/store.types.ts + line: 14 + context: AddressBookEntry, + - file: packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.tsx + line: 4 + context: import {AddressBookEntry} from '@webex/contact-center'; + AddressBookEntriesResponse: + imports: + - file: packages/contact-center/store/src/store.types.ts + line: 15 + context: AddressBookEntriesResponse, + - file: packages/contact-center/task/src/helper.ts + line: 2 + context: import {AddressBookEntriesResponse, AddressBookEntrySearchParams, ITask} from '@webex/contact-center + AddressBookEntrySearchParams: + imports: + - file: packages/contact-center/store/src/store.types.ts + line: 16 + context: AddressBookEntrySearchParams, + - file: packages/contact-center/task/src/helper.ts + line: 2 + context: import {AddressBookEntriesResponse, AddressBookEntrySearchParams, ITask} from '@webex/contact-center + ContactServiceQueuesResponse: + imports: + - file: packages/contact-center/store/src/store.types.ts + line: 17 + context: ContactServiceQueuesResponse, + ContactServiceQueueSearchParams: + imports: + - file: packages/contact-center/store/src/store.types.ts + line: 18 + context: ContactServiceQueueSearchParams, + AddressBook: + imports: + - file: packages/contact-center/store/src/store.types.ts + line: 19 + context: AddressBook, + OutdialAniEntriesResponse: + imports: + - file: packages/contact-center/task/src/helper.ts + line: 25 + context: import {OutdialAniEntriesResponse} from '@webex/contact-center/dist/types/services/config/types'; + Interaction: + imports: + - file: packages/contact-center/task/src/Utils/task-util.ts + line: 13 + context: import {ITask, Interaction} from '@webex/contact-center'; + Team: + imports: + - file: packages/contact-center/cc-components/src/components/StationLogin/station-login.types.ts + line: 5 + context: import {Team} from '@webex/contact-center/dist/types/types'; diff --git a/yarn.lock b/yarn.lock index f6c2d52f0..cc43e0fcb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2700,6 +2700,15 @@ __metadata: languageName: node linkType: hard +"@cspotcode/source-map-support@npm:^0.8.0": + version: 0.8.1 + resolution: "@cspotcode/source-map-support@npm:0.8.1" + dependencies: + "@jridgewell/trace-mapping": "npm:0.3.9" + checksum: 10c0/05c5368c13b662ee4c122c7bfbe5dc0b613416672a829f3e78bc49a357a197e0218d6e74e7c66cfcd04e15a179acab080bd3c69658c9fbefd0e1ccd950a07fc6 + languageName: node + linkType: hard + "@dabh/diagnostics@npm:^2.0.2": version: 2.0.3 resolution: "@dabh/diagnostics@npm:2.0.3" @@ -3466,7 +3475,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:^3.1.0": +"@jridgewell/resolve-uri@npm:^3.0.3, @jridgewell/resolve-uri@npm:^3.1.0": version: 3.1.2 resolution: "@jridgewell/resolve-uri@npm:3.1.2" checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e @@ -3497,6 +3506,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/trace-mapping@npm:0.3.9": + version: 0.3.9 + resolution: "@jridgewell/trace-mapping@npm:0.3.9" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.0.3" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + checksum: 10c0/fa425b606d7c7ee5bfa6a31a7b050dd5814b4082f318e0e4190f991902181b4330f43f4805db1dd4f2433fd0ed9cc7a7b9c2683f1deeab1df1b0a98b1e24055b + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.20, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" @@ -7603,6 +7622,45 @@ __metadata: languageName: node linkType: hard +"@ts-morph/common@npm:~0.28.1": + version: 0.28.1 + resolution: "@ts-morph/common@npm:0.28.1" + dependencies: + minimatch: "npm:^10.0.1" + path-browserify: "npm:^1.0.1" + tinyglobby: "npm:^0.2.14" + checksum: 10c0/d51276d840997e0f8f83e04f8b1689135bb12588a7ddbed575f87848d5737eeae31e242685d6449de27573e8ed30892157fea643393cb875e175f2711200bc50 + languageName: node + linkType: hard + +"@tsconfig/node10@npm:^1.0.7": + version: 1.0.12 + resolution: "@tsconfig/node10@npm:1.0.12" + checksum: 10c0/7bbbd7408cfaced86387a9b1b71cebc91c6fd701a120369735734da8eab1a4773fc079abd9f40c9e0b049e12586c8ac0e13f0da596bfd455b9b4c3faa813ebc5 + languageName: node + linkType: hard + +"@tsconfig/node12@npm:^1.0.7": + version: 1.0.11 + resolution: "@tsconfig/node12@npm:1.0.11" + checksum: 10c0/dddca2b553e2bee1308a056705103fc8304e42bb2d2cbd797b84403a223b25c78f2c683ec3e24a095e82cd435387c877239bffcb15a590ba817cd3f6b9a99fd9 + languageName: node + linkType: hard + +"@tsconfig/node14@npm:^1.0.0": + version: 1.0.3 + resolution: "@tsconfig/node14@npm:1.0.3" + checksum: 10c0/67c1316d065fdaa32525bc9449ff82c197c4c19092b9663b23213c8cbbf8d88b6ed6a17898e0cbc2711950fbfaf40388938c1c748a2ee89f7234fc9e7fe2bf44 + languageName: node + linkType: hard + +"@tsconfig/node16@npm:^1.0.2": + version: 1.0.4 + resolution: "@tsconfig/node16@npm:1.0.4" + checksum: 10c0/05f8f2734e266fb1839eb1d57290df1664fe2aa3b0fdd685a9035806daa635f7519bf6d5d9b33f6e69dd545b8c46bd6e2b5c79acb2b1f146e885f7f11a42a5bb + languageName: node + linkType: hard + "@tufjs/canonical-json@npm:2.0.0": version: 2.0.0 resolution: "@tufjs/canonical-json@npm:2.0.0" @@ -7984,6 +8042,13 @@ __metadata: languageName: node linkType: hard +"@types/js-yaml@npm:^4.0.9": + version: 4.0.9 + resolution: "@types/js-yaml@npm:4.0.9" + checksum: 10c0/24de857aa8d61526bbfbbaa383aa538283ad17363fcd5bb5148e2c7f604547db36646440e739d78241ed008702a8920665d1add5618687b6743858fae00da211 + languageName: node + linkType: hard + "@types/jsdom@npm:^20.0.0": version: 20.0.1 resolution: "@types/jsdom@npm:20.0.1" @@ -8180,6 +8245,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^25.5.0": + version: 25.5.0 + resolution: "@types/node@npm:25.5.0" + dependencies: + undici-types: "npm:~7.18.0" + checksum: 10c0/70c508165b6758c4f88d4f91abca526c3985eee1985503d4c2bd994dbaf588e52ac57e571160f18f117d76e963570ac82bd20e743c18987e82564312b3b62119 + languageName: node + linkType: hard + "@types/normalize-package-data@npm:^2.4.0, @types/normalize-package-data@npm:^2.4.3": version: 2.4.4 resolution: "@types/normalize-package-data@npm:2.4.4" @@ -13003,6 +13077,15 @@ __metadata: languageName: node linkType: hard +"acorn-walk@npm:^8.1.1": + version: 8.3.5 + resolution: "acorn-walk@npm:8.3.5" + dependencies: + acorn: "npm:^8.11.0" + checksum: 10c0/e31bf5b5423ed1349437029d66d708b9fbd1b77a644b031501e2c753b028d13b56348210ed901d5b1d0d86eb3381c0a0fc0d0998511a9d546d1194936266a332 + languageName: node + linkType: hard + "acorn@npm:^6.4.1": version: 6.4.2 resolution: "acorn@npm:6.4.2" @@ -13039,6 +13122,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.4.1": + version: 8.16.0 + resolution: "acorn@npm:8.16.0" + bin: + acorn: bin/acorn + checksum: 10c0/c9c52697227661b68d0debaf972222d4f622aa06b185824164e153438afa7b08273432ca43ea792cadb24dada1d46f6f6bb1ef8de9956979288cc1b96bf9914e + languageName: node + linkType: hard + "adaptive-expressions@npm:^4.15.0": version: 4.23.1 resolution: "adaptive-expressions@npm:4.23.1" @@ -13522,6 +13614,13 @@ __metadata: languageName: node linkType: hard +"arg@npm:^4.1.0": + version: 4.1.3 + resolution: "arg@npm:4.1.3" + checksum: 10c0/070ff801a9d236a6caa647507bdcc7034530604844d64408149a26b9e87c2f97650055c0f049abd1efc024b334635c01f29e0b632b371ac3f26130f4cf65997a + languageName: node + linkType: hard + "argparse@npm:^1.0.7": version: 1.0.10 resolution: "argparse@npm:1.0.10" @@ -14329,6 +14428,13 @@ __metadata: 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 + "bare-events@npm:^2.2.0": version: 2.5.0 resolution: "bare-events@npm:2.5.0" @@ -14572,6 +14678,15 @@ __metadata: 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:^2.3.1, braces@npm:^2.3.2": version: 2.3.2 resolution: "braces@npm:2.3.2" @@ -15788,6 +15903,13 @@ __metadata: languageName: node linkType: hard +"code-block-writer@npm:^13.0.3": + version: 13.0.3 + resolution: "code-block-writer@npm:13.0.3" + checksum: 10c0/87db97b37583f71cfd7eced8bf3f0a0a0ca53af912751a734372b36c08cd27f3e8a4878ec05591c0cd9ae11bea8add1423e132d660edd86aab952656dd41fd66 + languageName: node + linkType: hard + "collapse-white-space@npm:^1.0.2": version: 1.0.6 resolution: "collapse-white-space@npm:1.0.6" @@ -16574,6 +16696,13 @@ __metadata: languageName: node linkType: hard +"create-require@npm:^1.1.0": + version: 1.1.1 + resolution: "create-require@npm:1.1.1" + checksum: 10c0/157cbc59b2430ae9a90034a5f3a1b398b6738bf510f713edc4d4e45e169bc514d3d99dd34d8d01ca7ae7830b5b8b537e46ae8f3c8f932371b0875c0151d7ec91 + languageName: node + linkType: hard + "cross-fetch@npm:3.1.5": version: 3.1.5 resolution: "cross-fetch@npm:3.1.5" @@ -17660,6 +17789,13 @@ __metadata: languageName: node linkType: hard +"diff@npm:^4.0.1": + version: 4.0.4 + resolution: "diff@npm:4.0.4" + checksum: 10c0/855fb70b093d1d9643ddc12ea76dca90dc9d9cdd7f82c08ee8b9325c0dc5748faf3c82e2047ced5dcaa8b26e58f7903900be2628d0380a222c02d79d8de385df + languageName: node + linkType: hard + "diff@npm:^5.0.0, diff@npm:^5.1.0": version: 5.2.0 resolution: "diff@npm:5.2.0" @@ -19822,6 +19958,18 @@ __metadata: 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 + "fecha@npm:^4.2.0": version: 4.2.3 resolution: "fecha@npm:4.2.3" @@ -24296,6 +24444,17 @@ __metadata: languageName: node linkType: hard +"js-yaml@npm:^4.1.1": + version: 4.1.1 + resolution: "js-yaml@npm:4.1.1" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/561c7d7088c40a9bb53cc75becbfb1df6ae49b34b5e6e5a81744b14ae8667ec564ad2527709d1a6e7d5e5fa6d483aa0f373a50ad98d42fde368ec4a190d4fae7 + languageName: node + linkType: hard + "jsbn@npm:1.1.0": version: 1.1.0 resolution: "jsbn@npm:1.1.0" @@ -25687,7 +25846,7 @@ __metadata: languageName: node linkType: hard -"make-error@npm:1.x, make-error@npm:^1.3.6": +"make-error@npm:1.x, make-error@npm:^1.1.1, make-error@npm:^1.3.6": version: 1.3.6 resolution: "make-error@npm:1.3.6" checksum: 10c0/171e458d86854c6b3fc46610cfacf0b45149ba043782558c6875d9f42f222124384ad0b468c92e996d815a8a2003817a710c0a160e49c1c394626f76fa45396f @@ -26296,6 +26455,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^10.0.1": + version: 10.2.4 + resolution: "minimatch@npm:10.2.4" + dependencies: + brace-expansion: "npm:^5.0.2" + checksum: 10c0/35f3dfb7b99b51efd46afd378486889f590e7efb10e0f6a10ba6800428cf65c9a8dedb74427d0570b318d749b543dc4e85f06d46d2858bc8cac7e1eb49a95945 + languageName: node + linkType: hard + "minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -28452,6 +28620,13 @@ __metadata: languageName: node linkType: hard +"path-browserify@npm:^1.0.1": + version: 1.0.1 + resolution: "path-browserify@npm:1.0.1" + checksum: 10c0/8b8c3fd5c66bd340272180590ae4ff139769e9ab79522e2eb82e3d571a89b8117c04147f65ad066dccfb42fcad902e5b7d794b3d35e0fd840491a8ddbedf8c66 + languageName: node + linkType: hard + "path-dirname@npm:^1.0.0": version: 1.0.2 resolution: "path-dirname@npm:1.0.2" @@ -28661,6 +28836,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^4.0.3": + version: 4.0.4 + resolution: "picomatch@npm:4.0.4" + checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 + languageName: node + linkType: hard + "pid-port@npm:^0.1.0": version: 0.1.1 resolution: "pid-port@npm:0.1.1" @@ -33707,6 +33889,16 @@ __metadata: languageName: node linkType: hard +"tinyglobby@npm:^0.2.14": + 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 + "tippy.js@npm:^6.3.1": version: 6.3.7 resolution: "tippy.js@npm:6.3.7" @@ -34020,6 +34212,54 @@ __metadata: languageName: node linkType: hard +"ts-morph@npm:^27.0.2": + version: 27.0.2 + resolution: "ts-morph@npm:27.0.2" + dependencies: + "@ts-morph/common": "npm:~0.28.1" + code-block-writer: "npm:^13.0.3" + checksum: 10c0/224715cc6d97b8ff5afd3986f9629f912a0ebd83eaecbdca91c35cf10a98f607c663f666e7ea5e6afab00563d00dc80fa7a13552cc7f1cef735261c3217d0863 + languageName: node + linkType: hard + +"ts-node@npm:^10.9.2": + version: 10.9.2 + resolution: "ts-node@npm:10.9.2" + dependencies: + "@cspotcode/source-map-support": "npm:^0.8.0" + "@tsconfig/node10": "npm:^1.0.7" + "@tsconfig/node12": "npm:^1.0.7" + "@tsconfig/node14": "npm:^1.0.0" + "@tsconfig/node16": "npm:^1.0.2" + acorn: "npm:^8.4.1" + acorn-walk: "npm:^8.1.1" + arg: "npm:^4.1.0" + create-require: "npm:^1.1.0" + diff: "npm:^4.0.1" + make-error: "npm:^1.1.1" + v8-compile-cache-lib: "npm:^3.0.1" + yn: "npm:3.1.1" + peerDependencies: + "@swc/core": ">=1.2.50" + "@swc/wasm": ">=1.2.50" + "@types/node": "*" + typescript: ">=2.7" + peerDependenciesMeta: + "@swc/core": + optional: true + "@swc/wasm": + optional: true + bin: + ts-node: dist/bin.js + ts-node-cwd: dist/bin-cwd.js + ts-node-esm: dist/bin-esm.js + ts-node-script: dist/bin-script.js + ts-node-transpile-only: dist/bin-transpile.js + ts-script: dist/bin-script-deprecated.js + checksum: 10c0/5f29938489f96982a25ba650b64218e83a3357d76f7bede80195c65ab44ad279c8357264639b7abdd5d7e75fc269a83daa0e9c62fd8637a3def67254ecc9ddc2 + languageName: node + linkType: hard + "tsconfig-paths@npm:^3.15.0": version: 3.15.0 resolution: "tsconfig-paths@npm:3.15.0" @@ -34509,6 +34749,13 @@ __metadata: 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 + "unfetch@npm:^4.2.0": version: 4.2.0 resolution: "unfetch@npm:4.2.0" @@ -35091,6 +35338,13 @@ __metadata: languageName: node linkType: hard +"v8-compile-cache-lib@npm:^3.0.1": + version: 3.0.1 + resolution: "v8-compile-cache-lib@npm:3.0.1" + checksum: 10c0/bdc36fb8095d3b41df197f5fb6f11e3a26adf4059df3213e3baa93810d8f0cc76f9a74aaefc18b73e91fe7e19154ed6f134eda6fded2e0f1c8d2272ed2d2d391 + languageName: node + linkType: hard + "v8-compile-cache@npm:^2.0.3": version: 2.4.0 resolution: "v8-compile-cache@npm:2.4.0" @@ -35395,6 +35649,8 @@ __metadata: "@semantic-release/git": "npm:^10.0.1" "@semantic-release/github": "npm:^11.0.1" "@testing-library/react-hooks": "npm:^8.0.1" + "@types/js-yaml": "npm:^4.0.9" + "@types/node": "npm:^25.5.0" "@webex/cli-tools": "npm:0.0.0-next.2" "@webex/package-tools": "npm:0.0.0-next.6" babel-jest: "npm:^29.7.0" @@ -35405,6 +35661,7 @@ __metadata: husky: "npm:^9.1.7" jest: "npm:29.7.0" jest-canvas-mock: "npm:^2.5.2" + js-yaml: "npm:^4.1.1" node-gyp: "npm:^10.2.0" nodemailer: "npm:^7.0.3" os-browserify: "npm:^0.3.0" @@ -35415,6 +35672,8 @@ __metadata: semantic-release: "npm:^24.2.0" stream-browserify: "npm:^3.0.0" style-loader: "npm:^4.0.0" + ts-morph: "npm:^27.0.2" + ts-node: "npm:^10.9.2" typescript: "npm:^5.6.3" vm-browserify: "npm:^1.1.2" webpack: "npm:^5.96.1" @@ -36538,6 +36797,13 @@ __metadata: languageName: node linkType: hard +"yn@npm:3.1.1": + version: 3.1.1 + resolution: "yn@npm:3.1.1" + checksum: 10c0/0732468dd7622ed8a274f640f191f3eaf1f39d5349a1b72836df484998d7d9807fbea094e2f5486d6b0cd2414aad5775972df0e68f8604db89a239f0f4bf7443 + languageName: node + linkType: hard + "yocto-queue@npm:^0.1.0": version: 0.1.0 resolution: "yocto-queue@npm:0.1.0"