Skip to content

Commit f5bca6e

Browse files
Merge branch 'main' into main
2 parents 00b9057 + 5da39f5 commit f5bca6e

File tree

2,253 files changed

+16596
-4927
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

2,253 files changed

+16596
-4927
lines changed

.github/pull_request_template.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# PR Description:
2+
3+
4+
# Pull Request Checklist
5+
6+
## Overview
7+
- [ ] I have read and understood the [CONTRIBUTING.md](CONTRIBUTING.md) guidelines
8+
- [ ] My pull request has a descriptive title that accurately reflects the changes
9+
- [ ] I've included only files relevant to the changes described in the PR title and description
10+
- [ ] I've created a new branch in my forked repository for this contribution
11+
12+
## Code Quality
13+
- [ ] My code is relevant to ServiceNow developers
14+
- [ ] My code snippets expand meaningfully on official ServiceNow documentation (if applicable)
15+
- [ ] I've disclosed use of ES2021 features (if applicable)
16+
- [ ] I've tested my code snippets in a ServiceNow environment (where possible)
17+
18+
## Repository Structure Compliance
19+
- [ ] I've placed my code snippet(s) in one of the required top-level categories:
20+
- `Core ServiceNow APIs/`
21+
- `Server-Side Components/`
22+
- `Client-Side Components/`
23+
- `Modern Development/`
24+
- `Integration/`
25+
- `Specialized Areas/`
26+
- [ ] I've used appropriate sub-categories within the top-level categories
27+
- [ ] Each code snippet has its own folder with a descriptive name
28+
29+
## Documentation
30+
- [ ] I've included a README.md file for each code snippet
31+
- [ ] The README.md includes:
32+
- Description of the code snippet functionality
33+
- Usage instructions or examples
34+
- Any prerequisites or dependencies
35+
- (Optional) Screenshots or diagrams if helpful
36+
37+
## Restrictions
38+
- [ ] My PR does not include XML exports of ServiceNow records
39+
- [ ] My PR does not contain sensitive information (passwords, API keys, tokens)
40+
- [ ] My PR does not include changes that fall outside the described scope
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#!/usr/bin/env node
2+
3+
const { execSync } = require('child_process');
4+
5+
const allowedCategories = new Set([
6+
'Core ServiceNow APIs',
7+
'Server-Side Components',
8+
'Client-Side Components',
9+
'Modern Development',
10+
'Integration',
11+
'Specialized Areas'
12+
]);
13+
14+
function resolveDiffRange() {
15+
if (process.argv[2]) {
16+
return process.argv[2];
17+
}
18+
19+
const inCI = process.env.GITHUB_ACTIONS === 'true';
20+
if (!inCI) {
21+
return 'origin/main...HEAD';
22+
}
23+
24+
const base = process.env.GITHUB_BASE_REF ? `origin/${process.env.GITHUB_BASE_REF}` : 'origin/main';
25+
const head = process.env.GITHUB_SHA || 'HEAD';
26+
return `${base}...${head}`;
27+
}
28+
29+
function getChangedFiles(diffRange) {
30+
let output;
31+
try {
32+
output = execSync(`git diff --name-only --diff-filter=ACMR ${diffRange}`, {
33+
encoding: 'utf8',
34+
stdio: ['ignore', 'pipe', 'pipe']
35+
});
36+
} catch (error) {
37+
console.error('Failed to collect changed files. Ensure the base branch is fetched.');
38+
console.error(error.stderr?.toString() || error.message);
39+
process.exit(1);
40+
}
41+
42+
return output
43+
.split('\n')
44+
.map((line) => line.trim())
45+
.filter(Boolean);
46+
}
47+
48+
function validateFilePath(filePath) {
49+
const normalized = filePath.replace(/\\/g, '/');
50+
const segments = normalized.split('/');
51+
52+
// Check for invalid characters that break local file systems
53+
for (let i = 0; i < segments.length; i++) {
54+
const segment = segments[i];
55+
56+
// Check for trailing periods (invalid on Windows)
57+
if (segment.endsWith('.')) {
58+
return `Invalid folder/file name '${segment}' in path '${normalized}': Names cannot end with a period (.) as this breaks local file system sync on Windows.`;
59+
}
60+
61+
// Check for trailing spaces (invalid on Windows)
62+
if (segment.endsWith(' ')) {
63+
return `Invalid folder/file name '${segment}' in path '${normalized}': Names cannot end with a space as this breaks local file system sync on Windows.`;
64+
}
65+
66+
// Check for reserved Windows names
67+
const reservedNames = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'];
68+
const nameWithoutExt = segment.split('.')[0].toUpperCase();
69+
if (reservedNames.includes(nameWithoutExt)) {
70+
return `Invalid folder/file name '${segment}' in path '${normalized}': '${nameWithoutExt}' is a reserved name on Windows and will break local file system sync.`;
71+
}
72+
73+
// Check for invalid characters (Windows and general file system restrictions)
74+
const invalidChars = /[<>:"|?*\x00-\x1F]/;
75+
if (invalidChars.test(segment)) {
76+
return `Invalid folder/file name '${segment}' in path '${normalized}': Contains characters that are invalid on Windows file systems (< > : " | ? * or control characters).`;
77+
}
78+
}
79+
80+
if (!allowedCategories.has(segments[0])) {
81+
return null;
82+
}
83+
84+
// Files must live under: Category/Subcategory/SpecificUseCase/<file>
85+
if (segments.length < 4) {
86+
return `Move '${normalized}' under a valid folder hierarchy (Category/Subcategory/Use-Case/your-file). Files directly inside '${segments[0]}' or its subcategories are not allowed.`;
87+
}
88+
89+
return null;
90+
}
91+
92+
function main() {
93+
const diffRange = resolveDiffRange();
94+
const changedFiles = getChangedFiles(diffRange);
95+
96+
if (changedFiles.length === 0) {
97+
console.log('No relevant file changes detected.');
98+
return;
99+
}
100+
101+
const problems = [];
102+
103+
for (const filePath of changedFiles) {
104+
const issue = validateFilePath(filePath);
105+
if (issue) {
106+
problems.push(issue);
107+
}
108+
}
109+
110+
if (problems.length > 0) {
111+
console.error('Folder structure violations found:');
112+
for (const msg of problems) {
113+
console.error(` - ${msg}`);
114+
}
115+
process.exit(1);
116+
}
117+
118+
console.log('Folder structure looks good.');
119+
}
120+
121+
main();

.github/workflows/pages.yml

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# GitHub Pages deployment workflow
2+
name: Deploy GitHub Pages
3+
4+
on:
5+
workflow_dispatch:
6+
7+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
14+
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
15+
concurrency:
16+
group: pages-${{ github.ref }}
17+
cancel-in-progress: true
18+
19+
jobs:
20+
# Build and optimize job
21+
build:
22+
runs-on: ubuntu-latest
23+
steps:
24+
- name: Checkout
25+
uses: actions/checkout@v4
26+
27+
- name: Setup Node.js
28+
uses: actions/setup-node@v4
29+
with:
30+
node-version: '18'
31+
cache: 'npm'
32+
33+
- name: Install build dependencies
34+
run: |
35+
npm init -y
36+
npm install --save-dev html-minifier-terser clean-css-cli terser html-validate
37+
38+
- name: Validate HTML files
39+
run: |
40+
echo "Validating HTML files..."
41+
npx html-validate *.html pages/*.html || echo "HTML validation completed with warnings"
42+
43+
- name: Optimize assets
44+
run: |
45+
echo "Optimizing HTML files..."
46+
# Create backup directory
47+
mkdir -p .backup
48+
49+
# Minify HTML files (preserve original structure)
50+
find . -name "*.html" -not -path "./node_modules/*" -not -path "./.backup/*" | while read file; do
51+
echo "Minifying: $file"
52+
npx html-minifier-terser \
53+
--collapse-whitespace \
54+
--remove-comments \
55+
--remove-optional-tags \
56+
--remove-redundant-attributes \
57+
--remove-script-type-attributes \
58+
--remove-style-link-type-attributes \
59+
--minify-css \
60+
--minify-js \
61+
"$file" -o "$file.tmp" && mv "$file.tmp" "$file"
62+
done
63+
64+
# Minify CSS files if any exist
65+
if find . -name "*.css" -not -path "./node_modules/*" -not -path "./.backup/*" | grep -q .; then
66+
echo "Optimizing CSS files..."
67+
find . -name "*.css" -not -path "./node_modules/*" -not -path "./.backup/*" | while read file; do
68+
echo "Minifying: $file"
69+
npx cleancss "$file" -o "$file"
70+
done
71+
fi
72+
73+
# Remove build dependencies from final artifact
74+
rm -rf node_modules package*.json
75+
76+
- name: Setup Pages
77+
id: pages
78+
uses: actions/configure-pages@v4
79+
80+
- name: Upload artifact
81+
uses: actions/upload-pages-artifact@v3
82+
with:
83+
path: '.'
84+
85+
# Deployment job
86+
deploy:
87+
environment:
88+
name: github-pages
89+
url: ${{ steps.deployment.outputs.page_url }}
90+
runs-on: ubuntu-latest
91+
needs: build
92+
steps:
93+
- name: Deploy to GitHub Pages
94+
id: deployment
95+
uses: actions/deploy-pages@v4
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
name: Validate Folder Structure
2+
3+
on:
4+
pull_request_target:
5+
branches:
6+
- main
7+
8+
permissions:
9+
contents: read
10+
pull-requests: write
11+
12+
concurrency:
13+
group: folder-structure-${{ github.event.pull_request.number || github.run_id }}
14+
cancel-in-progress: true
15+
16+
jobs:
17+
structure:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- name: Checkout base repository
21+
uses: actions/checkout@v4
22+
with:
23+
fetch-depth: 0
24+
25+
- name: Cache validation script
26+
run: cp .github/scripts/validate-structure.js "$RUNNER_TEMP/validate-structure.js"
27+
28+
- name: Fetch pull request head
29+
id: fetch_head
30+
env:
31+
PR_REMOTE_URL: https://github.com/${{ github.event.pull_request.head.repo.full_name }}.git
32+
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
33+
run: |
34+
git remote remove pr >/dev/null 2>&1 || true
35+
git remote add pr "$PR_REMOTE_URL"
36+
if git fetch pr "$PR_HEAD_REF":pr-head --no-tags; then
37+
git checkout pr-head
38+
git fetch origin "${{ github.event.pull_request.base.ref }}"
39+
echo "fetched=true" >> "$GITHUB_OUTPUT"
40+
else
41+
echo "::warning::Unable to fetch fork repository. Skipping structure validation."
42+
echo "fetched=false" >> "$GITHUB_OUTPUT"
43+
fi
44+
45+
- name: Use Node.js 18
46+
uses: actions/setup-node@v4
47+
with:
48+
node-version: 18
49+
50+
- name: Validate folder layout
51+
if: ${{ steps.fetch_head.outputs.fetched == 'true' }}
52+
id: validate
53+
run: |
54+
set -euo pipefail
55+
56+
tmp_output=$(mktemp)
57+
tmp_error=$(mktemp)
58+
59+
set +e
60+
node "$RUNNER_TEMP/validate-structure.js" origin/${{ github.event.pull_request.base.ref }}...HEAD >"$tmp_output" 2>"$tmp_error"
61+
status=$?
62+
set -e
63+
64+
cat "$tmp_output"
65+
cat "$tmp_error" >&2
66+
67+
if grep -q 'Folder structure violations found' "$tmp_output" "$tmp_error"; then
68+
# Save validation output for use in PR comment
69+
cat "$tmp_output" "$tmp_error" > "$RUNNER_TEMP/validation_output.txt"
70+
echo "status=failed" >> "$GITHUB_OUTPUT"
71+
exit 0
72+
fi
73+
74+
if [ $status -ne 0 ]; then
75+
echo "::warning::Structure validation skipped because the diff could not be evaluated (exit code $status)."
76+
echo "status=skipped" >> "$GITHUB_OUTPUT"
77+
exit 0
78+
fi
79+
80+
echo "status=passed" >> "$GITHUB_OUTPUT"
81+
82+
- name: Close pull request on failure
83+
if: ${{ steps.validate.outputs.status == 'failed' }}
84+
uses: actions/github-script@v6
85+
with:
86+
github-token: ${{ github.token }}
87+
script: |
88+
const pullNumber = context.payload.pull_request.number;
89+
const owner = context.repo.owner;
90+
const repo = context.repo.repo;
91+
92+
const fs = require('fs');
93+
const output = fs.readFileSync(process.env.RUNNER_TEMP + '/validation_output.txt', 'utf8');
94+
95+
let commentBody = `Thank you for your contribution. However, it doesn't comply with our contributing guidelines.\n\n`;
96+
97+
// Check if the error is about invalid file/folder names
98+
if (output.includes('Names cannot end with a period') ||
99+
output.includes('Names cannot end with a space') ||
100+
output.includes('is a reserved name on Windows') ||
101+
output.includes('Contains characters that are invalid')) {
102+
commentBody += `**❌ Invalid File/Folder Names Detected**\n\n`;
103+
commentBody += `Your contribution contains file or folder names that will break when syncing to local file systems (especially Windows):\n\n`;
104+
commentBody += `\`\`\`\n${output}\n\`\`\`\n\n`;
105+
commentBody += `**Common issues:**\n`;
106+
commentBody += `- Folder/file names ending with a period (.) - not allowed on Windows\n`;
107+
commentBody += `- Folder/file names ending with spaces - not allowed on Windows\n`;
108+
commentBody += `- Reserved names like CON, PRN, AUX, NUL, COM1-9, LPT1-9 - not allowed on Windows\n`;
109+
commentBody += `- Invalid characters: < > : " | ? * or control characters\n\n`;
110+
commentBody += `Please rename these files/folders to be compatible with all operating systems.\n\n`;
111+
} else {
112+
commentBody += `As a reminder, the general requirements (as outlined in the [CONTRIBUTING.md file](https://github.com/ServiceNowDevProgram/code-snippets/blob/main/CONTRIBUTING.md)) are the following: follow the folder+subfolder guidelines and include a README.md file explaining what the code snippet does.\n\n`;
113+
commentBody += `**Validation errors:**\n\`\`\`\n${output}\n\`\`\`\n\n`;
114+
}
115+
116+
commentBody += `Review your contribution against the guidelines and make the necessary adjustments. Closing this for now. Once you make additional changes, feel free to re-open this Pull Request or create a new one.`;
117+
118+
await github.rest.issues.createComment({
119+
owner,
120+
repo,
121+
issue_number: pullNumber,
122+
body: commentBody.trim()
123+
});
124+
125+
await github.rest.pulls.update({
126+
owner,
127+
repo,
128+
pull_number: pullNumber,
129+
state: 'closed'
130+
});
131+
132+
- name: Mark job as failed if validation failed
133+
if: ${{ steps.validate.outputs.status == 'failed' }}
134+
run: exit 1

0 commit comments

Comments
 (0)