feat: Add automated MPL-2.0 license header validation (#2995)
improve the suggestion improve the code
This commit is contained in:
@@ -54,6 +54,31 @@ jobs:
|
|||||||
- name: Run HTMLHint
|
- name: Run HTMLHint
|
||||||
run: npx htmlhint "src" --config .htmlhintrc
|
run: npx htmlhint "src" --config .htmlhintrc
|
||||||
|
|
||||||
|
- name: Check file headers
|
||||||
|
run: |
|
||||||
|
echo "Checking for MPL-2.0 license headers in changed files..."
|
||||||
|
|
||||||
|
# Get list of changed files (comparing with base branch)
|
||||||
|
if [ "${{ github.event_name }}" == "pull_request" ]; then
|
||||||
|
BASE_SHA="${{ github.event.pull_request.base.sha }}"
|
||||||
|
else
|
||||||
|
BASE_SHA="${{ github.event.before }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get changed files
|
||||||
|
CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT $BASE_SHA HEAD | grep -E '\.(ts|js|html|css|scss)$' | grep -v -E '\.(spec\.ts|spec\.js|d\.ts)$' | grep -v -E '(environment\.ts|environment\.prod\.ts|polyfills\.ts|node_modules)' || true)
|
||||||
|
|
||||||
|
if [ -z "$CHANGED_FILES" ]; then
|
||||||
|
echo "✅ No relevant files changed"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "📋 Checking $(echo "$CHANGED_FILES" | wc -l) changed file(s)..."
|
||||||
|
echo "$CHANGED_FILES"
|
||||||
|
|
||||||
|
# Run header check on changed files
|
||||||
|
echo "$CHANGED_FILES" | xargs node scripts/check-file-headers.js
|
||||||
|
|
||||||
- name: Run production build
|
- name: Run production build
|
||||||
run: npm run build:prod
|
run: npm run build:prod
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
name: License Header Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main, dev, dev-angular-19]
|
||||||
|
paths:
|
||||||
|
- 'src/**/*.ts'
|
||||||
|
- 'src/**/*.js'
|
||||||
|
- 'src/**/*.html'
|
||||||
|
- 'src/**/*.css'
|
||||||
|
- 'src/**/*.scss'
|
||||||
|
workflow_dispatch: # Allow manual trigger
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-headers:
|
||||||
|
name: Validate MPL-2.0 Headers
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Use Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Check file headers
|
||||||
|
run: |
|
||||||
|
echo "Checking for MPL-2.0 license headers in changed files..."
|
||||||
|
|
||||||
|
# Get list of changed files in PR
|
||||||
|
BASE_SHA="${{ github.event.pull_request.base.sha }}"
|
||||||
|
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
|
||||||
|
|
||||||
|
# Get changed files (added, copied, modified, renamed, type changed)
|
||||||
|
CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT $BASE_SHA $HEAD_SHA | \
|
||||||
|
grep -E '\.(ts|js|html|css|scss)$' | \
|
||||||
|
grep -v -E '\.(spec\.ts|spec\.js|d\.ts)$' | \
|
||||||
|
grep -v -E '(environment\.ts|environment\.prod\.ts|polyfills\.ts|setup-jest\.ts|jest\.config\.ts|node_modules)' || true)
|
||||||
|
|
||||||
|
if [ -z "$CHANGED_FILES" ]; then
|
||||||
|
echo "No relevant source files changed in this PR"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Count files
|
||||||
|
FILE_COUNT=$(echo "$CHANGED_FILES" | wc -l)
|
||||||
|
echo "Checking $FILE_COUNT changed file(s)..."
|
||||||
|
echo ""
|
||||||
|
echo "$CHANGED_FILES"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Run the header check
|
||||||
|
echo "$CHANGED_FILES" | xargs node scripts/check-file-headers.js
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "All changed files have valid MPL-2.0 headers!"
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "Header validation failed!"
|
||||||
|
echo ""
|
||||||
|
echo "To fix locally:"
|
||||||
|
echo " npm run headers:add -- <file-path>"
|
||||||
|
echo ""
|
||||||
|
echo " Or add the header manually (see docs/FILE_HEADER_VALIDATION.md)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Comment on PR (if failed)
|
||||||
|
if: failure() && github.event_name == 'pull_request'
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
github.rest.issues.createComment({
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
body: '## License Header Validation Failed\n\n' +
|
||||||
|
'Some files are missing the required MPL-2.0 license header.\n\n' +
|
||||||
|
'### How to fix:\n\n' +
|
||||||
|
'1. **Automatically add headers:**\n' +
|
||||||
|
' ```bash\n' +
|
||||||
|
' npm run headers:add -- <file-path>\n' +
|
||||||
|
' ```\n\n' +
|
||||||
|
'2. **Or manually add the header:**\n' +
|
||||||
|
' ```typescript\n' +
|
||||||
|
' /**\n' +
|
||||||
|
' * Copyright since 2025 Mifos Initiative\n' +
|
||||||
|
' *\n' +
|
||||||
|
' * This Source Code Form is subject to the terms of the Mozilla Public\n' +
|
||||||
|
' * License, v. 2.0. If a copy of the MPL was not distributed with this\n' +
|
||||||
|
' * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n' +
|
||||||
|
' */\n' +
|
||||||
|
' ```\n\n' +
|
||||||
|
'3. **Check the build logs above** to see which files need headers.\n\n' +
|
||||||
|
'For more information, see [FILE_HEADER_VALIDATION.md](https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/blob/dev/web-app/docs/FILE_HEADER_VALIDATION.md)'
|
||||||
|
})
|
||||||
+12
-3
@@ -32,7 +32,9 @@
|
|||||||
"playwright": "playwright test",
|
"playwright": "playwright test",
|
||||||
"playwright:ui": "playwright test --ui",
|
"playwright:ui": "playwright test --ui",
|
||||||
"playwright:headed": "playwright test --headed",
|
"playwright:headed": "playwright test --headed",
|
||||||
"playwright:debug": "playwright test --debug"
|
"playwright:debug": "playwright test --debug",
|
||||||
|
"headers:check": "node scripts/check-file-headers.js",
|
||||||
|
"headers:add": "node scripts/add-file-headers.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "20.3.15",
|
"@angular/animations": "20.3.15",
|
||||||
@@ -134,8 +136,15 @@
|
|||||||
"npm": ">= 8.11.0"
|
"npm": ">= 8.11.0"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{js,jsx,ts,tsx}": "eslint --cache",
|
"*.{js,jsx,ts,tsx}": [
|
||||||
"*.{css,scss}": "stylelint",
|
"node scripts/check-file-headers.js",
|
||||||
|
"eslint --cache"
|
||||||
|
],
|
||||||
|
"*.{css,scss}": [
|
||||||
|
"node scripts/check-file-headers.js",
|
||||||
|
"stylelint"
|
||||||
|
],
|
||||||
|
"*.html": "node scripts/check-file-headers.js",
|
||||||
"*": "prettier --check --ignore-unknown"
|
"*": "prettier --check --ignore-unknown"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright since 2025 Mifos Initiative
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Headers to add based on file type
|
||||||
|
const HEADERS = {
|
||||||
|
code: `/**
|
||||||
|
* Copyright since 2025 Mifos Initiative
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
`,
|
||||||
|
html: `<!--
|
||||||
|
Copyright since 2025 Mifos Initiative
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
`
|
||||||
|
};
|
||||||
|
|
||||||
|
// File extensions
|
||||||
|
const FILE_EXTENSIONS = {
|
||||||
|
code: [
|
||||||
|
'.ts',
|
||||||
|
'.js',
|
||||||
|
'.scss',
|
||||||
|
'.css'
|
||||||
|
],
|
||||||
|
html: ['.html']
|
||||||
|
};
|
||||||
|
|
||||||
|
const EXCLUDE_PATTERNS = [
|
||||||
|
'node_modules',
|
||||||
|
'dist',
|
||||||
|
'coverage',
|
||||||
|
'.angular',
|
||||||
|
'e2e',
|
||||||
|
'cypress',
|
||||||
|
'playwright',
|
||||||
|
'.husky',
|
||||||
|
'setup-jest.ts',
|
||||||
|
'jest.config.ts',
|
||||||
|
'cypress.config.ts',
|
||||||
|
'playwright.config.ts',
|
||||||
|
'tailwind.config.js',
|
||||||
|
'proxy.conf.js',
|
||||||
|
'proxy.localhost.conf.js',
|
||||||
|
'version.js',
|
||||||
|
'environment.ts',
|
||||||
|
'environment.prod.ts',
|
||||||
|
'polyfills.ts',
|
||||||
|
'typings.d.ts',
|
||||||
|
'global.d.ts',
|
||||||
|
'.spec.ts',
|
||||||
|
'.spec.js',
|
||||||
|
'tsconfig'
|
||||||
|
];
|
||||||
|
|
||||||
|
function shouldExclude(filePath) {
|
||||||
|
const normalizedPath = filePath.replace(/\\/g, '/');
|
||||||
|
return EXCLUDE_PATTERNS.some((pattern) => {
|
||||||
|
if (pattern.startsWith('.') && pattern.lastIndexOf('.') > 0) {
|
||||||
|
return normalizedPath.endsWith(pattern);
|
||||||
|
}
|
||||||
|
return normalizedPath.includes(pattern);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHeaderType(filePath) {
|
||||||
|
const ext = path.extname(filePath).toLowerCase();
|
||||||
|
|
||||||
|
if (FILE_EXTENSIONS.html.includes(ext)) {
|
||||||
|
return 'html';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FILE_EXTENSIONS.code.includes(ext)) {
|
||||||
|
return 'code';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if file already has a header
|
||||||
|
*/
|
||||||
|
function hasHeader(content, headerType) {
|
||||||
|
if (headerType === 'code') {
|
||||||
|
return content.includes('Copyright since 2025 Mifos Initiative') && content.includes('Mozilla Public License');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headerType === 'html') {
|
||||||
|
return content.includes('Copyright since 2025 Mifos Initiative') && content.includes('Mozilla Public License');
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add header to a file
|
||||||
|
*/
|
||||||
|
function addHeaderToFile(filePath, dryRun = false) {
|
||||||
|
// Skip excluded files
|
||||||
|
if (shouldExclude(filePath)) {
|
||||||
|
console.log(`⏭️ Skipping ${filePath} (excluded)`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerType = getHeaderType(filePath);
|
||||||
|
|
||||||
|
if (!headerType) {
|
||||||
|
console.log(`⏭️ Skipping ${filePath} (unsupported file type)`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let content;
|
||||||
|
try {
|
||||||
|
content = fs.readFileSync(filePath, 'utf8');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Error reading ${filePath}: ${error.message}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if file already has a header
|
||||||
|
if (hasHeader(content, headerType)) {
|
||||||
|
console.log(`✓ ${filePath} (already has header)`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip empty files
|
||||||
|
if (content.trim().length === 0) {
|
||||||
|
console.log(`⏭️ Skipping ${filePath} (empty file)`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const header = HEADERS[headerType];
|
||||||
|
const newContent = header + content;
|
||||||
|
|
||||||
|
if (dryRun) {
|
||||||
|
console.log(`🔍 Would add header to: ${filePath}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(filePath, newContent, 'utf8');
|
||||||
|
console.log(`✅ Added header to: ${filePath}`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Error writing ${filePath}: ${error.message}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function
|
||||||
|
*/
|
||||||
|
function main() {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
// Parse flags
|
||||||
|
const dryRun = args.includes('--dry-run') || args.includes('-d');
|
||||||
|
const files = args.filter((arg) => !arg.startsWith('--') && !arg.startsWith('-'));
|
||||||
|
|
||||||
|
if (files.length === 0) {
|
||||||
|
console.log('Usage: node add-file-headers.js [options] <file1> <file2> ...');
|
||||||
|
console.log('\nOptions:');
|
||||||
|
console.log(' --dry-run, -d Show what would be done without making changes');
|
||||||
|
console.log('\nExample:');
|
||||||
|
console.log(' node add-file-headers.js --dry-run src/app/app.component.ts');
|
||||||
|
console.log(' node add-file-headers.js src/app/**/*.ts');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dryRun) {
|
||||||
|
console.log('🔍 DRY RUN MODE - No files will be modified\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
let modifiedCount = 0;
|
||||||
|
let skippedCount = 0;
|
||||||
|
|
||||||
|
files.forEach((filePath) => {
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
console.error(`❌ File not found: ${filePath}`);
|
||||||
|
skippedCount++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modified = addHeaderToFile(filePath, dryRun);
|
||||||
|
if (modified) {
|
||||||
|
modifiedCount++;
|
||||||
|
} else {
|
||||||
|
skippedCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n' + '='.repeat(50));
|
||||||
|
console.log(`Summary: ${modifiedCount} file(s) ${dryRun ? 'would be' : ''} modified, ${skippedCount} skipped`);
|
||||||
|
|
||||||
|
if (dryRun && modifiedCount > 0) {
|
||||||
|
console.log('\n💡 Run without --dry-run to apply changes');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright since 2025 Mifos Initiative
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Expected header patterns for different file types
|
||||||
|
const HEADERS = {
|
||||||
|
mpl: [
|
||||||
|
'/**',
|
||||||
|
' * Copyright since 2025 Mifos Initiative',
|
||||||
|
' *',
|
||||||
|
' * This Source Code Form is subject to the terms of the Mozilla Public',
|
||||||
|
' * License, v. 2.0. If a copy of the MPL was not distributed with this',
|
||||||
|
' * file, You can obtain one at http://mozilla.org/MPL/2.0/.',
|
||||||
|
' */'
|
||||||
|
],
|
||||||
|
html: [
|
||||||
|
'<!--',
|
||||||
|
' Copyright since 2025 Mifos Initiative',
|
||||||
|
'',
|
||||||
|
' This Source Code Form is subject to the terms of the Mozilla Public',
|
||||||
|
' License, v. 2.0. If a copy of the MPL was not distributed with this',
|
||||||
|
' file, You can obtain one at http://mozilla.org/MPL/2.0/.',
|
||||||
|
'-->'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// File extensions that should have headers
|
||||||
|
const FILE_EXTENSIONS = {
|
||||||
|
code: [
|
||||||
|
'.ts',
|
||||||
|
'.js',
|
||||||
|
'.scss',
|
||||||
|
'.css'
|
||||||
|
],
|
||||||
|
html: ['.html']
|
||||||
|
};
|
||||||
|
|
||||||
|
// Files/patterns to exclude from header checks
|
||||||
|
const EXCLUDE_PATTERNS = [
|
||||||
|
'node_modules',
|
||||||
|
'dist',
|
||||||
|
'coverage',
|
||||||
|
'.angular',
|
||||||
|
'e2e',
|
||||||
|
'cypress',
|
||||||
|
'playwright',
|
||||||
|
'.husky',
|
||||||
|
'setup-jest.ts',
|
||||||
|
'jest.config.ts',
|
||||||
|
'cypress.config.ts',
|
||||||
|
'playwright.config.ts',
|
||||||
|
'tailwind.config.js',
|
||||||
|
'proxy.conf.js',
|
||||||
|
'proxy.localhost.conf.js',
|
||||||
|
'version.js',
|
||||||
|
'environment.ts',
|
||||||
|
'environment.prod.ts',
|
||||||
|
'polyfills.ts',
|
||||||
|
'typings.d.ts',
|
||||||
|
'global.d.ts',
|
||||||
|
'.spec.ts',
|
||||||
|
'.spec.js',
|
||||||
|
'tsconfig'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a file should be excluded from header validation
|
||||||
|
*/
|
||||||
|
function shouldExclude(filePath) {
|
||||||
|
const normalizedPath = filePath.replace(/\\/g, '/');
|
||||||
|
return EXCLUDE_PATTERNS.some((pattern) => {
|
||||||
|
if (pattern.startsWith('.') && pattern.lastIndexOf('.') > 0) {
|
||||||
|
return normalizedPath.endsWith(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizedPath.includes(pattern);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the expected header for a file based on its extension
|
||||||
|
*/
|
||||||
|
function getExpectedHeader(filePath) {
|
||||||
|
const ext = path.extname(filePath).toLowerCase();
|
||||||
|
|
||||||
|
if (FILE_EXTENSIONS.html.includes(ext)) {
|
||||||
|
return HEADERS.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FILE_EXTENSIONS.code.includes(ext)) {
|
||||||
|
return HEADERS.mpl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize whitespace for comparison
|
||||||
|
*/
|
||||||
|
function normalizeWhitespace(str) {
|
||||||
|
return str.trim().replace(/\s+/g, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if file has the correct header
|
||||||
|
*/
|
||||||
|
function hasValidHeader(filePath) {
|
||||||
|
if (shouldExclude(filePath)) {
|
||||||
|
return true; // Skip excluded files
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedHeader = getExpectedHeader(filePath);
|
||||||
|
if (!expectedHeader) {
|
||||||
|
return true; // Skip files without defined header requirements
|
||||||
|
}
|
||||||
|
|
||||||
|
let content;
|
||||||
|
try {
|
||||||
|
content = fs.readFileSync(filePath, 'utf8');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error reading file ${filePath}: ${error.message}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle empty files
|
||||||
|
if (content.trim().length === 0) {
|
||||||
|
return true; // Allow empty files
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = content.split(/\r?\n/);
|
||||||
|
const headerLineCount = expectedHeader.length;
|
||||||
|
|
||||||
|
let startLine = 0;
|
||||||
|
if (lines[0] && lines[0].startsWith('#!')) {
|
||||||
|
startLine = 1;
|
||||||
|
if (lines[1] !== undefined && lines[1].trim() === '') {
|
||||||
|
startLine = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if file has enough lines
|
||||||
|
if (lines.length < startLine + headerLineCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare header lines
|
||||||
|
for (let i = 0; i < headerLineCount; i++) {
|
||||||
|
const actualLine = normalizeWhitespace(lines[startLine + i]);
|
||||||
|
const expectedLine = normalizeWhitespace(expectedHeader[i]);
|
||||||
|
|
||||||
|
if (actualLine !== expectedLine) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the expected header for display
|
||||||
|
*/
|
||||||
|
function formatExpectedHeader(header) {
|
||||||
|
return '\n' + header.join('\n') + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function
|
||||||
|
*/
|
||||||
|
function main() {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (args.length === 0) {
|
||||||
|
console.error('Usage: node check-file-headers.js <file1> <file2> ...');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const filesWithoutHeaders = [];
|
||||||
|
|
||||||
|
args.forEach((filePath) => {
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
console.error(`File not found: ${filePath}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasValidHeader(filePath)) {
|
||||||
|
filesWithoutHeaders.push(filePath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filesWithoutHeaders.length > 0) {
|
||||||
|
console.error('\n❌ Header validation failed!\n');
|
||||||
|
console.error('The following files are missing the required MPL-2.0 license header:\n');
|
||||||
|
|
||||||
|
filesWithoutHeaders.forEach((file) => {
|
||||||
|
console.error(` - ${file}`);
|
||||||
|
const expectedHeader = getExpectedHeader(file);
|
||||||
|
if (expectedHeader) {
|
||||||
|
console.error(`\n Expected header:${formatExpectedHeader(expectedHeader)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.error('\n💡 Please add the MPL-2.0 license header to these files.');
|
||||||
|
console.error(' Refer to existing files for the correct format.\n');
|
||||||
|
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ All files have valid headers');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
Reference in New Issue
Block a user