# Contributing Hey there, nice to see you! :wave: So you want to contribute? Awesome! (Before jumping into the hard work, please make sure you discussed the idea in an issue beforehand.) ## A note about the programming language Most of the codebase is written with [Typescript](https://www.typescriptlang.org/) (there are some bits pending in Javascript). This means that the code has to be transpiled before running and that you have to run `npm run build` before trying out your changes to the CLI. Don't worry if this is your first time with Typescript. The language reads like Javascript only that it is annotated with types and you're not forced to type every expression. For a quickstart tutorial you could look at [Typescript in 5 minutes](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html) from the creators of the language. ## Hints about integration tests In order to run integration tests, you need: - a source space the tests are run against (`CONTENTFUL_SPACE_ID`) and is called `contentful-migration` - a source organization the tests are run against (`CONTENTFUL_ORGANIZATION_ID`) - a management token for the defined space (`CONTENTFUL_INTEGRATION_TEST_CMA_TOKEN`) ```sh CONTENTFUL_SPACE_ID= \ CONTENTFUL_INTEGRATION_TEST_CMA_TOKEN=CFPAT-xxx \ CONTENTFUL_ORGANIZATION_ID= \ npm test ``` --- ## Prerequisites | Tool | Version | Notes | |---|---|---| | Node.js | >=22 (see `.nvmrc` for exact: v24) | Use `nvm use` to switch automatically | | npm | Bundled with Node | No `packageManager` field pinned | ## Getting Started ```bash # Clone and install git clone git@github.com:contentful/contentful-migration.git cd contentful-migration nvm use # source: .nvmrc → v24 npm ci # source: .npmrc has ignore-scripts=true npx allow-scripts # source: package.json → lavamoat.allowScripts # Build npm run build # source: package.json → scripts.build # Run unit tests npm run test:unit # source: package.json → scripts.test:unit ``` ## Development Workflow ```bash # Watch mode for unit tests npm run test:watch # source: package.json → scripts.test:watch # Build with source maps (dev config) npm run build-dev # source: package.json → scripts.build-dev # Run full test suite (requires env vars for integration/e2e) npm test # source: package.json → scripts.test ``` ## Commands Reference ### Build ```bash npm run build # source: package.json → scripts.build — clean + tsc npm run build-dev # source: package.json → scripts.build-dev — clean + tsc with dev tsconfig npm run clean # source: package.json → scripts.clean — rimraf built/ ``` ### Test ```bash npm run test:unit # source: package.json → scripts.test:unit — Vitest unit project npm run test:integration # source: package.json → scripts.test:integration — requires CMA credentials npm run test:e2e # source: package.json → scripts.test:e2e — end-to-end CLI tests npm run test:watch # source: package.json → scripts.test:watch — unit tests in watch mode npm run test:typescript-declaration # source: package.json → scripts.test:typescript-declaration — type-checks index.d.ts npm test # source: package.json → scripts.test — full suite (build + unit + integration + e2e + lint + type-check) ``` ### Lint & Format ```bash npm run lint # source: package.json → scripts.lint — ESLint npm run lint:fix # source: package.json → scripts.lint:fix — ESLint with --fix npm run format:check # source: package.json → scripts.format:check — Prettier check npm run format:fix # source: package.json → scripts.format:fix — Prettier write ``` ## Testing - **Framework:** Vitest (v4) - **Config:** `vitest.config.mts` — defines three test projects: `unit`, `integration`, `e2e` - **Location:** - Unit: `test/unit/**/*.spec.ts` - Integration: `test/integration/**/*.spec.ts` - End-to-end: `test/end-to-end/**/*.spec.ts` - **Fixtures:** `test/fixtures/` — Nock HTTP recordings for integration tests - **Re-recording fixtures:** Set `NOCK_RECORD=1` environment variable to update Nock fixtures ### Running integration/e2e tests locally Requires environment variables: - `CONTENTFUL_SPACE_ID` — test space ID - `CONTENTFUL_INTEGRATION_TEST_CMA_TOKEN` — CMA token with access to the test space - `CONTENTFUL_ORGANIZATION_ID` — organization ID for the test space ## Code Style & Conventions - **Prettier:** `.prettierrc.json` — single quotes, no semicolons, 100 char line width, no trailing commas - **ESLint:** `eslint.config.js` — uses `@contentful/eslint-config-backend` + prettier integration; max line length 120 (strings and template literals exempt) - **Pre-commit hook:** Husky + lint-staged runs Prettier and ESLint on staged files (`.husky/pre-commit`) - **TypeScript:** `tsconfig.json` — target ES2017, CommonJS modules, strict unused-variable checks, output to `built/` - **Source language:** TypeScript (some legacy JS in `src/lib/migration-steps/first-external-caller.js`) ## Commit Convention This repo uses [Conventional Commits](https://www.conventionalcommits.org/) enforced via Commitizen: ``` type(scope): description ``` Valid types: `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `perf`, `ci`, `build`, `revert` Use `npm run cm` (or `git cz`) for the interactive commit prompt. Examples: ``` feat(content-type): add taxonomy validation support fix(transform): handle undefined locale in transformEntries chore(deps): bump contentful-management to v12 ``` Breaking changes use `feat!:` or include `BREAKING CHANGE:` in the commit footer. These trigger a major version bump via semantic-release. ## Branch Strategy - `main` — production releases (npm `latest`) - `beta` — pre-release channel (npm `beta` dist-tag) - `dev` — development pre-release channel (npm `dev` dist-tag) - Feature branches — `feat/description`, `fix/description`, etc. ## Release Process Fully automated via **semantic-release** on merge to `main`, `beta`, or `dev`: 1. CI runs: build → lint → unit tests → integration tests → e2e tests 2. On success, semantic-release analyzes commits, determines version bump, generates changelog, publishes to npm, creates GitHub release No manual steps required. Version is `0.0.0-development` in source (semantic-release manages actual versions). ## Pull Requests - Ensure CI passes (build, lint, unit tests) - Integration/e2e tests run automatically in CI with secrets - Squash merge is the standard merge strategy - Dependabot PRs are auto-approved and auto-merged when CI passes ## CI/CD | Job | Trigger | What it does | |---|---|---| | Build (`build.yaml`) | Push to any branch, PRs | `npm ci`, `npx allow-scripts`, `npm run build`, caches `built/` | | Check (`check.yaml`) | After Build | Runs linter (`npm run lint`) and unit tests (`npm run test:unit`) | | Integration (`test-integration.yaml`) | After Build+Check | Runs integration tests with CMA credentials | | E2E (`test-e2e.yaml`) | After Build+Check+Integration | Runs end-to-end tests with CMA credentials | | Release (`release.yaml`) | Push to main/beta/dev (after all checks) | semantic-release publishes to npm | | CodeQL (`codeql.yaml`) | Push/PR | GitHub code scanning | | Dependabot auto-merge | Dependabot PRs | Auto-approves and requests merge for dependency updates | ## Adding New Migration Operations When adding support for a new DSL method (e.g., a new entity type or operation), follow this pattern: 1. **Intent** — Create a new intent class in `src/lib/intent/` (extend `BaseIntent`) 2. **Action** — Create a corresponding action in `src/lib/action/` that converts the intent to CMA requests 3. **Migration Steps** — Register the DSL method in `src/lib/migration-steps/` so user scripts can call it 4. **Offline API** — Add handling in `src/lib/offline-api/` so the operation can be simulated for validation 5. **Validator** — If the operation has constraints, add an intent validator in `src/lib/intent-validator/` 6. **Types** — Update `index.d.ts` with the new public TypeScript interface 7. **Tests** — Add unit tests in `test/unit/` and integration tests in `test/integration/` Existing examples to follow: look at any of the `tag-*` or `entry-transform*` files across these directories for a recent pattern. ## File-Level Guidance | Path | Notes | |---|---| | `index.d.ts` | **Public API types** — manually maintained; must stay in sync with DSL capabilities. Changes here affect all TypeScript consumers. | | `built/` | **Generated** — TypeScript compilation output; never edit directly. Gitignored. | | `test/fixtures/` | **Nock recordings** — auto-generated HTTP fixtures. Re-record with `NOCK_RECORD=1`, do not hand-edit. | | `examples/` | **Numbered migration examples** — referenced by README documentation; keep numbering sequential. | | `src/lib/migration-steps/` | **DSL proxy layer** — changes here affect the entire public migration API surface. High caution required. | | `src/lib/offline-api/` | **Offline validation engine** — simulates CMA locally; must stay in sync with real CMA behavior. | | `bin/contentful-migration` | **CLI entry point** — thin wrapper; requires `built/` to exist. | | `.npmrc` | Registry config — `ignore-scripts=true` for security; requires `npx allow-scripts` after install. |