From a69c38265c0e4d9d405a49fc940adc21017e88fb Mon Sep 17 00:00:00 2001 From: raj pandey Date: Mon, 25 May 2026 12:13:01 +0530 Subject: [PATCH 1/2] feat: migrate @contentstack/cli-cm-regex-validate to cli-plugins monorepo (v1) Migrates the cli-cm-regex-validate plugin from its standalone repo into the cli-plugins pnpm monorepo targeting v1 CLI dependencies. This is the first npm publication of this package under the @contentstack scope. - Add packages/contentstack-cli-cm-regex-validate with full source - Set version to 1.0.0 (first release), @contentstack/cli-command ^1.8.2 and @contentstack/cli-utilities ^1.18.3 - Upgrade oclif from ^3.17.2 to ^4.23.0 - Move jest from dependencies to devDependencies - Fix jest.config.ts testMatch to cover test/ directory (not just tests/) - Add composite: true and esModuleInterop: true to tsconfig.json - Wire into release-production-plugins.yml (tag: latest) - Wire into unit-test.yml - Update .github/config/release.json with regex-validate: false - Update README.md, AGENTS.md, and add REGEX-VALIDATE-MIGRATION.md - Update .talismanrc with checksums for new files Co-Authored-By: Claude Sonnet 4.6 --- .github/config/release.json | 1 + .../workflows/release-production-plugins.yml | 10 +- .github/workflows/unit-test.yml | 4 + .talismanrc | 16 ++- AGENTS.md | 53 +++++++++ README.md | 1 + REGEX-VALIDATE-MIGRATION.md | 56 +++++++++ .../.cursor/rules/README.md | 7 ++ .../.editorconfig | 11 ++ .../.eslintignore | 1 + .../.eslintrc | 23 ++++ .../.gitignore | 15 +++ .../.talismanrc | 4 + .../AGENTS.md | 46 ++++++++ .../CODEOWNERS | 11 ++ .../LICENSE | 21 ++++ .../README.md | 91 ++++++++++++++ .../SECURITY.md | 27 +++++ .../bin/dev | 6 + .../bin/dev.cmd | 3 + .../bin/dev.js | 7 ++ .../bin/run | 6 + .../bin/run.cmd | 3 + .../bin/run.js | 6 + .../jest.config.ts | 14 +++ .../messages/index.json | 43 +++++++ .../package.json | 87 ++++++++++++++ .../skills/README.md | 14 +++ .../skills/code-review/SKILL.md | 55 +++++++++ .../skills/contentstack-cli/SKILL.md | 57 +++++++++ .../skills/dev-workflow/SKILL.md | 57 +++++++++ .../skills/testing/SKILL.md | 66 +++++++++++ .../src/commands/cm/stacks/validate-regex.ts | 62 ++++++++++ .../src/index.ts | 1 + .../src/utils/connect-stack.ts | 37 ++++++ .../src/utils/generate-output.ts | 49 ++++++++ .../src/utils/interactive.ts | 52 ++++++++ .../src/utils/process-stack.ts | 37 ++++++ .../src/utils/safe-regex.ts | 57 +++++++++ .../test/data/invalidDocument.json | 97 +++++++++++++++ .../test/data/invalidRegex.json | 47 ++++++++ .../test/data/invalidRegexGf.json | 47 ++++++++ .../test/data/tableData.json | 1 + .../test/data/tableDataGf.json | 1 + .../test/data/validDocument.json | 25 ++++ .../test/mocha.opts | 5 + .../test/tsconfig.json | 9 ++ .../test/utils/connect-stack.test.ts | 71 +++++++++++ .../test/utils/generate-output.test.ts | 76 ++++++++++++ .../test/utils/interactive.test.ts | 79 +++++++++++++ .../test/utils/process-stack.test.ts | 111 ++++++++++++++++++ .../test/utils/safe-regex.test.ts | 53 +++++++++ .../tsconfig.json | 15 +++ 53 files changed, 1752 insertions(+), 2 deletions(-) create mode 100644 AGENTS.md create mode 100644 REGEX-VALIDATE-MIGRATION.md create mode 100644 packages/contentstack-cli-cm-regex-validate/.cursor/rules/README.md create mode 100644 packages/contentstack-cli-cm-regex-validate/.editorconfig create mode 100644 packages/contentstack-cli-cm-regex-validate/.eslintignore create mode 100644 packages/contentstack-cli-cm-regex-validate/.eslintrc create mode 100644 packages/contentstack-cli-cm-regex-validate/.gitignore create mode 100644 packages/contentstack-cli-cm-regex-validate/.talismanrc create mode 100644 packages/contentstack-cli-cm-regex-validate/AGENTS.md create mode 100644 packages/contentstack-cli-cm-regex-validate/CODEOWNERS create mode 100644 packages/contentstack-cli-cm-regex-validate/LICENSE create mode 100644 packages/contentstack-cli-cm-regex-validate/README.md create mode 100644 packages/contentstack-cli-cm-regex-validate/SECURITY.md create mode 100755 packages/contentstack-cli-cm-regex-validate/bin/dev create mode 100644 packages/contentstack-cli-cm-regex-validate/bin/dev.cmd create mode 100644 packages/contentstack-cli-cm-regex-validate/bin/dev.js create mode 100755 packages/contentstack-cli-cm-regex-validate/bin/run create mode 100644 packages/contentstack-cli-cm-regex-validate/bin/run.cmd create mode 100755 packages/contentstack-cli-cm-regex-validate/bin/run.js create mode 100644 packages/contentstack-cli-cm-regex-validate/jest.config.ts create mode 100644 packages/contentstack-cli-cm-regex-validate/messages/index.json create mode 100644 packages/contentstack-cli-cm-regex-validate/package.json create mode 100644 packages/contentstack-cli-cm-regex-validate/skills/README.md create mode 100644 packages/contentstack-cli-cm-regex-validate/skills/code-review/SKILL.md create mode 100644 packages/contentstack-cli-cm-regex-validate/skills/contentstack-cli/SKILL.md create mode 100644 packages/contentstack-cli-cm-regex-validate/skills/dev-workflow/SKILL.md create mode 100644 packages/contentstack-cli-cm-regex-validate/skills/testing/SKILL.md create mode 100644 packages/contentstack-cli-cm-regex-validate/src/commands/cm/stacks/validate-regex.ts create mode 100644 packages/contentstack-cli-cm-regex-validate/src/index.ts create mode 100644 packages/contentstack-cli-cm-regex-validate/src/utils/connect-stack.ts create mode 100644 packages/contentstack-cli-cm-regex-validate/src/utils/generate-output.ts create mode 100644 packages/contentstack-cli-cm-regex-validate/src/utils/interactive.ts create mode 100644 packages/contentstack-cli-cm-regex-validate/src/utils/process-stack.ts create mode 100644 packages/contentstack-cli-cm-regex-validate/src/utils/safe-regex.ts create mode 100644 packages/contentstack-cli-cm-regex-validate/test/data/invalidDocument.json create mode 100644 packages/contentstack-cli-cm-regex-validate/test/data/invalidRegex.json create mode 100644 packages/contentstack-cli-cm-regex-validate/test/data/invalidRegexGf.json create mode 100644 packages/contentstack-cli-cm-regex-validate/test/data/tableData.json create mode 100644 packages/contentstack-cli-cm-regex-validate/test/data/tableDataGf.json create mode 100644 packages/contentstack-cli-cm-regex-validate/test/data/validDocument.json create mode 100644 packages/contentstack-cli-cm-regex-validate/test/mocha.opts create mode 100644 packages/contentstack-cli-cm-regex-validate/test/tsconfig.json create mode 100644 packages/contentstack-cli-cm-regex-validate/test/utils/connect-stack.test.ts create mode 100644 packages/contentstack-cli-cm-regex-validate/test/utils/generate-output.test.ts create mode 100644 packages/contentstack-cli-cm-regex-validate/test/utils/interactive.test.ts create mode 100644 packages/contentstack-cli-cm-regex-validate/test/utils/process-stack.test.ts create mode 100644 packages/contentstack-cli-cm-regex-validate/test/utils/safe-regex.test.ts create mode 100644 packages/contentstack-cli-cm-regex-validate/tsconfig.json diff --git a/.github/config/release.json b/.github/config/release.json index 1ea83997c..4a21a112f 100755 --- a/.github/config/release.json +++ b/.github/config/release.json @@ -20,6 +20,7 @@ "launch": false, "branches": false, "apps-cli": false, + "regex-validate": false, "core": false } } diff --git a/.github/workflows/release-production-plugins.yml b/.github/workflows/release-production-plugins.yml index e17f9a817..972da31ed 100644 --- a/.github/workflows/release-production-plugins.yml +++ b/.github/workflows/release-production-plugins.yml @@ -143,7 +143,15 @@ jobs: package: ./packages/contentstack-query-export/package.json tag: latest - - name: Create Production Release + # Regex Validate + - name: Publishing regex-validate (Production) + uses: JS-DevTools/npm-publish@v3 + with: + token: ${{ secrets.NPM_TOKEN }} + package: ./packages/contentstack-cli-cm-regex-validate/package.json + tag: latest + + - name: Create Production Release id: create_release env: GITHUB_TOKEN: ${{ secrets.PKG_TOKEN }} diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 7c306cae7..bf19ad102 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -69,3 +69,7 @@ jobs: - name: Run tests for Contentstack Apps CLI working-directory: ./packages/contentstack-apps-cli run: npm run test:unit:report:json + + - name: Run tests for Contentstack Regex Validate plugin + working-directory: ./packages/contentstack-cli-cm-regex-validate + run: npm run test:unit diff --git a/.talismanrc b/.talismanrc index 3e8b96c8b..80b77dcda 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,4 +1,18 @@ fileignoreconfig: - filename: pnpm-lock.yaml - checksum: 89acb731dc98c886694fa4d267c11c188000b8ecbe5ee6e4809e5bc6ec33a2f3 + checksum: fa1dcf412ad0cdab8c7abf4b538cd300c1eb565d8f53721a6ac45a4e7610a9a5 + - filename: skills/framework/SKILL.md + checksum: c5746de64b1e7d1df051c4337de5eb32de6a4c85c7297aa408838d304bb2d771 + - filename: packages/contentstack-cli-cm-regex-validate/messages/index.json + checksum: 044b311bde624dcc3c12434174d6027dbb6b62eefdfae120570a1748f806c60c + - filename: packages/contentstack-cli-cm-regex-validate/skills/code-review/SKILL.md + checksum: b92ea1c8e2f901c9e1e60f6ef6986d348a40a7869c236e3c1f3ca53b553dbb8e + - filename: packages/contentstack-cli-cm-regex-validate/skills/contentstack-cli/SKILL.md + checksum: 9420a516ba6046b05748683c90e3817d091cef76c46e029cb3745d6c0c350838 + - filename: packages/contentstack-cli-cm-regex-validate/skills/dev-workflow/SKILL.md + checksum: b423dd35d0f7f0f25315e2a30198669b50db350f0ab2f917a1d3c4fbb0af0534 + - filename: packages/contentstack-cli-cm-regex-validate/src/utils/connect-stack.ts + checksum: c77c7c25efc6d043b26e3dd0a516e22ac50142fa9fa5ff3a53a7c9fb8f24ebd6 + - filename: packages/contentstack-cli-cm-regex-validate/test/utils/connect-stack.test.ts + checksum: 8fcd1dc2770a2a3f55ba462b7ffd3fc2e3cf45342c63e5b6dc5c1db4c2bd9738 version: '1.0' diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..daa849c63 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,53 @@ +# Contentstack CLI plugins – Agent guide + +**Universal entry point** for contributors and AI agents. Detailed conventions live in **`skills/*/SKILL.md`** (per-package). + +## What this repo is + +| Field | Detail | +| --- | --- | +| **Name:** | Contentstack CLI plugins (pnpm monorepo; root package name `csdx`) | +| **Purpose:** | OCLIF plugins that extend the Contentstack CLI (import/export, clone, migration, seed, audit, variants, Developer Hub apps, regex validation, etc.). | +| **Out of scope (if any):** | The **core** CLI aggregation lives in the separate `cli` monorepo; this repo ships plugin packages only. | + +## Tech stack (at a glance) + +| Area | Details | +| --- | --- | +| **Language** | TypeScript / JavaScript, Node **>= 18** (`engines` in root `package.json`) | +| **Build** | pnpm workspaces (`packages/*`); per package: `tsc`, OCLIF manifest/readme where applicable → `lib/` | +| **Tests** | Mocha + Chai (most packages); Jest + ts-jest (`contentstack-cli-cm-regex-validate`); layouts under `packages/*/test/` | +| **Lint / coverage** | ESLint in packages that define `lint` scripts; nyc where configured | +| **Other** | OCLIF v4, Husky | + +## Commands (quick reference) + +| Command type | Command | +| --- | --- | +| **Build** | `pnpm build` | +| **Test** | `pnpm test` | +| **Lint** | `pnpm run lint` in a package that defines `lint` (no root aggregate lint script) | + +CI: [.github/workflows/unit-test.yml](.github/workflows/unit-test.yml) and other workflows under [.github/workflows/](.github/workflows/). + +## Apps CLI plugin (`@contentstack/apps-cli`) + +- **Package path:** [packages/contentstack-apps-cli](packages/contentstack-apps-cli) +- **npm name:** `@contentstack/apps-cli` (unchanged for consumers) +- **Migrated from:** [contentstack/contentstack-apps-cli](https://github.com/contentstack/contentstack-apps-cli) — see [APPS-CLI-MIGRATION.md](APPS-CLI-MIGRATION.md) +- **v1 / v2:** This branch carries the **v1 line** (`@contentstack/cli-command ^1.8.2`, `@contentstack/cli-utilities ^1.18.3`). +- **Docs:** See [packages/contentstack-apps-cli/AGENTS.md](packages/contentstack-apps-cli/AGENTS.md) + +## Regex Validate plugin (`@contentstack/cli-cm-regex-validate`) + +- **Package path:** [packages/contentstack-cli-cm-regex-validate](packages/contentstack-cli-cm-regex-validate) +- **npm name:** `@contentstack/cli-cm-regex-validate` +- **Migrated from:** [contentstack/cli-cm-regex-validate](https://github.com/contentstack/cli-cm-regex-validate) — see [REGEX-VALIDATE-MIGRATION.md](REGEX-VALIDATE-MIGRATION.md) +- **v1 / v2:** This branch carries the **v1 line** (`@contentstack/cli-command ^1.8.2`, `@contentstack/cli-utilities ^1.18.3`, version `1.0.0`, npm tag `latest`). +- **Tests:** Jest + ts-jest (unlike most other packages which use Mocha + Chai) +- **Command:** Single command `cm:stacks:validate-regex` (short name `RGXVLD`) +- **Docs:** [packages/contentstack-cli-cm-regex-validate/AGENTS.md](packages/contentstack-cli-cm-regex-validate/AGENTS.md) + +## Using Cursor (optional) + +If you use **Cursor**, [.cursor/rules/README.md](.cursor/rules/README.md) only points to **`AGENTS.md`**—same docs as everyone else. diff --git a/README.md b/README.md index b3660d6ba..8c4291ab1 100644 --- a/README.md +++ b/README.md @@ -56,3 +56,4 @@ To get a more detailed documentation for every command, visit the [CLI section]( ## Useful Plugins - [Generate TypeScript typings from a Stack](https://github.com/Contentstack-Solutions/contentstack-cli-tsgen) +- [Validate regex fields in Content Types and Global Fields](https://github.com/contentstack/cli-plugins/tree/main/packages/contentstack-cli-cm-regex-validate) (`@contentstack/cli-cm-regex-validate`) diff --git a/REGEX-VALIDATE-MIGRATION.md b/REGEX-VALIDATE-MIGRATION.md new file mode 100644 index 000000000..ee712fdf7 --- /dev/null +++ b/REGEX-VALIDATE-MIGRATION.md @@ -0,0 +1,56 @@ +# Regex Validate plugin migration: standalone repo → cli-plugins monorepo + +## Summary + +The **@contentstack/cli-cm-regex-validate** plugin has moved from the standalone repository [contentstack/cli-cm-regex-validate](https://github.com/contentstack/cli-cm-regex-validate) into the [contentstack/cli-plugins](https://github.com/contentstack/cli-plugins) monorepo at **`packages/contentstack-cli-cm-regex-validate`**. + +The **npm package name is unchanged**: `@contentstack/cli-cm-regex-validate`. This is the **first npm release** — the package was previously not published. + +First release: **1.0.0** (v1 line) / **2.0.0-beta.0** (v2-beta line). + +## Repository and issue tracking + +| Before | After | +| --- | --- | +| Source: `github.com/contentstack/cli-cm-regex-validate` | Source: `github.com/contentstack/cli-plugins` → `packages/contentstack-cli-cm-regex-validate` | +| Issues: cli-cm-regex-validate repo | Issues: [cli-plugins issues](https://github.com/contentstack/cli-plugins/issues) (label or mention `regex-validate`) | + +The standalone **cli-cm-regex-validate** repository should be **archived** after the first release from cli-plugins. + +## Version lines + +| CLI line | cli-plugins branch | Plugin notes | +| --- | --- | --- | +| **1.x** | `v1-dev` / `main` | `@contentstack/cli-command ^1.8.2`, `@contentstack/cli-utilities ^1.18.3`; npm tag `latest`; version `1.0.0` | +| **2.x beta** | `v2-dev` / `v2-beta` | `@contentstack/cli-command ~2.0.0-beta.7`, `@contentstack/cli-utilities ~2.0.0-beta.8`; npm tag `beta`; version `2.0.0-beta.0` | + +## Install + +```bash +csdx plugins:install @contentstack/cli-cm-regex-validate +``` + +## Command (unchanged) + +| Command | Description | +| --- | --- | +| `csdx cm:stacks:validate-regex` | Validate fields with regex property in Content Types and Global Fields of a Stack | + +Flags: `-a` (token alias), `-c` (content types), `-g` (global fields), `-f` (CSV output path). + +## Local development + +```bash +cd cli-plugins +pnpm install +pnpm --filter @contentstack/cli-cm-regex-validate run build +pnpm --filter @contentstack/cli-cm-regex-validate test +``` + +## Test framework note + +This package uses **Jest + ts-jest** (unlike most other packages in this monorepo which use Mocha + Chai). Tests live under `packages/contentstack-cli-cm-regex-validate/test/` and run via `pnpm test` or `pnpm run test:unit`. + +## Related migrations + +- Apps CLI: [APPS-CLI-MIGRATION.md](./APPS-CLI-MIGRATION.md) diff --git a/packages/contentstack-cli-cm-regex-validate/.cursor/rules/README.md b/packages/contentstack-cli-cm-regex-validate/.cursor/rules/README.md new file mode 100644 index 000000000..445e605e7 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/.cursor/rules/README.md @@ -0,0 +1,7 @@ +# Cursor (optional) + +**Cursor** users: start at **[`AGENTS.md`](../../AGENTS.md)**. All conventions live in **`skills/*/SKILL.md`**. + +This folder only points contributors to **`AGENTS.md`** so editor-specific config does not duplicate the canonical docs. + +Path from this file to the repo root agent guide: **`../../AGENTS.md`** (two levels up from `.cursor/rules/`). diff --git a/packages/contentstack-cli-cm-regex-validate/.editorconfig b/packages/contentstack-cli-cm-regex-validate/.editorconfig new file mode 100644 index 000000000..beffa3084 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/packages/contentstack-cli-cm-regex-validate/.eslintignore b/packages/contentstack-cli-cm-regex-validate/.eslintignore new file mode 100644 index 000000000..502167fa0 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/.eslintignore @@ -0,0 +1 @@ +/lib diff --git a/packages/contentstack-cli-cm-regex-validate/.eslintrc b/packages/contentstack-cli-cm-regex-validate/.eslintrc new file mode 100644 index 000000000..31309fa40 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/.eslintrc @@ -0,0 +1,23 @@ +{ + "extends": ["eslint-config-oclif", "eslint-config-oclif-typescript"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "rules": { + "unicorn/prefer-module": "off", + "@typescript-eslint/no-require-imports": "off", + "unicorn/no-array-for-each": "off", + "camelcase": "off", + "@typescript-eslint/no-unused-vars": "error", + + "quotes": ["error", "single", { "avoidEscape": true }], + "semi": ["error", "never"], + "unicorn/import-style": "off", + "unicorn/prefer-node-protocol": "off", + "unicorn/consistent-function-scoping": "off", + "@typescript-eslint/ban-ts-comment": "off", + "object-curly-spacing": ["error", "never"] + } +} diff --git a/packages/contentstack-cli-cm-regex-validate/.gitignore b/packages/contentstack-cli-cm-regex-validate/.gitignore new file mode 100644 index 000000000..f9313a922 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/.gitignore @@ -0,0 +1,15 @@ +*-debug.log +*-error.log +*.log +.dccache +/.nyc_output +/dist +/lib +/tmp +/yarn.lock +node_modules +/coverage +/results +oclif.manifest.json +.DS_Store +tsconfig.tsbuildinfo \ No newline at end of file diff --git a/packages/contentstack-cli-cm-regex-validate/.talismanrc b/packages/contentstack-cli-cm-regex-validate/.talismanrc new file mode 100644 index 000000000..657de2be4 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/.talismanrc @@ -0,0 +1,4 @@ +fileignoreconfig: +- filename: package-lock.json + checksum: 88dcc5e2639a4fb4d04d1f1cc97581515b687290ac20abf1f18ce7476db36eef +version: "" \ No newline at end of file diff --git a/packages/contentstack-cli-cm-regex-validate/AGENTS.md b/packages/contentstack-cli-cm-regex-validate/AGENTS.md new file mode 100644 index 000000000..f9de80e2c --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/AGENTS.md @@ -0,0 +1,46 @@ +# cli-cm-regex-validate – Agent guide + +**Universal entry point** for contributors and AI agents. Detailed conventions live in **`skills/*/SKILL.md`**. + +## What this repo is + +| Field | Detail | +|-------|--------| +| **Name:** | [contentstack/cli-cm-regex-validate](https://github.com/contentstack/cli-cm-regex-validate) (`@contentstack/cli-cm-regex-validate` on npm) | +| **Purpose:** | Contentstack CLI oclif plugin with a single command, **`csdx cm:stacks:validate-regex`**, which scans content types and/or global fields in a stack for regex `format` values that fail the `safe-regex` check, then writes results to CSV and prints a summary table. User-facing copy lives in `messages/index.json`. | +| **Out of scope (if any):** | Not a general-purpose Contentstack SDK — only this plugin’s command, utils, and tests. | + +## Tech stack (at a glance) + +| Area | Details | +|------|---------| +| Language | TypeScript (`strict`), Node `>=14.0.0` per `package.json` engines | +| Build | npm; `prepack` runs `tsc -b`, oclif manifest, oclif readme — see `package.json` | +| Tests | Jest + ts-jest (`jest.config.ts`), `npm test`; suites under `test/utils/`, fixtures `test/data/*.json` | +| Lint / coverage | ESLint (`.eslintrc`), `npm run posttest` | +| Other | oclif v3, `@contentstack/cli-command`; CI: Node 22.x — [`.github/workflows/unit-tests.yml`](.github/workflows/unit-tests.yml). **CI runs Jest only** (`npm run test`); **ESLint is not run in CI** — run `npm run posttest` locally before merge. | + +## Commands (quick reference) + +| Command type | Command | +|--------------|---------| +| Build (release prep) | `npm run prepack` — cleans `lib`, compiles, generates oclif manifest and readme | +| Test | `npm test` | +| Lint | `npm run posttest` | + +CI runs `npm i` and `npm run test` on pull requests — see [`.github/workflows/unit-tests.yml`](.github/workflows/unit-tests.yml). It does **not** run `npm run posttest` (ESLint); run lint locally before merging. + +## Where the documentation lives: skills + +| Skill | Path | What it covers | +|-------|------|----------------| +| Development workflow | [`skills/dev-workflow/SKILL.md`](skills/dev-workflow/SKILL.md) | Commands, repo layout, naming, hooks, TDD, before merge | +| Testing | [`skills/testing/SKILL.md`](skills/testing/SKILL.md) | Jest, mocks, fixtures, no live API calls | +| Contentstack CLI | [`skills/contentstack-cli/SKILL.md`](skills/contentstack-cli/SKILL.md) | Command flow, SDK, schema walk, `safe-regex`, CSV/table output | +| Code review | [`skills/code-review/SKILL.md`](skills/code-review/SKILL.md) | PR and release checklist | + +An index with “when to use” hints is in [`skills/README.md`](skills/README.md). + +## Using Cursor (optional) + +If you use **Cursor**, [`.cursor/rules/README.md`](.cursor/rules/README.md) only points to **[`AGENTS.md`](AGENTS.md)** — same docs as everyone else. diff --git a/packages/contentstack-cli-cm-regex-validate/CODEOWNERS b/packages/contentstack-cli-cm-regex-validate/CODEOWNERS new file mode 100644 index 000000000..0496bc6ae --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/CODEOWNERS @@ -0,0 +1,11 @@ +* @contentstack/devex-pr-reviewers + +.github/workflows/sca-scan.yml @contentstack/security-admin + +.github/workflows/codeql-anaylsis.yml @contentstack/security-admin + +**/.snyk @contentstack/security-admin + +.github/workflows/policy-scan.yml @contentstack/security-admin + +.github/workflows/issues-jira.yml @contentstack/security-admin diff --git a/packages/contentstack-cli-cm-regex-validate/LICENSE b/packages/contentstack-cli-cm-regex-validate/LICENSE new file mode 100644 index 000000000..aff1142ee --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Contentstack + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/contentstack-cli-cm-regex-validate/README.md b/packages/contentstack-cli-cm-regex-validate/README.md new file mode 100644 index 000000000..7663fc999 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/README.md @@ -0,0 +1,91 @@ +# Regex Validation CLI Plugin + +The “Regex Validation” plugin in Contentstack CLI allows users to search for invalid regexes within the content types and global fields of their stack. + +Using the CLI “Regex Validation” plugin, you can find the invalid regexes within your stack +and rectify them. + +[![oclif](https://img.shields.io/badge/cli-oclif-brightgreen.svg)](https://oclif.io) +[![Version](https://img.shields.io/npm/v/cli-cm-regex-validate.svg)](https://npmjs.org/package/cli-cm-regex-validate) +[![Downloads/week](https://img.shields.io/npm/dw/cli-cm-regex-validate.svg)](https://npmjs.org/package/cli-cm-regex-validate) +[![License](https://img.shields.io/npm/l/cli-cm-regex-validate.svg)](https://github.com/contentstack/cli-cm-regex-validate/blob/master/package.json) + + +* [Regex Validation CLI Plugin](#regex-validation-cli-plugin) +* [Usage](#usage) +* [Commands](#commands) + + +# Usage + + + +#### Step 1: + +```sh-session +$ npm install -g @contentstack/cli + +$ csdx plugins:install https://github.com/contentstack/cli-cm-regex-validate/releases/download/v1.2.1/contentstack-cli-cm-regex-validate-1.2.1.tgz + +$ csdx plugins +running command... +@contentstack/cli-cm-regex-validate/1.2.1 darwin-arm64 node-v20.8.0 + +$ csdx --help [COMMAND] +USAGE + $ csdx COMMAND +... +``` + +#### Step 2: + +[Set the region](https://www.contentstack.com/docs/developers/cli/configure-regions-in-the-cli#set-region) + + + +#### Step 3: + +[Configured management token alias](https://www.contentstack.com/docs/developers/cli/cli-authentication#add-management-token) + +# Commands + + +* [`csdx cm:stacks:validate-regex`](#csdx-cmstacksvalidate-regex) + +## `csdx cm:stacks:validate-regex` + +This command is used to find all the invalid regexes present in the content types and global fields of your stack. + +``` +USAGE + $ csdx cm:stacks:validate-regex [-h] [-a ] [-c] [-g] [-f ] + +FLAGS + -a, --alias= Alias (name) assigned to the management token + -c, --contentType To find invalid regexes within the content types + -f, --filePath= [optional] The path or the location in your file system where the CSV output file should be + stored. + -g, --globalField To find invalid regexes within the global fields + -h, --help To show the flags that can be used with this CLI command + +DESCRIPTION + This command is used to find all the invalid regexes present in the content types and global fields of your stack. + +EXAMPLES + $ csdx cm:stacks:validate-regex + + $ csdx cm:stacks:validate-regex -a + + $ csdx cm:stacks:validate-regex -c + + $ csdx cm:stacks:validate-regex -g + + $ csdx cm:stacks:validate-regex -f + + $ csdx cm:stacks:validate-regex -a -c -g + + $ csdx cm:stacks:validate-regex -a -c -g -f +``` + +_See code: [src/commands/cm/stacks/validate-regex.ts](https://github.com/contentstack/cli-cm-regex-validate/blob/v1.2.6/src/commands/cm/stacks/validate-regex.ts)_ + diff --git a/packages/contentstack-cli-cm-regex-validate/SECURITY.md b/packages/contentstack-cli-cm-regex-validate/SECURITY.md new file mode 100644 index 000000000..1f44e3424 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/SECURITY.md @@ -0,0 +1,27 @@ +## Security + +Contentstack takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations. + +If you believe you have found a security vulnerability in any Contentstack-owned repository, please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Send email to [security@contentstack.com](mailto:security@contentstack.com). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + +- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) +- Full paths of source file(s) related to the manifestation of the issue +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- Step-by-step instructions to reproduce the issue +- Proof-of-concept or exploit code (if possible) +- Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +[https://www.contentstack.com/trust/](https://www.contentstack.com/trust/) diff --git a/packages/contentstack-cli-cm-regex-validate/bin/dev b/packages/contentstack-cli-cm-regex-validate/bin/dev new file mode 100755 index 000000000..2b3a0a4aa --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/bin/dev @@ -0,0 +1,6 @@ +#!/usr/bin/env ts-node + +(async () => { + const oclif = await import('@oclif/core') + await oclif.execute({development: true, dir: __dirname}) +})() diff --git a/packages/contentstack-cli-cm-regex-validate/bin/dev.cmd b/packages/contentstack-cli-cm-regex-validate/bin/dev.cmd new file mode 100644 index 000000000..32cf300c3 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/bin/dev.cmd @@ -0,0 +1,3 @@ +@echo off + +node -r ts-node/register "%~dp0\dev" %* diff --git a/packages/contentstack-cli-cm-regex-validate/bin/dev.js b/packages/contentstack-cli-cm-regex-validate/bin/dev.js new file mode 100644 index 000000000..548421afa --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/bin/dev.js @@ -0,0 +1,7 @@ +#!/usr/bin/env node + +// eslint-disable-next-line unicorn/prefer-top-level-await +(async () => { + const oclif = await import('@oclif/core') + await oclif.execute({development: true, dir: __dirname}) +})() diff --git a/packages/contentstack-cli-cm-regex-validate/bin/run b/packages/contentstack-cli-cm-regex-validate/bin/run new file mode 100755 index 000000000..475a5719b --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/bin/run @@ -0,0 +1,6 @@ +#!/usr/bin/env node +// eslint-disable-next-line unicorn/prefer-top-level-await +(async () => { + const oclif = await import('@oclif/core') + await oclif.execute({development: false, dir: __dirname}) + })() \ No newline at end of file diff --git a/packages/contentstack-cli-cm-regex-validate/bin/run.cmd b/packages/contentstack-cli-cm-regex-validate/bin/run.cmd new file mode 100644 index 000000000..968fc3075 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/bin/run.cmd @@ -0,0 +1,3 @@ +@echo off + +node "%~dp0\run" %* diff --git a/packages/contentstack-cli-cm-regex-validate/bin/run.js b/packages/contentstack-cli-cm-regex-validate/bin/run.js new file mode 100755 index 000000000..475a5719b --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/bin/run.js @@ -0,0 +1,6 @@ +#!/usr/bin/env node +// eslint-disable-next-line unicorn/prefer-top-level-await +(async () => { + const oclif = await import('@oclif/core') + await oclif.execute({development: false, dir: __dirname}) + })() \ No newline at end of file diff --git a/packages/contentstack-cli-cm-regex-validate/jest.config.ts b/packages/contentstack-cli-cm-regex-validate/jest.config.ts new file mode 100644 index 000000000..9290b176b --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/jest.config.ts @@ -0,0 +1,14 @@ +module.exports = { + roots: [''], + testMatch: ['**/tests/**/*.+(ts|tsx)', '**/?(*.)+(spec|test).+(ts|tsx)'], + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + 'node_modules/uuid/.+\\.js$': [ + 'babel-jest', + {presets: [['@babel/preset-env', {modules: 'commonjs'}]]}, + ], + }, + transformIgnorePatterns: ['/node_modules/(?!uuid/)'], + verbose: true, + collectCoverage: true, +} diff --git a/packages/contentstack-cli-cm-regex-validate/messages/index.json b/packages/contentstack-cli-cm-regex-validate/messages/index.json new file mode 100644 index 000000000..054203b43 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/messages/index.json @@ -0,0 +1,43 @@ +{ + "validateRegex": { + "command": { + "description": "This command is used to find all the invalid regexes present in the content types and global fields of your stack.", + "alias": "Alias (name) assigned to the management token", + "contentTypes": "To find invalid regexes within the content types", + "globalFields": "To find invalid regexes within the global fields", + "filePath": "[optional] The path or the location in your file system where the CSV output file should be stored.", + "help": "To show the flags that can be used with this CLI command", + "login": "https://www.contentstack.com/docs/developers/cli/cli-authentication/#login", + "addManagementToken": "https://www.contentstack.com/docs/developers/cli/cli-authentication/#add-management-token" + }, + "interactive": { + "requireToken": "Enter management token alias:", + "required": "Required.", + "selectSchema": "Select the module you need to check.", + "selectOne": "Select at least one option." + }, + "cliAction": { + "connectStackStart": "Connecting stack", + "connectStackStop": "Stack connection established. Time taken: ", + "processStackStart": "Processing stack", + "processStackStop": "Stack processing completed. Time taken: " + }, + "errors": { + "login": "Could not log in as authorization failed. Log in using csdx auth:login", + "tokenNotFound": "Token not found. Add a token using csdx auth:tokens:add", + "stack": { + "fetch": "Error in connecting to the stack. Please try again.", + "apiKey": "Invalid stack API Key provided.", + "contentTypes": "Error in querying content types.", + "globalFields": "Error in querying global fields." + }, + "csvOutput": "Failed to generate CSV output." + }, + "output": { + "tableOutput": "The following table shows the invalid regexes present in your stack.", + "csvOutput": "CSV output stored successfully at:", + "noInvalidRegex": "There are no invalid regexes in your stack.", + "docsLink": "To know more, visit our documentation site on catastrophic-backtracking: https://www.contentstack.com/docs/developers/create-content-types/validation-regex/#prevent-catastrophic-backtracking" + } + } +} diff --git a/packages/contentstack-cli-cm-regex-validate/package.json b/packages/contentstack-cli-cm-regex-validate/package.json new file mode 100644 index 000000000..4f324869b --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/package.json @@ -0,0 +1,87 @@ +{ + "name": "@contentstack/cli-cm-regex-validate", + "description": "Validate Fields with Regex Property of Content Type and Global Field in a Stack", + "version": "1.0.0", + "author": "Contentstack", + "bugs": "https://github.com/contentstack/cli-cm-regex-validate/issues", + "devDependencies": { + "@babel/preset-env": "^7.29.5", + "@oclif/plugin-help": "^6.2.49", + "@oclif/test": "^3.2.15", + "@types/chai": "^4.3.20", + "@types/jest": "^30.0.0", + "@types/jsonexport": "^3.0.5", + "@types/mocha": "^10.0.10", + "@types/node": "^18.19.130", + "@types/safe-regex": "^1.1.6", + "@typescript-eslint/eslint-plugin": "^8.59.2", + "chai": "^4.5.0", + "eslint": "^8.57.1", + "eslint-config-oclif": "^4.0.0", + "eslint-config-oclif-typescript": "^1.0.3", + "eslint-plugin-unicorn": "^48.0.1", + "globby": "^10.0.2", + "husky": "^9.1.7", + "mocha": "^10.8.2", + "nyc": "^15.1.0", + "oclif": "^3.17.2", + "ts-jest": "^29.4.9", + "ts-node": "^10.9.2", + "typescript": "^5.9.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "files": [ + "/lib", + "/bin", + "/npm-shrinkwrap.json", + "/oclif.manifest.json", + "/messages" + ], + "homepage": "https://github.com/contentstack/cli-cm-regex-validate", + "keywords": [ + "contentstack", + "cli", + "plugin", + "regex" + ], + "license": "MIT", + "oclif": { + "commands": "./lib/commands", + "bin": "csdx", + "devPlugins": [ + "@oclif/plugin-help" + ] + }, + "repository": "contentstack/cli-cm-regex-validate", + "scripts": { + "mocha": "nyc --extension .ts mocha --forbid-only \"test/**/*.test.ts\"", + "postpack": "rm -f oclif.manifest.json", + "posttest": "eslint . --ext .ts --config .eslintrc", + "prepack": "rm -rf lib && tsc -b && oclif manifest && oclif readme", + "test": "jest --detectOpenHandles --silent", + "version": "oclif-dev readme && git add README.md", + "prepare": "npx husky && chmod +x .husky/pre-commit" + }, + "dependencies": { + "@contentstack/cli-command": "^1.8.2", + "@contentstack/cli-utilities": "^1.18.3", + "@contentstack/management": "^1.30.2", + "cli-table3": "^0.6.5", + "cli-ux": "^6.0.9", + "inquirer": "12.11.1", + "jest": "^30.4.2", + "jsonexport": "^3.2.0", + "safe-regex": "^2.1.1", + "tslib": "^2.8.1" + }, + "csdxConfig": { + "shortCommandName": { + "cm:stacks:validate-regex": "RGXVLD" + } + }, + "overrides": { + "tmp": "^0.2.5" + } +} diff --git a/packages/contentstack-cli-cm-regex-validate/skills/README.md b/packages/contentstack-cli-cm-regex-validate/skills/README.md new file mode 100644 index 000000000..46e5e9574 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/skills/README.md @@ -0,0 +1,14 @@ +# Skills – cli-cm-regex-validate + +Source of truth for detailed guidance. Read [`AGENTS.md`](../AGENTS.md) first, then open the skill that matches your task. + +## When to use which skill + +| Skill folder | Use when | +|--------------|----------| +| `dev-workflow` | Build/test/lint commands, repo layout, naming, hooks, TDD, merge checklist | +| `testing` | Writing or refactoring Jest tests, mocks, fixtures under `test/data/` | +| `contentstack-cli` | Changing commands, utils, `messages/index.json`, SDK flow, `safe-regex`, or output | +| `code-review` | Reviewing PRs or preparing a release | + +Each folder contains `SKILL.md` with YAML frontmatter (`name`, `description`). diff --git a/packages/contentstack-cli-cm-regex-validate/skills/code-review/SKILL.md b/packages/contentstack-cli-cm-regex-validate/skills/code-review/SKILL.md new file mode 100644 index 000000000..1b2223556 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/skills/code-review/SKILL.md @@ -0,0 +1,55 @@ +--- +name: code-review +description: PR and release checklist for cli-cm-regex-validate (security, packaging, CI, messages) +--- + +# Code review – cli-cm-regex-validate + +## When to use + +- Reviewing a pull request for this repo +- Preparing a release or verifying packaging and CI alignment + +## Instructions + +- Confirm tests and lint pass locally (CI runs `npm test` only; see [`AGENTS.md`](../../AGENTS.md)); no secrets in commits; plugin `files` and `prepack` remain consistent; workflows aligned with Node version used in CI. + +Open [`skills/code-review/`](.) when reviewing PRs or before release (or point your agent at this folder if supported). + +## Checklist + +### Correctness and safety + +- [ ] Changes match the intended behavior for `cm:stacks:validate-regex` (flags, prompts, stack connection, CT/GF selection). +- [ ] No API keys, management tokens, or secrets in code or tests; Talisman/Snyk hooks still make sense for local workflow. +- [ ] New user-visible strings added to `messages/index.json` under `validateRegex` (avoid hard-coded copy in production paths). + +### Tests + +- [ ] `npm test` passes locally. +- [ ] New behavior covered by `test/utils/` tests and/or `test/data/` fixtures where appropriate. +- [ ] Mocks used for Management SDK and filesystem; no accidental live stack calls in unit tests. + +### Lint and types + +- [ ] ESLint passes (`posttest` or project eslint script). **Note:** CI does not run ESLint — only `npm test` in [`.github/workflows/unit-tests.yml`](../../.github/workflows/unit-tests.yml); lint must pass locally before merge. +- [ ] TypeScript changes respect `strict` and project conventions (see `tsconfig.json`, `.eslintrc`, and [`AGENTS.md`](../../AGENTS.md)). + +### Packaging and release + +- [ ] `package.json` `files` includes what ships (`lib`, `bin`, `oclif.manifest.json`, `messages`, etc.). +- [ ] `prepack` still runs `tsc`, `oclif manifest`, and `oclif readme` as needed for the plugin. +- [ ] Version bumps follow team process; release workflow (e.g. push to `main`) matches `.github/workflows/release.yml` expectations. + +### CI / security workflows + +- [ ] Unit test workflow exercises `npm test` on a supported Node version (see `.github/workflows/unit-tests.yml`). Expect Jest only there — not `posttest`/ESLint unless you add a separate workflow. +- [ ] SCA / policy workflows unchanged or intentionally updated; no silent downgrade of security checks. + +### Documentation + +- [ ] README or command examples updated if flags or behavior changed. + +## References + +- [Development workflow](../dev-workflow/SKILL.md) diff --git a/packages/contentstack-cli-cm-regex-validate/skills/contentstack-cli/SKILL.md b/packages/contentstack-cli-cm-regex-validate/skills/contentstack-cli/SKILL.md new file mode 100644 index 000000000..b5ef94e13 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/skills/contentstack-cli/SKILL.md @@ -0,0 +1,57 @@ +--- +name: contentstack-cli +description: Contentstack CLI plugin patterns for cm:stacks:validate-regex (SDK, schema, safe-regex, output) +--- + +# Contentstack CLI – cli-cm-regex-validate + +## When to use + +- Changing the `csdx cm:stacks:validate-regex` command or flags +- Editing utils under `src/utils/` or user-facing strings in `messages/index.json` +- Adjusting Management SDK usage, schema walking, `safe-regex`, or CSV/table output + +## Instructions + +- **Command:** `csdx cm:stacks:validate-regex` — validates regex `format` fields on content types and/or global fields using `safe-regex`. +- **Flow:** alias / flags → token → Management client → stack → fetch CT/GF → walk schema → CSV + table output. + +Open [`skills/contentstack-cli/`](.) when changing commands, utils, or `messages/index.json` (or point your agent at this folder if supported). + +## Patterns (regex validation plugin) + +### Plugin identity + +- Package: `@contentstack/cli-cm-regex-validate` +- Command id: `cm:stacks:validate-regex` (short name `RGXVLD` in `package.json` `csdxConfig` when present) +- Entry: `src/commands/cm/stacks/validate-regex.ts` + +### End-to-end flow + +1. **Parse** — `this.parse(ValidateRegex)`; flags: `alias`, `contentType`, `globalField`, `filePath`, `help`. +2. **Prompts** — If alias or module flags are missing, `inquireAlias` / `inquireModule` (`src/utils/interactive.ts`, Inquirer). +3. **Token** — `this.getToken(alias)` from Contentstack CLI; errors use `messages.validateRegex.errors.tokenNotFound` and `ref` to docs. +4. **Connect** — `connect-stack.ts`: `contentstackSdk.client({ host })`, optional `early_access` headers, `client.stack({ api_key, management_token })`. +5. **Process** — `process-stack.ts`: for each selected module, `stack.contentType()` / `stack.globalField()` → `.query({}).find()`, then `safe-regex.ts` on each item. +6. **Output** — `generate-output.ts`: `results.csv` via `jsonexport`, table via `cli-table3`, paths via `sanitizePath`; user copy from `messages/index.json`. + +### Schema traversal + +- Recurse into `schema` for `group` and `global_field`. +- For `blocks`, iterate `blocks` and each block’s `schema`. +- For each field with `format`, call `safe-regex`; collect module, title, UID, field metadata, and pattern for invalid rows. + +### User-facing strings + +- All strings live under `messages/index.json` → `validateRegex` (command, interactive, cliAction, errors, output). +- Docs link in output points to Contentstack guidance on catastrophic backtracking / validation regex. + +### Related packages + +- `@contentstack/cli-command`, `@contentstack/cli-utilities`, `@contentstack/management` +- `cli-ux` (spinner), `inquirer` (prompts), `safe-regex`, `jsonexport`, `cli-table3` + +## References + +- [Development workflow](../dev-workflow/SKILL.md) +- [Testing](../testing/SKILL.md) diff --git a/packages/contentstack-cli-cm-regex-validate/skills/dev-workflow/SKILL.md b/packages/contentstack-cli-cm-regex-validate/skills/dev-workflow/SKILL.md new file mode 100644 index 000000000..f486900fd --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/skills/dev-workflow/SKILL.md @@ -0,0 +1,57 @@ +--- +name: dev-workflow +description: Local and CI workflow for cli-cm-regex-validate — commands, layout, naming, hooks, and merge expectations +--- + +# Development workflow – cli-cm-regex-validate + +## When to use + +- Setting up or explaining how to build, test, and lint this repo +- Finding where commands, utils, tests, and messages live +- Before opening or merging a PR (validation checklist, TDD) + +## Instructions + +### Validation commands + +- `npm test` — Jest (`jest.config.ts`, ts-jest). Canonical test runner; CI runs only this in `.github/workflows/unit-tests.yml` (no ESLint in CI). +- `npm run posttest` — ESLint on `.ts` files. Run locally before merge; not executed by the unit-test workflow. + +### Local hooks + +If Husky is installed, pre-commit may run Talisman (secrets) and Snyk. Use `SKIP_HOOK=1` only when you understand the bypass. + +### TDD (recommended) + +1. **Red** — Add or change a failing test in `test/utils/` (or add a fixture in `test/data/`). +2. **Green** — Minimal change in `src/` to pass. +3. **Refactor** — Keep tests green; avoid drive-by refactors outside the task. + +### Repository layout + +| Area | Path | Role | +|------|------|------| +| Command | `src/commands/cm/stacks/validate-regex.ts` | oclif command `cm/stacks/validate-regex` | +| Utils | `src/utils/` | `connect-stack`, `process-stack`, `safe-regex`, `generate-output`, `interactive` | +| Messages | `messages/index.json` | User-facing strings for the command | +| Tests | `test/utils/*.test.ts` | Jest suites mirroring utils | +| Fixtures | `test/data/*.json` | JSON fixtures for schema and expected outputs | + +### Naming + +- Source files: kebab-case. +- Tests: describe behavior clearly (what should happen under which condition). + +### Before merging + +- Tests pass (`npm test`). +- Lint clean (`npm run posttest` or ESLint as configured in `package.json`). CI does not run ESLint; this is a local gate. + +## References + +- [Testing](../testing/SKILL.md) — Jest mocks, fixtures, no live API calls +- [Contentstack CLI](../contentstack-cli/SKILL.md) — Command flow, Management SDK, `safe-regex`, output +- [Code review](../code-review/SKILL.md) — PR and release checklist + +For workflow and layout questions, open the [`skills/dev-workflow/`](.) folder (or your agent tool’s equivalent to this path). diff --git a/packages/contentstack-cli-cm-regex-validate/skills/testing/SKILL.md b/packages/contentstack-cli-cm-regex-validate/skills/testing/SKILL.md new file mode 100644 index 000000000..aa31658f1 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/skills/testing/SKILL.md @@ -0,0 +1,66 @@ +--- +name: testing +description: Jest testing patterns for cli-cm-regex-validate (mocks, fixtures, no live API calls) +--- + +# Testing – cli-cm-regex-validate + +## When to use + +- Writing or refactoring unit tests for this plugin +- Adding or changing fixtures under `test/data/` +- Choosing mocks for Management SDK, `fs`, `cli-ux`, or `@contentstack/cli-utilities` + +## Instructions + +- **Runner:** Jest + ts-jest (`jest.config.ts`). Use `npm test` as the single source of truth. +- **Layout:** Tests in `test/utils/`; fixtures in `test/data/*.json`. +- **Mocks:** `@contentstack/management`, `fs`, `cli-ux`, `@contentstack/cli-utilities` as appropriate; never hit a real stack in unit tests. + +When using an agent or IDE that supports folder context, open [`skills/testing/`](.) for test-focused guidance. + +## Patterns (Jest) + +This project uses **Jest** and **ts-jest**. **`npm test`** (Jest) is the canonical test command; CI runs `npm test` from `.github/workflows/unit-tests.yml`. The `mocha` script in `package.json` is **not** what CI runs—do not use `npm run mocha` as the project test entry point. + +### File layout + +| Area | Path | +|------|------| +| Tests | `test/utils/*.test.ts` | +| Fixtures | `test/data/*.json` | + +Mirror utility names where it helps (`connect-stack.test.ts` vs `connect-stack.ts`). + +### Commands + +- `npm test` — runs Jest with `jest.config.ts` (roots, `testMatch`, ts-jest transform, coverage). + +### Mocking + +- **Management SDK:** `jest.mock('@contentstack/management')` and stub `client`, `stack`, and query chains as in `connect-stack.test.ts`. +- **Filesystem:** `jest.mock('fs')` when testing CSV path creation and `writeFileSync` / `mkdirSync`. +- **cli-ux:** mock `cli.action.start` / `stop` when asserting spinner behavior; avoid reassigning imported bindings with `@ts-ignore`—prefer `jest.mock('cli-ux', () => ({ ... }))` when needed. +- **cli-utilities:** mock `cliux.print` and `sanitizePath` in output tests when asserting printed messages and paths. + +### Fixtures + +- Load JSON with `require('../data/...')` for content type / global field documents and expected invalid-regex rows. +- Keeps tests readable and matches real API shape (schema, `data_type`, `format`, nested `group` / `blocks`). + +### Assertions + +- Use `toHaveBeenCalled`, `toHaveBeenCalledWith`, `toStrictEqual` for objects and arrays. +- Avoid deprecated matchers removed in newer Jest (e.g. prefer `toHaveBeenCalled` over legacy aliases). + +### Async tests + +- Use `async`/`await` for utilities that return promises; await `inquireAlias` / `inquireModule` when testing interactive flows with mocked `inquirer`. + +### Note on `package.json` + +- This repo lists **`jest`** under **`dependencies`** in `package.json`. Run tests via **`npm test`** and describe the framework as Jest in documentation and for agents. + +## References + +- [Development workflow](../dev-workflow/SKILL.md) diff --git a/packages/contentstack-cli-cm-regex-validate/src/commands/cm/stacks/validate-regex.ts b/packages/contentstack-cli-cm-regex-validate/src/commands/cm/stacks/validate-regex.ts new file mode 100644 index 000000000..ddb329433 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/src/commands/cm/stacks/validate-regex.ts @@ -0,0 +1,62 @@ +import {Command} from '@contentstack/cli-command' +import {flags} from '@contentstack/cli-utilities' + +import connectStack from '../../../utils/connect-stack' +import {inquireAlias, inquireModule} from '../../../utils/interactive' +const regexMessages = require('../../../../messages/index.json').validateRegex + +export default class ValidateRegex extends Command { + static description = regexMessages.command.description + + static flags: any = { + help: flags.help({char: 'h', description: regexMessages.command.help}), + alias: flags.string({ + char: 'a', + description: regexMessages.command.alias, + }), + contentType: flags.boolean({ + char: 'c', + description: regexMessages.command.contentTypes, + }), + globalField: flags.boolean({ + char: 'g', + description: regexMessages.command.globalFields, + }), + filePath: flags.string({ + char: 'f', + description: regexMessages.command.filePath, + }), + } + + static examples = [ + '$ csdx cm:stacks:validate-regex', + '$ csdx cm:stacks:validate-regex -a ', + '$ csdx cm:stacks:validate-regex -c', + '$ csdx cm:stacks:validate-regex -g', + '$ csdx cm:stacks:validate-regex -f ', + '$ csdx cm:stacks:validate-regex -a -c -g', + '$ csdx cm:stacks:validate-regex -a -c -g -f ', + ] + + async run() { + const commandObject = await this.parse(ValidateRegex) + await inquireAlias(commandObject.flags) + + let tokenDetails: any + try { + tokenDetails = await this.getToken(commandObject.flags.alias) + } catch { + this.error(regexMessages.errors.tokenNotFound, { + ref: regexMessages.command.addManagementToken, + }) + } + + await inquireModule(commandObject.flags) + + try { + await connectStack(commandObject.flags, this.cmaHost, tokenDetails) + } catch { + this.error(regexMessages.errors.stack.fetch) + } + } +} diff --git a/packages/contentstack-cli-cm-regex-validate/src/index.ts b/packages/contentstack-cli-cm-regex-validate/src/index.ts new file mode 100644 index 000000000..b1c6ea436 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/src/index.ts @@ -0,0 +1 @@ +export default {} diff --git a/packages/contentstack-cli-cm-regex-validate/src/utils/connect-stack.ts b/packages/contentstack-cli-cm-regex-validate/src/utils/connect-stack.ts new file mode 100644 index 000000000..ffa506257 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/src/utils/connect-stack.ts @@ -0,0 +1,37 @@ +import {cli} from 'cli-ux' +import * as contentstackSdk from '@contentstack/management' +import {configHandler} from '@contentstack/cli-utilities' +import processStack from './process-stack' +const regexMessages = require('../../messages/index.json').validateRegex + +export default async function connectStack( + flags: any, + host: string, + tokenDetails: any, +) { + try { + const startTime = Date.now() + cli.action.start(regexMessages.cliAction.connectStackStart, '', { + stdout: true, + }) + + const option: contentstackSdk.ContentstackConfig = { + host: host, + } + + // Adding early access headers + const earlyAccessHeaders = configHandler.get('earlyAccessHeaders') + if (earlyAccessHeaders && Object.keys(earlyAccessHeaders).length > 0) { + option.early_access = Object.values(earlyAccessHeaders) + } + + const client = contentstackSdk.client(option) + const stackInstance = client.stack({ + api_key: tokenDetails.apiKey, + management_token: tokenDetails.token, + }) + await processStack(flags, stackInstance, startTime) + } catch { + throw new Error(regexMessages.errors.stack.apiKey) + } +} diff --git a/packages/contentstack-cli-cm-regex-validate/src/utils/generate-output.ts b/packages/contentstack-cli-cm-regex-validate/src/utils/generate-output.ts new file mode 100644 index 000000000..1bce5ddf7 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/src/utils/generate-output.ts @@ -0,0 +1,49 @@ +import * as jsonexport from 'jsonexport' +import * as Table from 'cli-table3' +import * as path from 'path' +import * as fs from 'fs' +import {cliux, sanitizePath} from '@contentstack/cli-utilities' +const regexMessages = require('../../messages/index.json').validateRegex + +export default async function generateOutput( + flags: any, + invalidRegex: any, + tableData: any, +) { + if (invalidRegex.length > 0) { + const resultFile = 'results.csv' + let storagePath = path.resolve(__dirname, '../../results') + if (flags.filePath) { + storagePath = flags.filePath + } + + if (!fs.existsSync(storagePath)) { + fs.mkdirSync(storagePath, {recursive: true}) + } + + storagePath = path.resolve( + sanitizePath(storagePath), + sanitizePath(resultFile), + ) + jsonexport(invalidRegex, function (error: any, csv: any) { + if (error) { + throw new Error(regexMessages.errors.csvOutput) + } + + fs.writeFileSync(storagePath, csv) + }) + console.log(regexMessages.output.tableOutput) + const table = new Table({ + head: ['Module', 'Title', 'UID', 'Invalid Regex Count'], + }) + tableData.forEach((row: any) => { + table.push(row) + }) + const messageAndPath = `${regexMessages.output.csvOutput} ${storagePath}` + cliux.print(table.toString()) + cliux.print(messageAndPath) + cliux.print(regexMessages.output.docsLink) + } else { + cliux.print(regexMessages.output.noInvalidRegex) + } +} diff --git a/packages/contentstack-cli-cm-regex-validate/src/utils/interactive.ts b/packages/contentstack-cli-cm-regex-validate/src/utils/interactive.ts new file mode 100644 index 000000000..48f622b51 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/src/utils/interactive.ts @@ -0,0 +1,52 @@ +import inquirer from 'inquirer' +const regexMessages = require('../../messages/index.json').validateRegex + +export const validateAlias = async function (alias: any) { + if (!alias || alias.trim() === '') { + return regexMessages.interactive.required + } + + return true +} + +export async function inquireAlias(flags: any) { + if (!flags.alias || flags.alias.trim() === '') { + const input = [{ + type: 'input', + name: 'alias', + message: regexMessages.interactive.requireToken, + validate: validateAlias, + }] + const response = await inquirer.prompt(input) + flags.alias = response.alias + return flags + } +} + +export const validateModule = async function (choice: any) { + if (choice.length === 0) { + return regexMessages.interactive.selectOne + } + + return true +} + +export async function inquireModule(flags: any) { + if (!flags.contentType && !flags.globalField) { + const choices = [{ + type: 'checkbox', + name: 'choice', + message: regexMessages.interactive.selectSchema, + choices: [ + {name: 'Content Type', value: 'contentType', checked: true}, + {name: 'Global Field', value: 'globalField'}, + ], + validate: validateModule, + }] + const response = await inquirer.prompt(choices) + response.choice.forEach((ch: any) => { + flags[ch] = true + }) + return flags + } +} diff --git a/packages/contentstack-cli-cm-regex-validate/src/utils/process-stack.ts b/packages/contentstack-cli-cm-regex-validate/src/utils/process-stack.ts new file mode 100644 index 000000000..8d027151b --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/src/utils/process-stack.ts @@ -0,0 +1,37 @@ +import {cli} from 'cli-ux' +import safeRegex from './safe-regex' +import generateOutput from './generate-output' +const regexMessages = require('../../messages/index.json').validateRegex + +export default async function processStack(flags: any, stack: any, startTime: number) { + cli.action.stop(regexMessages.cliAction.connectStackStop + (Date.now() - startTime) + ' ms') + const processTime = Date.now() + cli.action.start(regexMessages.cliAction.processStackStart, '', {stdout: true}) + const query = {} + const invalidRegex: object[] = [] + const tableData: object[] = [] + if (flags.contentType) { + const contentTypes = stack.contentType().query(query).find() + await contentTypes.then((contentTypesObject: any) => { + contentTypesObject.items.forEach((contentType: any) => { + safeRegex(contentType, invalidRegex, tableData, 'Content Type') + }) + }).catch(() => { + throw new Error(regexMessages.errors.stack.contentTypes) + }) + } + + if (flags.globalField) { + const globalFields = stack.globalField().query(query).find() + await globalFields.then((globalFieldsObject: any) => { + globalFieldsObject.items.forEach((globalField: any) => { + safeRegex(globalField, invalidRegex, tableData, 'Global Field') + }) + }).catch(() => { + throw new Error(regexMessages.errors.stack.globalFields) + }) + } + + cli.action.stop(regexMessages.cliAction.processStackStop + (Date.now() - processTime) + ' ms') + await generateOutput(flags, invalidRegex, tableData) +} diff --git a/packages/contentstack-cli-cm-regex-validate/src/utils/safe-regex.ts b/packages/contentstack-cli-cm-regex-validate/src/utils/safe-regex.ts new file mode 100644 index 000000000..5a000d205 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/src/utils/safe-regex.ts @@ -0,0 +1,57 @@ +const safe = require('safe-regex') + +const safeRegex = (document: any, invalidRegex: any, tableData: any, type: string) => { + const beforeCount = invalidRegex.length + function checkSchemaFieldRegex(schema: any, currentPath: string, nested: boolean) { + let newPath = '' + function path(currentFieldPath: string, uid: string) { + return currentFieldPath.trim() === '' ? uid : [currentFieldPath, uid].join('.') + } + + schema.forEach((field: any) => { + newPath = path(currentPath, field.uid) + if ((field.data_type === 'group' || field.data_type === 'global_field') && field.schema) { + checkSchemaFieldRegex(field.schema, newPath, true) + } + + if (field.data_type === 'blocks') { + field.blocks.forEach((block: any) => { + if (block.schema) { + newPath = path(newPath, block.uid) + checkSchemaFieldRegex(block.schema, newPath, true) + newPath = path(currentPath, field.uid) + } + }) + } + + if (field.format) { + const result = safe(field.format) + if (result) { + newPath = '' + } else { + const regexObject = { + Module: type, + Title: document.title, + UID: document.uid, + 'Field Title': field.display_name, + 'Field UID': field.uid, + 'Field Path': newPath, + 'Invalid Regex': field.format, + } + invalidRegex.push(regexObject) + newPath = '' + } + } else { + newPath = '' + } + }) + const currentCount = invalidRegex.length - beforeCount + if (currentCount > 0 && !nested) { + tableData.push([type, document.title, document.uid, currentCount]) + } + } + + checkSchemaFieldRegex(document.schema, '', false) +} + +export default safeRegex diff --git a/packages/contentstack-cli-cm-regex-validate/test/data/invalidDocument.json b/packages/contentstack-cli-cm-regex-validate/test/data/invalidDocument.json new file mode 100644 index 000000000..5eb2585cf --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/test/data/invalidDocument.json @@ -0,0 +1,97 @@ +{ + "title": "Regex Fields", + "uid": "regex_fields", + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Regex 1", + "uid": "regex_1", + "format": "\\/(((([^\\/\\s\\*]+\\*?)+\\/)|(\\*{1,2})\\/)+)?(((([^\\/\\s\\*]+\\*?)+)|(\\*{1,2}))+)?", + "error_messages": { + "format": "\\/(((([^\\/\\s\\*]+\\*?)+\\/)|(\\*{1,2})\\/)+)?(((([^\\/\\s\\*]+\\*?)+)|(\\*{1,2}))+)?" + } + }, + { + "data_type": "text", + "display_name": "Regex 2", + "uid": "regex_2", + "format": "(beep|boop)*", + "error_messages": { + "format": "(beep|boop)*" + } + }, + { + "data_type": "group", + "display_name": "Regex Group", + "schema": [ + { + "data_type": "text", + "display_name": "Group Regex 1", + "uid": "group_regex_1", + "format": "\\/(((([^\\/\\s\\*]+\\*?)+\\/)|(\\*{1,2})\\/)+)?(((([^\\/\\s\\*]+\\*?)+)|(\\*{1,2}))+)?", + "error_messages": { + "format": "" + } + } + ], + "uid": "regex_group" + }, + { + "data_type": "group", + "display_name": "Regex Group", + "uid": "regex_group" + }, + { + "data_type": "blocks", + "display_name": "Regex Modular Blocks", + "blocks": [ + { + "title": "MB 1", + "uid": "mb_1", + "schema": [ + { + "data_type": "group", + "display_name": "Group", + "schema": [ + { + "data_type": "text", + "display_name": "MB Group Regex", + "uid": "mb_group_regex", + "format": "\\/(((([^\\/\\s\\*]+\\*?)+\\/)|(\\*{1,2})\\/)+)?(((([^\\/\\s\\*]+\\*?)+)|(\\*{1,2}))+)?", + "error_messages": { + "format": "" + } + } + ], + "uid": "group" + }, + { + "data_type": "text", + "display_name": "MB Regex", + "uid": "mb_regex", + "format": "\\/(((([^\\/\\s\\*]+\\*?)+\\/)|(\\*{1,2})\\/)+)?(((([^\\/\\s\\*]+\\*?)+)|(\\*{1,2}))+)?", + "error_messages": { + "format": "" + } + } + ] + }, + { + "title": "MB 2", + "uid": "mb_2" + } + ], + "uid": "regex_modular_blocks" + }, + { + "data_type": "text", + "display_name": "Regex 6", + "uid": "regex_6", + "format": "\\/(((([^\\/\\s\\*]+\\*?)+\\/)|(\\*{1,2})\\/)+)?(((([^\\/\\s\\*]+\\*?)+)|(\\*{1,2}))+)?", + "error_messages": { + "format": "" + } + } + ] +} diff --git a/packages/contentstack-cli-cm-regex-validate/test/data/invalidRegex.json b/packages/contentstack-cli-cm-regex-validate/test/data/invalidRegex.json new file mode 100644 index 000000000..f17029f1f --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/test/data/invalidRegex.json @@ -0,0 +1,47 @@ +[ + { + "Module": "Content Type", + "Title": "Regex Fields", + "UID": "regex_fields", + "Field Title": "Regex 1", + "Field UID": "regex_1", + "Field Path": "regex_1", + "Invalid Regex": "\\/(((([^\\/\\s\\*]+\\*?)+\\/)|(\\*{1,2})\\/)+)?(((([^\\/\\s\\*]+\\*?)+)|(\\*{1,2}))+)?" + }, + { + "Module": "Content Type", + "Title": "Regex Fields", + "UID": "regex_fields", + "Field Title": "Group Regex 1", + "Field UID": "group_regex_1", + "Field Path": "regex_group.group_regex_1", + "Invalid Regex": "\\/(((([^\\/\\s\\*]+\\*?)+\\/)|(\\*{1,2})\\/)+)?(((([^\\/\\s\\*]+\\*?)+)|(\\*{1,2}))+)?" + }, + { + "Module": "Content Type", + "Title": "Regex Fields", + "UID": "regex_fields", + "Field Title": "MB Group Regex", + "Field UID": "mb_group_regex", + "Field Path": "regex_modular_blocks.mb_1.group.mb_group_regex", + "Invalid Regex": "\\/(((([^\\/\\s\\*]+\\*?)+\\/)|(\\*{1,2})\\/)+)?(((([^\\/\\s\\*]+\\*?)+)|(\\*{1,2}))+)?" + }, + { + "Module": "Content Type", + "Title": "Regex Fields", + "UID": "regex_fields", + "Field Title": "MB Regex", + "Field UID": "mb_regex", + "Field Path": "regex_modular_blocks.mb_1.mb_regex", + "Invalid Regex": "\\/(((([^\\/\\s\\*]+\\*?)+\\/)|(\\*{1,2})\\/)+)?(((([^\\/\\s\\*]+\\*?)+)|(\\*{1,2}))+)?" + }, + { + "Module": "Content Type", + "Title": "Regex Fields", + "UID": "regex_fields", + "Field Title": "Regex 6", + "Field UID": "regex_6", + "Field Path": "regex_6", + "Invalid Regex": "\\/(((([^\\/\\s\\*]+\\*?)+\\/)|(\\*{1,2})\\/)+)?(((([^\\/\\s\\*]+\\*?)+)|(\\*{1,2}))+)?" + } +] diff --git a/packages/contentstack-cli-cm-regex-validate/test/data/invalidRegexGf.json b/packages/contentstack-cli-cm-regex-validate/test/data/invalidRegexGf.json new file mode 100644 index 000000000..584ef7621 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/test/data/invalidRegexGf.json @@ -0,0 +1,47 @@ +[ + { + "Module": "Global Field", + "Title": "Regex Fields", + "UID": "regex_fields", + "Field Title": "Regex 1", + "Field UID": "regex_1", + "Field Path": "regex_1", + "Invalid Regex": "\\/(((([^\\/\\s\\*]+\\*?)+\\/)|(\\*{1,2})\\/)+)?(((([^\\/\\s\\*]+\\*?)+)|(\\*{1,2}))+)?" + }, + { + "Module": "Global Field", + "Title": "Regex Fields", + "UID": "regex_fields", + "Field Title": "Group Regex 1", + "Field UID": "group_regex_1", + "Field Path": "regex_group.group_regex_1", + "Invalid Regex": "\\/(((([^\\/\\s\\*]+\\*?)+\\/)|(\\*{1,2})\\/)+)?(((([^\\/\\s\\*]+\\*?)+)|(\\*{1,2}))+)?" + }, + { + "Module": "Global Field", + "Title": "Regex Fields", + "UID": "regex_fields", + "Field Title": "MB Group Regex", + "Field UID": "mb_group_regex", + "Field Path": "regex_modular_blocks.mb_1.group.mb_group_regex", + "Invalid Regex": "\\/(((([^\\/\\s\\*]+\\*?)+\\/)|(\\*{1,2})\\/)+)?(((([^\\/\\s\\*]+\\*?)+)|(\\*{1,2}))+)?" + }, + { + "Module": "Global Field", + "Title": "Regex Fields", + "UID": "regex_fields", + "Field Title": "MB Regex", + "Field UID": "mb_regex", + "Field Path": "regex_modular_blocks.mb_1.mb_regex", + "Invalid Regex": "\\/(((([^\\/\\s\\*]+\\*?)+\\/)|(\\*{1,2})\\/)+)?(((([^\\/\\s\\*]+\\*?)+)|(\\*{1,2}))+)?" + }, + { + "Module": "Global Field", + "Title": "Regex Fields", + "UID": "regex_fields", + "Field Title": "Regex 6", + "Field UID": "regex_6", + "Field Path": "regex_6", + "Invalid Regex": "\\/(((([^\\/\\s\\*]+\\*?)+\\/)|(\\*{1,2})\\/)+)?(((([^\\/\\s\\*]+\\*?)+)|(\\*{1,2}))+)?" + } +] diff --git a/packages/contentstack-cli-cm-regex-validate/test/data/tableData.json b/packages/contentstack-cli-cm-regex-validate/test/data/tableData.json new file mode 100644 index 000000000..138e6bf6f --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/test/data/tableData.json @@ -0,0 +1 @@ +[["Content Type", "Regex Fields", "regex_fields", 5]] diff --git a/packages/contentstack-cli-cm-regex-validate/test/data/tableDataGf.json b/packages/contentstack-cli-cm-regex-validate/test/data/tableDataGf.json new file mode 100644 index 000000000..9a88c4003 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/test/data/tableDataGf.json @@ -0,0 +1 @@ +[["Global Field", "Regex Fields", "regex_fields", 5]] diff --git a/packages/contentstack-cli-cm-regex-validate/test/data/validDocument.json b/packages/contentstack-cli-cm-regex-validate/test/data/validDocument.json new file mode 100644 index 000000000..68ddff07f --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/test/data/validDocument.json @@ -0,0 +1,25 @@ +{ + "title": "Regex Fields", + "uid": "regex_fields", + "inbuilt_class": false, + "schema": [ + { + "data_type": "text", + "display_name": "Regex 1", + "uid": "regex_1", + "format": "(beep|boop)*", + "error_messages": { + "format": "" + } + }, + { + "data_type": "text", + "display_name": "Regex 2", + "uid": "regex_2", + "format": "", + "error_messages": { + "format": "" + } + } + ] +} diff --git a/packages/contentstack-cli-cm-regex-validate/test/mocha.opts b/packages/contentstack-cli-cm-regex-validate/test/mocha.opts new file mode 100644 index 000000000..73fb8366a --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/test/mocha.opts @@ -0,0 +1,5 @@ +--require ts-node/register +--watch-extensions ts +--recursive +--reporter spec +--timeout 5000 diff --git a/packages/contentstack-cli-cm-regex-validate/test/tsconfig.json b/packages/contentstack-cli-cm-regex-validate/test/tsconfig.json new file mode 100644 index 000000000..95898fced --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/test/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig", + "compilerOptions": { + "noEmit": true + }, + "references": [ + {"path": ".."} + ] +} diff --git a/packages/contentstack-cli-cm-regex-validate/test/utils/connect-stack.test.ts b/packages/contentstack-cli-cm-regex-validate/test/utils/connect-stack.test.ts new file mode 100644 index 000000000..00546edb8 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/test/utils/connect-stack.test.ts @@ -0,0 +1,71 @@ +import * as contentstackSdk from '@contentstack/management' +import connectStack from '../../src/utils/connect-stack' +import {cli} from 'cli-ux' +import processStack from '../../src/utils/process-stack' + +// Mock the entire module +jest.mock('@contentstack/management') +jest.mock('../../src/utils/generate-output.ts') +jest.mock('../../src/utils/process-stack.ts') + +/* @ts-ignore */ +cli = { + debug: jest.fn(), + error: jest.fn(), + action: { + start: jest.fn(), + stop: jest.fn(), + }, +} + +describe('Get Client from Management SDK, connect with Stack & process Stack', () => { + beforeEach(() => { + jest.restoreAllMocks() + }) + + test('Token details are Valid', async () => { + const host = 'api-contentstack.io' + const tokenDetails = { + apiKey: 'blt1234', + token: 'blt1234', + } + const flags = { + contentType: true, + globalField: true, + } + + // Mock the client function + const mockStack = jest.fn().mockResolvedValue({stack: {}}) + const mockClient = {stack: mockStack}; + (contentstackSdk.client as jest.Mock).mockReturnValue(mockClient) + + await connectStack(flags, host, tokenDetails) + expect(cli.action.start).toHaveBeenCalled() + expect(processStack).toHaveBeenCalled() + }) + + test('Token details is Invalid', async () => { + const host = 'api-contentstack.io' + const tokenDetails = { + apiKey: 'blt1234', + token: 'blt1234', + } + const flags = { + contentType: true, + globalField: true, + } + + // Mock the client function to reject + const mockStack = jest.fn().mockImplementation(() => { + throw new Error('Invalid stack API Key provided.') + }) + const mockClient = {stack: mockStack}; + (contentstackSdk.client as jest.Mock).mockReturnValue(mockClient) + + await expect(connectStack(flags, host, tokenDetails)).rejects.toEqual( + expect.any(Error), + ) + + expect(cli.action.start).toHaveBeenCalled() + }) +}) diff --git a/packages/contentstack-cli-cm-regex-validate/test/utils/generate-output.test.ts b/packages/contentstack-cli-cm-regex-validate/test/utils/generate-output.test.ts new file mode 100644 index 000000000..443e4e64b --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/test/utils/generate-output.test.ts @@ -0,0 +1,76 @@ +import * as path from 'path' +import * as fs from 'fs' +import generateOutput from '../../src/utils/generate-output' +const invalidJsonOutput = require('../data/invalidRegex.json') +const invalidTableOutput = require('../data/tableData.json') +const regexMessages = require('../../messages/index.json').validateRegex + +jest.mock('fs') +jest.mock('@contentstack/cli-utilities', () => ({ + cliux: { + print: jest.fn(), + }, + sanitizePath: (path: string) => path, +})) + +describe('Generate Output after Stack is Processed', () => { + beforeEach(() => { + jest.restoreAllMocks() + }) + + test('Filepath Flag is not set & Invalid Regex is found', async () => { + const consoleSpy = jest.spyOn(console, 'log') + await generateOutput({}, invalidJsonOutput, invalidTableOutput) + expect(consoleSpy).toHaveBeenCalledTimes(1) + expect(consoleSpy).toHaveBeenCalledWith(regexMessages.output.tableOutput) + }) + + test('Filepath Flag is set, Path already exists & Invalid Regex is found', async () => { + const flags = { + filePath: '/path/to/output/directory/', + } + const consoleSpy = jest.spyOn(console, 'log') + jest.spyOn(path, 'resolve') + jest.spyOn(fs, 'existsSync').mockImplementationOnce(() => { + return true + }) + await generateOutput(flags, invalidJsonOutput, invalidTableOutput) + expect(path.resolve).toHaveBeenCalled() + expect(fs.existsSync).toHaveBeenCalled() + expect(fs.writeFileSync).toHaveBeenCalled() + expect(consoleSpy).toHaveBeenCalledTimes(1) + expect(consoleSpy).toHaveBeenCalledWith(regexMessages.output.tableOutput) + }) + + test('Filepath Flag is set, Path does not exists & Invalid Regex is found', async () => { + const flags = { + filePath: '/path/to/output/directory/', + } + const consoleSpy = jest.spyOn(console, 'log') + jest.spyOn(path, 'resolve') + jest.spyOn(fs, 'existsSync').mockImplementationOnce(() => { + return false + }) + await generateOutput(flags, invalidJsonOutput, invalidTableOutput) + expect(path.resolve).toHaveBeenCalled() + expect(fs.existsSync).toHaveBeenCalled() + expect(fs.mkdirSync).toHaveBeenCalled() + expect(fs.writeFileSync).toHaveBeenCalled() + expect(consoleSpy).toHaveBeenCalledTimes(1) + expect(consoleSpy).toHaveBeenCalledWith(regexMessages.output.tableOutput) + }) + + test('File is getting saved', async () => { + const consoleSpy = jest.spyOn(console, 'log') + await generateOutput({}, invalidJsonOutput, invalidTableOutput) + expect(consoleSpy).toHaveBeenCalledTimes(1) + expect(consoleSpy).toHaveBeenCalledWith(regexMessages.output.tableOutput) + expect(fs.writeFileSync).toHaveBeenCalled() + }) + + test('Invalid Regex is not found', async () => { + const consoleSpy = jest.spyOn(console, 'log') + await generateOutput({}, [], []) + expect(consoleSpy).toHaveBeenCalledTimes(0) + }) +}) diff --git a/packages/contentstack-cli-cm-regex-validate/test/utils/interactive.test.ts b/packages/contentstack-cli-cm-regex-validate/test/utils/interactive.test.ts new file mode 100644 index 000000000..46e8d157c --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/test/utils/interactive.test.ts @@ -0,0 +1,79 @@ +import inquirer from 'inquirer' +import {inquireAlias, inquireModule, validateAlias, validateModule} from '../../src/utils/interactive' +const regexMessages = require('../../messages/index.json').validateRegex + +describe('Interactive', () => { + beforeEach(() => { + jest.restoreAllMocks() + }) + + test('Alias Token Flag is Set', async () => { + const flags = {alias: 'Test Token'} + const response = await inquireAlias(flags) + expect(response).toBeUndefined() + }) + + test('Alias Token is not Entered', async () => { + const alias = '' + const response = await validateAlias(alias) + expect(response).toBe(regexMessages.interactive.required) + }) + + test('Alias Token is Entered', async () => { + const alias = 'Test Token' + const flags = {} + const response = await validateAlias(alias) + expect(response).toBe(true); + (jest.spyOn(inquirer, 'prompt') as jest.Mock).mockImplementation(() => + Promise.resolve({alias: alias}), + ) + await inquireAlias(flags) + }) + + test('Module Flags are Set', async () => { + async function testModuleFlags(flags: object) { + const response = await inquireModule(flags) + expect(response).toBeUndefined() + } + + testModuleFlags({contentType: true}) + testModuleFlags({globalField: true}) + testModuleFlags({contentType: true, globalField: true}) + }) + + test('Module is not Selected', async () => { + const choice: string[] = [] + const response = await validateModule(choice) + expect(response).toBe(regexMessages.interactive.selectOne) + }) + + test('Content Type Module is Selected', async () => { + const choice: string[] = ['contentType'] + const response = await validateModule(choice) + expect(response).toBe(true); + (jest.spyOn(inquirer, 'prompt') as jest.Mock).mockImplementation(() => + Promise.resolve({choice: choice}), + ) + await inquireModule(choice) + }) + + test('Global Field Module is Selected', async () => { + const choice: string[] = ['globalField'] + const response = await validateModule(choice) + expect(response).toBe(true); + (jest.spyOn(inquirer, 'prompt') as jest.Mock).mockImplementation(() => + Promise.resolve({choice: choice}), + ) + await inquireModule(choice) + }) + + test('Both Modules are Selected', async () => { + const choice: string[] = ['contentType', 'globalField'] + const response = await validateModule(choice) + expect(response).toBe(true); + (jest.spyOn(inquirer, 'prompt') as jest.Mock).mockImplementation(() => + Promise.resolve({choice: choice}), + ) + await inquireModule(choice) + }) +}) diff --git a/packages/contentstack-cli-cm-regex-validate/test/utils/process-stack.test.ts b/packages/contentstack-cli-cm-regex-validate/test/utils/process-stack.test.ts new file mode 100644 index 000000000..a244d51f2 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/test/utils/process-stack.test.ts @@ -0,0 +1,111 @@ +import {cli} from 'cli-ux' +import processStack from '../../src/utils/process-stack' +import generateOutput from '../../src/utils/generate-output' +const validDocument = require('../data/validDocument.json') +const regexMessages = require('../../messages/index.json').validateRegex + +jest.mock('../../src/utils/generate-output.ts') + +/* @ts-ignore */ +cli = ({ + debug: jest.fn(), + error: jest.fn(), + action: { + start: jest.fn(), + stop: jest.fn(), + }}) + +describe('Process Stack', () => { + beforeEach(() => { + jest.restoreAllMocks() + }) + + test('Process Stack with Content Type & Global Field selected & valid Data', async () => { + const stack = { + name: 'stack', + contentType: jest.fn().mockImplementation(() => { + return { + query: jest.fn().mockImplementation(() => { + return { + find: jest.fn().mockResolvedValue(Promise.resolve({items: [validDocument]})), + } + }), + } + }), + globalField: jest.fn().mockImplementation(() => { + return { + query: jest.fn().mockImplementation(() => { + return { + find: jest.fn().mockResolvedValue(Promise.resolve({items: [validDocument]})), + } + }), + } + }), + } + const startTime = Date.now() + await processStack({contentType: true}, stack, startTime) + await processStack({globalField: true}, stack, startTime) + expect(cli.action.stop).toHaveBeenCalled() + expect(cli.action.start).toHaveBeenCalled() + expect(cli.action.stop).toHaveBeenCalled() + expect(generateOutput).toHaveBeenCalled() + }) + + test('Process Stack with Content Type selected & invalid Content Type Data', async () => { + const contentTypeData = { + title: 'Regex Fields', + uid: 'regex_fields', + } + const stack = { + name: 'stack', + contentType: jest.fn().mockImplementation(() => { + return { + query: jest.fn().mockImplementation(() => { + return { + find: jest.fn().mockResolvedValue(Promise.resolve({items: [contentTypeData]})), + } + }), + } + }), + } + try { + const startTime = Date.now() + await processStack({contentType: true}, stack, startTime) + expect(cli.action.stop).toHaveBeenCalled() + expect(cli.action.start).toHaveBeenCalled() + expect(cli.action.stop).toHaveBeenCalled() + expect(generateOutput).not.toHaveBeenCalled() + } catch (error:any) { + expect(error.message).toBe(regexMessages.errors.stack.contentTypes) + } + }) + + test('Process Stack with Global Field selected & Invalid Global Field Data', async () => { + const globalFieldData = { + title: 'Regex Fields', + uid: 'regex_fields', + } + const stack = { + name: 'stack', + globalField: jest.fn().mockImplementation(() => { + return { + query: jest.fn().mockImplementation(() => { + return { + find: jest.fn().mockResolvedValue(Promise.resolve({items: [globalFieldData]})), + } + }), + } + }), + } + try { + const startTime = Date.now() + await processStack({globalField: true}, stack, startTime) + expect(cli.action.stop).toHaveBeenCalled() + expect(cli.action.start).toHaveBeenCalled() + expect(cli.action.stop).toHaveBeenCalled() + expect(generateOutput).not.toHaveBeenCalled() + } catch (error:any) { + expect(error.message).toBe(regexMessages.errors.stack.globalFields) + } + }) +}) diff --git a/packages/contentstack-cli-cm-regex-validate/test/utils/safe-regex.test.ts b/packages/contentstack-cli-cm-regex-validate/test/utils/safe-regex.test.ts new file mode 100644 index 000000000..3c9c0fc11 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/test/utils/safe-regex.test.ts @@ -0,0 +1,53 @@ +import safeRegex from '../../src/utils/safe-regex' +const validDocument = require('../data/validDocument.json') +const invalidDocument = require('../data/invalidDocument.json') +const invalidJsonOutput = require('../data/invalidRegex.json') +const invalidTableOutput = require('../data/tableData.json') +const invalidJsonOutputGf = require('../data/invalidRegexGf.json') +const invalidTableOutputGf = require('../data/tableDataGf.json') + +describe('Safe Regex Check in Schema', () => { + beforeEach(() => { + jest.restoreAllMocks() + }) + + describe('Content Type', () => { + test('Process Schema with Valid Regex', async () => { + const invalidRegex: object[] = [] + const tableData: object[] = [] + const moduleType = 'Content Type' + safeRegex(validDocument, invalidRegex, tableData, moduleType) + expect(invalidRegex).toStrictEqual([]) + expect(tableData).toStrictEqual([]) + }) + + test('Process Schema with Invalid Regex', async () => { + const invalidRegex: object[] = [] + const tableData: object[] = [] + const moduleType = 'Content Type' + safeRegex(invalidDocument, invalidRegex, tableData, moduleType) + expect(invalidRegex).toStrictEqual(invalidJsonOutput) + expect(tableData).toStrictEqual(invalidTableOutput) + }) + }) + + describe('Global Field', () => { + test('Process Schema with Valid Regex', async () => { + const invalidRegex: object[] = [] + const tableData: object[] = [] + const moduleType = 'Global Field' + safeRegex(validDocument, invalidRegex, tableData, moduleType) + expect(invalidRegex).toStrictEqual([]) + expect(tableData).toStrictEqual([]) + }) + + test('Process Schema with Invalid Regex', async () => { + const invalidRegex: object[] = [] + const tableData: object[] = [] + const moduleType = 'Global Field' + safeRegex(invalidDocument, invalidRegex, tableData, moduleType) + expect(invalidRegex).toStrictEqual(invalidJsonOutputGf) + expect(tableData).toStrictEqual(invalidTableOutputGf) + }) + }) +}) diff --git a/packages/contentstack-cli-cm-regex-validate/tsconfig.json b/packages/contentstack-cli-cm-regex-validate/tsconfig.json new file mode 100644 index 000000000..611aafc51 --- /dev/null +++ b/packages/contentstack-cli-cm-regex-validate/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "declaration": true, + "importHelpers": true, + "module": "commonjs", + "outDir": "lib", + "rootDir": "src", + "strict": true, + "target": "es2017", + "skipLibCheck": true + }, + "include": [ + "src/**/*" + ] +} From 1c00c8269fa3b1836cb891fd5637a40b7f284d5c Mon Sep 17 00:00:00 2001 From: raj pandey Date: Mon, 25 May 2026 12:48:17 +0530 Subject: [PATCH 2/2] fix: correct esModuleInterop imports and tsconfig for regex-validate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add esModuleInterop: true and composite: true to tsconfig.json (were missing from v1 branch) - Fix generate-output.ts: import jsonexport/cli-table3 as default imports instead of namespace imports — with esModuleInterop, CJS default-export modules require `import X from 'X'` not `import * as X from 'X'` (fixes TS2349/TS2351/TS1259) Co-Authored-By: Claude Sonnet 4.6 --- .../src/utils/generate-output.ts | 4 ++-- packages/contentstack-cli-cm-regex-validate/tsconfig.json | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/contentstack-cli-cm-regex-validate/src/utils/generate-output.ts b/packages/contentstack-cli-cm-regex-validate/src/utils/generate-output.ts index 1bce5ddf7..266b823be 100644 --- a/packages/contentstack-cli-cm-regex-validate/src/utils/generate-output.ts +++ b/packages/contentstack-cli-cm-regex-validate/src/utils/generate-output.ts @@ -1,5 +1,5 @@ -import * as jsonexport from 'jsonexport' -import * as Table from 'cli-table3' +import jsonexport from 'jsonexport' +import Table from 'cli-table3' import * as path from 'path' import * as fs from 'fs' import {cliux, sanitizePath} from '@contentstack/cli-utilities' diff --git a/packages/contentstack-cli-cm-regex-validate/tsconfig.json b/packages/contentstack-cli-cm-regex-validate/tsconfig.json index 611aafc51..2756c5ea0 100644 --- a/packages/contentstack-cli-cm-regex-validate/tsconfig.json +++ b/packages/contentstack-cli-cm-regex-validate/tsconfig.json @@ -7,7 +7,9 @@ "rootDir": "src", "strict": true, "target": "es2017", - "skipLibCheck": true + "skipLibCheck": true, + "esModuleInterop": true, + "composite": true }, "include": [ "src/**/*"