diff --git a/.cursor/rules/README.md b/.cursor/rules/README.md new file mode 100644 index 0000000..4f82b8a --- /dev/null +++ b/.cursor/rules/README.md @@ -0,0 +1,23 @@ +# Cursor rules + +Rules give context-aware guidance for this **Contentstack HTML → JSON RTE migration CLI plugin** (`@contentstack/cli-cm-migrate-rte`). + +## Rules overview + +| File | Purpose | +|------|---------| +| `dev-workflow.md` | TDD, structure, validation commands (always applied) | +| `contentstack-cli.mdc` | Contentstack CLI utilities, migration flow, Management API habits | +| `testing.mdc` | Mocha / Chai / Sinon, nock, and coverage | +| `oclif-commands.mdc` | Command flags and delegation patterns | + +## How they attach + +- **Always**: `dev-workflow.md` +- **Commands** (`src/commands/**`): `oclif-commands.mdc` +- **Migration logic** (`src/lib/**`): `contentstack-cli.mdc` +- **Tests** (`test/**`): `testing.mdc` + +## Chat shortcuts + +You can `@`-mention rule topics (for example testing or OCLIF) depending on how your workspace maps rule names. diff --git a/.cursor/rules/contentstack-cli.mdc b/.cursor/rules/contentstack-cli.mdc new file mode 100644 index 0000000..c0c22c3 --- /dev/null +++ b/.cursor/rules/contentstack-cli.mdc @@ -0,0 +1,40 @@ +--- +description: "HTML→JSON RTE migration plugin: CLI utilities, migration flow, and Management API habits" +globs: + - "src/lib/**/*.js" + - "src/lib/**/*.json" +alwaysApply: false +--- + +# Contentstack CLI — migrate HTML RTE + +This package is **`@contentstack/cli-cm-migrate-rte`**. Business logic lives in **`src/lib/util/index.js`** (there is no `src/core/` or `src/services/` layer). + +## Stack and auth + +- Use **`@contentstack/cli-utilities`**: `managementSDKClient`, `cliux`, `flags`, `pathValidator`, `isAuthenticated`, `doesBranchExist`, etc. +- Resolve credentials like **`migrate-html-rte.js`**: config **`alias`** (management token via `getToken`) or **`stack-api-key`** (requires logged-in CLI session per `getStack`). +- Pass **`this.cmaHost`** from the command into stack options as **`host`**. +- **Never** log or persist secrets (API keys, management tokens). + +## Migration orchestration + +1. **`normalizeFlags`** — maps deprecated camelCase flag names to kebab-case. +2. **`getConfig`** — JSON file via **`--config-path`** / `pathValidator`, or inline paths from **`--html-path`** / **`--json-path`**; validated with **jsonschema** against **`config_schema.json`**; optional **`cliux.confirm`** unless **`--yes`**. +3. **`getStack`** — `managementSDKClient` with `application: json-rte-migration/`, branch check via **`doesBranchExist`**. +4. **`updateSingleContentTypeEntries`** or **`updateContentTypeForGlobalField`** — fetch content type / global field, validate RTE paths (**`isPathValid`** / **`traverseSchemaForField`**), batch entries with **`delay`** and **`batch-limit`**, convert HTML with **jsdom** + **`collapse-whitespace`** + **`htmlToJson`** from **`@contentstack/json-rte-serializer`**, update entries; track **`errorEntriesUid`** on repeated update failures. + +## Paths and schema + +- **`paths`** entries map **`from`** (HTML RTE uid path) → **`to`** (JSON RTE uid path); support groups, **blocks**, **global_field**, and **experience_container** traversal as in **`setEntryData`** / **`isPathValid`**. +- HTML and JSON RTE must sit at the **same depth**; single/multiple cardinality must **match** between source and target fields. + +## API usage + +- Batching uses entry **query** + **find** with **`include_count`**, **skip** / **limit**, optional **locale** filter. +- Local **retry** loops exist for batch fetch and **`handleEntryUpdate`** (transient failures); when adding new Management API calls, respect **rate limits** and handle **429** / transient errors consistently with existing patterns. +- **Mock all** HTTP / SDK usage in tests (**nock**, **sinon**); no live stack calls in unit tests. + +## Not in this plugin + +- This is **not** query-based export: there is no **`QueryExporter`**. Do not assume **`@contentstack/cli-cm-export`** export pipelines unless you are integrating something explicitly shared. diff --git a/.cursor/rules/dev-workflow.md b/.cursor/rules/dev-workflow.md new file mode 100644 index 0000000..7e70c2b --- /dev/null +++ b/.cursor/rules/dev-workflow.md @@ -0,0 +1,63 @@ +--- +description: "Core development workflow and TDD patterns - always applied" +globs: ["**/*.js", "**/*.json"] +alwaysApply: true +--- + +# Development Workflow + +## Quick Reference + +For detailed patterns, see project skills: + +- `@skills/testing` — Testing and TDD +- `@skills/contentstack-cli` — Migration command, `src/lib/util`, Contentstack APIs +- `@skills/framework` — Config validation, CLI UX, retries, shared helpers +- `@skills/code-review` — PR review checklist + +## TDD workflow (recommended) + +For **new behavior or bug fixes**, prefer working in three steps: + +1. **RED** → Write a failing test (or extend an existing one) +2. **GREEN** → Minimal code to pass +3. **REFACTOR** → Clean up while tests stay green + +**Exceptions (no new test required when behavior is unchanged):** pure refactors, documentation-only edits, comments/config-only tweaks, trivial typos. + +## Guidelines + +- **Coverage:** aim high; **~80%** (lines/branches/functions) is an **aspirational target**, not a CI gate in this repo +- **JavaScript** — follow existing style in `src/`; prefer small, focused functions and clear naming +- **NO test.skip or .only** in commits + +## File structure (this repo) + +- **Command**: `src/commands/cm/entries/migrate-html-rte.js` — `JsonMigrationCommand`, flags, thin `run()` +- **Migration logic**: `src/lib/util/index.js` — stack client, config, batch entry updates, HTML→JSON via `@contentstack/json-rte-serializer` + jsdom +- **Config schema**: `src/lib/util/config_schema.json` +- **Tests**: `test/**/*.test.js` (Mocha); fixtures under `test/dummy/`, helpers under `test/utils/` + +## Naming conventions + +- **Files**: `kebab-case.js` where the codebase already uses it; test files `*.test.js` +- **Classes**: `PascalCase` +- **Functions**: `camelCase` +- **Tests**: `should [behavior] when [condition]` + +## Before coding + +1. Read relevant `@skills/*` references +2. For behavior changes: prefer a failing test first, then implement +3. Refactor and run tests + +## Validation commands + +- `npm test` — All tests with nyc (`mocha --require test/setup.js --forbid-only "test/**/*.test.js"`) +- `npm run prepack` — Regenerate `oclif.manifest.json` and README (run before release packaging as needed) + +## Commit suggestions + +- Conventional commits are helpful but optional: `feat(scope): description` +- Tests passing before merge +- No stray `console.log` / `debugger` (beyond intentional CLI output already in the command) diff --git a/.cursor/rules/oclif-commands.mdc b/.cursor/rules/oclif-commands.mdc new file mode 100644 index 0000000..c37fbaf --- /dev/null +++ b/.cursor/rules/oclif-commands.mdc @@ -0,0 +1,45 @@ +--- +description: "OCLIF / Contentstack command patterns for migrate-html-rte" +globs: + - "src/commands/**/*.js" + - "test/commands/**/*.js" +alwaysApply: false +--- + +# Command standards (`cm:entries:migrate-html-rte`) + +This plugin uses **`Command`** from **`@contentstack/cli-command`** and **`flags`** from **`@contentstack/cli-utilities`**. Follow **`src/commands/cm/entries/migrate-html-rte.js`** as the source of truth. + +## Structure + +```javascript +const { Command } = require("@contentstack/cli-command"); +const { flags } = require("@contentstack/cli-utilities"); +const { /* getStack, getConfig, ... */ } = require("../../../lib/util"); + +class JsonMigrationCommand extends Command { + async run() { + const { flags: migrateRteFlags } = await this.parse(JsonMigrationCommand); + // normalizeFlags → getConfig → getStack → updateSingleContentTypeEntries / updateContentTypeForGlobalField + } +} + +JsonMigrationCommand.description = "..."; +JsonMigrationCommand.flags = { /* flags.string / flags.boolean / flags.integer */ }; +JsonMigrationCommand.examples = [ "...", "csdx cm:entries:migrate-html-rte ..." ]; + +module.exports = JsonMigrationCommand; +``` + +## Practices + +- **Validate early**: ensure **`paths`** (from config or flags) exist; stack auth via alias or stack-api-key as documented. +- **Delegate**: keep **`run()`** thin; migration, batching, and HTML→JSON conversion stay in **`src/lib/util/index.js`**. +- **User feedback**: success summary and **`errorEntriesUid`** reporting already use **chalk**; keep messages actionable. +- **Errors**: use **`this.error(error.message, { exit: 2 })`** for command-level failures. + +## Flags + +- Define flags with **`flags.string`**, **`flags.boolean`**, **`flags.integer`**, etc., on the **class** (`JsonMigrationCommand.flags`). +- Use **`dependsOn`** where flags are coupled (e.g. **`html-path`** depends on **`json-path`**). +- Prefer kebab-case flag names; **`normalizeFlags`** in util accepts legacy camelCase keys for backward compatibility. diff --git a/.cursor/rules/testing.mdc b/.cursor/rules/testing.mdc new file mode 100644 index 0000000..d08849a --- /dev/null +++ b/.cursor/rules/testing.mdc @@ -0,0 +1,68 @@ +--- +description: "Testing patterns and TDD workflow" +globs: + - "test/**/*.js" + - "**/*.test.js" +alwaysApply: true +--- + +# Testing Standards + +## TDD workflow + +1. For behavior changes, prefer a failing test first +2. Minimal code to pass +3. Refactor while keeping tests green + +Pure refactors and docs-only changes may skip new tests when behavior is unchanged. + +## Coverage + +- **~80%** (lines, branches, functions) is an **aspirational** goal, not enforced as a hard gate here +- Test both success and failure paths +- Mock all external dependencies + +## Test patterns + +```javascript +const { expect } = require("chai"); +const sinon = require("sinon"); + +describe("[ModuleName]", () => { + beforeEach(() => { + sinon.stub(SomeClient.prototype, "fetch").resolves(mockData); + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should [expected behavior] when [condition]", async () => { + const input = { /* test data */ }; + const result = await moduleUnderTest(input); + expect(result).to.deep.equal(expectedOutput); + }); +}); +``` + +## Mocking standards + +- Use **sinon** for stubs/spies; use **nock** for HTTP where the tests already do +- Never make real API calls in tests +- Mock at module boundaries (**`managementSDKClient`**, **`command.cmaAPIUrl`**, etc.), not irrelevant internals + +## Layout + +- Tests live under **`test/**/*.test.js`** (Mocha), loaded via **`test/setup.js`** +- JSON fixtures under **`test/dummy/`**; shared helpers under **`test/utils/`** + +## Common mock patterns + +```javascript +const nock = require("nock"); +const sinon = require("sinon"); +const { managementSDKClient } = require("@contentstack/cli-utilities"); + +sinon.stub(managementSDKClient, "call").resolves(mockClient); +nock("https://example.cma.url").get(/\/v3\//).reply(200, mockPayload); +``` diff --git a/.cursor/skills/SKILL.md b/.cursor/skills/SKILL.md new file mode 100644 index 0000000..a0d3a60 --- /dev/null +++ b/.cursor/skills/SKILL.md @@ -0,0 +1,5 @@ +# Project skills + +Agent-oriented skills for **cli-cm-migrate-rte** (`@contentstack/cli-cm-migrate-rte`) live under **[`skills/`](../../skills/)**. + +Reference in chat with `@skills/` (for example `@skills/contentstack-cli`, `@skills/testing`). diff --git a/.gitignore b/.gitignore index 2560a99..fc39948 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ node_modules oclif.manifest.json # Husky 9 internal folder (created by "husky" on install; do not commit) .husky/_ -*.log \ No newline at end of file +*.log +.vscode/ \ No newline at end of file diff --git a/.talismanrc b/.talismanrc index 3bf1d9e..1f61805 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,8 +1,14 @@ fileignoreconfig: -- filename: package-lock.json - checksum: dcbdc2bb8ecdfc2768a0acad70ac3658d0dedfe7394ec9b2522ed1d2dfd02d83 -- filename: .husky/pre-commit - checksum: 7a12030ddfea18d6f85edc25f1721fb2009df00fdd42bab66b05de25ab3e32b2 -- filename: .github/workflows/check-version-bump.yml - checksum: b33b47fae4e126cd9a3489a9324e8b2cf30b38ebdb3dd99e2d453d4bfc0922ad -version: "" \ No newline at end of file + - filename: package-lock.json + checksum: dcbdc2bb8ecdfc2768a0acad70ac3658d0dedfe7394ec9b2522ed1d2dfd02d83 + - filename: .husky/pre-commit + checksum: 7a12030ddfea18d6f85edc25f1721fb2009df00fdd42bab66b05de25ab3e32b2 + - filename: .github/workflows/check-version-bump.yml + checksum: b33b47fae4e126cd9a3489a9324e8b2cf30b38ebdb3dd99e2d453d4bfc0922ad + - filename: .cursor/rules/contentstack-cli.mdc + checksum: 95be6d57f956007c930116afcd0a3869d789ebaedcecb71a7f7cfd806f979029 + - filename: skills/code-review/references/code-review-checklist.md + checksum: ff75324c78ce3f4838a5e52a7c6f300301a7c79102dac82f0a9928a4a1589085 + - filename: skills/contentstack-cli/references/contentstack-patterns.md + checksum: 432d1dab0ade2651ca127349b419899f398174669806450570c6d2575a8244eb +version: "" diff --git a/skills/README.md b/skills/README.md new file mode 100644 index 0000000..bed28d8 --- /dev/null +++ b/skills/README.md @@ -0,0 +1,24 @@ +# Skills + +Reusable agent guidance for **`@contentstack/cli-cm-migrate-rte`** (HTML RTE → JSON RTE migration). Use with any tool that supports file references. + +## Quick reference + +| Skill | Use when | +|-------|----------| +| **contentstack-cli** | Command, `src/lib/util`, Contentstack Management API, migration flow | +| **testing** | Mocha, Chai, Sinon, nock, TDD, coverage | +| **framework** | Config schema, CLI confirmation, retries, progress, shared helpers | +| **code-review** | PR / change review | + +## How to reference + +``` +Follow @skills/contentstack-cli and @skills/testing for this change. +``` + +## Project context + +- **Stack:** JavaScript, OCLIF (via `@contentstack/cli-command`), Contentstack CLI utilities, Mocha / Chai / Sinon, nock, nyc +- **Layout:** `src/commands/cm/entries/migrate-html-rte.js` → `src/lib/util/` (`config_schema.json`, migration helpers) +- **Tests:** `test/**/*.test.js` diff --git a/skills/code-review/SKILL.md b/skills/code-review/SKILL.md new file mode 100644 index 0000000..2d429a6 --- /dev/null +++ b/skills/code-review/SKILL.md @@ -0,0 +1,51 @@ +--- +name: code-review +description: PR review checklist for @contentstack/cli-cm-migrate-rte and similar CLI plugins. Use when reviewing changes to migrate-html-rte, lib/util, or tests. +--- + +# Code Review Skill + +## Quick Reference + +- **[Code Review Checklist](./references/code-review-checklist.md)** — Severity levels and full checklist + +## 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 categories + +1. **Security** — No hardcoded secrets; safe logging +2. **Correctness** — Migration logic, path validation, entry updates +3. **Architecture** — Thin command, logic in `src/lib/util` +4. **Performance** — Batching, delays, avoid redundant API calls +5. **Testing** — nock/sinon boundaries, no live stack +6. **Conventions** — JavaScript style consistent with `src/` + +## Quick checklist template + +```markdown +## Security +- [ ] No secrets in code or logs + +## Correctness +- [ ] HTML/JSON path rules and global-field behavior preserved +- [ ] Errors surfaced to the operator + +## Architecture +- [ ] Command stays thin; util holds migration logic + +## Testing +- [ ] Behavior changes covered; mocks at boundaries + +## Conventions +- [ ] Matches existing JS patterns in repo +``` + +## Usage + +Open **references/code-review-checklist.md** for the full structured review. diff --git a/skills/code-review/references/code-review-checklist.md b/skills/code-review/references/code-review-checklist.md new file mode 100644 index 0000000..8fb7b7f --- /dev/null +++ b/skills/code-review/references/code-review-checklist.md @@ -0,0 +1,95 @@ +# Code Review Checklist + +PR review guidelines for **`@contentstack/cli-cm-migrate-rte`** and related CLI work. + +## 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) + +## 1. Security + +### Authentication and secrets + +- [ ] No hardcoded API keys, tokens, or credentials +- [ ] Sensitive data not logged to console or files +- [ ] Management tokens and stack keys never echoed in error messages + +### Input validation + +- [ ] Flags and config validated (**jsonschema** / required paths) +- [ ] Config file path resolved via **`pathValidator`** +- [ ] API responses handled before mutating entry payloads + +### Error handling + +- [ ] Errors do not leak secrets +- [ ] User-facing messages are actionable + +## 2. Correctness + +### Migration logic + +- [ ] HTML and JSON RTE paths validated (**`isPathValid`**) when schema changes touch migration +- [ ] Global-field path expansion (**`getGlobalFieldPath`**, **`updateMigrationPath`**) remains correct for referred content types +- [ ] **`errorEntriesUid`** updated consistently on persistent update failures + +### Async and API usage + +- [ ] Async flows properly awaited +- [ ] Batching (**skip** / **limit**, **include_count**) correct for large entry sets +- [ ] Locale filtering behaves as documented when **`config.locale`** is set + +## 3. Architecture + +### Code organization + +- [ ] **`migrate-html-rte.js`** stays focused on parse → config → stack → dispatch +- [ ] Heavy logic remains in **`src/lib/util/index.js`** +- [ ] New helpers colocated with related functions (path traversal, conversion, batching) + +### Modularity + +- [ ] Functions testable without running full CLI +- [ ] Duplication avoided between global-field and content-type paths where possible + +## 4. Performance + +- [ ] **`delay`** and **`batch-limit`** respected; no busy loops +- [ ] Retries bounded (same spirit as existing **3**-attempt patterns) +- [ ] No unnecessary duplicate fetches of content types or entries + +## 5. Testing + +- [ ] New or changed behavior covered in **`test/**/*.test.js`** +- [ ] **nock** / **sinon** used; no live Management API calls +- [ ] No **`.only`** / **`.skip`** committed + +## 6. CLI-specific + +### Command + +- [ ] **`JsonMigrationCommand.flags`** documented and consistent with **`normalizeFlags`** +- [ ] **`examples`** updated if usage changes +- [ ] Exit code **2** on command error via **`this.error`** + +### User experience + +- [ ] Progress and summaries understandable +- [ ] Confirmation path still clear when not using **`--yes`** + +## 7. Contentstack integration + +- [ ] Auth paths (**alias** vs **stack-api-key**) unchanged in intent unless explicitly documented +- [ ] Branch handling uses **`doesBranchExist`** when branch is set +- [ ] Rate limits and pacing considered when adding Management API calls + +## Before approving + +- [ ] Critical issues resolved +- [ ] **`npm test`** passes +- [ ] Migration safety considered (data loss, partial updates, idempotency where relevant) +- [ ] README / oclif docs updated if command surface changes diff --git a/skills/contentstack-cli/SKILL.md b/skills/contentstack-cli/SKILL.md new file mode 100644 index 0000000..316273f --- /dev/null +++ b/skills/contentstack-cli/SKILL.md @@ -0,0 +1,29 @@ +--- +name: contentstack-cli +description: Contentstack CLI migrate-html-rte plugin — OCLIF command, src/lib/util, cli-utilities, and HTML→JSON RTE migration. Use for commands, migration logic, and Management API usage in this repo. +--- + +# Contentstack CLI — migrate HTML RTE + +## Quick reference + +- **[Contentstack patterns](references/contentstack-patterns.md)** — Command shape, migration pipeline, API habits +- **[Framework patterns](../framework/references/framework-patterns.md)** — Config, CLI UX, retries + +## This package + +- **Command:** `JsonMigrationCommand` in `src/commands/cm/entries/migrate-html-rte.js` extends **`Command`** from **`@contentstack/cli-command`**. +- **Orchestration:** `src/lib/util/index.js` — stack client, config, batch updates, HTML→JSON conversion, global-field handling. +- **Schema:** `src/lib/util/config_schema.json` validated with **jsonschema**. +- **Integration:** **`@contentstack/cli-utilities`**, **`@contentstack/json-rte-serializer`**, **jsdom**, **oclif** (manifest / readme). + +## Practices + +- Authenticate and build the management client via **`@contentstack/cli-utilities`**; never log secrets. +- Keep **`run()`** thin; delegate to **`src/lib/util`** helpers. +- Respect rate limits; align retries and delays with existing **`updateEntriesInBatch`** / **`handleEntryUpdate`** behavior when adding API calls. +- Tests: mock SDK and HTTP (**nock**); no real stack access in unit tests. + +## Usage + +Use this skill when changing migration behavior, flags, config, or Contentstack API usage. Open **references/contentstack-patterns.md** for longer detail. diff --git a/skills/contentstack-cli/references/contentstack-patterns.md b/skills/contentstack-cli/references/contentstack-patterns.md new file mode 100644 index 0000000..60d6cd4 --- /dev/null +++ b/skills/contentstack-cli/references/contentstack-patterns.md @@ -0,0 +1,87 @@ +# Contentstack patterns — migrate HTML RTE plugin + +Guidance for **`@contentstack/cli-cm-migrate-rte`**: migrate entry field data from HTML RTE to JSON RTE using the Management API. + +## Repository layout + +| Area | Role | +|------|------| +| `src/commands/cm/entries/migrate-html-rte.js` | CLI entry: flags, `normalizeFlags`, `getConfig`, `getStack`, dispatch migration | +| `src/lib/util/index.js` | Stack client, config loading/validation, batching, path validation, HTML→JSON, global-field fan-out | +| `src/lib/util/config_schema.json` | JSON Schema for migration config (paths, locale, delay, batch-limit, etc.) | + +There is **no** `src/core/` or `src/services/` directory in this repo. + +## Command pattern + +Use **`@contentstack/cli-command`** and **`@contentstack/cli-utilities`**: + +```javascript +const { Command } = require("@contentstack/cli-command"); +const { flags } = require("@contentstack/cli-utilities"); +const { + getStack, + getConfig, + getToken, + updateSingleContentTypeEntries, + updateContentTypeForGlobalField, + normalizeFlags, +} = require("../../../lib/util"); + +class JsonMigrationCommand extends Command { + async run() { + const { flags: migrateRteFlags } = await this.parse(JsonMigrationCommand); + const normalizedFlags = normalizeFlags(migrateRteFlags); + const config = await getConfig(normalizedFlags); + const stackOptions = { host: this.cmaHost }; + if (config.alias) stackOptions.token = getToken(config.alias); + if (config["stack-api-key"]) stackOptions.stackApiKey = config["stack-api-key"]; + if (config.branch) stackOptions.branch = config.branch; + const stack = await getStack(stackOptions); + if (config["global-field"]) { + await updateContentTypeForGlobalField(stack, config["content-type"], config); + } else { + await updateSingleContentTypeEntries(stack, config["content-type"], config); + } + } +} + +JsonMigrationCommand.flags = { + "config-path": flags.string({ char: "c", ... }), + alias: flags.string({ char: "a", ... }), + // see migrate-html-rte.js for full flags +}; + +module.exports = JsonMigrationCommand; +``` + +### Conventions + +- Validate config early (**`paths`** required); surface schema errors via **`throwConfigError`** messages. +- Use **`cliux.confirm`** for interactive confirmation unless **`--yes`**. +- Prefer **chalk** for user-visible success/error lines in the command. + +## Migration pipeline (conceptual) + +1. **Load config** — file (**`pathValidator`**) or flags; **jsonschema** validation against **`config_schema.json`**. +2. **Resolve stack** — **`managementSDKClient`** with branch validation (**`doesBranchExist`**). +3. **Content type or global field** — fetch schema; for global fields, expand **`paths`** with **`getGlobalFieldPath`** / **`updateMigrationPath`**. +4. **Validate paths** — **`isPathValid`** ensures HTML RTE (**`allow_rich_text`**) and JSON RTE (**`allow_json_rte`**) exist, same parent depth, matching multiple/single. +5. **Entries** — paginated **query** + **find**, **`delay`** between entries, **`convertHtmlToJson`** per field path; **`handleEntryUpdate`** with retries; collect **`errorEntriesUid`** on persistent failure. + +## Authentication and secrets + +- **`alias`**: management token from CLI (**`getToken`**). +- **`stack-api-key`**: requires authenticated CLI session (**`isAuthenticated`**) in **`getStack`**. +- Do not print tokens or keys in logs or migration output. + +## API and rate limits + +- Contentstack Management API is rate-limited; existing code uses **delays** and **retries** on batch fetch and entry update. +- In tests, stub **`command.cmaAPIUrl`**, use **nock** for `/v3/` routes, and stub **`managementSDKClient`** as in **`test/commands/json-migration.test.js`**. + +--- + +## Other CLI plugins (context) + +Other Contentstack CLI packages may use **bulk-operation** logs or **BaseBulkCommand**. **This plugin** uses a single command class and **`src/lib/util`** batching only. diff --git a/skills/framework/SKILL.md b/skills/framework/SKILL.md new file mode 100644 index 0000000..aba7bfc --- /dev/null +++ b/skills/framework/SKILL.md @@ -0,0 +1,21 @@ +--- +name: framework +description: Config validation, CLI UX, retries, and shared helpers for @contentstack/cli-cm-migrate-rte. Use when working in src/lib/util or changing confirmation/progress/delay behavior — align with @contentstack/cli-utilities. +--- + +# Framework Patterns Skill + +## Quick Reference + +- **[Framework Patterns](references/framework-patterns.md)** — Config schema, cliux, delays/retries, progress, lodash/jsdom/serializer usage + +## Core topics (this repo) + +- **Configuration** — **`getConfig`**, **`pathValidator`**, **jsonschema** + **`config_schema.json`**, **`normalizeFlags`** for legacy keys +- **CLI UX** — **`cliux.confirm`**, **`cliux.progress`**, **chalk** output in the command +- **Resilience** — **`delay`**, retry counters in **`updateEntriesInBatch`** and **`handleEntryUpdate`** +- **Transformation** — **JSDOM**, **`collapse-whitespace`**, **`htmlToJson`**, **`applyDirtyAttributesToBlock`** + +## Usage + +Use this skill when adjusting config shape, user prompts, pacing, or shared migration helpers in **`src/lib/util`**. Prefer extending existing functions over new parallel frameworks. diff --git a/skills/framework/references/framework-patterns.md b/skills/framework/references/framework-patterns.md new file mode 100644 index 0000000..a53293d --- /dev/null +++ b/skills/framework/references/framework-patterns.md @@ -0,0 +1,46 @@ +# Framework Patterns + +Config, CLI UX, and helper patterns for **`@contentstack/cli-cm-migrate-rte`**. Prefer matching **`src/lib/util/index.js`** and **`@contentstack/cli-utilities`** before introducing new abstractions. + +## Configuration + +- **File config:** `require(pathValidator(configPath))` then **`checkConfig`** with **jsonschema** against **`config_schema.json`**. +- **Flag-only config:** build a minimal object with **`paths`**: `[{ from: html-path, to: json-path }]`, plus **`delay`**, **`batch-limit`**, **`locale`**, **`branch`**, **`alias`** / **`stack-api-key`** as set. +- **User confirmation:** **`confirmConfig`** prints **`prettyPrint`** and **`cliux.confirm`** unless **`--yes`**. +- **Errors:** **`throwConfigError`** maps schema validation to short user-facing messages. + +```javascript +const Validator = require("jsonschema").Validator; +const configSchema = require("./config_schema.json"); + +function checkConfig(config) { + const v = new Validator(); + const res = v.validate(config, configSchema, { throwError: true, nestedErrors: true }); + return res.valid; +} +``` + +## CLI utilities + +- **`cliux`** — confirmations and progress (**`customBar`** in util) +- **`pathValidator`** — safe resolution of **`--config-path`** +- **`managementSDKClient`**, **`isAuthenticated`**, **`doesBranchExist`** — stack/session consistency + +## Delays and retries + +- **`delay(ms)`** — spacing between entry updates and between locale passes +- **Batch fetch** — up to **3** retries with **5s** pause on failure +- **`handleEntryUpdate`** — up to **3** retries; failures recorded under **`config.errorEntriesUid`** + +When adding new network-heavy loops, follow the same **retry budget** and **user-visible logging** style. + +## HTML → JSON + +- **`convertHtmlToJson`** — **JSDOM** body, **`collapse-whitespace`**, **`htmlToJson`** from **`@contentstack/json-rte-serializer`** +- **`applyDirtyAttributesToBlock`** — post-process for block **`attrs.dirty`** + +Keep DOM and serializer concerns inside these helpers so tests can stub **`htmlToJson`** or **`JSDOM`** at boundaries if needed. + +## Lodash and paths + +- Heavy use of **`get`**, **`set`**, **`find`**, **`cloneDeep`**, **`isEmpty`** for nested entry data and schema walking — stay consistent with existing **`setEntryData`** / **`traverseSchemaForField`** patterns when extending field types. diff --git a/skills/testing/SKILL.md b/skills/testing/SKILL.md new file mode 100644 index 0000000..3a411d9 --- /dev/null +++ b/skills/testing/SKILL.md @@ -0,0 +1,51 @@ +--- +name: testing +description: Mocha/Chai/Sinon/nock testing and TDD for @contentstack/cli-cm-migrate-rte. Use when writing or debugging tests under test/ or adjusting coverage. +--- + +# Testing Patterns + +## Quick Reference + +For comprehensive testing guidance, see: + +- **[Testing Patterns](./references/testing-patterns.md)** — Mocha, Chai, Sinon, nock, fixtures +- **[Development Workflow](./references/development-workflow.md)** — TDD process and validation + +## TDD workflow summary + +**RED → GREEN → REFACTOR** for behavior changes; pure refactors / docs-only may skip new tests when behavior is unchanged. + +## Key testing rules + +- **~80% coverage** (lines, branches, functions) is **aspirational**, not a hard CI gate here +- **Use sinon** for stubs; **nock** for HTTP where tests already pattern-match CMA URLs +- **Never make real API calls** in tests +- **Mock at module boundaries** (SDK, `command.cmaAPIUrl`), not irrelevant internals +- **Test both success and failure paths** +- **Use descriptive test names**: "should [behavior] when [condition]" + +## Quick test template + +```javascript +const { expect } = require("chai"); +const sinon = require("sinon"); + +describe("[ComponentName]", () => { + beforeEach(() => { + sinon.stub(ExternalApi.prototype, "fetch").resolves(mockData); + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should [expected behavior] when [condition]", async () => { + // Arrange, Act, Assert + }); +}); +``` + +## Usage + +Open **references/testing-patterns.md** for organization, nock patterns, and alignment with **`test/setup.js`**. diff --git a/skills/testing/references/development-workflow.md b/skills/testing/references/development-workflow.md new file mode 100644 index 0000000..6cf9ef3 --- /dev/null +++ b/skills/testing/references/development-workflow.md @@ -0,0 +1,85 @@ +# Development Workflow + +Core development rules and Test-Driven Development (TDD) workflow for **`@contentstack/cli-cm-migrate-rte`**. + +## TDD workflow (recommended) + +For **new behavior or bug fixes**, prefer: + +1. **RED** → Failing test (or extended test) +2. **GREEN** → Minimal code to pass +3. **REFACTOR** → Improve while tests stay green + +**Exceptions:** pure refactors, documentation-only edits, and trivial non-behavior changes may skip new tests. + +## Guidelines + +- Prefer **clear tests** over heavy setup when you can +- **NO test.skip or .only** in commits +- **~80% coverage** (lines, branches, functions) is **aspirational**, not a CI gate +- **JavaScript** — match existing patterns in `src/`; favor readability and small functions + +## File structure (this repo) + +- **Command:** `src/commands/cm/entries/migrate-html-rte.js` +- **Migration logic:** `src/lib/util/index.js`, `src/lib/util/config_schema.json` +- **Tests:** `test/**/*.test.js`; fixtures `test/dummy/`; helpers `test/utils/` + +## Naming conventions + +- **Files:** `kebab-case.js` where used; tests `*.test.js` +- **Classes:** `PascalCase` +- **Functions / variables:** `camelCase` +- **Test descriptions:** "should [behavior] when [condition]" + +## Code quality + +### Error handling + +- Throw **`Error`** with clear messages for users and tests +- Avoid swallowing errors; command layer maps to **`this.error`** where appropriate + +### Import style + +Follow **`src/lib/util/index.js`**: external packages first, then Node built-ins, then relative paths — stay consistent with neighboring files. + +## Testing + +### Coverage + +- Aim high; **~80%** is a guideline +- Mock SDK and HTTP; use **`test/dummy`** JSON for API shapes + +### Test structure + +```javascript +describe("[ComponentName]", () => { + beforeEach(() => { + sinon.stub(ExternalService.prototype, "method").resolves(mockData); + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should [expected behavior] when [condition]", async () => { + const result = await component.method(input); + expect(result).to.deep.equal(expectedOutput); + }); +}); +``` + +## Commit suggestions + +- Conventional commits are optional: `feat(scope): description` +- Include tests when you change behavior +- Run **`npm test`** before pushing +- No debugging code (`debugger`) left in + +## Development process + +1. **Understand** → Read relevant `@skills/*` and `.cursor/rules` as needed +2. **Plan** → Break work into testable units +3. **Test first** → When adding behavior, prefer failing test then implementation +4. **Validate** → `npm test` +5. **Review** → Self-review against `@skills/code-review` diff --git a/skills/testing/references/testing-patterns.md b/skills/testing/references/testing-patterns.md new file mode 100644 index 0000000..0315b3a --- /dev/null +++ b/skills/testing/references/testing-patterns.md @@ -0,0 +1,93 @@ +# Testing Patterns + +Testing best practices and TDD workflow for **`@contentstack/cli-cm-migrate-rte`**. + +## TDD workflow + +**RED → GREEN → REFACTOR** for behavior changes. Pure refactors / docs-only may skip new tests when behavior is unchanged. + +## Test structure + +### Basic template + +```javascript +const { expect } = require("chai"); +const sinon = require("sinon"); + +describe("[ComponentName]", () => { + beforeEach(() => { + sinon.stub(SomeClient.prototype, "fetch").resolves(mockData); + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should [expected behavior] when [condition]", async () => { + const input = { /* test data */ }; + const result = await moduleUnderTest(input); + expect(result).to.deep.equal(expectedOutput); + }); +}); +``` + +### Command / integration-style tests + +See **`test/commands/json-migration.test.js`**: **`@oclif/test`** **`runCommand`**, **nock** against CMA URLs, stubs on **`command`** from **`src/lib/util`**, fixtures from **`test/utils`** and **`test/dummy`**. + +## Key testing rules + +### Coverage + +- **~80%** (lines, branches, functions) is **aspirational**, not a hard gate (`npm test` runs nyc with **`--check-coverage=false`**) +- Test both success and failure paths +- Include edge cases for config, locales, global fields, and path validation when touching that code + +### Mocking standards + +- **sinon** for stubs/spies on SDK and CLI helpers +- **nock** for HTTP; **`nock.cleanAll()`** in setup where needed +- **Never** hit a real stack from unit tests +- Restore mocks in **`afterEach`** to avoid leakage + +### Test patterns + +- Descriptive names: "should [behavior] when [condition]" +- Minimal focused setup +- Group related cases in **`describe`** blocks + +## Test organization + +### File structure + +- Specs: **`test/**/*.test.js`** (e.g. **`test/commands/json-migration.test.js`**) +- Fixtures: **`test/dummy/**/*.json`** +- Shared helpers: **`test/utils/index.js`** +- Mocha entry: **`test/setup.js`** (required by **`npm test`**) + +### Test run + +```text +npm test +# → nyc --check-coverage=false mocha --require test/setup.js --forbid-only "test/**/*.test.js" +``` + +## Error testing + +```javascript +it("should reject when config is invalid", async () => { + try { + await subject(invalidConfig); + expect.fail("expected rejection"); + } catch (err) { + expect(err.message).to.match(/mandatory|Invalid/i); + } +}); +``` + +## Coverage and quality checklist + +- [ ] Public behavior you change has test coverage +- [ ] Error paths covered where risky +- [ ] Mocks restored; no **`.only`** / **`.skip`** committed +- [ ] No real Management API calls