diff --git a/.cursor/commands/code-review.md b/.cursor/commands/code-review.md new file mode 100644 index 0000000000..14dd499df1 --- /dev/null +++ b/.cursor/commands/code-review.md @@ -0,0 +1,154 @@ +--- +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-config` - Review changes in specific package +- `/code-review --package-type plugin` - Review plugin packages only (auth, config) +- `/code-review --package-type library` - Review library packages (command, utilities, dev-dependencies) + +### 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**: Proper placement in `packages/` structure +- **pnpm workspace**: Correct `package.json` workspace 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 +- **Plugin packages** (auth, config): Have `oclif.commands` configuration +- **Library packages** (command, utilities, dev-dependencies): Proper exports in package.json +- **Main package** (contentstack): Aggregates plugins correctly +- **Dependency versions**: Using beta versions appropriately (~version ranges) + +### 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 `@contentstack/cli-command` Command +- **Command structure**: Proper `static description`, `static examples`, `static flags` +- **Topic organization**: Uses `cm` topic structure (`cm:config:set`, `cm:auth:login`) +- **Error handling**: Uses `handleAndLogError` from utilities +- **Flag validation**: Early validation and user-friendly error messages +- **Service delegation**: Commands orchestrate, services implement 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 0000000000..fb473ecf26 --- /dev/null +++ b/.cursor/commands/execute-tests.md @@ -0,0 +1,246 @@ +--- +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-config` - Run tests for config package +- `/execute-tests contentstack-auth` - Run tests for auth package +- `/execute-tests contentstack-command` - Run tests for command package +- `/execute-tests contentstack-utilities` - Run tests for utilities package +- `/execute-tests packages/contentstack-config/` - 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 6 packages: +- `contentstack` - Main CLI package +- `contentstack-auth` - Authentication plugin +- `contentstack-config` - Configuration plugin +- `contentstack-command` - Base Command class (library) +- `contentstack-utilities` - Utilities library +- `contentstack-dev-dependencies` - Dev dependencies + +### 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-config --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-config package +cd packages/contentstack-config && pnpm test + +# For all packages with parallel execution +pnpm -r --filter './packages/*' run test + +# For specific test file +cd packages/contentstack-config && npx mocha "test/unit/commands/region.test.ts" + +# With coverage +pnpm -r --filter './packages/*' 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 0000000000..148ead7678 --- /dev/null +++ b/.cursor/rules/README.md @@ -0,0 +1,84 @@ +# Cursor Rules + +Context-aware rules that load automatically based on the files you're editing, optimized for this modularized Contentstack CLI. + +## Rule Files + +| File | Scope | Always Applied | Purpose | +|------|-------|----------------|---------| +| `dev-workflow.md` | `**/*.ts`, `**/*.js`, `**/*.json` | Yes | Monorepo TDD workflow, pnpm workspace patterns (6 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-core.mdc` | `packages/contentstack/src/**/*.ts`, `packages/contentstack/src/**/*.js` | No | Core package plugin aggregation, hooks, and entry point patterns | + +## 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`) → `oclif-commands.mdc` + `typescript.mdc` + `dev-workflow.md` +- **Core package files** (`packages/contentstack/src/**/*.ts`) → `contentstack-core.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 +- **6 packages** under `packages/`: + - `contentstack` - Main CLI entry point (bin/run.js) + - `contentstack-auth` - Authentication plugin + - `contentstack-config` - Configuration plugin + - `contentstack-command` - Base Command class for plugins + - `contentstack-utilities` - Shared utilities and helpers + - `contentstack-dev-dependencies` - Development dependencies + +### Build Configuration +- **pnpm workspaces** configuration +- **Shared dependencies**: `@contentstack/cli-command`, `@contentstack/cli-utilities` +- **Build process**: TypeScript compilation → `lib/` directories +- **OCLIF manifest** generation for command discovery + +### Actual Patterns Detected +- **Testing**: Mocha + Chai (not Jest or Sinon-heavy) +- **TypeScript**: Mixed strict mode adoption +- **Commands**: Extend `@oclif/core` Command class +- **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 +- **Core Package**: See `contentstack-core.mdc` for plugin aggregation and hook 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-core.mdc b/.cursor/rules/contentstack-core.mdc new file mode 100644 index 0000000000..730daaa5f9 --- /dev/null +++ b/.cursor/rules/contentstack-core.mdc @@ -0,0 +1,358 @@ +--- +description: "Contentstack core CLI package patterns — plugin aggregation, hooks, and entry point" +globs: ["packages/contentstack/src/**/*.ts", "packages/contentstack/src/**/*.js"] +alwaysApply: false +--- + +# Contentstack Core Package Standards + +## Overview + +The `@contentstack/cli` core package is the entry point for the entire CLI. Unlike plugin packages (auth, config), it: +- **Aggregates all plugins** — declared in `oclif.plugins` array in `package.json` +- **Implements hooks** — `init` and `prerun` hooks in `src/hooks/` for global behaviors +- **Shares interfaces** — Core types used across all plugins in `src/interfaces/` +- **Provides utilities** — Helper classes like `CsdxContext` in `src/utils/` +- **Has no command files** — Commands are provided by plugin packages + +## Architecture + +### Entry Point + +```typescript +// ✅ GOOD - bin/run.js (CommonJS) +// This is the executable entry point referenced in package.json "bin" +// Standard OCLIF entry point pattern +``` + +### Package Configuration + +The `oclif` configuration in `package.json`: +```json +{ + "oclif": { + "bin": "csdx", + "topicSeparator": ":", + "helpClass": "./lib/help.js", + "plugins": [ + "@oclif/plugin-help", + "@oclif/plugin-not-found", + "@oclif/plugin-plugins", + "@contentstack/cli-config", + "@contentstack/cli-auth" + // ... more plugins + ], + "hooks": { + "init": [ + "./lib/hooks/init/context-init", + "./lib/hooks/init/utils-init" + ], + "prerun": [ + "./lib/hooks/prerun/init-context-for-command", + "./lib/hooks/prerun/command-deprecation-check", + "./lib/hooks/prerun/default-rate-limit-check", + "./lib/hooks/prerun/latest-version-warning" + ] + }, + "topics": { + "auth": { "description": "Perform authentication-related activities" }, + "config": { "description": "Perform configuration related activities" }, + "cm": { "description": "Perform content management activities" } + } + } +} +``` + +## Hook Lifecycle + +### OCLIF Hook Execution Order + +1. **CLI initialization** → Node process starts +2. **`init` hooks** → Set up global context and utilities (executed once) +3. **Command detection** → OCLIF matches command name to plugin +4. **`prerun` hooks** → Validate state, check auth, prepare for command execution (per command) +5. **Command execution** → Plugin command's `run()` method executes + +### Init Hooks + +Init hooks run once during CLI startup. Use them for expensive setup operations. + +```typescript +// ✅ GOOD - src/hooks/init/context-init.ts +// Initialize CLI context that commands depend on +import { CsdxContext } from '../../utils'; +import { configHandler } from '@contentstack/cli-utilities'; + +export default function (opts): void { + // Store command ID for session-based log organization + if (opts.id) { + configHandler.set('currentCommandId', opts.id); + } + // Make context available to all commands via this.config.context + this.config.context = new CsdxContext(opts, this.config); +} +``` + +### Prerun Hooks + +Prerun hooks run before each command. Use them for validation and state checks. + +```typescript +// ✅ GOOD - src/hooks/prerun/auth-guard.ts +// Validate authentication before running protected commands + +import { cliux, isAuthenticated, managementSDKClient } from '@contentstack/cli-utilities'; + +export default async function (opts): Promise { + const { context: { region = null } = {} } = this.config; + + // Validate region is set (required for all non-region commands) + if (opts.Command.id !== 'config:set:region') { + if (!region) { + cliux.error('No region found, please set a region via config:set:region'); + this.exit(); + return; + } + } + + // Example: Validate auth for protected commands + if (isProtectedCommand(opts.Command.id)) { + if (!isAuthenticated()) { + cliux.error('Please log in to execute this command'); + this.exit(); + } + } +} +``` + +### Hook Patterns + +#### Accessing Configuration +```typescript +// ✅ GOOD - Access global config in hooks +export default function (opts): void { + const { config } = this; // OCLIF Config object + const { context, region } = config; // Custom properties set by other hooks +} +``` + +#### Async Hooks +```typescript +// ✅ GOOD - Async hooks for operations requiring I/O +export default async function (opts): Promise { + const client = await managementSDKClient({ host: this.config.region.cma }); + const user = await client.getUser(); + // Hook runs to completion before command starts +} +``` + +#### Early Exit +```typescript +// ✅ GOOD - Exit hook execution when validation fails +export default function (opts): void { + if (!isValid()) { + cliux.error('Validation failed'); + this.exit(); // Stops command from executing + return; + } +} +``` + +## Context Object + +The `CsdxContext` class wraps OCLIF config and adds CLI-specific state. + +```typescript +// ✅ GOOD - Accessing context in commands +import { CLIConfig } from '../interfaces'; + +export default class MyCommand extends Command { + async run(): Promise { + const config: CLIConfig = this.config; + const { context } = config; + + // Available context properties: + // - context.id: unique session identifier + // - context.user: authenticated user info (authtoken, email) + // - context.region: current region configuration + // - context.config: regional configuration + // - context.plugin: current plugin metadata + } +} +``` + +## Shared Interfaces + +Interfaces in `src/interfaces/index.ts` are exported and consumed by all plugins. + +```typescript +// ✅ GOOD - Define shared types +export interface Context { + id: string; + user: { + authtoken: string; + email: string; + }; + region: Region; + plugin: Plugin; + config: any; +} + +export interface CLIConfig extends Config { + context: Context; +} + +export interface Region { + name: string; + cma: string; // Content Management API endpoint + cda: string; // Content Delivery API endpoint +} +``` + +## Utilities + +Core utilities in `src/utils/` provide shared functionality. + +```typescript +// ✅ GOOD - src/utils/context-handler.ts +// Wrapper around context initialization and access +export class CsdxContext { + constructor(opts: any, config: any) { + this.id = opts.id || generateId(); + this.region = config.region; + this.user = extractUserFromToken(); + } +} + +// Export utilities for use in hooks and contexts +export { CsdxContext }; +``` + +## Plugin Registration + +Plugins are registered via `oclif.plugins` in `package.json`. Each plugin package must: + +1. **Provide commands** — via `oclif.commands` in its `package.json` +2. **Be installed** — as a dependency in the core package +3. **Be listed** — in `oclif.plugins` array for auto-discovery + +```json +{ + "dependencies": { + "@contentstack/cli-config": "~1.20.0-beta.1", + "@contentstack/cli-auth": "~1.8.0-beta.1" + }, + "oclif": { + "plugins": [ + "@contentstack/cli-config", + "@contentstack/cli-auth" + ] + } +} +``` + +### Plugin Discovery + +OCLIF automatically discovers commands in: +1. Built-in plugins (`@oclif/plugin-help`, etc.) +2. Core package commands (none in contentstack core) +3. Registered plugins (listed in `oclif.plugins`) + +## Differences from Plugin Packages + +| Aspect | Core Package | Plugin Package | +|--------|--------------|----------------| +| **OCLIF config** | No `commands` field | Has `oclif.commands: "./lib/commands"` | +| **Source structure** | `src/hooks/`, `src/interfaces/`, `src/utils/` | `src/commands/`, `src/services/` | +| **Entry point** | `bin/run.js` | None | +| **Dependencies** | References all plugins | Depends on `@contentstack/cli-command` | +| **Execution role** | Aggregates and initializes | Implements business logic | + +## Build Process + +The core package build includes hook compilation and OCLIF manifest generation. + +```bash +# In package.json scripts +"build": "pnpm compile && oclif manifest && oclif readme" +``` + +### Build Steps + +1. **compile** — TypeScript → JavaScript in `lib/` +2. **oclif manifest** — Generate `oclif.manifest.json` for plugin discovery +3. **oclif readme** — Generate README with available commands + +### Build Artifacts + +- `lib/` — Compiled hooks, utilities, interfaces +- `oclif.manifest.json` — Plugin and command registry +- `bin/run.js` — Executable entry point +- `README.md` — Generated command documentation + +## Testing Hooks + +Hooks cannot be tested with standard command testing. Test hook behavior by: + +1. **Unit test hook functions** — Import and invoke directly +2. **Integration test via CLI** — Run commands that trigger hooks +3. **Mock OCLIF config** — Provide mocked `this.config` object + +```typescript +// ✅ GOOD - Test hook function directly +import contextInit from '../src/hooks/init/context-init'; + +describe('context-init hook', () => { + it('should set context on config', () => { + const mockConfig = { context: null }; + const hookContext = { config: mockConfig }; + const opts = { id: 'test-command' }; + + contextInit.call(hookContext, opts); + + expect(mockConfig.context).to.exist; + }); +}); +``` + +## Error Handling in Hooks + +Hooks should fail fast and provide clear error messages to users. + +```typescript +// ✅ GOOD - Clear error messages with user guidance +export default function (opts): void { + if (!isRegionSet()) { + cliux.error('No region configured'); + cliux.print('Run: csdx config:set:region --region us', { color: 'blue' }); + this.exit(); + } +} +``` + +## Best Practices + +### Hook Organization +- Keep hooks focused on a single concern (validation, initialization, etc.) +- Use descriptive names that indicate when they run (`prerun-`, `init-`) +- Initialize dependencies in `init` hooks, not in `prerun` hooks + +### Performance +- Minimize work in `init` hooks (they run once per CLI session) +- Cache expensive operations in context for reuse +- Avoid repeated API calls across hooks + +### Ordering +- Place hooks that prepare data before hooks that consume it +- Auth validation (`auth-guard`) should run after region validation +- Version warnings can run last (non-critical) + +### Context Usage +- Store computed values in context to avoid recalculation +- Make context available to all commands via `this.config.context` +- Document context properties that plugins should expect + +### Plugin Development +- Ensure plugins depend on `@contentstack/cli-command`, not the core package +- Commands should extend the shared Command base class +- Plugins should not modify or depend on core hooks directly diff --git a/.cursor/rules/dev-workflow.md b/.cursor/rules/dev-workflow.md new file mode 100644 index 0000000000..517867b0ef --- /dev/null +++ b/.cursor/rules/dev-workflow.md @@ -0,0 +1,211 @@ +--- +description: "Core development workflow and TDD patterns - always applied" +globs: ["**/*.ts", "**/*.js", "**/*.json"] +alwaysApply: true +--- + +# Development Workflow + +## Monorepo Structure + +### Package Organization +This modularized CLI has 6 packages under `packages/`: + +1. **contentstack** - Main CLI package + - Entry point: `bin/run.js` + - Aggregates all plugins + +2. **contentstack-auth** - Authentication plugin + - Commands: `cm:auth:*` + - Handles login/logout flows + +3. **contentstack-config** - Configuration plugin + - Commands: `cm:config:*`, `cm:region:*`, etc. + - Manages CLI settings and preferences + +4. **contentstack-command** - Base Command class (library) + - Shared Command base for all plugins + - Utilities and helpers for command development + +5. **contentstack-utilities** - Utilities library + - Shared helpers and utilities + - Used by all packages + +6. **contentstack-dev-dependencies** - Dev dependencies + - Centralized development dependencies + +### pnpm Workspace Configuration +```json +{ + "workspaces": ["packages/*"] +} +``` + +### Development Commands +```bash +# Install dependencies for all packages +pnpm install + +# Run command across all packages +pnpm -r --filter './packages/*' + +# Work on specific package +cd packages/contentstack-config +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 0000000000..7ca9bc25ab --- /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 0000000000..daf6de1089 --- /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 0000000000..ea4d82a265 --- /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 0000000000..422807ad04 --- /dev/null +++ b/.cursor/skills/SKILL.md @@ -0,0 +1,31 @@ +--- +name: contentstack-cli-skills +description: Collection of project-specific skills for Contentstack CLI 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 6 CLI 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**: 6 pnpm workspace packages under `packages/` +- **Tech Stack**: TypeScript, OCLIF v4, Mocha+Chai, pnpm workspaces +- **Packages**: `@contentstack/cli` (main), `@contentstack/cli-auth`, `@contentstack/cli-config`, `@contentstack/cli-command`, `@contentstack/cli-utilities`, `@contentstack/cli-dev-dependencies` +- **Build**: TypeScript → `lib/` directories, OCLIF manifest generation diff --git a/.cursor/skills/code-review/SKILL.md b/.cursor/skills/code-review/SKILL.md new file mode 100644 index 0000000000..bc647259c0 --- /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 0000000000..682cc86a40 --- /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 0000000000..f5a29a9a3b --- /dev/null +++ b/.cursor/skills/contentstack-cli/SKILL.md @@ -0,0 +1,150 @@ +--- +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 `BaseCommand` (package-level) or `Command` from `@contentstack/cli-command` +- Validate flags early: `if (!flags.region) this.error('Region is required')` +- Delegate to services/utils: commands handle CLI, utilities handle logic +- Show progress: `cliux.success('✅ Operation completed')` +- Include command examples: `static examples = ['$ csdx auth:login', '$ csdx auth:login -u email@example.com']` + +### Command Topics +- Auth commands: `auth:login`, `auth:logout`, `auth:whoami`, `auth:tokens:add`, `auth:tokens:remove`, `auth:tokens:index` +- Config commands: `config:get:region`, `config:set:region`, `config:remove:proxy`, etc. +- File pattern: `src/commands/auth/login.ts` → command `cm:auth:login` + +### 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 + +```typescript +export abstract class BaseCommand extends Command { + protected contextDetails!: Context; + + async init(): Promise { + await super.init(); + this.contextDetails = { + command: this.context?.info?.command || 'unknown', + userId: configHandler.get('userUid'), + email: configHandler.get('email') + }; + } + + protected async catch(err: Error & { exitCode?: number }): Promise { + return super.catch(err); + } +} +``` + +## Authentication Patterns + +### Login Command Example +```typescript +async run(): Promise { + const { flags: loginFlags } = await this.parse(LoginCommand); + + if (loginFlags.oauth) { + 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'); +} +``` + +### Check Authentication +```typescript +if (!configHandler.get('authenticationMethod')) { + throw new CLIError('Authentication required. Please login first.'); +} +``` + +## Configuration Patterns + +### Config Set/Get/Remove Commands +- Use `configHandler.get()` and `configHandler.set()` +- Support interactive mode when no flags provided +- Display results with `cliux.success()` or `cliux.print()` + +### Region Configuration +```typescript +const selectedRegion = args.region || await interactive.askRegions(); +const regionDetails = regionHandler.setRegion(selectedRegion); +cliux.success(`Region set to ${regionDetails.name}`); +cliux.success(`CMA host: ${regionDetails.cma}`); +``` + +## 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 0000000000..36bf7d2f42 --- /dev/null +++ b/.cursor/skills/contentstack-cli/references/contentstack-patterns.md @@ -0,0 +1,476 @@ +# Contentstack CLI Patterns + +Contentstack CLI development patterns, OCLIF commands, API integration, and configuration workflows. + +## OCLIF Command Structure + +### Base Command Pattern +```typescript +import { BaseCommand } from '../../base-command'; +import { FlagInput } from '@contentstack/cli-utilities'; +import { cliux, handleAndLogError } from '@contentstack/cli-utilities'; + +export default class MyCommand extends BaseCommand { + static description = 'Clear description of command'; + static aliases = ['shortcut']; // Optional alias + + static flags: FlagInput = { + region: flags.string({ + char: 'r', + description: 'Target region', + required: false, + default: 'us' + }), + verbose: flags.boolean({ + char: 'v', + description: 'Show verbose output', + default: false + }) + }; + + static examples = [ + '$ csdx my:command', + '$ csdx my:command --region eu', + '$ csdx shortcut' + ]; + + async run(): Promise { + try { + const { flags: parsedFlags } = await this.parse(MyCommand); + + // Validate flags + if (!parsedFlags.region) { + this.error('Region is required'); + } + + // Implementation + cliux.print('Processing...', { color: 'blue' }); + const result = await this.execute(parsedFlags); + + cliux.success('✅ Operation completed successfully'); + } catch (error) { + handleAndLogError(error, { + ...this.contextDetails, + module: 'my-command' + }); + } + } + + private async execute(flags: any): Promise { + // Implementation here + } +} +``` + +### Command Topics and Naming +Commands are organized by topic hierarchy: +- `src/commands/auth/login.ts` → command `cm:auth:login` +- `src/commands/auth/tokens/add.ts` → command `cm:auth:tokens:add` +- `src/commands/config/set/region.ts` → command `cm:config:set:region` +- `src/commands/config/get/region.ts` → command `cm:config:get:region` + +### 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 0000000000..80be284d90 --- /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 0000000000..8c1d4fc190 --- /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 0000000000..d53591924e --- /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 0000000000..fa4d481092 --- /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 e03b666036..c1a11d597e 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,6 +1,18 @@ fileignoreconfig: - - filename: .github/workflows/release-production-pipeline.yml - checksum: 48264fdeb61cbbed348c7271aae5e155651f490aca063dbb1d54f2c15a154090 - filename: pnpm-lock.yaml - checksum: c64bb59690952523cb0c314de42499c168d3e08b1810e4583a546c5773bd89fc + checksum: 0143e8facd41a337309a64a6ce5505b3fd4ae9cf926bd664a32ae7f3c2ac4a20 + - filename: packages/contentstack-utilities/src/proxy-helper.ts + checksum: 2169f25563bca3a0fe54edd00c73646ed56d36aa7e8effe904de26b0c1633759 + - filename: .cursor/skills/code-review/SKILL.md + checksum: 29d812ac5c2ed4c55490f8d31e15eb592851601a6a141354cb458b1b9f1daa7a + - filename: .cursor/skills/contentstack-cli/SKILL.md + checksum: 2130a0c665821cd2831644aa238afef09992388f67f682c867820c0d98ab2406 + - filename: .cursor/skills/contentstack-cli/references/contentstack-patterns.md + checksum: 159d7e31f9e32ceb708c702699c36459ed2210a61ff29ebb78efb66cec466128 + - filename: .cursor/skills/code-review/references/code-review-checklist.md + checksum: bdf7453f08d7209deaee411f47a1132ee872b28f0eb082563dfe20aa56eab057 + - filename: .cursor/skills/testing/references/testing-patterns.md + checksum: 0a6cb66f27eda46b40508517063a2f43fea1b4b8df878e7ddff404ab7fc126f8 + - filename: .github/workflows/release-production-pipeline.yml + checksum: 4aef94feea3ea0538162a9454cfd30457ec85e3123672f0933713e3d113d4504 version: '1.0' diff --git a/packages/contentstack-auth/README.md b/packages/contentstack-auth/README.md index 8734c38a50..21e157a7ff 100644 --- a/packages/contentstack-auth/README.md +++ b/packages/contentstack-auth/README.md @@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli-auth $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-auth/1.8.0-beta.1 darwin-arm64 node-v22.13.1 +@contentstack/cli-auth/1.8.0 darwin-arm64 node-v22.13.1 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-config/README.md b/packages/contentstack-config/README.md index c43d8558b1..e39ce4a065 100644 --- a/packages/contentstack-config/README.md +++ b/packages/contentstack-config/README.md @@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli-config $ csdx COMMAND running command... $ csdx (--version) -@contentstack/cli-config/1.20.0-beta.1 darwin-arm64 node-v22.13.1 +@contentstack/cli-config/1.20.1 darwin-arm64 node-v22.13.1 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack-config/package.json b/packages/contentstack-config/package.json index 8179321b72..e0c887f16c 100644 --- a/packages/contentstack-config/package.json +++ b/packages/contentstack-config/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli-config", "description": "Contentstack CLI plugin for configuration", - "version": "1.20.0", + "version": "1.20.1", "author": "Contentstack", "scripts": { "build": "pnpm compile && oclif manifest && oclif readme", @@ -19,7 +19,7 @@ "@contentstack/utils": "~1.7.0", "@oclif/core": "^4.8.3", "@oclif/plugin-help": "^6.2.28", - "lodash": "^4.17.23" + "lodash": "^4.18.1" }, "overrides": { "@oclif/core": { diff --git a/packages/contentstack-utilities/package.json b/packages/contentstack-utilities/package.json index 1a88316be9..b95fd0a1ff 100644 --- a/packages/contentstack-utilities/package.json +++ b/packages/contentstack-utilities/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/cli-utilities", - "version": "1.18.0", + "version": "1.18.1", "description": "Utilities for contentstack projects", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -43,7 +43,7 @@ "inquirer-search-list": "^1.2.6", "js-yaml": "^4.1.1", "klona": "^2.0.6", - "lodash": "^4.17.23", + "lodash": "^4.18.1", "mkdirp": "^1.0.4", "open": "^8.4.2", "ora": "^5.4.1", diff --git a/packages/contentstack/README.md b/packages/contentstack/README.md index 75d7bd85fb..72470f249b 100644 --- a/packages/contentstack/README.md +++ b/packages/contentstack/README.md @@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli $ csdx COMMAND running command... $ csdx (--version|-v) -@contentstack/cli/1.60.0-beta.7 darwin-arm64 node-v22.13.1 +@contentstack/cli/1.60.1 darwin-arm64 node-v22.13.1 $ csdx --help [COMMAND] USAGE $ csdx COMMAND diff --git a/packages/contentstack/package.json b/packages/contentstack/package.json index 6cf688b92c..a41af7ec95 100755 --- a/packages/contentstack/package.json +++ b/packages/contentstack/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli", "description": "Command-line tool (CLI) to interact with Contentstack", - "version": "1.60.0", + "version": "1.60.1", "author": "Contentstack", "bin": { "csdx": "./bin/run.js" @@ -32,10 +32,10 @@ "@contentstack/cli-cm-migrate-rte": "~1.6.4", "@contentstack/cli-cm-seed": "~1.15.0", "@contentstack/cli-command": "~1.8.0", - "@contentstack/cli-config": "~1.20.0", + "@contentstack/cli-config": "~1.20.1", "@contentstack/cli-launch": "^1.9.6", "@contentstack/cli-migration": "~1.12.0", - "@contentstack/cli-utilities": "~1.18.0", + "@contentstack/cli-utilities": "~1.18.1", "@contentstack/cli-variants": "~1.4.0", "@contentstack/management": "~1.27.5", "@oclif/core": "^4.8.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae676ea922..bc71c9f4d9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -61,7 +61,7 @@ importers: specifier: ~1.8.0 version: link:../contentstack-command '@contentstack/cli-config': - specifier: ~1.20.0 + specifier: ~1.20.1 version: link:../contentstack-config '@contentstack/cli-launch': specifier: ^1.9.6 @@ -70,7 +70,7 @@ importers: specifier: ~1.12.0 version: 1.12.0(@types/node@14.18.63)(debug@4.4.3) '@contentstack/cli-utilities': - specifier: ~1.18.0 + specifier: ~1.18.1 version: link:../contentstack-utilities '@contentstack/cli-variants': specifier: ~1.4.0 @@ -334,8 +334,8 @@ importers: specifier: ^6.2.28 version: 6.2.37 lodash: - specifier: ^4.17.23 - version: 4.17.23 + specifier: ^4.18.1 + version: 4.18.1 devDependencies: '@oclif/test': specifier: ^4.1.13 @@ -434,8 +434,8 @@ importers: specifier: ^2.0.6 version: 2.0.6 lodash: - specifier: ^4.17.23 - version: 4.17.23 + specifier: ^4.18.1 + version: 4.18.1 mkdirp: specifier: ^1.0.4 version: 1.0.4 @@ -4141,6 +4141,9 @@ packages: lodash@4.17.23: resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + log-symbols@1.0.2: resolution: {integrity: sha512-mmPrW0Fh2fxOzdBbFv4g1m6pR72haFLPJ2G5SJEELf1y+iaQrDG6cWCPjy54RHYbZAt7X+ls690Kw62AdWXBzQ==} engines: {node: '>=0.10.0'} @@ -6425,7 +6428,7 @@ snapshots: chalk: 4.1.2 fast-csv: 4.3.6 fs-extra: 11.3.4 - lodash: 4.17.23 + lodash: 4.18.1 uuid: 9.0.1 winston: 3.19.0 transitivePeerDependencies: @@ -6457,7 +6460,7 @@ snapshots: '@oclif/plugin-help': 6.2.37 chalk: 4.1.2 just-diff: 6.0.2 - lodash: 4.17.23 + lodash: 4.18.1 transitivePeerDependencies: - '@types/node' - debug @@ -6472,7 +6475,7 @@ snapshots: chalk: 4.1.2 dotenv: 16.6.1 inquirer: 8.2.7(@types/node@14.18.63) - lodash: 4.17.23 + lodash: 4.18.1 winston: 3.19.0 transitivePeerDependencies: - '@types/node' @@ -6489,7 +6492,7 @@ snapshots: '@oclif/plugin-help': 6.2.37 chalk: 4.1.2 inquirer: 8.2.7(@types/node@14.18.63) - lodash: 4.17.23 + lodash: 4.18.1 merge: 2.1.1 ora: 5.4.1 prompt: 1.3.0 @@ -6523,7 +6526,7 @@ snapshots: big-json: 3.2.0 bluebird: 3.7.2 chalk: 4.1.2 - lodash: 4.17.23 + lodash: 4.18.1 merge: 2.1.1 mkdirp: 1.0.4 progress-stream: 2.0.0 @@ -6541,7 +6544,7 @@ snapshots: big-json: 3.2.0 chalk: 4.1.2 fs-extra: 11.3.4 - lodash: 4.17.23 + lodash: 4.18.1 merge: 2.1.1 mkdirp: 1.0.4 winston: 3.19.0 @@ -6561,7 +6564,7 @@ snapshots: chalk: 4.1.2 debug: 4.4.3(supports-color@8.1.1) fs-extra: 11.3.4 - lodash: 4.17.23 + lodash: 4.18.1 marked: 4.3.0 merge: 2.1.1 mkdirp: 1.0.4 @@ -6583,7 +6586,7 @@ snapshots: collapse-whitespace: 1.1.7 jsdom: 20.0.3 jsonschema: 1.5.0 - lodash: 4.17.23 + lodash: 4.18.1 nock: 13.5.6 omit-deep-lodash: 1.1.7 sinon: 21.0.1 @@ -6636,7 +6639,7 @@ snapshots: '@contentstack/utils': 1.7.1 '@oclif/core': 4.8.3 '@oclif/plugin-help': 6.2.37 - lodash: 4.17.23 + lodash: 4.18.1 transitivePeerDependencies: - '@types/node' - debug @@ -6662,7 +6665,7 @@ snapshots: form-data: 4.0.4 graphql: 16.13.0 ini: 3.0.1 - lodash: 4.17.23 + lodash: 4.18.1 open: 8.4.2 rollup: 4.59.0 winston: 3.19.0 @@ -6716,7 +6719,7 @@ snapshots: inquirer-search-list: 1.2.6 js-yaml: 4.1.1 klona: 2.0.6 - lodash: 4.17.23 + lodash: 4.18.1 mkdirp: 1.0.4 open: 8.4.2 ora: 5.4.1 @@ -6751,7 +6754,7 @@ snapshots: inquirer-search-list: 1.2.6 js-yaml: 4.1.1 klona: 2.0.6 - lodash: 4.17.23 + lodash: 4.18.1 mkdirp: 1.0.4 open: 8.4.2 ora: 5.4.1 @@ -6773,7 +6776,7 @@ snapshots: '@contentstack/cli-utilities': 1.18.0(@types/node@14.18.63)(debug@4.4.3) '@oclif/core': 4.8.3 '@oclif/plugin-help': 6.2.37 - lodash: 4.17.23 + lodash: 4.18.1 mkdirp: 1.0.4 winston: 3.19.0 transitivePeerDependencies: @@ -6783,7 +6786,7 @@ snapshots: '@contentstack/json-rte-serializer@2.1.0': dependencies: array-flat-polyfill: 1.0.1 - lodash: 4.17.23 + lodash: 4.18.1 lodash.clonedeep: 4.5.0 lodash.flatten: 4.4.0 lodash.isempty: 4.4.0 @@ -6803,7 +6806,7 @@ snapshots: buffer: 6.0.3 form-data: 4.0.5 husky: 9.1.7 - lodash: 4.17.23 + lodash: 4.18.1 otplib: 12.0.1 qs: 6.15.0 stream-browserify: 3.0.0 @@ -7262,7 +7265,7 @@ snapshots: ansis: 3.17.0 debug: 4.4.3(supports-color@8.1.1) http-call: 5.3.0 - lodash: 4.17.23 + lodash: 4.18.1 registry-auth-token: 5.1.1 transitivePeerDependencies: - supports-color @@ -8439,7 +8442,7 @@ snapshots: async@2.6.4: dependencies: - lodash: 4.17.23 + lodash: 4.18.1 async@3.2.3: {} @@ -9428,7 +9431,7 @@ snapshots: indent-string: 4.0.0 is-builtin-module: 3.2.1 jsesc: 3.1.0 - lodash: 4.17.23 + lodash: 4.18.1 pluralize: 8.0.0 read-pkg-up: 7.0.1 regexp-tree: 0.1.27 @@ -9615,7 +9618,7 @@ snapshots: '@types/lodash': 4.17.24 '@types/node': 14.18.63 '@types/sinon': 21.0.0 - lodash: 4.17.23 + lodash: 4.18.1 mock-stdin: 1.0.0 nock: 13.5.6 stdout-stderr: 0.1.13 @@ -10123,7 +10126,7 @@ snapshots: cli-cursor: 3.1.0 figures: 3.2.0 inquirer: 8.2.7(@types/node@14.18.63) - lodash: 4.17.23 + lodash: 4.18.1 rxjs: 6.6.7 inquirer-search-checkbox@1.0.0: @@ -10148,7 +10151,7 @@ snapshots: cli-width: 2.2.1 external-editor: 2.2.0 figures: 2.0.0 - lodash: 4.17.23 + lodash: 4.18.1 mute-stream: 0.0.7 run-async: 2.4.1 rx-lite: 4.0.8 @@ -10165,7 +10168,7 @@ snapshots: cli-cursor: 3.1.0 cli-width: 3.0.0 figures: 3.2.0 - lodash: 4.17.23 + lodash: 4.18.1 mute-stream: 0.0.8 ora: 5.4.1 run-async: 2.4.1 @@ -10633,6 +10636,8 @@ snapshots: lodash@4.17.23: {} + lodash@4.18.1: {} + log-symbols@1.0.2: dependencies: chalk: 1.1.3 @@ -10974,7 +10979,7 @@ snapshots: fs-extra: 8.1.0 github-slugger: 2.0.0 got: 13.0.0 - lodash: 4.17.23 + lodash: 4.18.1 normalize-package-data: 6.0.2 semver: 7.7.4 sort-package-json: 2.15.1