Skip to content

Commit c939de0

Browse files
Merge pull request #75 from DSACMS/sachin/jsonValidationImplementation
JSON Schema Validation
2 parents 0945982 + e2fc1bd commit c939de0

10 files changed

Lines changed: 308 additions & 87 deletions

File tree

.github/workflows/updateCodeJSON.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
name: Update Code.json
22
on:
33
workflow_dispatch:
4+
pull_request:
5+
types: [opened, synchronize]
6+
paths:
7+
- "code.json"
48

59
permissions:
610
contents: write
@@ -18,12 +22,12 @@ jobs:
1822

1923
- name: Update code.json
2024
id: generator
21-
uses: DSACMS/automated-codejson-generator@main
25+
uses: DSACMS/automated-codejson-generator@sachin/jsonValidationImplementation
2226
with:
2327
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24-
ADMIN_TOKEN: ${{ secrets.ADMIN_PAT }}
28+
ADMIN_TOKEN: ${{ secrets.ADMIN_PAT }}
2529
BRANCH: "main"
26-
SKIP_PR: "true"
30+
SKIP_PR: "false"
2731

2832
- name: Post update information
2933
run: |
@@ -33,4 +37,3 @@ jobs:
3337
elif [ "${{ steps.generator.outputs.method_used }}" = "pull_request" ]; then
3438
echo "Created pull request: ${{ steps.generator.outputs.pr_url }}"
3539
fi
36-

CONTRIBUTING.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@ npm run package
3838
npm test
3939
```
4040

41+
## Validation
42+
43+
The action uses [Zod](https://zod.dev/) for schema validation, automatically validating code.json in two scenarios:
44+
45+
### 1. Before Generation
46+
47+
Every time the action generates or updates code.json (via schedule or workflow_dispatch), it validates the output before creating a PR or pushing. If validation fails, no changes are made.
48+
49+
### 2. On PR Edits
50+
51+
When the `pull_request` trigger is configured, the action validates code.json whenever it's edited in a PR. This ensures users cannot accidentally merge invalid JSON.
52+
4153
### Workflow and Branching
4254

4355
We follow the [GitHub Flow Workflow](https://guides.github.com/introduction/flow/):

README.md

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,13 @@ ADMIN_TOKEN:
3333
```yaml
3434
updated:
3535
description: "Boolean indicating whether code.json was updated"
36+
3637
pr_url:
3738
description: "URL of the created pull request if changes were made via PR"
39+
3840
commit_sha:
3941
description: "SHA of the commit if pushed directly to branch"
42+
4043
method_used:
4144
description: "Method used for the update: 'direct_push' or 'pull_request'"
4245
```
@@ -45,14 +48,18 @@ method_used:
4548
4649
### Option 1: Direct Push
4750
48-
This approach tries to push directly to the branch using a Personal Access Token, but falls back to creating a pull request if the direct push fails.
51+
This approach tries to push directly to the branch using a Personal Access Token, but falls back to creating a pull request if the direct push fails. When users need to edit code.json, they should create a PR which will automatically validate their changes.
4952
5053
```yaml
51-
name: Update Code.json (Smart Mode)
54+
name: Update Code.json
5255
on:
5356
schedule:
5457
- cron: 0 0 1 * * # First day of every month
5558
workflow_dispatch:
59+
pull_request:
60+
types: [opened, synchronize]
61+
paths:
62+
- "code.json"
5663

5764
permissions:
5865
contents: write
@@ -73,7 +80,7 @@ jobs:
7380
uses: DSACMS/automated-codejson-generator@v1.2.0
7481
with:
7582
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
76-
ADMIN_TOKEN: ${{ secrets.ADMIN_PAT }} # PAT with admin/push permissions
83+
ADMIN_TOKEN: ${{ secrets.ADMIN_PAT }} # PAT with admin/push permissions
7784
BRANCH: "main"
7885
SKIP_PR: "true"
7986

@@ -89,14 +96,18 @@ jobs:
8996
9097
### Option 2: Pull Request Only
9198
92-
This approach always creates a pull request, ensuring code review for all changes.
99+
This approach always creates a pull request for both automatic generation and validation of manual edits, ensuring code review for all changes.
93100
94101
```yaml
95102
name: Update Code.json
96103
on:
97104
schedule:
98105
- cron: 0 0 1 * * # First day of every month
99106
workflow_dispatch:
107+
pull_request:
108+
types: [opened, synchronize]
109+
paths:
110+
- "code.json"
100111

101112
permissions:
102113
contents: write
@@ -113,13 +124,29 @@ jobs:
113124
fetch-depth: 0
114125

115126
- name: Update code.json
116-
uses: DSACMS/automated-codejson-generator@latest
127+
uses: DSACMS/automated-codejson-generator@v1.2.0
117128
with:
118129
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
119130
BRANCH: "main"
120-
SKIP_PR: "false"
131+
SKIP_PR: "false"
121132
```
122133
134+
### How It Works
135+
136+
**Automatic Generation**
137+
138+
- The action calculates metadata, validates it, and creates a PR or pushes directly
139+
- Validation ensures only valid code.json is created
140+
- Users can then fill in manual fields by editing the PR
141+
142+
**PR Validation**
143+
144+
- When users edit code.json in a PR, validation runs automatically on every commit
145+
- The PR cannot be merged if validation fails (when branch protection is enabled)
146+
- Error messages help users fix issues quickly
147+
148+
**Important:** For direct push mode, users should always create PRs when manually editing code.json to ensure validation runs. Direct edits to the main branch will not be validated by this action.
149+
123150
## Setting Up Personal Access Token (PAT)
124151
125152
To use the direct push functionality, you'll need to create a Personal Access Token:
@@ -128,14 +155,14 @@ To use the direct push functionality, you'll need to create a Personal Access To
128155
129156
1. **Go to GitHub Settings**: Navigate to your GitHub account settings
130157
2. **Developer Settings**: Click on "Developer settings" in the left sidebar
131-
3. **Personal Access Tokens**: Choose "Tokens (classic)" or "Fine-grained tokens"
158+
3. **Personal Access Tokens**: Choose "Tokens (classic)"
132159
4. **Generate New Token**: Click "Generate new token"
133160
5. **Configure Token**:
134-
- **Name**: Give it a descriptive name like "Code.json Generator"
161+
- **Name**: Give it a name like "code.json Generator"
135162
- **Expiration**: Set appropriate expiration (recommend 90 days or 1 year)
136-
- **Scopes**:
137-
- For classic tokens: Select `repo` (full repository access)
138-
- For fine-grained tokens: Select `Contents` (write) and `Metadata` (read)
163+
- **Scopes**:
164+
- Select `repo` (full repository access)
165+
6. **Store Token**: Copy and paste your token and store it for the next part
139166

140167
### Adding PAT to Repository
141168

@@ -148,8 +175,7 @@ To use the direct push functionality, you'll need to create a Personal Access To
148175
5. **Save**: Click "Add secret"
149176

150177
⚠️ _Please make sure the following are enabled within your Repository Action Settings in order to work properly_ ⚠️
151-
<img width="789" height="361" alt="Screenshot 2025-08-05 at 1 44 36 PM" src="https://github.com/user-attachments/assets/3795dc0e-c4c4-4378-8eb2-b7b9d861c08a" />
152-
178+
<img width="789" height="361" alt="Screenshot 2025-08-05 at 1 44 36 PM" src="https://github.com/user-attachments/assets/3795dc0e-c4c4-4378-8eb2-b7b9d861c08a" />
153179

154180
## Generation Context
155181

@@ -173,7 +199,7 @@ The automated code.json generator calculates specific fields by analyzing your r
173199

174200
**dateLastModified**: This uses your repository's last update timestamp, reflecting the most recent changes. No configuration needed.
175201

176-
**dateMetaDataLastUpdated**: The generator sets this to the current timestamp each time it runs, providing a record of when the metadata was last refreshed. No configuration needed.
202+
**dateMetadataLastUpdated**: The generator sets this to the current timestamp each time it runs, providing a record of when the metadata was last refreshed. No configuration needed.
177203

178204
**feedbackMechanism**: The repository's issues URL in the format of {repositoryURL}/issues. If you already have a code.json file with existing feedback mechanisms, the generator preserves those values. No configuration needed.
179205

@@ -213,6 +239,7 @@ An up-to-date list of core team members can be found in [MAINTAINERS.md](MAINTAI
213239
.
214240
├── src/
215241
│ ├── model.ts # TypeScript interfaces for code.json schema
242+
│ ├── validation.ts # Zod schema definitions and validation logic
216243
│ ├── main.ts # Main action logic
217244
│ ├── helper.ts # Helper functions for GitHub API interactions
218245
│ └── index.ts # Action entrypoint

code.json

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,10 @@
2828
"forks": 2,
2929
"clones": 0
3030
},
31-
"platforms": [
32-
"web",
33-
"linux"
34-
],
35-
"categories": [
36-
"developer-tools",
37-
"automation"
38-
],
31+
"platforms": ["web", "linux"],
32+
"categories": ["developer-tools", "automation"],
3933
"softwareType": "standalone/backend",
40-
"languages": [
41-
"TypeScript",
42-
"JavaScript"
43-
],
34+
"languages": ["TypeScript", "JavaScript"],
4435
"maintenance": "internal",
4536
"contractNumber": [],
4637
"SBOM": "https://github.com/DSACMS/automated-codejson-generator/network/dependencies",
@@ -50,11 +41,9 @@
5041
"date": {
5142
"created": "2025-02-07T16:29:38Z",
5243
"lastModified": "2025-09-17T22:40:02Z",
53-
"metaDataLastUpdated": "2025-09-26T15:08:24.592Z"
44+
"metadataLastUpdated": "2025-09-26T15:08:24.592Z"
5445
},
55-
"tags": [
56-
"cmsoss-tier2"
57-
],
46+
"tags": ["cmsoss-tier2"],
5847
"contact": {
5948
"email": "opensource@cms.hhs.gov",
6049
"name": "CMS Open Source Team"
@@ -66,15 +55,9 @@
6655
"userInput": false,
6756
"fismaLevel": "Low",
6857
"group": "CMS/OA/DSAC",
69-
"projects": [
70-
"SHARE IT Act"
71-
],
58+
"projects": ["SHARE IT Act"],
7259
"systems": [],
73-
"subsetInHealthcare": [
74-
"Operational"
75-
],
76-
"userType": [
77-
"Government"
78-
],
60+
"subsetInHealthcare": ["Operational"],
61+
"userType": ["Government"],
7962
"maturityModelTier": 3
80-
}
63+
}

package-lock.json

Lines changed: 11 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
"dependencies": {
4040
"@actions/core": "^1.11.1",
4141
"@octokit/action": "^7.0.0",
42-
"octokit-plugin-create-pull-request": "^6.0.0"
42+
"octokit-plugin-create-pull-request": "^6.0.0",
43+
"zod": "^4.1.11"
4344
},
4445
"devDependencies": {
4546
"@eslint/compat": "^1.2.6",

src/helper.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { exec } from "child_process";
77
import { promisify } from "util";
88

99
import { CodeJSON, BasicRepoInfo } from "./model.js";
10+
import { validateCodeJSON } from "./validation.js";
1011

1112
const execAsync = promisify(exec);
1213

@@ -66,7 +67,7 @@ export async function calculateMetaData(): Promise<Partial<CodeJSON>> {
6667
date: {
6768
created: basicInfo.date.created,
6869
lastModified: basicInfo.date.lastModified,
69-
metaDataLastUpdated: basicInfo.date.metaDataLastUpdated,
70+
metadataLastUpdated: basicInfo.date.metadataLastUpdated,
7071
},
7172
};
7273
} catch (error) {
@@ -99,7 +100,7 @@ async function getBasicInfo(): Promise<BasicRepoInfo> {
99100
date: {
100101
created: repoData.data.created_at,
101102
lastModified: repoData.data.updated_at,
102-
metaDataLastUpdated: new Date().toISOString(),
103+
metadataLastUpdated: new Date().toISOString(),
103104
},
104105
};
105106
} catch (error) {
@@ -139,6 +140,37 @@ export async function getBaseBranch(): Promise<string> {
139140
}
140141
}
141142

143+
//===============================================
144+
// Validation
145+
//===============================================
146+
export async function validateOnly(): Promise<void> {
147+
try {
148+
const codeJSON = await readJSON("/github/workspace/code.json");
149+
150+
if (!codeJSON) {
151+
core.setFailed(
152+
"code.json file not found, is empty, or contains invalid JSON syntax...",
153+
);
154+
return;
155+
}
156+
157+
const validationErrors = validateCodeJSON(codeJSON);
158+
159+
if (validationErrors.length > 0) {
160+
const errorMessage = `code.json validation failed with ${validationErrors.length} error(s):\n\n${validationErrors.map((err, idx) => `${idx + 1}. ${err}`).join("\n")}`;
161+
core.setFailed(errorMessage);
162+
return;
163+
}
164+
165+
core.info("code.json is valid!");
166+
core.setOutput("validated", true);
167+
} catch (error) {
168+
core.setFailed(`validation error: ${error}`);
169+
}
170+
}
171+
172+
export { validateCodeJSON };
173+
142174
//===============================================
143175
// Data Handling
144176
//===============================================

0 commit comments

Comments
 (0)