diff --git a/.cursor/commands/code-review.md b/.cursor/commands/code-review.md new file mode 100644 index 00000000..669fec77 --- /dev/null +++ b/.cursor/commands/code-review.md @@ -0,0 +1,156 @@ +--- +name: code-review +description: Automated PR review using comprehensive checklist tailored for modularized Contentstack CLI +--- + +# Code Review Command + +## Usage Patterns + +### Scope-Based Reviews +- `/code-review` - Review all current changes with full checklist +- `/code-review --scope typescript` - Focus on TypeScript configuration and patterns +- `/code-review --scope testing` - Focus on Mocha/Chai test patterns +- `/code-review --scope oclif` - Focus on command structure and OCLIF patterns +- `/code-review --scope packages` - Focus on package structure and organization + +### Severity Filtering +- `/code-review --severity critical` - Show only critical issues (security, breaking changes) +- `/code-review --severity high` - Show high and critical issues +- `/code-review --severity all` - Show all issues including suggestions + +### Package-Aware Reviews +- `/code-review --package contentstack-import` - Review changes in import package +- `/code-review --package contentstack-export` - Review changes in export package +- `/code-review --package-type plugin` - Review all plugin packages (all 12 packages are plugins) +- `/code-review --package-scope cm` - Review CM (content management) related packages + +### File Type Focus +- `/code-review --files commands` - Review command files only +- `/code-review --files tests` - Review test files only +- `/code-review --files utils` - Review utility files + +## Comprehensive Review Checklist + +### Monorepo Structure Compliance +- **Package organization**: 12 plugin packages under `packages/contentstack-*` +- **pnpm workspace**: Correct `pnpm-workspace.yaml` configuration +- **Build artifacts**: No `lib/` directories committed to version control +- **Dependencies**: Proper use of shared utilities (`@contentstack/cli-command`, `@contentstack/cli-utilities`) +- **Scripts**: Consistent build, test, and lint scripts across packages + +### Package-Specific Structure +- **All packages are plugins**: Each has `oclif.commands` configuration pointing to `./lib/commands` +- **Plugin topics**: All commands under `cm:` topic (content management) +- **Base commands**: Each plugin defines its own `BaseCommand` extending `@contentstack/cli-command` Command +- **Inter-plugin dependencies**: Some plugins depend on others (e.g., import depends on audit) +- **Dependency versions**: Using consistent versions across plugins + +### TypeScript Standards +- **Configuration compliance**: Follows package TypeScript config (`strict: false`, `target: es2017`) +- **Naming conventions**: kebab-case files, PascalCase classes, camelCase functions +- **Import patterns**: ES modules with proper default/named exports +- **Type safety**: No unnecessary `any` types in production code + +### OCLIF Command Patterns +- **Base class usage**: Extends plugin-specific `BaseCommand` or `@contentstack/cli-command` Command +- **Command structure**: Proper `static id`, `static description`, `static examples`, `static flags` +- **Topic organization**: Uses `cm:stacks:*` structure (`cm:stacks:import`, `cm:stacks:export`, `cm:stacks:audit`) +- **Error handling**: Uses `handleAndLogError` from utilities with context +- **Flag validation**: Early validation and user-friendly error messages +- **Service delegation**: Commands are thin, services handle business logic + +### Testing Excellence (Mocha/Chai Stack) +- **Framework compliance**: Uses Mocha + Chai (not Jest) +- **File patterns**: Follows `*.test.ts` naming convention +- **Directory structure**: Proper placement in `test/unit/` +- **Test organization**: Arrange-Act-Assert pattern consistently used +- **Isolation**: Proper setup/teardown with beforeEach/afterEach +- **No real API calls**: All external dependencies properly mocked + +### Error Handling Standards +- **Consistent patterns**: Use `handleAndLogError` from utilities +- **User-friendly messages**: Clear error descriptions for end users +- **Logging**: Proper use of `log.debug` for diagnostic information +- **Status messages**: Use `cliux` for user feedback (success, error, info) + +### Build and Compilation +- **TypeScript compilation**: Clean compilation with no errors +- **OCLIF manifest**: Generated for command discovery +- **README generation**: Commands documented in package README +- **Source maps**: Properly configured for debugging +- **No build artifacts in commit**: `.gitignore` excludes `lib/` directories + +### Testing Coverage +- **Test structure**: Tests in `test/unit/` with descriptive names +- **Command testing**: Uses @oclif/test for command validation +- **Error scenarios**: Tests for both success and failure paths +- **Mocking**: All dependencies properly mocked + +### Package.json Compliance +- **Correct metadata**: name, description, version, author +- **Script definitions**: build, compile, test, lint scripts present +- **Dependencies**: Correct versions of shared packages +- **Main/types**: Properly configured for library packages +- **OCLIF config**: Present for plugin packages + +### Security and Best Practices +- **No secrets**: No API keys or tokens in code or tests +- **Input validation**: Proper validation of user inputs and flags +- **Process management**: Appropriate use of error codes +- **File operations**: Safe handling of file system operations + +### Code Quality +- **Naming consistency**: Follow established conventions +- **Comments**: Only for non-obvious logic (no "narration" comments) +- **Error messages**: Clear, actionable messages for users +- **Module organization**: Proper separation of concerns + +## Review Execution + +### Automated Checks +1. **Lint compliance**: ESLint checks for code style +2. **TypeScript compiler**: Successful compilation to `lib/` directories +3. **Test execution**: All tests pass successfully +4. **Build verification**: Build scripts complete without errors + +### Manual Review Focus Areas +1. **Command usability**: Clear help text and realistic examples +2. **Error handling**: Appropriate error messages and recovery options +3. **Test quality**: Comprehensive test coverage for critical paths +4. **Monorepo consistency**: Consistent patterns across all packages +5. **Flag design**: Intuitive flag names and combinations + +### Common Issues to Flag +- **Inconsistent TypeScript settings**: Mixed strict mode without reason +- **Real API calls in tests**: Unmocked external dependencies +- **Missing error handling**: Commands that fail silently +- **Poor test organization**: Tests without clear Arrange-Act-Assert +- **Build artifacts committed**: `lib/` directories in version control +- **Unclear error messages**: Non-actionable error descriptions +- **Inconsistent flag naming**: Similar flags with different names +- **Missing command examples**: Examples not showing actual usage + +## Repository-Specific Checklist + +### For Modularized CLI +- [ ] Command properly extends `@contentstack/cli-command` Command +- [ ] Flags defined with proper types from `@contentstack/cli-utilities` +- [ ] Error handling uses `handleAndLogError` utility +- [ ] User feedback uses `cliux` utilities +- [ ] Tests use Mocha + Chai pattern with mocked dependencies +- [ ] Package.json has correct scripts (build, compile, test, lint) +- [ ] TypeScript compiles with no errors +- [ ] Tests pass: `pnpm test` +- [ ] No `.only` or `.skip` in test files +- [ ] Build succeeds: `pnpm run build` +- [ ] OCLIF manifest generated successfully + +### Before Merge +- [ ] All review items addressed +- [ ] No build artifacts in commit +- [ ] Tests added for new functionality +- [ ] Documentation updated if needed +- [ ] No console.log() statements (use log.debug instead) +- [ ] Error messages are user-friendly +- [ ] No secrets or credentials in code diff --git a/.cursor/commands/execute-tests.md b/.cursor/commands/execute-tests.md new file mode 100644 index 00000000..a27de0cd --- /dev/null +++ b/.cursor/commands/execute-tests.md @@ -0,0 +1,252 @@ +--- +name: execute-tests +description: Run tests by scope, file, or module with intelligent filtering for this pnpm monorepo +--- + +# Execute Tests Command + +## Usage Patterns + +### Monorepo-Wide Testing +- `/execute-tests` - Run all tests across all packages +- `/execute-tests --coverage` - Run all tests with coverage reporting +- `/execute-tests --parallel` - Run package tests in parallel using pnpm + +### Package-Specific Testing +- `/execute-tests contentstack-import` - Run tests for import package +- `/execute-tests contentstack-export` - Run tests for export package +- `/execute-tests contentstack-audit` - Run tests for audit package +- `/execute-tests contentstack-clone` - Run tests for clone package +- `/execute-tests packages/contentstack-import/` - Run tests using path + +### Scope-Based Testing +- `/execute-tests unit` - Run unit tests only (`test/unit/**/*.test.ts`) +- `/execute-tests commands` - Run command tests (`test/unit/commands/**/*.test.ts`) +- `/execute-tests services` - Run service layer tests + +### File Pattern Testing +- `/execute-tests *.test.ts` - Run all TypeScript tests +- `/execute-tests test/unit/commands/` - Run tests for specific directory + +### Watch and Development +- `/execute-tests --watch` - Run tests in watch mode with file monitoring +- `/execute-tests --debug` - Run tests with debug output enabled +- `/execute-tests --bail` - Stop on first test failure + +## Intelligent Filtering + +### Repository-Aware Detection +- **Test patterns**: All use `*.test.ts` naming convention +- **Directory structures**: Standard `test/unit/` layout +- **Test locations**: `packages/*/test/unit/**/*.test.ts` +- **Build exclusion**: Ignores `lib/` directories (compiled artifacts) + +### Package Structure +The monorepo contains 12 CLI plugin packages: +- `contentstack-audit` - Stack audit and fix operations +- `contentstack-bootstrap` - Seed/bootstrap stacks +- `contentstack-branches` - Git-based branch management +- `contentstack-bulk-publish` - Bulk publish operations +- `contentstack-clone` - Clone/duplicate stacks +- `contentstack-export` - Export stack content +- `contentstack-export-to-csv` - Export to CSV format +- `contentstack-import` - Import content to stacks +- `contentstack-import-setup` - Import setup and validation +- `contentstack-migration` - Content migration workflows +- `contentstack-seed` - Seed stacks with data +- `contentstack-variants` - Manage content variants + +### Monorepo Integration +- **pnpm workspace support**: Uses `pnpm -r --filter` for package targeting +- **Dependency awareness**: Understands package interdependencies +- **Parallel execution**: Leverages pnpm's parallel capabilities +- **Selective testing**: Can target specific packages or file patterns + +### Framework Detection +- **Mocha configuration**: Respects `.mocharc.json` files per package +- **TypeScript compilation**: Handles test TypeScript setup +- **Test setup**: Detects test helper initialization files +- **Test timeout**: 30 seconds standard (configurable per package) + +## Execution Examples + +### Common Workflows +```bash +# Run all tests with coverage +/execute-tests --coverage + +# Test specific package during development +/execute-tests contentstack-import --watch + +# Run only command tests across all packages +/execute-tests commands + +# Run unit tests with detailed output +/execute-tests --debug + +# Test until first failure (quick feedback) +/execute-tests --bail +``` + +### Package-Specific Commands Generated +```bash +# For contentstack-import package +cd packages/contentstack-import && pnpm test + +# For all packages with parallel execution +pnpm -r run test + +# For specific test file +cd packages/contentstack-import && npx mocha "test/unit/commands/import.test.ts" + +# With coverage +pnpm -r run test:coverage +``` + +## Configuration Awareness + +### Mocha Integration +- Respects individual package `.mocharc.json` configurations +- Handles TypeScript compilation via ts-node/register +- Supports test helpers and initialization files +- Manages timeout settings per package (default 30 seconds) + +### Test Configuration +```json +// .mocharc.json +{ + "require": [ + "test/helpers/init.js", + "ts-node/register", + "source-map-support/register" + ], + "recursive": true, + "timeout": 30000, + "spec": "test/**/*.test.ts" +} +``` + +### pnpm Workspace Features +- Leverages workspace dependency resolution +- Supports filtered execution by package patterns +- Enables parallel test execution across packages +- Respects package-specific scripts and configurations + +## Test Structure + +### Standard Test Organization +``` +packages/*/ +├── test/ +│ └── unit/ +│ ├── commands/ # Command-specific tests +│ ├── services/ # Service/business logic tests +│ └── utils/ # Utility function tests +└── src/ + ├── commands/ # CLI commands + ├── services/ # Business logic + └── utils/ # Utilities +``` + +### Test File Naming +- **Pattern**: `*.test.ts` across all packages +- **Location**: `test/unit/` directories +- **Organization**: Mirrors `src/` structure for easy navigation + +## Performance Optimization + +### Parallel Testing +```bash +# Run tests in parallel for faster feedback +pnpm -r --filter './packages/*' run test + +# Watch mode during development +/execute-tests --watch +``` + +### Selective Testing +- Run only affected packages' tests during development +- Use `--bail` to stop on first failure for quick iteration +- Target specific test files for focused debugging + +## Troubleshooting + +### Common Issues + +**Tests not found** +- Check that files follow `*.test.ts` pattern +- Verify files are in `test/unit/` directory +- Ensure `.mocharc.json` has correct spec pattern + +**TypeScript compilation errors** +- Verify `tsconfig.json` in package root +- Check that `ts-node/register` is in `.mocharc.json` requires +- Run `pnpm compile` to check TypeScript errors + +**Watch mode not detecting changes** +- Verify `--watch` flag is supported in your Mocha version +- Check that file paths are correct +- Ensure no excessive `.gitignore` patterns + +**Port conflicts** +- Tests should not use hard-coded ports +- Use dynamic port allocation or test isolation +- Check for process cleanup in `afterEach` hooks + +## Best Practices + +### Test Execution +- Run tests before committing: `pnpm test` +- Use `--bail` during development for quick feedback +- Run full suite before opening PR +- Check coverage for critical paths + +### Test Organization +- Keep tests close to source code structure +- Use descriptive test names +- Group related tests with `describe` blocks +- Clean up resources in `afterEach` + +### Debugging +- Use `--debug` flag for detailed output +- Add `log.debug()` statements in tests +- Run individual test files for isolation +- Use `--bail` to stop at first failure + +## Integration with CI/CD + +### GitHub Actions +- Runs `pnpm test` on pull requests +- Enforces test passage before merge +- May include coverage reporting +- Runs linting and build verification + +### Local Development +```bash +# Before committing +pnpm test +pnpm run lint +pnpm run build + +# Or use watch mode for faster iteration +pnpm test --watch +``` + +## Coverage Reporting + +### Coverage Commands +```bash +# Run tests with coverage +/execute-tests --coverage + +# Coverage output location +coverage/ +├── index.html # HTML report +├── coverage-summary.json # JSON summary +└── lcov.info # LCOV format +``` + +### Coverage Goals +- **Team aspiration**: 80% minimum coverage +- **Focus on**: Critical business logic and error paths +- **Not critical**: Utility functions and edge cases diff --git a/.cursor/rules/README.md b/.cursor/rules/README.md new file mode 100644 index 00000000..cbb51b61 --- /dev/null +++ b/.cursor/rules/README.md @@ -0,0 +1,99 @@ +# Cursor Rules + +Context-aware rules that load automatically based on the files you're editing, optimized for this CLI plugins monorepo. + +## Rule Files + +| File | Scope | Always Applied | Purpose | +|------|-------|----------------|---------| +| `dev-workflow.md` | `**/*.ts`, `**/*.js`, `**/*.json` | Yes | Monorepo TDD workflow, pnpm workspace patterns (12 plugin packages) | +| `typescript.mdc` | `**/*.ts`, `**/*.tsx` | No | TypeScript configurations and naming conventions | +| `testing.mdc` | `**/test/**/*.ts`, `**/test/**/*.js`, `**/__tests__/**/*.ts`, `**/*.spec.ts`, `**/*.test.ts` | Yes | Mocha, Chai test patterns and test structure | +| `oclif-commands.mdc` | `**/commands/**/*.ts`, `**/base-command.ts` | No | OCLIF command patterns and CLI validation | +| `contentstack-plugin.mdc` | `packages/contentstack-*/src/**/*.ts`, `packages/contentstack-*/src/**/*.js` | No | CLI plugin package patterns, commands, services, and inter-plugin dependencies | + +## Commands + +| File | Trigger | Purpose | +|------|---------|---------| +| `execute-tests.md` | `/execute-tests` | Run tests by scope, package, or module with monorepo awareness | +| `code-review.md` | `/code-review` | Automated PR review with CLI-specific checklist | + +## Loading Behaviour + +### File Type Mapping +- **TypeScript files** → `typescript.mdc` + `dev-workflow.md` +- **Command files** (`packages/*/src/commands/**/*.ts`) → `oclif-commands.mdc` + `typescript.mdc` + `dev-workflow.md` +- **Base command files** (`packages/*/src/base-command.ts`, `packages/*/*base-command.ts`) → `oclif-commands.mdc` + `typescript.mdc` + `dev-workflow.md` +- **Plugin package files** (`packages/contentstack-*/src/**/*.ts`) → `contentstack-plugin.mdc` + `typescript.mdc` + `dev-workflow.md` +- **Test files** (`packages/*/test/**/*.{ts,js}`) → `testing.mdc` + `dev-workflow.md` +- **Utility files** (`packages/*/src/utils/**/*.ts`) → `typescript.mdc` + `dev-workflow.md` + +### Package-Specific Loading +- **Plugin packages** (with `oclif.commands`) → Full command and utility rules +- **Library packages** → TypeScript and utility rules only + +## Repository-Specific Features + +### Monorepo Structure + +This is a **CLI plugins** monorepo with 12 plugin packages under `packages/`: +- `contentstack-audit` - Stack audit and fix operations +- `contentstack-bootstrap` - Seed/bootstrap stacks with content +- `contentstack-branches` - Git-based branch management for stacks +- `contentstack-bulk-publish` - Bulk publish operations for entries/assets +- `contentstack-clone` - Clone/duplicate stacks +- `contentstack-export` - Export stack content to filesystem +- `contentstack-export-to-csv` - Export stack data to CSV format +- `contentstack-import` - Import content into stacks +- `contentstack-import-setup` - Setup and validation for imports +- `contentstack-migration` - Content migration workflows +- `contentstack-seed` - Seed stacks with generated data +- `contentstack-variants` - Manage content variants + +All plugins depend on: +- `@contentstack/cli-command` - Base Command class +- `@contentstack/cli-utilities` - Shared utilities and helpers +- Optionally on each other (e.g., `contentstack-import` depends on `@contentstack/cli-audit`) + +### Build Configuration +- **pnpm workspaces** configuration (all 12 plugins under `packages/`) +- **Shared dependencies**: Each plugin depends on `@contentstack/cli-command` and `@contentstack/cli-utilities` +- **Inter-plugin dependencies**: Some plugins depend on others (e.g., import → audit) +- **Build process**: TypeScript compilation → `lib/` directories +- **OCLIF manifest** generation per plugin for command discovery + +### Actual Patterns Detected +- **Testing**: Mocha + Chai (consistent across all plugins) +- **TypeScript**: Strict mode for type safety +- **Commands**: Extend `@contentstack/cli-command` Command class with plugin-specific base-commands +- **Topics**: All commands under `cm:` topic (content management) +- **Services/Modules**: Domain-specific business logic organized by concern +- **Build artifacts**: `lib/` directories (excluded from rules) + +## Performance Benefits + +- **Lightweight loading** - Only relevant rules activate based on file patterns +- **Precise glob patterns** - Avoid loading rules for build artifacts +- **Context-aware** - Rules load based on actual file structure + +## Design Principles + +### Validated Against Codebase +- Rules reflect **actual patterns** found in repository +- Glob patterns match **real file structure** +- Examples use **actual dependencies** and APIs + +### Lightweight and Focused +- Each rule has **single responsibility** +- Package-specific variations acknowledged +- `alwaysApply: true` only for truly universal patterns + +## Quick Reference + +For detailed patterns: +- **Testing**: See `testing.mdc` for Mocha/Chai test structure +- **Commands**: See `oclif-commands.mdc` for command development +- **Plugins**: See `contentstack-plugin.mdc` for plugin architecture and patterns +- **Development**: See `dev-workflow.md` for TDD and monorepo workflow +- **TypeScript**: See `typescript.mdc` for type safety patterns diff --git a/.cursor/rules/contentstack-plugin.mdc b/.cursor/rules/contentstack-plugin.mdc new file mode 100644 index 00000000..e653d2c5 --- /dev/null +++ b/.cursor/rules/contentstack-plugin.mdc @@ -0,0 +1,475 @@ +--- +description: "Contentstack CLI plugin package patterns — commands, services, base-commands, and inter-plugin dependencies" +globs: ["packages/contentstack-*/src/**/*.ts", "packages/contentstack-*/src/**/*.js"] +alwaysApply: false +--- + +# Contentstack CLI Plugin Standards + +## Overview + +The **cli-plugins** monorepo contains 12 OCLIF plugin packages under `packages/`: +- `contentstack-audit`, `contentstack-bootstrap`, `contentstack-branches`, `contentstack-bulk-publish`, `contentstack-clone`, `contentstack-export`, `contentstack-export-to-csv`, `contentstack-import`, `contentstack-import-setup`, `contentstack-migration`, `contentstack-seed`, `contentstack-variants` + +Each plugin is a self-contained OCLIF package that: +- **Defines commands** — via `oclif.commands` in `package.json` pointing to `./lib/commands` +- **Depends on shared libraries** — `@contentstack/cli-command` (Base Command class), `@contentstack/cli-utilities` (shared utils and services) +- **May depend on other plugins** — e.g., `contentstack-import` depends on `@contentstack/cli-audit` for audit operations +- **Implements business logic** — in services, modules, and utility classes +- **Has a local base-command** — extending `@contentstack/cli-command` Command class with plugin-specific initialization and flags + +## Architecture + +### Package Structure + +``` +packages/contentstack-import/ +├── src/ +│ ├── commands/ +│ │ └── cm/ +│ │ └── stacks/ +│ │ └── import.ts +│ ├── services/ +│ │ ├── import-service.ts +│ │ └── validation-service.ts +│ ├── modules/ +│ │ ├── entries.ts +│ │ ├── assets.ts +│ │ └── ... +│ ├── base-command.ts (or import-base-command.ts) +│ ├── types/ +│ ├── interfaces/ +│ ├── messages/ +│ └── utils/ +├── test/ +│ └── unit/ +│ ├── commands/ +│ ├── services/ +│ └── ... +├── package.json +├── tsconfig.json +└── .mocharc.json +``` + +### Package Configuration + +Each plugin's `package.json` declares its commands and declares itself as an OCLIF plugin: + +```json +{ + "name": "@contentstack/cli-cm-import", + "oclif": { + "commands": "./lib/commands", + "topics": { + "stacks": { + "description": "Manage stacks" + } + } + }, + "dependencies": { + "@contentstack/cli-command": "~1.8.0", + "@contentstack/cli-utilities": "~1.18.0", + "@contentstack/cli-audit": "~1.19.0" + } +} +``` + +## Base Command Pattern + +### Plugin-Specific Base Command + +Each plugin defines its own `BaseCommand` (or specialized variant like `AuditBaseCommand`): + +```typescript +// ✅ GOOD - packages/contentstack-audit/src/base-command.ts +import { Command } from '@contentstack/cli-command'; +import { Flags, FlagInput } from '@contentstack/cli-utilities'; + +export abstract class BaseCommand extends Command { + protected sharedConfig = { + basePath: process.cwd(), + }; + + static baseFlags: FlagInput = { + config: Flags.string({ + char: 'c', + description: 'Path to config file', + }), + 'data-dir': Flags.string({ + char: 'd', + description: 'Data directory path', + }), + }; + + public async init(): Promise { + await super.init(); + const { args, flags } = await this.parse({ + flags: this.ctor.flags, + args: this.ctor.args, + strict: this.ctor.strict !== false, + }); + this.args = args; + this.flags = flags; + } +} +``` + +### Specialized Base Commands + +Some plugins define specialized versions for specific concerns: + +```typescript +// ✅ GOOD - packages/contentstack-audit/src/audit-base-command.ts +// Extends BaseCommand with audit-specific logic +import { BaseCommand } from './base-command'; + +export abstract class AuditBaseCommand extends BaseCommand { + // Audit-specific initialization and helpers + protected async runAudit(): Promise { + // Common audit logic + } +} + +// Usage in commands +export default class AuditFixCommand extends AuditBaseCommand { + async run(): Promise { + await this.runAudit(); + } +} +``` + +## Command Structure + +### CM Topic Commands + +Commands are organized under the `cm` topic with subtopics for domains: + +```typescript +// ✅ GOOD - packages/contentstack-import/src/commands/cm/stacks/import.ts +import { Flags, FlagInput, cliux } from '@contentstack/cli-utilities'; +import { BaseCommand } from '../../../base-command'; + +export default class ImportCommand extends BaseCommand { + static id = 'cm:stacks:import'; + static description = 'Import content into a stack'; + static examples = [ + '$ csdx cm:stacks:import -k -d ', + '$ csdx cm:stacks:import -k -d --content-types entry,asset', + ]; + + static flags: FlagInput = { + 'stack-api-key': Flags.string({ + char: 'k', + description: 'Stack API key', + required: true, + }), + 'data-dir': Flags.string({ + char: 'd', + description: 'Directory with import data', + required: true, + }), + 'content-types': Flags.string({ + description: 'Content types to import (comma-separated)', + default: 'all', + }), + }; + + async run(): Promise { + try { + const { flags } = this; + cliux.loaderV2('Starting import...'); + + // Delegate to service + const importService = new ImportService(flags); + await importService.import(); + + cliux.success('Import completed'); + } catch (error) { + handleAndLogError(error, { + module: 'import', + command: this.id, + }); + } + } +} +``` + +## Service and Module Patterns + +### Service Layer + +Services encapsulate business logic and are used by commands: + +```typescript +// ✅ GOOD - packages/contentstack-import/src/services/import-service.ts +import { cliux, log } from '@contentstack/cli-utilities'; + +export class ImportService { + constructor(private flags: any) {} + + async import(): Promise { + // Orchestrate import workflow + await this.validateInput(); + await this.loadData(); + await this.importContent(); + } + + private async validateInput(): Promise { + log.debug('Validating input', { module: 'import-service' }); + // Validation logic + } + + private async loadData(): Promise { + // Load data from directory + } + + private async importContent(): Promise { + // Import content logic + } +} +``` + +### Module Pattern + +Some plugins use modules for domain-specific operations: + +```typescript +// ✅ GOOD - packages/contentstack-import/src/modules/entries.ts +import isEmpty from 'lodash/isEmpty'; +import { log } from '@contentstack/cli-utilities'; + +export class Entries { + constructor(private client: any, private basePath: string) {} + + async import(entries: any[]): Promise { + if (isEmpty(entries)) { + log.debug('No entries to import'); + return; + } + + for (const entry of entries) { + await this.importEntry(entry); + } + } + + private async importEntry(entry: any): Promise { + // Import individual entry + } +} +``` + +## Shared Dependencies + +All plugins depend on core libraries: + +```json +{ + "dependencies": { + "@contentstack/cli-command": "~1.8.0", + "@contentstack/cli-utilities": "~1.18.0" + } +} +``` + +### Common Utilities Used + +```typescript +// ✅ GOOD - Import and use shared utilities +import { + cliux, // CLI UI helpers (loaders, tables, success/error) + ux, // OCLIF ux utilities + log, // Structured logging + handleAndLogError, // Error handling with context + configHandler, // CLI configuration management + sanitizePath, // Path sanitization + managementSDKClient, // Contentstack API client factory +} from '@contentstack/cli-utilities'; + +import { Command, Interfaces } from '@contentstack/cli-command'; +``` + +## Inter-Plugin Dependencies + +Plugins can depend on other plugins to share functionality: + +```json +{ + "dependencies": { + "@contentstack/cli-audit": "~1.19.0" + } +} +``` + +### Using Shared Plugin Code + +```typescript +// ✅ GOOD - Import from other plugin packages +import { AuditService } from '@contentstack/cli-audit'; + +export class ImportService { + async import(): Promise { + // Do import + await new AuditService().runAudit(); + } +} +``` + +## Error Handling + +Error handling follows a consistent pattern across plugins: + +```typescript +// ✅ GOOD - Comprehensive error handling +import { handleAndLogError, CLIError } from '@contentstack/cli-utilities'; + +async run(): Promise { + try { + // Business logic + const result = await this.importService.import(); + return result; + } catch (error) { + // Log error with module/command context + handleAndLogError(error, { + module: 'import', + command: this.id, + dataDir: this.flags['data-dir'], + }); + this.exit(1); + } +} +``` + +## Build Process + +Each plugin builds independently but follows the same pattern: + +```bash +# In package.json scripts +"build": "pnpm compile && oclif manifest" +``` + +### Build Steps + +1. **compile** — TypeScript → JavaScript in `lib/` +2. **oclif manifest** — Generate `oclif.manifest.json` for command discovery + +### Build Artifacts + +- `lib/` — Compiled commands, services, modules +- `oclif.manifest.json` — Command registry for this plugin +- `README.md` — Generated command documentation (optional) + +## Configuration and Messaging + +### Messages + +Plugins store user-facing strings in centralized message files: + +```typescript +// ✅ GOOD - packages/contentstack-import/src/messages/index.ts +export const importMsg = { + IMPORT_START: 'Starting import...', + IMPORT_SUCCESS: 'Import completed successfully', + VALIDATION_ERROR: 'Validation failed: {{reason}}', +}; +``` + +### Configuration + +Plugin-specific defaults are stored in config files: + +```typescript +// ✅ GOOD - packages/contentstack-import/src/config/index.ts +export default { + batchSize: 50, + retryAttempts: 3, + timeout: 30000, + logLevel: 'info', +}; +``` + +## Testing Patterns + +### Command Testing + +Test commands using `@oclif/test`: + +```typescript +// ✅ GOOD - packages/contentstack-import/test/unit/commands/import.test.ts +import { test } from '@oclif/test'; +import { expect } from 'chai'; + +describe('ImportCommand', () => { + test + .stdout() + .command(['cm:stacks:import', '--help']) + .it('shows help message', (ctx) => { + expect(ctx.stdout).to.contain('Import content'); + }); + + test + .command(['cm:stacks:import', '-k', 'test-key', '-d', '/tmp/data']) + .it('runs import command'); +}); +``` + +### Service Testing + +Test services with unit tests: + +```typescript +// ✅ GOOD - packages/contentstack-import/test/unit/services/import-service.test.ts +import { expect } from 'chai'; +import { ImportService } from '../../../src/services/import-service'; + +describe('ImportService', () => { + let service: ImportService; + + beforeEach(() => { + service = new ImportService({ + 'stack-api-key': 'test-key', + 'data-dir': '/tmp/data', + }); + }); + + it('should validate input before import', async () => { + // Test validation logic + }); + + it('should handle import errors', async () => { + // Test error handling + }); +}); +``` + +## Best Practices + +### Command Design +- Keep commands thin — delegate to services +- Validate flags early and fail fast +- Provide clear error messages with actionable guidance +- Include command examples in static `examples` + +### Service Design +- Keep services focused on a single domain +- Make services testable by accepting dependencies via constructor +- Use modules for complex domain operations +- Document service public API clearly + +### Error Handling +- Always provide context in error logs (module, command, relevant state) +- Use structured error types (CLIError) for user-facing errors +- Include remediation guidance in error messages +- Log all errors, even those caught and handled + +### Testing +- Test commands with @oclif/test +- Test services with unit tests and mocks +- Cover both happy path and error cases +- Use fixtures for test data, not live APIs + +### Dependencies +- Always use `@contentstack/cli-utilities` for CLI concerns +- Use `@contentstack/cli-command` as base for commands +- Depend on other plugins only if truly needed (watch for circular deps) +- Pin dependency versions to minor (`~` semver) for stability diff --git a/.cursor/rules/dev-workflow.md b/.cursor/rules/dev-workflow.md new file mode 100644 index 00000000..4bfe9136 --- /dev/null +++ b/.cursor/rules/dev-workflow.md @@ -0,0 +1,206 @@ +--- +description: "Core development workflow and TDD patterns - always applied" +globs: ["**/*.ts", "**/*.js", "**/*.json"] +alwaysApply: true +--- + +# Development Workflow + +## Monorepo Structure + +### Package Organization +This **CLI plugins** monorepo has 12 packages under `packages/`: + +1. **contentstack-audit** - Stack audit and fix operations +2. **contentstack-bootstrap** - Seed/bootstrap stacks +3. **contentstack-branches** - Git-based branch management +4. **contentstack-bulk-publish** - Bulk publish operations +5. **contentstack-clone** - Clone/duplicate stacks +6. **contentstack-export** - Export stack content +7. **contentstack-export-to-csv** - Export to CSV format +8. **contentstack-import** - Import content to stacks +9. **contentstack-import-setup** - Import setup and validation +10. **contentstack-migration** - Content migration workflows +11. **contentstack-seed** - Seed stacks with data +12. **contentstack-variants** - Manage content variants + +All plugins depend on `@contentstack/cli-command` and `@contentstack/cli-utilities`. Some plugins also depend on each other. + +### pnpm Workspace Configuration +```json +{ + "workspaces": ["packages/*"] +} +``` + +### Development Commands +```bash +# Install dependencies for all packages +pnpm install + +# Run command across all packages +pnpm -r run + +# Run command in specific package +pnpm -r --filter '@contentstack/cli-cm-import' test + +# Work on specific package +cd packages/contentstack-import +pnpm test +``` + +## TDD Workflow - MANDATORY + +1. **RED** → Write ONE failing test in `test/unit/**/*.test.ts` +2. **GREEN** → Write minimal code in `src/` to pass +3. **REFACTOR** → Improve code quality while keeping tests green + +### Test-First Examples +```typescript +// ✅ GOOD - Write test first +describe('ConfigService', () => { + it('should load configuration', async () => { + // Arrange - Set up mocks + const mockConfig = { region: 'us', alias: 'default' }; + + // Act - Call the method + const result = await configService.load(); + + // Assert - Verify behavior + expect(result).to.deep.equal(mockConfig); + }); +}); +``` + +## Critical Rules + +### Testing Standards +- **NO implementation before tests** - Test-driven development only +- **Mock all external dependencies** - No real API calls in tests +- **Use Mocha + Chai** - Standard testing stack +- **Coverage aspiration**: 80% minimum + +### Code Quality +- **TypeScript configuration**: Varies by package +- **NO test.skip or .only in commits** - Clean test suites only +- **Proper error handling** - Clear error messages + +### Build Process +```bash +# Standard build process for each package +pnpm run build # tsc compilation + oclif manifest +pnpm run test # Run test suite +pnpm run lint # ESLint checks +``` + +## Package-Specific Patterns + +### Plugin Packages (auth, config) +- Have `oclif.commands` in `package.json` +- Commands in `src/commands/cm/**/*.ts` +- Built commands in `lib/commands/` +- Extend `@oclif/core` Command class +- Script: `build`: compiles TypeScript, generates OCLIF manifest and README + +### Library Packages (command, utilities, dev-dependencies) +- No OCLIF commands configuration +- Pure TypeScript/JavaScript libraries +- Consumed by other packages +- `main` points to `lib/index.js` + +### Main CLI Package (contentstack) +- Entry point through `bin/run.js` +- Aggregates plugin commands +- Package dependencies reference plugin packages + +## Script Conventions + +### Build Scripts +```json +{ + "build": "pnpm compile && oclif manifest && oclif readme", + "compile": "tsc -b tsconfig.json", + "prepack": "pnpm compile && oclif manifest && oclif readme", + "test": "mocha \"test/unit/**/*.test.ts\"", + "lint": "eslint src/**/*.ts" +} +``` + +### Key Build Steps +1. **compile** - TypeScript compilation to `lib/` +2. **oclif manifest** - Generate command manifest for discovery +3. **oclif readme** - Generate command documentation + +## Quick Reference + +For detailed patterns, see: +- `@testing` - Mocha, Chai test patterns +- `@oclif-commands` - Command structure and validation +- `@dev-workflow` (this document) - Monorepo workflow and TDD + +## Development Checklist + +### Before Starting Work +- [ ] Identify target package in `packages/` +- [ ] Check existing tests in `test/unit/` +- [ ] Understand command structure if working on commands +- [ ] Set up proper TypeScript configuration + +### During Development +- [ ] Write failing test first +- [ ] Implement minimal code to pass +- [ ] Mock external dependencies +- [ ] Follow naming conventions (kebab-case files, PascalCase classes) + +### Before Committing +- [ ] All tests pass: `pnpm test` +- [ ] No `.only` or `.skip` in test files +- [ ] Build succeeds: `pnpm run build` +- [ ] TypeScript compilation clean +- [ ] Proper error handling implemented + +## Common Patterns + +### Service/Class Architecture +```typescript +// ✅ GOOD - Separate concerns +export default class ConfigCommand extends Command { + static description = 'Manage CLI configuration'; + + async run(): Promise { + try { + const service = new ConfigService(); + await service.execute(); + this.log('Configuration updated successfully'); + } catch (error) { + this.error('Configuration update failed'); + } + } +} +``` + +### Error Handling +```typescript +// ✅ GOOD - Clear error messages +try { + await this.performAction(); +} catch (error) { + if (error instanceof ValidationError) { + this.error(`Invalid input: ${error.message}`); + } else { + this.error('Operation failed'); + } +} +``` + +## CI/CD Integration + +### GitHub Actions +- Uses workflow files in `.github/workflows/` +- Runs linting, tests, and builds on pull requests +- Enforces code quality standards + +### Pre-commit Hooks +- Husky integration for pre-commit checks +- Prevents commits with linting errors +- Located in `.husky/` diff --git a/.cursor/rules/oclif-commands.mdc b/.cursor/rules/oclif-commands.mdc new file mode 100644 index 00000000..7ca9bc25 --- /dev/null +++ b/.cursor/rules/oclif-commands.mdc @@ -0,0 +1,352 @@ +--- +description: 'OCLIF command development patterns and CLI best practices' +globs: ['**/commands/**/*.ts', '**/base-command.ts'] +alwaysApply: false +--- + +# OCLIF Command Standards + +## Command Structure + +### Standard Command Pattern +```typescript +// ✅ GOOD - Standard command structure +import { Command } from '@contentstack/cli-command'; +import { cliux, flags, FlagInput, handleAndLogError } from '@contentstack/cli-utilities'; + +export default class ConfigSetCommand extends Command { + static description = 'Set CLI configuration values'; + + static flags: FlagInput = { + region: flags.string({ + char: 'r', + description: 'Set region (us/eu)', + }), + alias: flags.string({ + char: 'a', + description: 'Configuration alias', + }), + }; + + static examples = [ + 'csdx config:set --region eu', + 'csdx config:set --region us --alias default', + ]; + + async run(): Promise { + try { + const { flags: configFlags } = await this.parse(ConfigSetCommand); + // Command logic here + } catch (error) { + handleAndLogError(error, { module: 'config-set' }); + } + } +} +``` + +## Base Classes + +### Command Base Class +```typescript +// ✅ GOOD - Extend Command from @contentstack/cli-command +import { Command } from '@contentstack/cli-command'; + +export default class MyCommand extends Command { + async run(): Promise { + // Command implementation + } +} +``` + +### Custom Base Classes +```typescript +// ✅ GOOD - Create custom base classes for shared functionality +export abstract class BaseCommand extends Command { + protected contextDetails = { + command: this.id || 'unknown', + }; + + async init(): Promise { + await super.init(); + log.debug('Command initialized', this.contextDetails); + } +} +``` + +## OCLIF Configuration + +### Package.json Setup +```json +{ + "oclif": { + "commands": "./lib/commands", + "bin": "csdx", + "topicSeparator": ":" + } +} +``` + +### Command Topics +- All commands use `cm` topic: `cm:config:set`, `cm:auth:login` +- Built commands live in `lib/commands` (compiled from `src/commands`) +- Commands use nested directories: `src/commands/config/set.ts` → `cm:config:set` + +### Command Naming +- **Topic hierarchy**: `config/remove/proxy.ts` → `cm:config:remove:proxy` +- **Descriptive names**: Use verb-noun pattern (`set`, `remove`, `show`) +- **Grouping**: Related commands share parent topics + +## Flag Management + +### Flag Definition Patterns +```typescript +// ✅ GOOD - Define flags clearly +static flags: FlagInput = { + 'stack-api-key': flags.string({ + char: 'k', + description: 'Stack API key', + required: false, + }), + region: flags.string({ + char: 'r', + description: 'Set region', + options: ['us', 'eu'], + }), + verbose: flags.boolean({ + char: 'v', + description: 'Show verbose output', + default: false, + }), +}; +``` + +### Flag Parsing +```typescript +// ✅ GOOD - Parse and validate flags +async run(): Promise { + const { flags: parsedFlags } = await this.parse(MyCommand); + + // Validate flag combinations + if (!parsedFlags['stack-api-key'] && !parsedFlags.alias) { + this.error('Either --stack-api-key or --alias is required'); + } + + // Use parsed flags + const region = parsedFlags.region || 'us'; +} +``` + +## Error Handling + +### Standard Error Pattern +```typescript +// ✅ GOOD - Use handleAndLogError from utilities +try { + await this.executeCommand(); +} catch (error) { + handleAndLogError(error, { module: 'my-command' }); +} +``` + +### User-Friendly Messages +```typescript +// ✅ GOOD - Clear user feedback +import { cliux } from '@contentstack/cli-utilities'; + +// Success message +cliux.success('Configuration updated successfully', { color: 'green' }); + +// Error message +cliux.error('Invalid region specified', { color: 'red' }); + +// Info message +cliux.print('Setting region to eu', { color: 'blue' }); +``` + +## Validation Patterns + +### Early Validation +```typescript +// ✅ GOOD - Validate flags early +async run(): Promise { + const { flags } = await this.parse(MyCommand); + + // Validate required flags + if (!flags.region) { + this.error('--region is required'); + } + + // Validate flag values + if (!['us', 'eu'].includes(flags.region)) { + this.error('Region must be "us" or "eu"'); + } + + // Proceed with validated input +} +``` + +## Progress and Logging + +### User Feedback +```typescript +// ✅ GOOD - Provide user feedback +import { log, cliux } from '@contentstack/cli-utilities'; + +// Regular logging +this.log('Starting configuration update...'); + +// Debug logging +log.debug('Detailed operation information', { context: 'data' }); + +// Status messages +cliux.print('Processing...', { color: 'blue' }); +``` + +### Progress Indication +```typescript +// ✅ GOOD - Show progress for long operations +cliux.print('Processing items...', { color: 'blue' }); +let count = 0; +for (const item of items) { + await this.processItem(item); + count++; + cliux.print(`Processed ${count}/${items.length} items`, { color: 'blue' }); +} +``` + +## Command Delegation + +### Service Layer Separation +```typescript +// ✅ GOOD - Commands orchestrate, services implement +async run(): Promise { + try { + const { flags } = await this.parse(MyCommand); + const config = this.buildConfig(flags); + const service = new ConfigService(config); + + await service.execute(); + cliux.success('Operation completed successfully'); + } catch (error) { + this.handleError(error); + } +} +``` + +## Testing Commands + +### OCLIF Test Support +```typescript +// ✅ GOOD - Use @oclif/test for command testing +import { test } from '@oclif/test'; + +describe('cm:config:set', () => { + test + .stdout() + .command(['cm:config:set', '--help']) + .it('shows help', ctx => { + expect(ctx.stdout).to.contain('Set CLI configuration'); + }); + + test + .stdout() + .command(['cm:config:set', '--region', 'eu']) + .it('sets region to eu', ctx => { + expect(ctx.stdout).to.contain('success'); + }); +}); +``` + +## Log Integration + +### Debug Logging +```typescript +// ✅ GOOD - Use structured debug logging +import { log } from '@contentstack/cli-utilities'; + +log.debug('Command started', { + command: this.id, + flags: this.flags, + timestamp: new Date().toISOString(), +}); + +log.debug('Processing complete', { + itemsProcessed: count, + module: 'my-command', +}); +``` + +### Error Context +```typescript +// ✅ GOOD - Include context in error handling +try { + await operation(); +} catch (error) { + handleAndLogError(error, { + module: 'config-set', + command: 'cm:config:set', + flags: { region: 'eu' }, + }); +} +``` + +## Multi-Topic Commands + +### Nested Command Structure +```typescript +// File: src/commands/config/show.ts +export default class ShowConfigCommand extends Command { + static description = 'Show current configuration'; + static examples = ['csdx config:show']; + async run(): Promise { } +} + +// File: src/commands/config/set.ts +export default class SetConfigCommand extends Command { + static description = 'Set configuration values'; + static examples = ['csdx config:set --region eu']; + async run(): Promise { } +} + +// Generated commands: +// - cm:config:show +// - cm:config:set +``` + +## Best Practices + +### Command Organization +```typescript +// ✅ GOOD - Well-organized command +export default class MyCommand extends Command { + static description = 'Clear, concise description'; + + static flags: FlagInput = { + // Define all flags + }; + + static examples = [ + 'csdx my:command', + 'csdx my:command --flag value', + ]; + + async run(): Promise { + try { + const { flags } = await this.parse(MyCommand); + await this.execute(flags); + } catch (error) { + handleAndLogError(error, { module: 'my-command' }); + } + } + + private async execute(flags: Flags): Promise { + // Implementation + } +} +``` + +### Clear Help Text +- Write description as action-oriented statement +- Provide multiple examples for common use cases +- Document each flag with clear description +- Show output format or examples of results diff --git a/.cursor/rules/testing.mdc b/.cursor/rules/testing.mdc new file mode 100644 index 00000000..daf6de10 --- /dev/null +++ b/.cursor/rules/testing.mdc @@ -0,0 +1,323 @@ +--- +description: 'Testing patterns and TDD workflow' +globs: ['**/test/**/*.ts', '**/test/**/*.js', '**/__tests__/**/*.ts', '**/*.spec.ts', '**/*.test.ts'] +alwaysApply: true +--- + +# Testing Standards + +## Framework Stack + +### Primary Testing Tools +- **Mocha** - Test runner (used across all packages) +- **Chai** - Assertion library +- **@oclif/test** - Command testing support (for plugin packages) + +### Test Setup +- TypeScript compilation via ts-node/register +- Source map support for stack traces +- Global test timeout: 30 seconds (configurable per package) + +## Test File Patterns + +### Naming Conventions +- **Primary**: `*.test.ts` (standard pattern across all packages) +- **Location**: `test/unit/**/*.test.ts` (most packages) + +### Directory Structure +``` +packages/*/ +├── test/ +│ └── unit/ +│ ├── commands/ # Command-specific tests +│ ├── services/ # Service/business logic tests +│ └── utils/ # Utility function tests +└── src/ # Source code + ├── commands/ # CLI commands + ├── services/ # Business logic + └── utils/ # Utilities +``` + +## Mocha Configuration + +### Standard Setup (.mocharc.json) +```json +{ + "require": [ + "test/helpers/init.js", + "ts-node/register", + "source-map-support/register" + ], + "recursive": true, + "timeout": 30000, + "spec": "test/**/*.test.ts" +} +``` + +### TypeScript Compilation +```json +// package.json scripts +{ + "test": "mocha \"test/unit/**/*.test.ts\"", + "test:coverage": "nyc mocha \"test/unit/**/*.test.ts\"" +} +``` + +## Test Structure + +### Standard Test Pattern +```typescript +// ✅ GOOD - Comprehensive test structure +describe('ConfigService', () => { + let service: ConfigService; + + beforeEach(() => { + service = new ConfigService(); + }); + + describe('loadConfig()', () => { + it('should load configuration successfully', async () => { + // Arrange + const expectedConfig = { region: 'us' }; + + // Act + const result = await service.loadConfig(); + + // Assert + expect(result).to.deep.equal(expectedConfig); + }); + + it('should handle missing configuration', async () => { + // Arrange & Act & Assert + await expect(service.loadConfig()).to.be.rejectedWith('Config not found'); + }); + }); +}); +``` + +### Async/Await Pattern +```typescript +// ✅ GOOD - Use async/await in tests +it('should process data asynchronously', async () => { + const result = await service.processAsync(); + expect(result).to.exist; +}); + +// ✅ GOOD - Explicit Promise handling +it('should return a promise', () => { + return service.asyncMethod().then(result => { + expect(result).to.be.true; + }); +}); +``` + +## Mocking Patterns + +### Class Mocking +```typescript +// ✅ GOOD - Mock class dependencies +class MockConfigService { + async loadConfig() { + return { region: 'us' }; + } +} + +it('should use mocked service', async () => { + const mockService = new MockConfigService(); + const result = await mockService.loadConfig(); + expect(result.region).to.equal('us'); +}); +``` + +### Function Stubs +```typescript +// ✅ GOOD - Stub module functions if needed +beforeEach(() => { + // Stub file system operations + // Stub network calls +}); + +afterEach(() => { + // Restore original implementations +}); +``` + +## Command Testing + +### OCLIF Test Pattern +```typescript +// ✅ GOOD - Test commands with @oclif/test +import { test } from '@oclif/test'; + +describe('cm:config:region', () => { + test + .stdout() + .command(['cm:config:region', '--help']) + .it('shows help message', ctx => { + expect(ctx.stdout).to.contain('Display region'); + }); + + test + .stdout() + .command(['cm:config:region']) + .it('shows current region', ctx => { + expect(ctx.stdout).to.contain('us'); + }); +}); +``` + +### Command Flag Testing +```typescript +// ✅ GOOD - Test command flags and arguments +describe('cm:config:set', () => { + test + .command(['cm:config:set', '--help']) + .it('shows usage information'); + + test + .command(['cm:config:set', '--region', 'eu']) + .it('sets region to eu'); +}); +``` + +## Error Testing + +### Error Handling +```typescript +// ✅ GOOD - Test error scenarios +it('should throw ValidationError on invalid input', async () => { + const invalidInput = ''; + await expect(service.validate(invalidInput)) + .to.be.rejectedWith('Invalid input'); +}); + +it('should handle network errors gracefully', async () => { + // Mock network failure + const result = await service.fetchWithRetry(); + expect(result).to.be.null; +}); +``` + +### Error Types +```typescript +// ✅ GOOD - Test specific error types +it('should throw appropriate error', async () => { + try { + await service.failingOperation(); + } catch (error) { + expect(error).to.be.instanceof(ValidationError); + expect(error.code).to.equal('INVALID_CONFIG'); + } +}); +``` + +## Test Data Management + +### Mock Data Organization +```typescript +// ✅ GOOD - Organize test data +const mockData = { + validConfig: { + region: 'us', + timeout: 30000, + }, + invalidConfig: { + region: '', + }, + users: [ + { email: 'user1@example.com', name: 'User 1' }, + { email: 'user2@example.com', name: 'User 2' }, + ], +}; +``` + +### Test Helpers +```typescript +// ✅ GOOD - Create reusable test utilities +export function createMockConfig(overrides?: Partial): Config { + return { + region: 'us', + timeout: 30000, + ...overrides, + }; +} + +export function createMockService( + config: Config = createMockConfig() +): ConfigService { + return new ConfigService(config); +} +``` + +## Coverage + +### Coverage Goals +- **Team aspiration**: 80% minimum coverage +- **Current enforcement**: Applied consistently across packages +- **Focus areas**: Critical business logic and error paths + +### Coverage Reporting +```bash +# Run tests with coverage +pnpm test:coverage + +# Coverage reports generated in: +# - coverage/index.html (HTML report) +# - coverage/coverage-summary.json (JSON report) +``` + +## Critical Testing Rules + +- **No real external calls** - Mock all dependencies +- **Test both success and failure paths** - Cover error scenarios completely +- **One assertion per test** - Focus each test on single behavior +- **Use descriptive test names** - Test name should explain what's tested +- **Arrange-Act-Assert** - Follow AAA pattern consistently +- **Test command validation** - Verify flag validation and error messages +- **Clean up after tests** - Restore any mocked state + +## Best Practices + +### Test Organization +```typescript +// ✅ GOOD - Organize related tests +describe('AuthCommand', () => { + describe('login', () => { + it('should authenticate user'); + it('should save token'); + }); + + describe('logout', () => { + it('should clear token'); + it('should reset config'); + }); +}); +``` + +### Async Test Patterns +```typescript +// ✅ GOOD - Handle async operations properly +it('should complete async operation', async () => { + const promise = service.asyncMethod(); + expect(promise).to.be.instanceof(Promise); + + const result = await promise; + expect(result).to.equal('success'); +}); +``` + +### Isolation +```typescript +// ✅ GOOD - Ensure test isolation +describe('ConfigService', () => { + let service: ConfigService; + + beforeEach(() => { + service = new ConfigService(); + }); + + afterEach(() => { + // Clean up resources + }); +}); +``` diff --git a/.cursor/rules/typescript.mdc b/.cursor/rules/typescript.mdc new file mode 100644 index 00000000..ea4d82a2 --- /dev/null +++ b/.cursor/rules/typescript.mdc @@ -0,0 +1,246 @@ +--- +description: 'TypeScript strict mode standards and naming conventions' +globs: ['**/*.ts', '**/*.tsx'] +alwaysApply: false +--- + +# TypeScript Standards + +## Configuration + +### Standard Configuration (All Packages) +```json +{ + "compilerOptions": { + "declaration": true, + "importHelpers": true, + "module": "commonjs", + "outDir": "lib", + "rootDir": "src", + "strict": false, // Relaxed for compatibility + "target": "es2017", + "sourceMap": false, + "allowJs": true, // Mixed JS/TS support + "skipLibCheck": true, + "esModuleInterop": true + }, + "include": ["src/**/*"] +} +``` + +### Root Configuration +```json +// tsconfig.json - Baseline configuration +{ + "compilerOptions": { + "strict": false, + "module": "commonjs", + "target": "es2017", + "declaration": true, + "outDir": "lib", + "rootDir": "src" + } +} +``` + +## Naming Conventions (Actual Usage) + +### Files +- **Primary pattern**: `kebab-case.ts` (e.g., `base-command.ts`, `config-handler.ts`) +- **Single-word modules**: `index.ts`, `types.ts` +- **Commands**: Follow OCLIF topic structure (`cm/auth/login.ts`, `cm/config/region.ts`) + +### Classes +```typescript +// ✅ GOOD - PascalCase for classes +export default class ConfigCommand extends Command { } +export class AuthService { } +export class ValidationError extends Error { } +``` + +### Functions and Methods +```typescript +// ✅ GOOD - camelCase for functions +export async function loadConfig(): Promise { } +async validateInput(input: string): Promise { } +createCommandContext(): CommandContext { } +``` + +### Constants +```typescript +// ✅ GOOD - SCREAMING_SNAKE_CASE for constants +const DEFAULT_REGION = 'us'; +const MAX_RETRIES = 3; +const API_BASE_URL = 'https://api.contentstack.io'; +``` + +### Interfaces and Types +```typescript +// ✅ GOOD - PascalCase for types +export interface CommandConfig { + region: string; + alias?: string; +} + +export type CommandResult = { + success: boolean; + message?: string; +}; +``` + +## Import/Export Patterns + +### ES Modules (Preferred) +```typescript +// ✅ GOOD - ES import/export syntax +import { Command } from '@oclif/core'; +import type { CommandConfig } from '../types'; +import { loadConfig } from '../utils'; + +export default class ConfigCommand extends Command { } +export { CommandConfig }; +``` + +### Default Exports +```typescript +// ✅ GOOD - Default export for commands and main classes +export default class ConfigCommand extends Command { } +``` + +### Named Exports +```typescript +// ✅ GOOD - Named exports for utilities and types +export async function delay(ms: number): Promise { } +export interface CommandOptions { } +export type ActionResult = 'success' | 'failure'; +``` + +## Type Definitions + +### Local Types +```typescript +// ✅ GOOD - Define types close to usage +export interface AuthOptions { + email: string; + password: string; + token?: string; +} + +export type ConfigResult = { + success: boolean; + config?: Record; +}; +``` + +### Type Organization +```typescript +// ✅ GOOD - Organize types in dedicated files +// src/types/index.ts +export interface CommandConfig { } +export interface AuthConfig { } +export type ConfigValue = string | number | boolean; +``` + +## Null Safety + +### Function Return Types +```typescript +// ✅ GOOD - Explicit return types +export async function getConfig(): Promise { + return await this.loadFromFile(); +} + +export function createDefaults(): CommandConfig { + return { + region: 'us', + timeout: 30000, + }; +} +``` + +### Null/Undefined Handling +```typescript +// ✅ GOOD - Handle null/undefined explicitly +function processConfig(config: CommandConfig | null): void { + if (!config) { + throw new Error('Configuration is required'); + } + // Process config safely +} +``` + +## Error Handling Types + +### Custom Error Classes +```typescript +// ✅ GOOD - Typed error classes +export class ValidationError extends Error { + constructor( + message: string, + public readonly code?: string + ) { + super(message); + this.name = 'ValidationError'; + } +} +``` + +### Error Union Types +```typescript +// ✅ GOOD - Model expected errors +type AuthResult = { + success: true; + data: T; +} | { + success: false; + error: string; +}; +``` + +## Strict Mode Adoption + +### Current Status +- Most packages use `strict: false` for compatibility +- Gradual migration path available +- Team working toward stricter TypeScript + +### Gradual Adoption +```typescript +// ✅ ACCEPTABLE - Comments for known issues +// TODO: Fix type issues in legacy code +const legacyData = unknownData as unknown; +``` + +## Package-Specific Patterns + +### Command Packages (auth, config) +- Extend `@oclif/core` Command +- Define command flags with `static flags` +- Use @oclif/core flag utilities +- Define command-specific types + +### Library Packages (command, utilities) +- No OCLIF dependencies +- Pure TypeScript interfaces +- Consumed by command packages +- Focus on type safety for exports + +### Main Package (contentstack) +- Aggregates command plugins +- May have common types +- Shared interfaces for plugin integration + +## Export Patterns + +### Package Exports (lib/index.js) +```typescript +// ✅ GOOD - Barrel exports for libraries +export { Command } from './command'; +export { loadConfig } from './config'; +export type { CommandConfig, AuthOptions } from './types'; +``` + +### Entry Points +- Libraries export from `lib/index.js` +- Commands export directly as default classes +- Type definitions included via `types` field in package.json diff --git a/.cursor/skills/SKILL.md b/.cursor/skills/SKILL.md new file mode 100644 index 00000000..db406d53 --- /dev/null +++ b/.cursor/skills/SKILL.md @@ -0,0 +1,32 @@ +--- +name: contentstack-cli-skills +description: Collection of project-specific skills for Contentstack CLI plugins monorepo development. Use when working with CLI commands, testing, framework utilities, or reviewing code changes. +--- + +# Contentstack CLI Skills + +Project-specific skills for the pnpm monorepo containing 12 CLI plugin packages. + +## Skills Overview + +| Skill | Purpose | Trigger | +|-------|---------|---------| +| **testing** | Testing patterns, TDD workflow, and test automation for CLI development | When writing tests or debugging test failures | +| **framework** | Core utilities, configuration, logging, and framework patterns | When working with utilities, config, or error handling | +| **contentstack-cli** | CLI commands, OCLIF patterns, authentication and configuration workflows | When implementing commands or integrating APIs | +| **code-review** | PR review guidelines and monorepo-aware checks | When reviewing code or pull requests | + +## Quick Links + +- **[Testing Skill](./testing/SKILL.md)** — TDD patterns, test structure, mocking strategies +- **[Framework Skill](./framework/SKILL.md)** — Utilities, configuration, logging, error handling +- **[Contentstack CLI Skill](./contentstack-cli/SKILL.md)** — Command development, API integration, auth/config patterns +- **[Code Review Skill](./code-review/SKILL.md)** — Review checklist with monorepo awareness + +## Repository Context + +- **Monorepo**: 12 pnpm workspace packages under `packages/` (all CLI plugins for content management) +- **Tech Stack**: TypeScript, OCLIF v4, Mocha+Chai, pnpm workspaces +- **Packages**: `@contentstack/cli-cm-*` scope (import, export, audit, bootstrap, branches, bulk-publish, clone, export-to-csv, import-setup, migration, seed, variants) +- **Dependencies**: All plugins depend on `@contentstack/cli-command` and `@contentstack/cli-utilities` +- **Build**: TypeScript → `lib/` directories, OCLIF manifest generation per plugin diff --git a/.cursor/skills/code-review/SKILL.md b/.cursor/skills/code-review/SKILL.md new file mode 100644 index 00000000..bc647259 --- /dev/null +++ b/.cursor/skills/code-review/SKILL.md @@ -0,0 +1,77 @@ +--- +name: code-review +description: Automated PR review checklist covering security, performance, architecture, and code quality. Use when reviewing pull requests, examining code changes, or performing code quality assessments. +--- + +# Code Review Skill + +## Quick Reference + +For comprehensive review guidelines, see: +- **[Code Review Checklist](./references/code-review-checklist.md)** - Complete PR review guidelines with severity levels and checklists + +## Review Process + +### Severity Levels +- 🔴 **Critical**: Must fix before merge (security, correctness, breaking changes) +- 🟡 **Important**: Should fix (performance, maintainability, best practices) +- 🟢 **Suggestion**: Consider improving (style, optimization, readability) + +### Quick Review Categories + +1. **Security** - No hardcoded secrets, input validation, secure error handling +2. **Correctness** - Logic validation, error scenarios, data integrity +3. **Architecture** - Code organization, design patterns, modularity +4. **Performance** - Efficiency, resource management, concurrency +5. **Testing** - Test coverage, quality tests, TDD compliance +6. **Conventions** - TypeScript standards, code style, documentation +7. **Monorepo** - Cross-package imports, workspace dependencies, manifest validity + +## Quick Checklist Template + +```markdown +## Security Review +- [ ] No hardcoded secrets or tokens +- [ ] Input validation present +- [ ] Error handling secure (no sensitive data in logs) + +## Correctness Review +- [ ] Logic correctly implemented +- [ ] Edge cases handled +- [ ] Error scenarios covered +- [ ] Async/await chains correct + +## Architecture Review +- [ ] Proper code organization +- [ ] Design patterns followed +- [ ] Good modularity +- [ ] No circular dependencies + +## Performance Review +- [ ] Efficient implementation +- [ ] No unnecessary API calls +- [ ] Memory leaks avoided +- [ ] Concurrency handled correctly + +## Testing Review +- [ ] Adequate test coverage (80%+) +- [ ] Quality tests (not just passing) +- [ ] TDD compliance +- [ ] Both success and failure paths tested + +## Code Conventions +- [ ] TypeScript strict mode +- [ ] Consistent naming conventions +- [ ] No unused imports or variables +- [ ] Documentation adequate + +## Monorepo Checks +- [ ] Cross-package imports use published names +- [ ] Workspace dependencies declared correctly +- [ ] OCLIF manifest updated if commands changed +- [ ] No breaking changes to exported APIs +``` + +## Usage + +Use the comprehensive checklist guide for detailed review guidelines, common issues, severity assessment, and best practices for code quality in the Contentstack CLI monorepo. diff --git a/.cursor/skills/code-review/references/code-review-checklist.md b/.cursor/skills/code-review/references/code-review-checklist.md new file mode 100644 index 00000000..682cc86a --- /dev/null +++ b/.cursor/skills/code-review/references/code-review-checklist.md @@ -0,0 +1,373 @@ +# Code Review Checklist + +Automated PR review guidelines covering security, performance, architecture, and code quality for the Contentstack CLI monorepo. + +## Review Process + +### Severity Levels +- **🔴 Critical** (must fix before merge): + - Security vulnerabilities + - Logic errors causing incorrect behavior + - Breaking API changes + - Hardcoded secrets or credentials + - Data loss scenarios + +- **🟡 Important** (should fix): + - Performance regressions + - Code maintainability issues + - Missing error handling + - Test coverage gaps + - Best practice violations + +- **🟢 Suggestion** (consider improving): + - Code readability improvements + - Minor optimizations + - Documentation enhancements + - Style inconsistencies + +## Security Review + +### Secrets and Credentials +- [ ] No hardcoded API keys, tokens, or passwords +- [ ] No secrets in environment variables exposed in logs +- [ ] No secrets in test data or fixtures +- [ ] Sensitive data not logged or exposed in error messages +- [ ] Config files don't contain real credentials + +### Input Validation +```typescript +// ✅ GOOD - Validate all user input +if (!region || typeof region !== 'string') { + throw new CLIError('Region must be a non-empty string'); +} + +if (!['us', 'eu', 'au'].includes(region)) { + throw new CLIError('Invalid region specified'); +} + +// ❌ BAD - No validation +const result = await client.setRegion(region); +``` + +### Error Handling +- [ ] No sensitive data in error messages +- [ ] Stack traces don't leak system information +- [ ] Error messages are user-friendly +- [ ] All errors properly caught and handled +- [ ] No raw error objects exposed to users + +### Authentication +- [ ] OAuth/token handling is secure +- [ ] No storing plaintext passwords +- [ ] Session tokens validated +- [ ] Auth state properly managed +- [ ] Rate limiting respected + +## Correctness Review + +### Logic Validation +- [ ] Business logic correctly implemented +- [ ] Algorithm is correct for the problem +- [ ] State transitions valid +- [ ] Data types used correctly +- [ ] Comparisons use correct operators (=== not ==) + +### Error Scenarios +- [ ] API failures handled (404, 429, 500, etc.) +- [ ] Network timeouts managed +- [ ] Empty/null responses handled +- [ ] Invalid input rejected +- [ ] Partial failures handled gracefully + +### Async/Await and Promises +```typescript +// ✅ GOOD - Proper async handling +async run(): Promise { + try { + const result = await this.fetchData(); + await this.processData(result); + } catch (error) { + handleAndLogError(error, this.contextDetails); + } +} + +// ❌ BAD - Missing await +async run(): Promise { + const result = this.fetchData(); // Missing await! + await this.processData(result); +} +``` + +### Data Integrity +- [ ] No race conditions +- [ ] State mutations atomic +- [ ] Data validation before mutations +- [ ] Rollback strategy for failures +- [ ] Concurrent operations safe + +## Architecture Review + +### Code Organization +- [ ] Classes/functions have single responsibility +- [ ] No circular dependencies +- [ ] Proper module structure +- [ ] Clear separation of concerns +- [ ] Commands delegate to services/utils + +### Design Patterns +```typescript +// ✅ GOOD - Service layer separation +export default class MyCommand extends BaseCommand { + async run(): Promise { + const service = new MyService(this.contextDetails); + const result = await service.execute(); + } +} + +// ❌ BAD - Logic in command +export default class MyCommand extends BaseCommand { + async run(): Promise { + const data = await client.fetch(); + for (const item of data) { + // Complex business logic here + } + } +} +``` + +### Modularity +- [ ] Services are reusable +- [ ] Utils are generic and testable +- [ ] Dependencies injected +- [ ] No tight coupling +- [ ] Easy to test in isolation + +### Type Safety +- [ ] TypeScript strict mode enabled +- [ ] No `any` types without justification +- [ ] Interfaces defined for contracts +- [ ] Generic types used appropriately +- [ ] Type narrowing correct + +## Performance Review + +### API Calls +- [ ] Rate limiting respected (10 req/sec for Contentstack) +- [ ] No unnecessary API calls in loops +- [ ] Pagination implemented for large datasets +- [ ] Caching used where appropriate +- [ ] Batch operations considered + +### Memory Management +- [ ] No memory leaks in event listeners +- [ ] Large arrays streamed not loaded fully +- [ ] Cleanup in try/finally blocks +- [ ] File handles properly closed +- [ ] Resources released after use + +### Concurrency +```typescript +// ✅ GOOD - Controlled concurrency +const results = await Promise.all( + items.map(item => processItem(item)) +); + +// ❌ BAD - Unbounded concurrency +for (const item of items) { + promises.push(processItem(item)); // No limit! +} +``` + +### Efficiency +- [ ] Algorithms are efficient for scale +- [ ] String concatenation uses proper methods +- [ ] Unnecessary computations avoided +- [ ] Data structures appropriate +- [ ] No repeated lookups without caching + +## Testing Review + +### Coverage +- [ ] 80%+ line coverage achieved +- [ ] 80%+ branch coverage +- [ ] Critical paths fully tested +- [ ] Error paths tested +- [ ] Edge cases included + +### Test Quality +```typescript +// ✅ GOOD - Quality test +it('should throw error when region is empty', () => { + expect(() => validateRegion('')) + .to.throw('Region is required'); +}); + +// ❌ BAD - Weak test +it('should validate region', () => { + validateRegion('us'); // No assertion! +}); +``` + +### Testing Patterns +- [ ] Descriptive test names +- [ ] Arrange-Act-Assert pattern +- [ ] One assertion per test (focus) +- [ ] Mocks properly isolated +- [ ] No test interdependencies + +### TDD Compliance +- [ ] Tests written before implementation +- [ ] All tests pass +- [ ] Code coverage requirements met +- [ ] Tests are maintainable +- [ ] Both success and failure paths covered + +## Code Conventions + +### TypeScript Standards +- [ ] `strict: true` in tsconfig +- [ ] No `any` types (use `unknown` if needed) +- [ ] Proper null/undefined handling +- [ ] Type annotations on public APIs +- [ ] Generics used appropriately + +### Naming Conventions +- [ ] Classes: PascalCase (`MyClass`) +- [ ] Functions: camelCase (`myFunction`) +- [ ] Constants: UPPER_SNAKE_CASE (`MY_CONST`) +- [ ] Booleans start with verb (`isActive`, `hasData`) +- [ ] Descriptive names (not `x`, `temp`, `data`) + +### Code Style +- [ ] No unused imports +- [ ] No unused variables +- [ ] Proper indentation (consistent spacing) +- [ ] Line length reasonable (<100 chars) +- [ ] Imports organized (group by type) + +### Documentation +- [ ] Complex logic documented +- [ ] Public APIs have JSDoc comments +- [ ] Non-obvious decisions explained +- [ ] Examples provided where helpful +- [ ] README updated if needed + +## Monorepo-Specific Checks + +### Cross-Package Imports +```typescript +// ✅ GOOD - Use published package names +import { configHandler } from '@contentstack/cli-utilities'; +import { BaseCommand } from '@contentstack/cli-command'; + +// ❌ BAD - Relative paths across packages +import { configHandler } from '../../../contentstack-utilities/src'; +``` + +### Workspace Dependencies +- [ ] Dependencies declared in correct `package.json` +- [ ] Versions consistent across packages +- [ ] No circular dependencies between packages +- [ ] Shared deps in root if used by multiple packages +- [ ] No installing globally when local version exists + +### OCLIF Configuration +- [ ] Command file paths correct in `package.json` +- [ ] OCLIF manifest regenerated (`pnpm build && pnpm oclif manifest`) +- [ ] New commands registered in plugin list if needed +- [ ] Topic separators consistent (`:`) +- [ ] Command names follow pattern + +### Build and Publishing +- [ ] TypeScript compiles without errors +- [ ] Build output in `lib/` directory +- [ ] No build artifacts committed +- [ ] ESLint passes without warnings +- [ ] Tests pass in CI environment + +## Common Issues to Look For + +### Security +- [ ] Config with default passwords +- [ ] API keys in client-side code +- [ ] SQL injection (N/A here, but parameterization pattern) +- [ ] XSS in CLI output (escape HTML if needed) + +### Performance +- [ ] API calls in loops +- [ ] Synchronous file I/O on large files +- [ ] Memory not released properly +- [ ] Rate limiting ignored + +### Logic +- [ ] Off-by-one errors in loops +- [ ] Wrong comparison operators +- [ ] Async/await chains broken +- [ ] Null/undefined not handled + +### Testing +- [ ] Tests pass locally but fail in CI +- [ ] Mocks not properly restored +- [ ] Race conditions in tests +- [ ] Hardcoded timeouts + +## Review Checklist Template + +```markdown +## Security +- [ ] No hardcoded secrets +- [ ] Input validation present +- [ ] Error handling secure + +## Correctness +- [ ] Logic is correct +- [ ] Edge cases handled +- [ ] Error scenarios covered + +## Architecture +- [ ] Good code organization +- [ ] Design patterns followed +- [ ] Modularity intact + +## Performance +- [ ] Efficient implementation +- [ ] Rate limits respected +- [ ] Memory managed properly + +## Testing +- [ ] Adequate coverage +- [ ] Quality tests +- [ ] Both paths tested + +## Conventions +- [ ] TypeScript standards met +- [ ] Code style consistent +- [ ] Documentation adequate + +## Monorepo +- [ ] Package imports correct +- [ ] Dependencies declared properly +- [ ] Manifest/build updated +- [ ] No breaking changes +``` + +## Approval Criteria + +**APPROVE when:** +- ✅ All 🔴 Critical items addressed +- ✅ Most 🟡 Important items addressed (document exceptions) +- ✅ Code follows team conventions +- ✅ Tests pass and coverage sufficient +- ✅ Monorepo integrity maintained + +**REQUEST CHANGES when:** +- ❌ Any 🔴 Critical issues present +- ❌ Multiple 🟡 Important issues unaddressed +- ❌ Test coverage below 80% +- ❌ Architecture concerns not resolved +- ❌ Breaking changes not documented + +**COMMENT for:** +- 💬 🟢 Suggestions (non-blocking) +- 💬 Questions about implementation +- 💬 Appreciation for good patterns diff --git a/.cursor/skills/contentstack-cli/SKILL.md b/.cursor/skills/contentstack-cli/SKILL.md new file mode 100644 index 00000000..df669104 --- /dev/null +++ b/.cursor/skills/contentstack-cli/SKILL.md @@ -0,0 +1,178 @@ +--- +name: contentstack-cli +description: Contentstack CLI development patterns, OCLIF commands, API integration, and authentication/configuration workflows. Use when working with Contentstack CLI plugins, OCLIF commands, CLI commands, or Contentstack API integration. +--- + +# Contentstack CLI Development + +## Quick Reference + +For comprehensive patterns, see: +- **[Contentstack Patterns](./references/contentstack-patterns.md)** - Complete CLI commands, API integration, and configuration patterns +- **[Framework Patterns](../framework/references/framework-patterns.md)** - Utilities, configuration, and error handling + +## Key Patterns Summary + +### OCLIF Command Structure +- Extend plugin-specific `BaseCommand` or `Command` from `@contentstack/cli-command` +- Validate flags early: `if (!flags['stack-api-key']) this.error('Stack API key is required')` +- Delegate to services/modules: commands handle CLI, services handle business logic +- Show progress: `cliux.success('✅ Operation completed')` +- Include command examples: `static examples = ['$ csdx cm:stacks:import -k -d ./data', '$ csdx cm:stacks:export -k ']` + +### Command Topics +- CM topic commands: `cm:stacks:import`, `cm:stacks:export`, `cm:stacks:audit`, `cm:stacks:clone`, etc. +- File pattern: `src/commands/cm/stacks/import.ts` → command `cm:stacks:import` +- Plugin structure: Each package defines commands in `oclif.commands` pointing to `./lib/commands` + +### Flag Patterns +```typescript +static flags: FlagInput = { + username: flags.string({ + char: 'u', + description: 'Email address', + required: false + }), + oauth: flags.boolean({ + description: 'Enable SSO', + default: false, + exclusive: ['username', 'password'] + }) +}; +``` + +### Logging and Error Handling +- Use structured logging: `log.debug('Message', { context: 'data' })` +- Include contextDetails: `handleAndLogError(error, { ...this.contextDetails, module: 'auth-login' })` +- User feedback: `cliux.success()`, `cliux.error()`, `throw new CLIError()` + +### I18N Messages +- Store user-facing strings in `messages/*.json` files +- Load with `messageHandler` from utilities +- Example: `messages/en.json` for English strings + +## Command Base Class Pattern + +Each plugin defines its own `BaseCommand` extending `@contentstack/cli-command`: + +```typescript +export abstract class BaseCommand extends Command { + protected sharedConfig = { basePath: process.cwd() }; + + static baseFlags: FlagInput = { + config: Flags.string({ + char: 'c', + description: 'Path to config file', + }), + 'data-dir': Flags.string({ + char: 'd', + description: 'Data directory path', + }), + }; + + async init(): Promise { + await super.init(); + const { args, flags } = await this.parse({ + flags: this.ctor.flags, + args: this.ctor.args, + }); + this.args = args; + this.flags = flags; + } +} +``` + +Specialized base commands extend this for domain-specific concerns (e.g., `AuditBaseCommand` for audit operations). + +## Plugin Development Patterns + +### Import Plugin Example +```typescript +// packages/contentstack-import/src/commands/cm/stacks/import.ts +export default class ImportCommand extends BaseCommand { + static id = 'cm:stacks:import'; + static description = 'Import content into a stack'; + + static flags: FlagInput = { + 'stack-api-key': Flags.string({ + char: 'k', + description: 'Stack API key', + required: true, + }), + 'data-dir': Flags.string({ + char: 'd', + description: 'Directory with import data', + required: true, + }), + }; + + async run(): Promise { + const { flags } = this; + const importService = new ImportService(flags); + await importService.import(); + cliux.success('✅ Import completed'); + } +} +``` + +### Service Layer Pattern +Services encapsulate business logic separate from CLI concerns: + +```typescript +export class ImportService { + async import(): Promise { + await this.validateInput(); + await this.loadData(); + await this.importContent(); + } +} +``` + +### Module Pattern +Complex domains split work across modules: + +```typescript +export class Entries { + async import(entries: any[]): Promise { + for (const entry of entries) { + await this.importEntry(entry); + } + } +} +``` + +## API Integration + +### Management SDK Client +```typescript +import { managementSDKClient } from '@contentstack/cli-utilities'; + +const client = await managementSDKClient({ + host: this.cmaHost, + skipTokenValidity: true +}); + +const stack = client.stack({ api_key: stackApiKey }); +const entries = await stack.entry().query().find(); +``` + +### Error Handling for API Calls +```typescript +try { + const result = await this.client.stack().entry().fetch(); +} catch (error) { + if (error.status === 401) { + throw new CLIError('Authentication failed. Please login again.'); + } else if (error.status === 404) { + throw new CLIError('Entry not found.'); + } + handleAndLogError(error, { + module: 'entry-fetch', + entryId: entryUid + }); +} +``` + +## Usage + +Reference the comprehensive patterns guide above for detailed implementations, examples, and best practices for CLI command development, authentication flows, configuration management, and API integration. diff --git a/.cursor/skills/contentstack-cli/references/contentstack-patterns.md b/.cursor/skills/contentstack-cli/references/contentstack-patterns.md new file mode 100644 index 00000000..2ee74721 --- /dev/null +++ b/.cursor/skills/contentstack-cli/references/contentstack-patterns.md @@ -0,0 +1,476 @@ +# Contentstack CLI Patterns + +Contentstack CLI plugin development patterns, OCLIF commands, API integration, and workflows. + +## OCLIF Command Structure + +### Plugin Base Command Pattern +```typescript +import { BaseCommand } from '../../base-command'; +import { FlagInput, Flags } from '@contentstack/cli-utilities'; +import { cliux, handleAndLogError } from '@contentstack/cli-utilities'; + +export default class ImportCommand extends BaseCommand { + static id = 'cm:stacks:import'; + static description = 'Import content into a stack'; + + static flags: FlagInput = { + 'stack-api-key': Flags.string({ + char: 'k', + description: 'Stack API key', + required: true, + }), + 'data-dir': Flags.string({ + char: 'd', + description: 'Directory with import data', + required: true, + }), + verbose: Flags.boolean({ + char: 'v', + description: 'Show verbose output', + default: false + }) + }; + + static examples = [ + '$ csdx cm:stacks:import -k -d ./data', + '$ csdx cm:stacks:import -k -d ./data --verbose', + ]; + + async run(): Promise { + try { + const { flags: parsedFlags } = await this.parse(ImportCommand); + + // Validate flags + if (!parsedFlags['stack-api-key']) { + this.error('Stack API key is required'); + } + + // Delegate to service + cliux.print('Starting import...', { color: 'blue' }); + const importService = new ImportService(parsedFlags); + const result = await importService.import(); + + cliux.success('✅ Import completed successfully'); + } catch (error) { + handleAndLogError(error, { + module: 'import-command', + stackApiKey: this.flags['stack-api-key'] + }); + } + } +} +``` + +### Command Topics and Naming +Commands are organized by topic hierarchy under `cm`: +- `src/commands/cm/stacks/import.ts` → command `cm:stacks:import` +- `src/commands/cm/stacks/export.ts` → command `cm:stacks:export` +- `src/commands/cm/stacks/audit/index.ts` → command `cm:stacks:audit` +- `src/commands/cm/stacks/audit/fix.ts` → command `cm:stacks:audit:fix` + +### Flag Validation Patterns + +#### Early Validation +```typescript +async run(): Promise { + const { flags } = await this.parse(MyCommand); + + // Validate required flags + if (!flags.region) { + this.error('--region is required'); + } + + // Validate flag values + const validRegions = ['us', 'eu', 'au']; + if (!validRegions.includes(flags.region)) { + this.error(`Region must be one of: ${validRegions.join(', ')}`); + } +} +``` + +#### Exclusive Flags +```typescript +static flags: FlagInput = { + username: flags.string({ + char: 'u', + exclusive: ['oauth'] // Cannot use with oauth flag + }), + oauth: flags.boolean({ + exclusive: ['username', 'password'] + }) +}; +``` + +#### Dependent Flags +```typescript +static flags: FlagInput = { + cma: flags.string({ + dependsOn: ['cda', 'name'] + }), + cda: flags.string({ + dependsOn: ['cma', 'name'] + }) +}; +``` + +## Authentication Commands + +### Login Command +```typescript +export default class LoginCommand extends BaseCommand { + static description = 'User sessions login'; + static aliases = ['login']; + + static flags: FlagInput = { + username: flags.string({ + char: 'u', + description: 'Email address of your Contentstack account', + exclusive: ['oauth'] + }), + password: flags.string({ + char: 'p', + description: 'Password of your Contentstack account', + exclusive: ['oauth'] + }), + oauth: flags.boolean({ + description: 'Enable single sign-on (SSO)', + default: false, + exclusive: ['username', 'password'] + }) + }; + + async run(): Promise { + try { + const managementAPIClient = await managementSDKClient({ + host: this.cmaHost, + skipTokenValidity: true + }); + + const { flags: loginFlags } = await this.parse(LoginCommand); + authHandler.client = managementAPIClient; + + if (loginFlags.oauth) { + log.debug('Starting OAuth flow', this.contextDetails); + oauthHandler.host = this.cmaHost; + await oauthHandler.oauth(); + } else { + const username = loginFlags.username || await interactive.askUsername(); + const password = loginFlags.password || await interactive.askPassword(); + await authHandler.login(username, password); + } + + cliux.success('✅ Authenticated successfully'); + } catch (error) { + handleAndLogError(error, this.contextDetails); + } + } +} +``` + +### Logout Command +```typescript +export default class LogoutCommand extends BaseCommand { + static description = 'Logout from Contentstack'; + + async run(): Promise { + try { + await authHandler.setConfigData('logout'); + cliux.success('✅ Logged out successfully'); + } catch (error) { + handleAndLogError(error, this.contextDetails); + } + } +} +``` + +### Token Management +```typescript +// Add token +export default class TokenAddCommand extends BaseCommand { + static description = 'Add authentication token'; + + static flags: FlagInput = { + email: flags.string({ + char: 'e', + description: 'Email address', + required: true + }), + label: flags.string({ + char: 'l', + description: 'Token label', + required: false + }) + }; + + async run(): Promise { + const { flags } = await this.parse(TokenAddCommand); + // Add token logic + cliux.success('✅ Token added successfully'); + } +} +``` + +## Configuration Commands + +### Config Get Command +```typescript +export default class ConfigGetCommand extends BaseCommand { + static description = 'Get CLI configuration values'; + + async run(): Promise { + try { + const region = configHandler.get('region'); + cliux.print(`Region: ${region}`); + } catch (error) { + handleAndLogError(error, { ...this.contextDetails, module: 'config-get' }); + } + } +} +``` + +### Config Set Command +```typescript +export default class RegionSetCommand extends BaseCommand { + static description = 'Set region for CLI'; + + static args = { + region: args.string({ description: 'Region name (AWS-NA, AWS-EU, etc.)' }) + }; + + static examples = [ + '$ csdx config:set:region', + '$ csdx config:set:region AWS-NA', + '$ csdx config:set:region --cma --cda --ui-host --name "Custom"' + ]; + + async run(): Promise { + try { + const { args, flags } = await this.parse(RegionSetCommand); + + let selectedRegion = args.region; + if (!selectedRegion) { + selectedRegion = await interactive.askRegions(); + } + + const regionDetails = regionHandler.setRegion(selectedRegion); + await authHandler.setConfigData('logout'); // Reset auth on region change + + cliux.success(`✅ Region set to ${regionDetails.name}`); + cliux.print(`CMA host: ${regionDetails.cma}`); + cliux.print(`CDA host: ${regionDetails.cda}`); + } catch (error) { + handleAndLogError(error, { ...this.contextDetails, module: 'config-set-region' }); + } + } +} +``` + +### Config Remove Command +```typescript +export default class ProxyRemoveCommand extends BaseCommand { + static description = 'Remove proxy configuration'; + + async run(): Promise { + try { + configHandler.remove('proxy'); + cliux.success('✅ Proxy configuration removed'); + } catch (error) { + handleAndLogError(error, this.contextDetails); + } + } +} +``` + +## API Integration + +### Using Management SDK Client +```typescript +import { managementSDKClient } from '@contentstack/cli-utilities'; + +// Initialize client +const managementClient = await managementSDKClient({ + host: this.cmaHost, + skipTokenValidity: false +}); + +// Get stack +const stack = managementClient.stack({ api_key: stackApiKey }); + +// Fetch entry +const entry = await stack.entry(entryUid).fetch(); + +// Query entries +const entries = await stack + .entry() + .query({ query: { title: 'My Entry' } }) + .find(); + +// Update entry +const updatedEntry = await stack.entry(entryUid).update({ ...entry }); +``` + +### Error Handling for API Calls +```typescript +try { + const stack = client.stack({ api_key: apiKey }); + const entry = await stack.entry(uid).fetch(); +} catch (error: any) { + if (error.status === 401) { + throw new CLIError('Authentication failed. Please login again.'); + } else if (error.status === 404) { + throw new CLIError(`Entry with UID "${uid}" not found.`); + } else if (error.status === 429) { + throw new CLIError('Rate limited. Please try again later.'); + } + + handleAndLogError(error, { + module: 'entry-service', + entryUid: uid, + stackApiKey: apiKey + }); +} +``` + +## User Input and Interaction + +### Interactive Prompts +```typescript +import { interactive } from '../../utils'; + +// Ask for region selection +const region = await interactive.askRegions(); + +// Ask for username +const username = await interactive.askUsername(); + +// Ask for password +const password = await interactive.askPassword(); + +// Ask custom question +const customResponse = await cliux.prompt('Enter your choice:'); +``` + +### User Feedback +```typescript +// Success message +cliux.success('✅ Operation completed'); + +// Error message +cliux.error('❌ Operation failed'); + +// Info message +cliux.print('Processing...', { color: 'blue' }); + +// Show data +cliux.table([ + { name: 'Alice', region: 'us', status: 'active' }, + { name: 'Bob', region: 'eu', status: 'inactive' } +]); +``` + +## Logging Patterns + +### Structured Logging +```typescript +log.debug('LoginCommand started', this.contextDetails); +log.debug('Management API client initialized', this.contextDetails); +log.debug('Token parsed', { + ...this.contextDetails, + flags: loginFlags +}); + +try { + await this.performOperation(); +} catch (error) { + log.debug('Operation failed', { + ...this.contextDetails, + error: error.message, + errorCode: error.code + }); +} +``` + +### Context Details +The `BaseCommand` provides `contextDetails` with: +```typescript +contextDetails = { + command: 'auth:login', + userId: '12345', + email: 'user@example.com', + sessionId: 'session-123' +}; +``` + +## Messages (i18n) + +### Store User Strings +```json +// messages/en.json +{ + "auth": { + "login": { + "success": "Authentication successful", + "failed": "Authentication failed" + }, + "logout": { + "success": "Logged out successfully" + } + }, + "config": { + "region": { + "set": "Region set to {{name}}" + } + } +} +``` + +### Use Message Handler +```typescript +import { messageHandler } from '@contentstack/cli-utilities'; + +const message = messageHandler.get(['auth', 'login', 'success']); +cliux.success(message); +``` + +## Best Practices + +### Command Organization +```typescript +export default class MyCommand extends BaseCommand { + // 1. Static properties + static description = '...'; + static examples = [...]; + static flags = {...}; + + // 2. Instance variables + private someHelper: Helper; + + // 3. run method + async run(): Promise { + try { + const { flags } = await this.parse(MyCommand); + await this.execute(flags); + } catch (error) { + handleAndLogError(error, this.contextDetails); + } + } + + // 4. Private helper methods + private async execute(flags: any): Promise {} + private validate(input: any): void {} +} +``` + +### Error Messages +- Be specific about what went wrong +- Provide actionable feedback +- Example: "Region must be AWS-NA, AWS-EU, or AWS-AU" +- Not: "Invalid region" + +### Progress Indication +```typescript +cliux.print('🔄 Processing...', { color: 'blue' }); +// ... operation ... +cliux.success('✅ Completed successfully'); +``` diff --git a/.cursor/skills/framework/SKILL.md b/.cursor/skills/framework/SKILL.md new file mode 100644 index 00000000..80be284d --- /dev/null +++ b/.cursor/skills/framework/SKILL.md @@ -0,0 +1,142 @@ +--- +name: framework +description: Core utilities, configuration, logging, and framework patterns for CLI development. Use when working with utilities, configuration management, error handling, or core framework components. +--- + +# Framework Patterns + +## Quick Reference + +For comprehensive framework guidance, see: +- **[Framework Patterns](./references/framework-patterns.md)** - Complete utilities, configuration, logging, and framework patterns + +## Core Utilities from @contentstack/cli-utilities + +### Configuration Management +```typescript +import { configHandler } from '@contentstack/cli-utilities'; + +// Get config values +const region = configHandler.get('region'); +const email = configHandler.get('email'); +const authToken = configHandler.get('authenticationMethod'); + +// Set config values +configHandler.set('region', 'us'); +``` + +### Logging Framework +```typescript +import { log } from '@contentstack/cli-utilities'; + +// Use structured logging +log.debug('Debug message', { context: 'data' }); +log.info('Information message', { userId: '123' }); +log.warn('Warning message'); +log.error('Error message', { errorCode: 'ERR_001' }); +``` + +### Error Handling +```typescript +import { handleAndLogError, CLIError } from '@contentstack/cli-utilities'; + +try { + await operation(); +} catch (error) { + handleAndLogError(error, { + module: 'my-command', + command: 'cm:auth:login' + }); +} + +// Or throw CLI errors +throw new CLIError('User-friendly error message'); +``` + +### CLI UX / User Output +```typescript +import { cliux } from '@contentstack/cli-utilities'; + +// Success message +cliux.success('Operation completed successfully'); + +// Error message +cliux.error('Something went wrong'); + +// Print message with color +cliux.print('Processing...', { color: 'blue' }); + +// Prompt user for input +const response = await cliux.prompt('Enter region:'); + +// Show table +cliux.table([ + { name: 'Alice', region: 'us' }, + { name: 'Bob', region: 'eu' } +]); +``` + +### HTTP Client +```typescript +import { httpClient } from '@contentstack/cli-utilities'; + +// Make HTTP requests with built-in error handling +const response = await httpClient.request({ + url: 'https://api.contentstack.io/v3/stacks', + method: 'GET', + headers: { 'Authorization': `Bearer ${token}` } +}); +``` + +## Command Base Class + +```typescript +import { Command } from '@contentstack/cli-command'; + +export default class MyCommand extends Command { + static description = 'My command description'; + + static flags = { + region: flags.string({ + char: 'r', + description: 'Set region' + }) + }; + + async run(): Promise { + const { flags } = await this.parse(MyCommand); + // Command logic here + } +} +``` + +## Error Handling Patterns + +### With Context +```typescript +try { + const result = await this.client.stack().entry().fetch(); +} catch (error) { + handleAndLogError(error, { + module: 'auth-service', + command: 'cm:auth:login', + userId: this.contextDetails.userId, + email: this.contextDetails.email + }); +} +``` + +### Custom Errors +```typescript +if (response.status === 401) { + throw new CLIError('Authentication failed. Please login again.'); +} + +if (response.status === 429) { + throw new CLIError('Rate limited. Please try again later.'); +} +``` + +## Usage + +Reference the comprehensive patterns guide above for detailed implementations of configuration, logging, error handling, utilities, and dependency injection patterns. diff --git a/.cursor/skills/framework/references/framework-patterns.md b/.cursor/skills/framework/references/framework-patterns.md new file mode 100644 index 00000000..8c1d4fc1 --- /dev/null +++ b/.cursor/skills/framework/references/framework-patterns.md @@ -0,0 +1,399 @@ +# Framework Patterns + +Core utilities, configuration, logging, and framework patterns for Contentstack CLI development. + +## Configuration Management + +The `@contentstack/cli-utilities` package exports `configHandler` for centralized configuration access. + +### Using configHandler +```typescript +import { configHandler } from '@contentstack/cli-utilities'; + +// Get config values (no arguments returns all config) +const allConfig = configHandler.get(); + +// Get specific config +const region = configHandler.get('region'); +const email = configHandler.get('email'); +const authToken = configHandler.get('authenticationMethod'); +const userUid = configHandler.get('userUid'); +const oauthOrgUid = configHandler.get('oauthOrgUid'); + +// Set config +configHandler.set('region', 'us'); +configHandler.set('email', 'user@example.com'); +``` + +### Config Keys +- `region` - Current region setting (us, eu, etc.) +- `email` - User email address +- `authenticationMethod` - Auth method used +- `userUid` - User unique identifier +- `oauthOrgUid` - OAuth organization UID +- `authenticationMethod` - Authentication method + +## Logging Framework + +The `@contentstack/cli-utilities` exports a winston-based `log` (v2Logger) for structured logging. + +### Structured Logging +```typescript +import { log } from '@contentstack/cli-utilities'; + +// Debug level +log.debug('Starting operation', { + command: 'cm:auth:login', + timestamp: new Date().toISOString() +}); + +// Info level +log.info('Operation completed', { + itemsProcessed: 100, + duration: 5000 +}); + +// Warn level +log.warn('Deprecated flag used', { + flag: '--old-flag', + alternative: '--new-flag' +}); + +// Error level +log.error('Operation failed', { + errorCode: 'ERR_AUTH_001', + message: 'Invalid credentials' +}); +``` + +### Log Context Creation +```typescript +import { createLogContext } from '@contentstack/cli-utilities'; + +// Create context for logging +const logContext = createLogContext( + command, // command name + module, // module name + authMethod // authentication method +); + +// Use in command +const contextDetails = { + ...logContext, + userId: configHandler.get('userUid'), + email: configHandler.get('email') +}; +``` + +## Error Handling Framework + +The utilities provide error handling functions and error classes. + +### handleAndLogError Function +```typescript +import { handleAndLogError } from '@contentstack/cli-utilities'; + +try { + await risky operation(); +} catch (error) { + handleAndLogError(error, { + module: 'config-set-region', + command: 'cm:config:set:region', + flags: { region: 'eu' } + }); +} +``` + +### CLIError Class +```typescript +import { CLIError } from '@contentstack/cli-utilities'; + +// Throw user-friendly errors +if (!region) { + throw new CLIError('Region is required'); +} + +if (invalidEnvironments.length > 0) { + throw new CLIError(`Invalid environments: ${invalidEnvironments.join(', ')}`); +} +``` + +### Error Context +```typescript +// Include context for debugging +try { + const response = await this.client.fetch(); +} catch (error) { + handleAndLogError(error, { + module: 'asset-service', + command: this.id, + context: { + userId: this.contextDetails.userId, + email: this.contextDetails.email, + region: configHandler.get('region') + } + }); +} +``` + +## CLI UX / User Output + +The `cliux` utility provides user-friendly output functions. + +### Success Messages +```typescript +import { cliux } from '@contentstack/cli-utilities'; + +// Simple success +cliux.success('Configuration updated successfully'); + +// Success with details +cliux.success('Region set to us'); +cliux.success('CMA host: https://api.contentstack.io'); +cliux.success('CDA host: https://cdn.contentstack.io'); +``` + +### Error Messages +```typescript +cliux.error('Authentication failed'); +cliux.error('Invalid region: custom'); +cliux.error('Environment not found or inaccessible'); +``` + +### Print with Color +```typescript +// Blue for info +cliux.print('Processing items...', { color: 'blue' }); + +// Show progress +cliux.print(`Progress: ${completed}/${total} items`, { color: 'blue' }); + +// Status messages +cliux.print('✅ Operation completed', { color: 'green' }); +cliux.print('🔄 Syncing configuration...', { color: 'blue' }); +``` + +### User Input +```typescript +// Prompt for string input +const region = await cliux.prompt('Enter region:'); + +// Prompt with choices (using inquirer) +const response = await cliux.prompt('Select action:', { + choices: ['publish', 'unpublish', 'delete'] +}); +``` + +### Display Tables +```typescript +// Display data in table format +cliux.table([ + { name: 'Alice', region: 'us', status: 'active' }, + { name: 'Bob', region: 'eu', status: 'inactive' } +]); + +// With custom columns +const data = [ + { uid: 'entry-1', title: 'Entry 1', locale: 'en' }, + { uid: 'entry-2', title: 'Entry 2', locale: 'en' } +]; +cliux.table(data); +``` + +## HTTP Client + +The `httpClient` provides HTTP request functionality with error handling. + +### Basic Requests +```typescript +import { httpClient } from '@contentstack/cli-utilities'; + +// GET request +const response = await httpClient.request({ + url: 'https://api.contentstack.io/v3/stacks', + method: 'GET', + headers: { 'Authorization': `Bearer ${token}` } +}); + +// POST request +const postResponse = await httpClient.request({ + url: 'https://api.contentstack.io/v3/stacks', + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ name: 'My Stack' }) +}); +``` + +### Error Handling +```typescript +try { + const response = await httpClient.request({ + url: endpoint, + method: 'GET', + headers: getAuthHeaders() + }); +} catch (error: any) { + if (error.status === 429) { + cliux.error('Rate limited. Please try again later.'); + } else if (error.status === 401) { + cliux.error('Authentication failed. Please login again.'); + } else { + handleAndLogError(error, { module: 'http-client' }); + } +} +``` + +## Command Base Class + +Commands should extend `Command` from `@contentstack/cli-command`. + +### Basic Command Structure +```typescript +import { Command } from '@contentstack/cli-command'; +import { FlagInput, args } from '@contentstack/cli-utilities'; + +export default class MyCommand extends Command { + static description = 'Clear description of what this command does'; + + static flags: FlagInput = { + region: flags.string({ + char: 'r', + description: 'Target region (us/eu)', + required: false + }), + verbose: flags.boolean({ + char: 'v', + description: 'Show verbose output', + default: false + }) + }; + + static args = { + name: args.string({ description: 'Name of item', required: false }) + }; + + static examples = [ + '$ csdx my:command', + '$ csdx my:command --region eu' + ]; + + async run(): Promise { + try { + const { args, flags } = await this.parse(MyCommand); + // Validate flags + if (!flags.region) { + this.error('--region is required'); + } + + // Implementation + this.log('Starting operation...'); + // ... perform operation ... + cliux.success('Operation completed'); + } catch (error) { + handleAndLogError(error, { module: 'my-command' }); + } + } +} +``` + +### Command Lifecycle +```typescript +export abstract class BaseCommand extends Command { + public async init(): Promise { + await super.init(); + // Initialize context, config, logging + this.contextDetails = createLogContext( + this.context?.info?.command, + '', + configHandler.get('authenticationMethod') + ); + } + + protected async catch(err: Error & { exitCode?: number }): Promise { + // Custom error handling + return super.catch(err); + } + + protected async finally(_: Error | undefined): Promise { + // Cleanup after command + return super.finally(_); + } +} +``` + +## Authentication Patterns + +### Auth Handler +```typescript +import { authHandler } from '@contentstack/cli-utilities'; + +// Check if authenticated +const isAuthenticated = !!configHandler.get('authenticationMethod'); + +// Get auth token +const token = await authHandler.getToken(); + +// Set config data (e.g., during logout) +await authHandler.setConfigData('logout'); +``` + +### Checking Authentication in Commands +```typescript +if (!configHandler.get('authenticationMethod')) { + throw new CLIError('Authentication required. Please login first.'); +} +``` + +## Common Patterns + +### Error and Success Pattern +```typescript +async run(): Promise { + try { + this.log('Starting operation...'); + const result = await this.performOperation(); + cliux.success(`✅ Success: ${result}`); + } catch (error) { + handleAndLogError(error, { + module: 'my-command', + command: this.id + }); + } +} +``` + +### Progress Reporting Pattern +```typescript +cliux.print('Processing items...', { color: 'blue' }); +let count = 0; +for (const item of items) { + await this.processItem(item); + count++; + cliux.print(`Progress: ${count}/${items.length} items`, { color: 'blue' }); +} +cliux.success(`✅ Processed ${count} items`); +``` + +### Dependency Injection Pattern +```typescript +export class MyService { + constructor( + private configHandler: any, + private logger: any, + private httpClient: any + ) {} + + async execute(): Promise { + this.logger.debug('Starting service'); + const config = this.configHandler.get('region'); + // Use injected dependencies + } +} + +// In command +const service = new MyService(configHandler, log, httpClient); +await service.execute(); +``` diff --git a/.cursor/skills/testing/SKILL.md b/.cursor/skills/testing/SKILL.md new file mode 100644 index 00000000..d5359192 --- /dev/null +++ b/.cursor/skills/testing/SKILL.md @@ -0,0 +1,200 @@ +--- +name: testing +description: Testing patterns, TDD workflow, and test automation for CLI development. Use when writing tests, implementing TDD, setting up test coverage, or debugging test failures. +--- + +# Testing Patterns + +## Quick Reference + +For comprehensive testing guidance, see: +- **[Testing Patterns](./references/testing-patterns.md)** - Complete testing best practices and TDD workflow +- See also `.cursor/rules/testing.mdc` for workspace-wide testing standards + +## TDD Workflow Summary + +**Simple RED-GREEN-REFACTOR:** +1. **RED** → Write failing test +2. **GREEN** → Make it pass with minimal code +3. **REFACTOR** → Improve code quality while keeping tests green + +## Key Testing Rules + +- **80% minimum coverage** (lines, branches, functions) +- **Class-based mocking** (no external libraries; extend and override methods) +- **Never make real API calls** in tests +- **Mock at service boundaries**, not implementation details +- **Test both success and failure paths** +- **Use descriptive test names**: "should [behavior] when [condition]" + +## Quick Test Template + +```typescript +describe('[ServiceName]', () => { + let service: [ServiceName]; + + beforeEach(() => { + service = new [ServiceName](); + }); + + afterEach(() => { + // Clean up any resources + }); + + it('should [expected behavior] when [condition]', async () => { + // Arrange + const input = { /* test data */ }; + + // Act + const result = await service.method(input); + + // Assert + expect(result).to.deep.equal(expectedOutput); + }); + + it('should throw error when [error condition]', async () => { + // Arrange & Act & Assert + await expect(service.failingMethod()) + .to.be.rejectedWith('Expected error message'); + }); +}); +``` + +## Common Mock Patterns + +### Class-Based Mocking +```typescript +// Mock a service by extending it +class MockContentstackClient extends ContentstackClient { + async fetch() { + return mockData; + } +} + +it('should use mocked client', async () => { + const mockClient = new MockContentstackClient(config); + const result = await mockClient.fetch(); + expect(result).to.deep.equal(mockData); +}); +``` + +### Constructor Injection +```typescript +class RateLimiter { + async execute(operation: () => Promise): Promise { + return operation(); + } +} + +class MyService { + constructor(private rateLimiter: RateLimiter) {} + + async doWork() { + return this.rateLimiter.execute(() => this.performWork()); + } +} + +it('should rate limit operations', () => { + const mockLimiter = { execute: () => Promise.resolve('result') }; + const service = new MyService(mockLimiter as any); + // test service behavior +}); +``` + +## Running Tests + +### Run all tests in workspace +```bash +pnpm test +``` + +### Run tests for specific package +```bash +pnpm --filter @contentstack/cli-auth test +pnpm --filter @contentstack/cli-config test +``` + +### Run tests with coverage +```bash +pnpm test:coverage +``` + +### Run tests in watch mode +```bash +pnpm test:watch +``` + +### Run specific test file +```bash +pnpm test -- test/unit/commands/auth/login.test.ts +``` + +## Test Organization + +### File Structure +- Mirror source structure: `test/unit/commands/auth/`, `test/unit/services/`, `test/unit/utils/` +- Use consistent naming: `[module-name].test.ts` +- Integration tests: `test/integration/` + +### Test Data Management +```typescript +// Create mock data factories in test/fixtures/ +const mockAuthToken = { token: 'abc123', expiresAt: Date.now() + 3600000 }; +const mockConfig = { region: 'us', email: 'test@example.com' }; +``` + +## Error Testing + +### Rate Limit Handling +```typescript +it('should handle rate limit errors', async () => { + const error = new Error('Rate limited'); + (error as any).status = 429; + + class MockClient { + fetch() { throw error; } + } + + try { + await new MockClient().fetch(); + expect.fail('Should have thrown'); + } catch (err: any) { + expect(err.status).to.equal(429); + } +}); +``` + +### Validation Error Testing +```typescript +it('should throw validation error for invalid input', () => { + expect(() => service.validateRegion('')) + .to.throw('Region is required'); +}); +``` + +## Coverage and Quality + +### Coverage Requirements +```json +"nyc": { + "check-coverage": true, + "lines": 80, + "functions": 80, + "branches": 80, + "statements": 80 +} +``` + +### Quality Checklist +- [ ] All public methods tested +- [ ] Error paths covered (success + failure) +- [ ] Edge cases included +- [ ] No real API calls +- [ ] Descriptive test names +- [ ] Minimal test setup +- [ ] Tests run < 5s per test file +- [ ] 80%+ coverage achieved + +## Usage + +Reference the comprehensive patterns guide above for detailed test structures, mocking strategies, error testing patterns, and coverage requirements. diff --git a/.cursor/skills/testing/references/testing-patterns.md b/.cursor/skills/testing/references/testing-patterns.md new file mode 100644 index 00000000..fa4d4810 --- /dev/null +++ b/.cursor/skills/testing/references/testing-patterns.md @@ -0,0 +1,358 @@ +# Testing Patterns + +Testing best practices and TDD workflow for Contentstack CLI monorepo development. + +## TDD Workflow + +**Simple RED-GREEN-REFACTOR:** +1. **RED** → Write failing test +2. **GREEN** → Make it pass with minimal code +3. **REFACTOR** → Improve code quality while keeping tests green + +## Test Structure Standards + +### Basic Test Template +```typescript +describe('[ComponentName]', () => { + let component: [ComponentName]; + + beforeEach(() => { + // Setup mocks and test data + component = new [ComponentName](); + }); + + afterEach(() => { + // Clean up resources + }); + + it('should [expected behavior] when [condition]', async () => { + // Arrange + const input = { /* test data */ }; + + // Act + const result = await component.method(input); + + // Assert + expect(result).to.deep.equal(expectedOutput); + }); +}); +``` + +### Command Testing Example +```typescript +import { test } from '@oclif/test'; + +describe('config:set:region', () => { + test + .stdout() + .command(['config:set:region', '--help']) + .it('shows help', ctx => { + expect(ctx.stdout).to.contain('Set CLI region'); + }); + + test + .stdout() + .command(['config:set:region', 'AWS-NA']) + .it('sets region to AWS-NA', ctx => { + expect(ctx.stdout).to.contain('success'); + }); +}); +``` + +### Service Testing Example +```typescript +describe('AuthService', () => { + let authService: AuthService; + let mockConfig: any; + + beforeEach(() => { + mockConfig = { region: 'us', email: 'test@example.com' }; + authService = new AuthService(mockConfig); + }); + + it('should authenticate user with valid credentials', async () => { + const result = await authService.login('test@example.com', 'password'); + expect(result).to.have.property('token'); + }); + + it('should throw error on invalid credentials', async () => { + await expect(authService.login('test@example.com', 'wrong')) + .to.be.rejectedWith('Authentication failed'); + }); +}); +``` + +## Key Testing Rules + +### Coverage Requirements +- **80% minimum** coverage (lines, branches, functions) +- Test both success and failure paths +- Include edge cases and error scenarios + +### Mocking Standards +- **Use class-based mocking** (extend and override methods) +- **Never make real API calls** in tests +- **Mock at service boundaries**, not implementation details +- Clean up resources in `afterEach()` to prevent test pollution + +### Test Patterns +- Use descriptive test names: "should [behavior] when [condition]" +- Keep test setup minimal and focused +- Prefer async/await patterns +- Group related tests in `describe` blocks + +## Common Mock Patterns + +### Service Mocking +```typescript +// Mock a service by extending and overriding methods +class MockContentstackClient { + async fetch() { + return { + uid: 'entry-1', + title: 'Mock Entry', + content_type: 'page' + }; + } +} + +it('should process entry', async () => { + const mockClient = new MockContentstackClient(); + const result = await mockClient.fetch(); + expect(result.uid).to.equal('entry-1'); +}); +``` + +### Dependency Injection Mocking +```typescript +class RateLimiter { + constructor(private maxConcurrent: number = 1) {} + + async execute(operation: () => Promise): Promise { + return operation(); + } +} + +class MyService { + constructor(private rateLimiter: RateLimiter) {} + + async doWork() { + return this.rateLimiter.execute(() => this.performWork()); + } +} + +it('should use rate limiter', async () => { + // Pass in mock with minimal implementation + const mockLimiter = { execute: (fn) => fn() } as any; + const service = new MyService(mockLimiter); + + const result = await service.doWork(); + expect(result).to.exist; +}); +``` + +### API Error Simulation +```typescript +class MockClient { + fetch(endpoint: string) { + if (endpoint === '/rate-limited') { + const error = new Error('Rate limited'); + (error as any).status = 429; + throw error; + } + return Promise.resolve({ data: 'ok' }); + } +} + +it('should handle rate limit errors', async () => { + const client = new MockClient(); + + try { + await client.fetch('/rate-limited'); + expect.fail('Should have thrown'); + } catch (error: any) { + expect(error.status).to.equal(429); + expect(error.message).to.include('Rate limited'); + } +}); +``` + +### Configuration Mocking +```typescript +it('should load config from mock', async () => { + const mockConfig = { + region: 'us', + email: 'test@example.com', + authToken: 'token-123', + get: (key: string) => mockConfig[key as keyof typeof mockConfig] + }; + + const service = new ConfigService(mockConfig); + expect(service.region).to.equal('us'); +}); +``` + +## Error Testing Patterns + +### Rate Limit Handling +```typescript +it('should retry on rate limit error', async () => { + let callCount = 0; + + class MockService { + async call() { + callCount++; + if (callCount === 1) { + const error = new Error('Rate limited'); + (error as any).status = 429; + throw error; + } + return 'success'; + } + } + + const service = new MockService(); + + // First call throws, simulate retry + try { await service.call(); } catch (e) { /* expected */ } + const result = await service.call(); + + expect(result).to.equal('success'); + expect(callCount).to.equal(2); +}); +``` + +### Validation Error Testing +```typescript +it('should throw validation error for invalid input', () => { + class Validator { + validate(region: string): void { + if (!region || region === '') { + throw new Error('Region is required'); + } + } + } + + const validator = new Validator(); + expect(() => validator.validate('')) + .to.throw('Region is required'); +}); +``` + +### Async Error Handling +```typescript +it('should handle async operation failures', async () => { + class FailingService { + async performAsync() { + throw new Error('Operation failed'); + } + } + + const service = new FailingService(); + + try { + await service.performAsync(); + expect.fail('Should have thrown error'); + } catch (error: any) { + expect(error.message).to.include('Operation failed'); + } +}); +``` + +## Test Organization + +### File Structure +- Mirror source structure: `test/unit/services/auth-service.test.ts` +- Use consistent naming: `[module-name].test.ts` +- Group integration tests: `test/integration/` +- Commands: `test/unit/commands/auth/login.test.ts` +- Services: `test/unit/services/config-service.test.ts` +- Utils: `test/unit/utils/region-handler.test.ts` + +### Test Data Management +- Create mock data in test files or in `test/fixtures/` for reuse +- Use realistic test data that matches actual API responses +- Share common mocks across test files in a factory pattern + +### Test Configuration +```javascript +// .mocharc.json +{ + "require": [ + "test/helpers/init.js", + "ts-node/register", + "source-map-support/register" + ], + "recursive": true, + "timeout": 30000, + "spec": "test/**/*.test.ts" +} +``` + +## Monorepo Testing Commands + +### Run all tests across workspace +```bash +pnpm test +``` + +### Run tests for specific package +```bash +pnpm --filter @contentstack/cli-auth test +pnpm --filter @contentstack/cli-config test +pnpm --filter @contentstack/cli-command test +``` + +### Run tests with coverage +```bash +pnpm test:coverage +``` + +### Run tests in watch mode +```bash +pnpm test:watch +``` + +### Run specific test file +```bash +pnpm test -- test/unit/commands/config/set/region.test.ts +``` + +### Run tests matching pattern +```bash +pnpm test -- --grep "should authenticate user" +``` + +## Coverage and Quality + +### Coverage Enforcement +```json +"nyc": { + "check-coverage": true, + "lines": 80, + "functions": 80, + "branches": 80, + "statements": 80 +} +``` + +### Coverage Report Generation +```bash +# Generate coverage reports +pnpm test:coverage + +# HTML report available at coverage/index.html +open coverage/index.html +``` + +### Quality Checklist +- [ ] All public methods tested +- [ ] Error paths covered (success + failure) +- [ ] Edge cases included +- [ ] No real API calls in tests +- [ ] Descriptive test names +- [ ] Minimal test setup (fast to run) +- [ ] Tests complete in < 5 seconds per file +- [ ] 80%+ coverage achieved +- [ ] Mocks properly isolated per test +- [ ] No test pollution (afterEach cleanup) diff --git a/.talismanrc b/.talismanrc index 19d1f22f..af0bb2bf 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,4 +1,20 @@ fileignoreconfig: - filename: pnpm-lock.yaml - checksum: fbef3fb41830dfd725b6b546eca9b9c22a215d74ef552f1913cb6bc36f1cff58 + checksum: 92fd925e53e6d5803c85b8323f042a776aea597198a21b8ed8da542769638a38 + - filename: .cursor/skills/code-review/SKILL.md + checksum: 29d812ac5c2ed4c55490f8d31e15eb592851601a6a141354cb458b1b9f1daa7a + - filename: .cursor/rules/oclif-commands.mdc + checksum: 8e269309cbfc9687e4a889c4a7983f145e77066d515dae53968d7553ae726b41 + - filename: .cursor/skills/code-review/references/code-review-checklist.md + checksum: bdf7453f08d7209deaee411f47a1132ee872b28f0eb082563dfe20aa56eab057 + - filename: .cursor/commands/code-review.md + checksum: a2737c43d58de842cf48c06b0471648a7c38b5fa8854d7c30f3d9258cd8b48f9 + - filename: .cursor/skills/testing/references/testing-patterns.md + checksum: 0a6cb66f27eda46b40508517063a2f43fea1b4b8df878e7ddff404ab7fc126f8 + - filename: .cursor/skills/contentstack-cli/SKILL.md + checksum: 45f0d0c81086eaee850311e0caae198cf6dd2a7bc73bd1340b320b15047c6dae + - filename: .cursor/rules/contentstack-plugin.mdc + checksum: 4d41211088c2302a533559bb1e7e80fe69e6980f23c9a2e90b8ea9d03ba3f040 + - filename: .cursor/skills/contentstack-cli/references/contentstack-patterns.md + checksum: 9888d481b6a1ae8c7102d9efed0fdbae2b7592f582a62c8bff6deccf03fdf341 version: '1.0' diff --git a/packages/contentstack-bootstrap/package.json b/packages/contentstack-bootstrap/package.json index 1e8c6547..e15efebd 100644 --- a/packages/contentstack-bootstrap/package.json +++ b/packages/contentstack-bootstrap/package.json @@ -22,7 +22,7 @@ "@contentstack/cli-utilities": "~1.18.0", "@oclif/core": "^4.3.0", "@oclif/plugin-help": "^6.2.37", - "inquirer": "8.2.7", + "inquirer": "^9.3.0", "mkdirp": "^1.0.4", "tar": "^7.5.11" }, diff --git a/packages/contentstack-bulk-publish/package.json b/packages/contentstack-bulk-publish/package.json index ab8ec99e..a4083385 100644 --- a/packages/contentstack-bulk-publish/package.json +++ b/packages/contentstack-bulk-publish/package.json @@ -12,7 +12,7 @@ "@oclif/plugin-help": "^6.2.28", "chalk": "^4.1.2", "dotenv": "^16.5.0", - "inquirer": "8.2.7", + "inquirer": "^9.3.0", "lodash": "^4.17.23", "winston": "^3.17.0" }, diff --git a/packages/contentstack-clone/package.json b/packages/contentstack-clone/package.json index 9a6620c6..5ee59fc7 100644 --- a/packages/contentstack-clone/package.json +++ b/packages/contentstack-clone/package.json @@ -13,7 +13,7 @@ "@oclif/core": "^4.3.0", "@oclif/plugin-help": "^6.2.28", "chalk": "^4.1.2", - "inquirer": "8.2.7", + "inquirer": "^9.3.0", "lodash": "^4.17.23", "merge": "^2.1.1", "ora": "^5.4.1", diff --git a/packages/contentstack-export-to-csv/package.json b/packages/contentstack-export-to-csv/package.json index 286bf4be..d8ddf91b 100644 --- a/packages/contentstack-export-to-csv/package.json +++ b/packages/contentstack-export-to-csv/package.json @@ -10,7 +10,7 @@ "@oclif/core": "^4.8.0", "@oclif/plugin-help": "^6.2.32", "fast-csv": "^4.3.6", - "inquirer": "8.2.7", + "inquirer": "^9.3.0", "inquirer-checkbox-plus-prompt": "1.4.2", "mkdirp": "^3.0.1" }, diff --git a/packages/contentstack-seed/package.json b/packages/contentstack-seed/package.json index e3ab6196..59e1c6e2 100644 --- a/packages/contentstack-seed/package.json +++ b/packages/contentstack-seed/package.json @@ -8,7 +8,7 @@ "@contentstack/cli-cm-import": "~1.32.0", "@contentstack/cli-command": "~1.8.0", "@contentstack/cli-utilities": "~1.18.0", - "inquirer": "8.2.7", + "inquirer": "^9.3.0", "mkdirp": "^1.0.4", "tar": "^7.5.11", "tmp": "^0.2.5" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd80ea07..534a2cb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -128,8 +128,8 @@ importers: specifier: ^6.2.37 version: 6.2.37 inquirer: - specifier: 8.2.7 - version: 8.2.7(@types/node@14.18.63) + specifier: ^9.3.0 + version: 9.3.8(@types/node@14.18.63) mkdirp: specifier: ^1.0.4 version: 1.0.4 @@ -271,8 +271,8 @@ importers: specifier: ^16.5.0 version: 16.6.1 inquirer: - specifier: 8.2.7 - version: 8.2.7(@types/node@22.19.12) + specifier: ^9.3.0 + version: 9.3.8(@types/node@22.19.12) lodash: specifier: ^4.17.23 version: 4.17.23 @@ -329,8 +329,8 @@ importers: specifier: ^4.1.2 version: 4.1.2 inquirer: - specifier: 8.2.7 - version: 8.2.7(@types/node@14.18.63) + specifier: ^9.3.0 + version: 9.3.8(@types/node@14.18.63) lodash: specifier: ^4.17.23 version: 4.17.23 @@ -526,11 +526,11 @@ importers: specifier: ^4.3.6 version: 4.3.6 inquirer: - specifier: 8.2.7 - version: 8.2.7(@types/node@20.19.34) + specifier: ^9.3.0 + version: 9.3.8(@types/node@20.19.34) inquirer-checkbox-plus-prompt: specifier: 1.4.2 - version: 1.4.2(inquirer@8.2.7(@types/node@20.19.34)) + version: 1.4.2(inquirer@9.3.8(@types/node@20.19.34)) mkdirp: specifier: ^3.0.1 version: 3.0.1 @@ -890,8 +890,8 @@ importers: specifier: ~1.18.0 version: 1.18.0(@types/node@14.18.63)(debug@4.4.3) inquirer: - specifier: 8.2.7 - version: 8.2.7(@types/node@14.18.63) + specifier: ^9.3.0 + version: 9.3.8(@types/node@14.18.63) mkdirp: specifier: ^1.0.4 version: 1.0.4 @@ -4355,6 +4355,10 @@ packages: resolution: {integrity: sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==} engines: {node: '>=12.0.0'} + inquirer@9.3.8: + resolution: {integrity: sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w==} + engines: {node: '>=18'} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -5830,6 +5834,10 @@ packages: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} + run-async@3.0.0: + resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} + engines: {node: '>=0.12.0'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -11891,12 +11899,12 @@ snapshots: ini@1.3.8: {} - inquirer-checkbox-plus-prompt@1.4.2(inquirer@8.2.7(@types/node@20.19.34)): + inquirer-checkbox-plus-prompt@1.4.2(inquirer@9.3.8(@types/node@20.19.34)): dependencies: chalk: 4.1.2 cli-cursor: 3.1.0 figures: 3.2.0 - inquirer: 8.2.7(@types/node@20.19.34) + inquirer: 9.3.8(@types/node@20.19.34) lodash: 4.17.23 rxjs: 6.6.7 @@ -11991,6 +11999,57 @@ snapshots: transitivePeerDependencies: - '@types/node' + inquirer@9.3.8(@types/node@14.18.63): + dependencies: + '@inquirer/external-editor': 1.0.3(@types/node@14.18.63) + '@inquirer/figures': 1.0.15 + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 1.0.0 + ora: 5.4.1 + run-async: 3.0.0 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + transitivePeerDependencies: + - '@types/node' + + inquirer@9.3.8(@types/node@20.19.34): + dependencies: + '@inquirer/external-editor': 1.0.3(@types/node@20.19.34) + '@inquirer/figures': 1.0.15 + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 1.0.0 + ora: 5.4.1 + run-async: 3.0.0 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + transitivePeerDependencies: + - '@types/node' + + inquirer@9.3.8(@types/node@22.19.12): + dependencies: + '@inquirer/external-editor': 1.0.3(@types/node@22.19.12) + '@inquirer/figures': 1.0.15 + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 1.0.0 + ora: 5.4.1 + run-async: 3.0.0 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + transitivePeerDependencies: + - '@types/node' + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -13681,6 +13740,8 @@ snapshots: run-async@2.4.1: {} + run-async@3.0.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3