@carecard/validate 3.18.0 → 3.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/.agents/skills/carecard-workspace-standards/SKILL.md +49 -20
  2. package/.agents/skills/github-pr-create-update/SKILL.md +22 -1
  3. package/.agents/skills/github-pr-merge-cleanup/SKILL.md +22 -1
  4. package/.agents/skills/logged-in-user-profile-page/SKILL.md +22 -1
  5. package/.agents/skills/pkg-publish/SKILL.md +22 -1
  6. package/.agents/skills/pkg-validate-coding-standards-and-best-practices/SKILL.md +47 -9
  7. package/.agents/skills/pkg-validate-validation-library/SKILL.md +45 -14
  8. package/.agents/skills/software-design-patterns-and-clean-code/SKILL.md +23 -3
  9. package/.codex/AGENTS.md +41 -10
  10. package/.github/workflows/auto-draft-pr.yml +173 -151
  11. package/.github/workflows/ci.yml +35 -32
  12. package/.husky/pre-commit +4 -4
  13. package/.prettierrc.js +10 -9
  14. package/AGENTS.md +19 -0
  15. package/eslint.config.mjs +60 -8
  16. package/index.d.ts +108 -107
  17. package/index.js +6 -6
  18. package/lib/validate.js +230 -157
  19. package/lib/validateNewUserRoleRequest.js +68 -60
  20. package/lib/validateProperties.js +326 -326
  21. package/lib/validateWhitelistProperties.js +182 -142
  22. package/lint-staged.config.mjs +36 -0
  23. package/package.json +66 -60
  24. package/readme.md +22 -1
  25. package/scripts/canonicalTestCommand.test.mjs +32 -0
  26. package/scripts/packageTaskRunner.audit.mjs +37 -0
  27. package/scripts/packageTaskRunner.test.mjs +50 -72
  28. package/scripts/runPackageTask.mjs +103 -58
  29. package/scripts/testOrder/randomizeTestOrder.cjs +35 -25
  30. package/scripts/testOrder/randomizeTestOrder.test.mjs +19 -19
  31. package/scripts/testOrder/testOrderPolicy.audit.mjs +54 -0
  32. package/scripts/testParallel/parallelTestPolicy.audit.mjs +63 -0
  33. package/scripts/testParallel/runIndexedMochaTests.cjs +45 -43
  34. package/scripts/testParallel/runIndexedMochaTests.test.mjs +13 -7
  35. package/scripts/testOrder/testOrderPolicy.test.mjs +0 -48
  36. package/scripts/testParallel/parallelTestPolicy.test.mjs +0 -43
@@ -20,158 +20,180 @@ name: Auto Draft PR
20
20
  # names such as `feature/foo/bar`, `fix/bug-1`, `hotfix/...`, etc.
21
21
 
22
22
  on:
23
- push:
24
- branches:
25
- - 'feature/**'
26
- - 'feat/**'
27
- - 'improvement/**'
28
- - 'releases/**'
29
- - 'release*'
30
- - 'hotfix/**'
31
- - 'iss/**'
32
- - 'fix/**'
33
- - 'main-*'
34
- - 'main-**'
35
- - 'data**'
36
- - 'data/**'
37
- paths-ignore:
38
- - '**.md'
39
- workflow_dispatch:
23
+ push:
24
+ branches:
25
+ - 'feature/**'
26
+ - 'feat/**'
27
+ - 'improvement/**'
28
+ - 'releases/**'
29
+ - 'release*'
30
+ - 'hotfix/**'
31
+ - 'iss/**'
32
+ - 'fix/**'
33
+ - 'main-*'
34
+ - 'main-**'
35
+ - 'data**'
36
+ - 'data/**'
37
+ paths-ignore:
38
+ - '**.md'
39
+ workflow_dispatch:
40
40
 
41
41
  permissions:
42
- contents: read
43
- issues: write
44
- pull-requests: write
42
+ contents: read
43
+ issues: write
44
+ pull-requests: write
45
45
 
46
46
  jobs:
47
- open-draft-pr:
48
- runs-on: ubuntu-latest
49
-
50
- steps:
51
- - name: Determine target base branch
52
- id: target
53
- env:
54
- HEAD_BRANCH: ${{ github.ref_name }}
55
- run: |
56
- set -euo pipefail
57
- echo "Head branch: $HEAD_BRANCH"
58
-
59
- # Skip protected/base branches – we never open a PR from them to themselves.
60
- case "$HEAD_BRANCH" in
61
- main|master|development|develop)
62
- echo "Branch '$HEAD_BRANCH' is a base branch – skipping PR creation."
63
- echo "skip=true" >> "$GITHUB_OUTPUT"
64
- exit 0
65
- ;;
66
- esac
67
-
68
- if [[ "$HEAD_BRANCH" == main-* ]]; then
69
- BASE="main"
70
- else
71
- BASE="development"
72
- fi
73
-
74
- echo "Target base branch: $BASE"
75
- echo "base=$BASE" >> "$GITHUB_OUTPUT"
76
- echo "head=$HEAD_BRANCH" >> "$GITHUB_OUTPUT"
77
- echo "skip=false" >> "$GITHUB_OUTPUT"
78
-
79
- - name: Checkout repository
80
- if: steps.target.outputs.skip == 'false'
81
- uses: actions/checkout@v7
82
- with:
83
- fetch-depth: 0
84
-
85
- - name: Create draft Pull Request (if missing)
86
- if: steps.target.outputs.skip == 'false'
87
- env:
88
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
89
- HEAD: ${{ steps.target.outputs.head }}
90
- BASE: ${{ steps.target.outputs.base }}
91
- REPO: ${{ github.repository }}
92
- PR_ASSIGNEE: ${{ github.actor }}
93
- PR_REVIEWER: singh-pankaj-k
94
- run: |
95
- set -euo pipefail
96
-
97
- # Avoid creating duplicates: check for an existing open PR for this head -> base.
98
- # `gh pr list --head` expects the branch name (without owner prefix) for same-repo PRs.
99
- EXISTING=$(gh pr list \
100
- --repo "$REPO" \
101
- --state open \
102
- --head "$HEAD" \
103
- --base "$BASE" \
104
- --json number \
105
- --jq '.[0].number')
106
-
107
- if [[ -n "${EXISTING:-}" ]]; then
108
- echo "An open PR already exists for $HEAD -> $BASE (#$EXISTING). Updating PR metadata."
109
- PR_NUMBER="$EXISTING"
110
- else
111
- TITLE="Draft: merge \`$HEAD\` into \`$BASE\`"
112
- BODY=$'Auto-created draft PR for branch `'"$HEAD"$'`.\n\nTarget base: `'"$BASE"$'`\n\nMark this PR as ready for review when you want it to be reviewed/merged.'
113
-
114
- gh pr create \
115
- --repo "$REPO" \
116
- --draft \
117
- --base "$BASE" \
118
- --head "$HEAD" \
119
- --title "$TITLE" \
120
- --body "$BODY"
121
-
122
- PR_NUMBER=$(gh pr list \
123
- --repo "$REPO" \
124
- --state open \
125
- --head "$HEAD" \
126
- --base "$BASE" \
127
- --json number \
128
- --jq '.[0].number')
129
- fi
130
-
131
- gh pr edit "$PR_NUMBER" \
132
- --repo "$REPO" \
133
- --add-assignee "$PR_ASSIGNEE"
134
-
135
- PR_AUTHOR=$(gh pr view "$PR_NUMBER" \
136
- --repo "$REPO" \
137
- --json author \
138
- --jq '.author.login')
139
-
140
- if [[ "$PR_AUTHOR" == "$PR_REVIEWER" ]]; then
141
- echo "Skipping reviewer request because $PR_REVIEWER is the PR author."
142
- else
143
- gh pr edit "$PR_NUMBER" \
144
- --repo "$REPO" \
145
- --add-reviewer "$PR_REVIEWER"
146
- fi
147
-
148
- - name: Assign matching issue ticket (best effort)
149
- if: steps.target.outputs.skip == 'false'
150
- env:
151
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
152
- HEAD: ${{ steps.target.outputs.head }}
153
- REPO: ${{ github.repository }}
154
- PR_ASSIGNEE: ${{ github.actor }}
155
- run: |
156
- set -euo pipefail
157
-
158
- TICKET_NUMBER=""
159
- if [[ "$HEAD" =~ (^|/)(iss|issue|issues)[/-]?#?([0-9]+)([^0-9]|$) ]]; then
160
- TICKET_NUMBER="${BASH_REMATCH[3]}"
161
- fi
162
-
163
- if [[ -z "$TICKET_NUMBER" ]]; then
164
- echo "No GitHub issue ticket number found in branch '$HEAD'. Skipping ticket assignment."
165
- exit 0
166
- fi
167
-
168
- if ! gh issue view "$TICKET_NUMBER" --repo "$REPO" --json number; then
169
- echo "GitHub issue #$TICKET_NUMBER was not found. Skipping ticket assignment."
170
- exit 0
171
- fi
172
-
173
- if gh issue edit "$TICKET_NUMBER" --repo "$REPO" --add-assignee "$PR_ASSIGNEE"; then
174
- echo "Assigned issue ticket #$TICKET_NUMBER to $PR_ASSIGNEE."
175
- else
176
- echo "::warning::Could not assign issue ticket #$TICKET_NUMBER to $PR_ASSIGNEE."
177
- fi
47
+ open-draft-pr:
48
+ runs-on: ubuntu-latest
49
+
50
+ steps:
51
+ - name: Determine target base branch
52
+ id: target
53
+ env:
54
+ HEAD_BRANCH: ${{ github.ref_name }}
55
+ run: |
56
+ set -euo pipefail
57
+ echo "Head branch: $HEAD_BRANCH"
58
+
59
+ # Skip protected/base branches – we never open a PR from them to themselves.
60
+ case "$HEAD_BRANCH" in
61
+ main|master|development|develop)
62
+ echo "Branch '$HEAD_BRANCH' is a base branch – skipping PR creation."
63
+ echo "skip=true" >> "$GITHUB_OUTPUT"
64
+ exit 0
65
+ ;;
66
+ esac
67
+
68
+ if [[ "$HEAD_BRANCH" == main-* ]]; then
69
+ BASE="main"
70
+ else
71
+ BASE="development"
72
+ fi
73
+
74
+ echo "Target base branch: $BASE"
75
+ echo "base=$BASE" >> "$GITHUB_OUTPUT"
76
+ echo "head=$HEAD_BRANCH" >> "$GITHUB_OUTPUT"
77
+ echo "skip=false" >> "$GITHUB_OUTPUT"
78
+
79
+ - name: Checkout repository
80
+ if: steps.target.outputs.skip == 'false'
81
+ uses: actions/checkout@v7
82
+ with:
83
+ fetch-depth: 0
84
+
85
+ - name: Create draft Pull Request (if missing)
86
+ if: steps.target.outputs.skip == 'false'
87
+ env:
88
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
89
+ HEAD: ${{ steps.target.outputs.head }}
90
+ BASE: ${{ steps.target.outputs.base }}
91
+ REPO: ${{ github.repository }}
92
+ PR_ASSIGNEE: ${{ github.actor }}
93
+ PR_REVIEWER: singh-pankaj-k
94
+ run: |
95
+ set -euo pipefail
96
+
97
+ # Pattern: Query Object - resolves the exact open PR for this head and base.
98
+ find_open_pull_request() {
99
+ gh pr list \
100
+ --repo "$REPO" \
101
+ --state open \
102
+ --head "$HEAD" \
103
+ --base "$BASE" \
104
+ --json number \
105
+ --jq '.[0].number // empty'
106
+ }
107
+
108
+ PR_NUMBER="$(find_open_pull_request)"
109
+ if [[ -n "$PR_NUMBER" ]]; then
110
+ echo "An open PR already exists for $HEAD -> $BASE (#$PR_NUMBER). Updating PR metadata."
111
+ else
112
+ TITLE="Draft: merge \`$HEAD\` into \`$BASE\`"
113
+ BODY=$'Auto-created draft PR for branch `'"$HEAD"$'`.\n\nTarget base: `'"$BASE"$'`\n\nMark this PR as ready for review when you want it to be reviewed/merged.'
114
+
115
+ CREATE_OUTPUT=""
116
+ if CREATE_OUTPUT="$(gh pr create \
117
+ --repo "$REPO" \
118
+ --draft \
119
+ --base "$BASE" \
120
+ --head "$HEAD" \
121
+ --title "$TITLE" \
122
+ --body "$BODY" 2>&1)"; then
123
+ printf '%s\n' "$CREATE_OUTPUT"
124
+ else
125
+ CREATE_STATUS=$?
126
+ if PR_NUMBER="$(find_open_pull_request)"; then
127
+ :
128
+ else
129
+ RECOVERY_STATUS=$?
130
+ printf '%s\n' "$CREATE_OUTPUT" >&2
131
+ exit "$RECOVERY_STATUS"
132
+ fi
133
+
134
+ if [[ -z "$PR_NUMBER" ]]; then
135
+ printf '%s\n' "$CREATE_OUTPUT" >&2
136
+ exit "$CREATE_STATUS"
137
+ fi
138
+
139
+ echo "Another workflow run created PR #$PR_NUMBER for $HEAD -> $BASE."
140
+ fi
141
+
142
+ if [[ -z "$PR_NUMBER" ]]; then
143
+ PR_NUMBER="$(find_open_pull_request)"
144
+ fi
145
+ fi
146
+
147
+ if [[ ! "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]]; then
148
+ printf 'Expected an open PR number for %s -> %s, got %q\n' \
149
+ "$HEAD" "$BASE" "$PR_NUMBER" >&2
150
+ exit 1
151
+ fi
152
+
153
+ gh pr edit "$PR_NUMBER" \
154
+ --repo "$REPO" \
155
+ --add-assignee "$PR_ASSIGNEE"
156
+
157
+ PR_AUTHOR=$(gh pr view "$PR_NUMBER" \
158
+ --repo "$REPO" \
159
+ --json author \
160
+ --jq '.author.login')
161
+
162
+ if [[ "$PR_AUTHOR" == "$PR_REVIEWER" ]]; then
163
+ echo "Skipping reviewer request because $PR_REVIEWER is the PR author."
164
+ else
165
+ gh pr edit "$PR_NUMBER" \
166
+ --repo "$REPO" \
167
+ --add-reviewer "$PR_REVIEWER"
168
+ fi
169
+
170
+ - name: Assign matching issue ticket (best effort)
171
+ if: steps.target.outputs.skip == 'false'
172
+ env:
173
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
174
+ HEAD: ${{ steps.target.outputs.head }}
175
+ REPO: ${{ github.repository }}
176
+ PR_ASSIGNEE: ${{ github.actor }}
177
+ run: |
178
+ set -euo pipefail
179
+
180
+ TICKET_NUMBER=""
181
+ if [[ "$HEAD" =~ (^|/)(iss|issue|issues)[/-]?#?([0-9]+)([^0-9]|$) ]]; then
182
+ TICKET_NUMBER="${BASH_REMATCH[3]}"
183
+ fi
184
+
185
+ if [[ -z "$TICKET_NUMBER" ]]; then
186
+ echo "No GitHub issue ticket number found in branch '$HEAD'. Skipping ticket assignment."
187
+ exit 0
188
+ fi
189
+
190
+ if ! gh issue view "$TICKET_NUMBER" --repo "$REPO" --json number; then
191
+ echo "GitHub issue #$TICKET_NUMBER was not found. Skipping ticket assignment."
192
+ exit 0
193
+ fi
194
+
195
+ if gh issue edit "$TICKET_NUMBER" --repo "$REPO" --add-assignee "$PR_ASSIGNEE"; then
196
+ echo "Assigned issue ticket #$TICKET_NUMBER to $PR_ASSIGNEE."
197
+ else
198
+ echo "::warning::Could not assign issue ticket #$TICKET_NUMBER to $PR_ASSIGNEE."
199
+ fi
@@ -1,42 +1,45 @@
1
1
  name: CI_Tests
2
2
 
3
3
  on:
4
- push:
5
- branches:
6
- - development
7
- - main
8
- pull_request:
9
- branches:
10
- - development
11
- - main
12
- types:
13
- - opened
14
- - reopened
15
- - synchronize
16
- - ready_for_review
17
- paths-ignore:
18
- - '**.md'
4
+ push:
5
+ branches:
6
+ - development
7
+ - main
8
+ pull_request:
9
+ branches:
10
+ - development
11
+ - main
12
+ types:
13
+ - opened
14
+ - reopened
15
+ - synchronize
16
+ - ready_for_review
17
+ paths-ignore:
18
+ - '**.md'
19
19
 
20
20
  jobs:
21
- test:
22
- name: CI_Tests
23
- runs-on: ubuntu-latest
21
+ test:
22
+ name: CI_Tests
23
+ runs-on: ubuntu-latest
24
24
 
25
- steps:
26
- - name: Checkout repository
27
- uses: actions/checkout@v7
25
+ steps:
26
+ - name: Checkout repository
27
+ uses: actions/checkout@v7
28
28
 
29
- - name: Set up Node.js
30
- uses: actions/setup-node@v6
31
- with:
32
- node-version: 24.18.0
33
- cache: 'npm'
29
+ - name: Set up Node.js
30
+ uses: actions/setup-node@v6
31
+ with:
32
+ node-version: 24.18.0
33
+ cache: 'npm'
34
34
 
35
- - name: Install dependencies
36
- run: npm ci
35
+ - name: Install dependencies
36
+ run: npm ci
37
37
 
38
- - name: Run tests
39
- run: npm run test:All
38
+ - name: Run lint
39
+ run: npm run lint
40
40
 
41
- - name: Run tests with coverage
42
- run: npm run test:coverage
41
+ - name: Check formatting
42
+ run: npm run format:check
43
+
44
+ - name: Run complete test suite
45
+ run: npm test
package/.husky/pre-commit CHANGED
@@ -3,8 +3,8 @@ set -eu
3
3
  repository_root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)"
4
4
  cd "$repository_root"
5
5
 
6
- npm run lint:fix
7
- npm run format
6
+ npm run lint-staged
7
+ npm run lint
8
+ npm run format:check
8
9
 
9
- # Run tests
10
- npm run test:All
10
+ npm test
package/.prettierrc.js CHANGED
@@ -1,11 +1,12 @@
1
1
  module.exports = {
2
- arrowParens: 'avoid',
3
- bracketSameLine: true,
4
- bracketSpacing: true,
5
- singleQuote: true,
6
- trailingComma: 'all',
7
- printWidth: 140,
8
- tabWidth: 4,
9
- useTabs: false,
10
- endOfLine: 'auto',
2
+ arrowParens: 'avoid',
3
+ bracketSameLine: true,
4
+ bracketSpacing: true,
5
+ singleQuote: true,
6
+ trailingComma: 'all',
7
+ printWidth: 100,
8
+ semi: true,
9
+ tabWidth: 2,
10
+ useTabs: false,
11
+ endOfLine: 'lf',
11
12
  };
package/AGENTS.md CHANGED
@@ -13,3 +13,22 @@
13
13
  Non-negotiable repository isolation rule: Every repository must run its Husky hooks and tests using only files, code, fixtures, dependencies, and services contained within that repository. Tests and Husky scripts must not import, require, read, execute, or otherwise depend on sibling repositories or paths outside the repository root. app-e2e-tests is the only exception because cross-repository end-to-end testing is its explicit responsibility.
14
14
 
15
15
  Non-negotiable code organization rule: Functions with the same or equivalent behavior must use the same or clearly corresponding descriptive names across CareCard repositories, and equivalent functionality must live in files with the same names within each repository's established architecture. No backward compatibility names, aliases, or duplicate locations are allowed.
16
+
17
+ ## TDD And Validation
18
+
19
+ Test Driven Development is a non-negotiable requirement.
20
+
21
+ The sole purpose of automated tests is to verify observable functionality and externally visible behavior.
22
+ Tests must validate what the system does through its public interfaces and expected outcomes.
23
+
24
+ Tests must not assert, inspect, or depend on implementation details, including but not limited to:
25
+
26
+ - The existence of specific lines of code, statements, functions, classes, files, or modules.
27
+ - Specific algorithms, control flow, variable names, method calls, code snippets, or internal implementation choices.
28
+ - Any internal structure that can change without changing externally observable behavior.
29
+
30
+ A correct implementation may be completely rewritten or refactored without requiring changes to functional tests, provided its externally observable behavior remains unchanged.
31
+
32
+ Any test that fails solely because the implementation changed while the externally observable behavior remained correct is incorrectly designed and must be rewritten or removed.
33
+
34
+ This requirement is mandatory for all new tests and must be applied whenever existing tests are modified.
package/eslint.config.mjs CHANGED
@@ -1,14 +1,66 @@
1
+ import js from '@eslint/js';
2
+ import tsParser from '@typescript-eslint/parser';
1
3
  import { defineConfig, globalIgnores } from 'eslint/config';
4
+ import globals from 'globals';
2
5
 
3
6
  const eslintConfig = defineConfig([
4
- globalIgnores([
5
- // Default ignores of eslint-config-next:
6
- '.next/**',
7
- 'out/**',
8
- 'build/**',
9
- 'next-env.d.ts',
10
- 'node_modules/**',
11
- ]),
7
+ globalIgnores([
8
+ // Default ignores of eslint-config-next:
9
+ '.next/**',
10
+ 'out/**',
11
+ 'build/**',
12
+ 'next-env.d.ts',
13
+ 'node_modules/**',
14
+ 'coverage/**',
15
+ 'dist/**',
16
+ ]),
17
+ {
18
+ name: 'carecard/braced-control-flow',
19
+ files: ['**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}'],
20
+ languageOptions: {
21
+ ecmaVersion: 'latest',
22
+ parserOptions: {
23
+ ecmaFeatures: {
24
+ jsx: true,
25
+ },
26
+ },
27
+ },
28
+ rules: {
29
+ curly: ['error', 'all'],
30
+ },
31
+ },
32
+ {
33
+ name: 'carecard/javascript-recommended',
34
+ files: ['**/*.{js,jsx,mjs,cjs}'],
35
+ languageOptions: {
36
+ ecmaVersion: 'latest',
37
+ sourceType: 'commonjs',
38
+ globals: {
39
+ ...globals.node,
40
+ ...globals.mocha,
41
+ },
42
+ },
43
+ rules: {
44
+ ...js.configs.recommended.rules,
45
+ eqeqeq: ['error', 'smart'],
46
+ 'no-unused-vars': ['error', { argsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }],
47
+ 'no-var': 'error',
48
+ },
49
+ },
50
+ {
51
+ name: 'carecard/esm-source-type',
52
+ files: ['**/*.mjs'],
53
+ languageOptions: {
54
+ sourceType: 'module',
55
+ },
56
+ },
57
+ {
58
+ name: 'carecard/typescript-parser',
59
+ files: ['**/*.{ts,tsx,mts,cts}'],
60
+ languageOptions: {
61
+ parser: tsParser,
62
+ },
63
+ },
12
64
  ]);
13
65
 
14
66
  export default eslintConfig;