From cbfc87bbe5d8e1406859aab961f495df248c2b52 Mon Sep 17 00:00:00 2001 From: Gabriel Stein Date: Mon, 2 Mar 2026 08:45:46 -0800 Subject: [PATCH 1/5] feat: add copyright date check & fix automation --- .husky/pre-commit | 2 +- package.json | 2 + tools/copyright/sync-header-years.mjs | 138 +++++++++++++++++++++ tools/copyright/sync-header-years.test.mjs | 70 +++++++++++ 4 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 tools/copyright/sync-header-years.mjs create mode 100644 tools/copyright/sync-header-years.test.mjs diff --git a/.husky/pre-commit b/.husky/pre-commit index e75ce0cb6..6a163a88e 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npx lint-staged && npx nx affected:lint && npx nx affected:build +node tools/copyright/sync-header-years.mjs && npx lint-staged && npx nx affected:lint && npx nx affected:build diff --git a/package.json b/package.json index 76ce17a3e..bd3868bfb 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "ci:version": "changeset version && pnpm install --no-frozen-lockfile && pnpm nx format:write --uncommitted", "changeset": "changeset", "commit": "git cz", + "copyright:check": "node ./tools/copyright/sync-header-years.mjs --check", + "copyright:sync": "node ./tools/copyright/sync-header-years.mjs", "docs": "nx affected --target=typedoc", "e2e": "CI=true nx affected:e2e", "format:staged": "pretty-quick --staged", diff --git a/tools/copyright/sync-header-years.mjs b/tools/copyright/sync-header-years.mjs new file mode 100644 index 000000000..9079f1cd6 --- /dev/null +++ b/tools/copyright/sync-header-years.mjs @@ -0,0 +1,138 @@ +#!/usr/bin/env node + +import { execFileSync } from 'node:child_process'; +import { readFileSync, statSync, writeFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +function isCliExecution() { + return process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href; +} + +function run() { + const args = new Set(process.argv.slice(2)); + const checkOnly = args.has('--check'); + const currentYear = new Date().getFullYear(); + + const stagedFiles = getStagedFiles(); + const stagedFileData = []; + const invalidFiles = []; + const changedFiles = []; + + for (const file of stagedFiles) { + if (!isFile(file)) { + continue; + } + const absolutePath = resolve(process.cwd(), file); + const original = safeReadUtf8(absolutePath); + if (original === null) { + continue; + } + stagedFileData.push({ file, absolutePath, original }); + if (hasInvalidPingCopyrightHeader(original)) { + invalidFiles.push(file); + } + } + + if (invalidFiles.length > 0) { + console.error('Invalid Ping copyright header year format in staged files:'); + for (const file of invalidFiles) { + console.error(`- ${file}`); + } + process.exit(1); + } + + for (const { file, absolutePath, original } of stagedFileData) { + const updated = updateCopyrightYears(original, currentYear); + if (updated === original) { + continue; + } + changedFiles.push(file); + if (!checkOnly) { + writeFileSync(absolutePath, updated, 'utf8'); + } + } + + if (!checkOnly && changedFiles.length > 0) { + execFileSync('git', ['add', '--', ...changedFiles], { stdio: 'inherit' }); + } + + if (checkOnly && changedFiles.length > 0) { + console.error('Stale Ping copyright years found in staged files:'); + for (const file of changedFiles) { + console.error(`- ${file}`); + } + process.exit(1); + } +} + +function getStagedFiles() { + const output = execFileSync('git', ['diff', '--cached', '--name-only', '--diff-filter=ACMR'], { + encoding: 'utf8', + }).trim(); + + if (!output) { + return []; + } + return output.split('\n').filter(Boolean); +} + +function isFile(filePath) { + try { + return statSync(filePath).isFile(); + } catch { + return false; + } +} + +function safeReadUtf8(filePath) { + try { + return readFileSync(filePath, 'utf8'); + } catch { + return null; + } +} + +export function updateCopyrightYears(content, year) { + const regex = + /(^.*(?:©\s*|©\s*)?Copyright(?:\s*\(c\))?\s+)(\d{4})(?:([ \t]*-[ \t]*)(\d{4}))?(\s+Ping Identity(?: Corporation)?\b.*$)/gim; + + return content.replace(regex, (_, prefix, startYear, separator, endYear, suffix) => { + const start = Number.parseInt(startYear, 10); + const end = endYear ? Number.parseInt(endYear, 10) : start; + + if (Number.isNaN(start) || Number.isNaN(end) || end >= year) { + return `${prefix}${startYear}${endYear ? `${separator}${endYear}` : ''}${suffix}`; + } + if (!endYear) { + return `${prefix}${startYear} - ${year}${suffix}`; + } + return `${prefix}${startYear}${separator}${year}${suffix}`; + }); +} + +export function hasInvalidPingCopyrightHeader(content) { + const lines = content.split(/\r?\n/); + for (const line of lines) { + if (!MAYBE_PING_COPYRIGHT_LINE_REGEX.test(line)) { + continue; + } + if (!HEADER_COMMENT_LINE_REGEX.test(line)) { + continue; + } + if (!VALID_PING_COPYRIGHT_LINE_REGEX.test(line)) { + return true; + } + } + return false; +} + +const MAYBE_PING_COPYRIGHT_LINE_REGEX = + /(?:©\s*|©\s*)?Copyright(?:\s*\(c\))?.*Ping Identity(?: Corporation)?/i; +const HEADER_COMMENT_LINE_REGEX = /^\s*(?:\/\*+|\*+|\/\/+|#+|', + ].join('\n'); + const actual = updateCopyrightYears(input, 2026); + assert.equal( + actual, + [ + '/* © Copyright 2020-2026 Ping Identity. */', + '', + ].join('\n'), + ); +}); + +test('does not update non-Ping headers', () => { + const input = '/* Copyright 2020-2025 Example Corp. */'; + const actual = updateCopyrightYears(input, 2026); + assert.equal(actual, input); +}); + +test('updates Ping Identity Corporation ranges with spaces and (c)', () => { + const input = '/* Copyright (c) 2023 - 2026 Ping Identity Corporation. All right reserved. */'; + const actual = updateCopyrightYears(input, 2026); + assert.equal( + actual, + '/* Copyright (c) 2023 - 2026 Ping Identity Corporation. All right reserved. */', + ); +}); + +test('updates Ping Identity Corporation stale single year with (c) to range', () => { + const input = '/* Copyright (c) 2025 - 2026 Ping Identity Corporation. All right reserved. */'; + const actual = updateCopyrightYears(input, 2026); + assert.equal( + actual, + '/* Copyright (c) 2025 - 2026 Ping Identity Corporation. All right reserved. */', + ); +}); + +test('flags Ping headers without a valid year', () => { + const input = '/* Copyright Ping Identity Corporation. All right reserved. */'; + assert.equal(hasInvalidPingCopyrightHeader(input), true); +}); + +test('does not flag valid Ping headers', () => { + const input = '/* Copyright (c) 2020 - 2026 Ping Identity Corporation. */'; + assert.equal(hasInvalidPingCopyrightHeader(input), false); +}); + +test('does not flag non-header Ping copyright text', () => { + const input = 'This document is Copyright Ping Identity Corporation.'; + assert.equal(hasInvalidPingCopyrightHeader(input), false); +}); From 2c7f8b333eabd0685e2d2f55cd10899213a143d9 Mon Sep 17 00:00:00 2001 From: Gabriel Stein Date: Tue, 17 Mar 2026 11:49:27 -0700 Subject: [PATCH 2/5] feat: validate space around dates; update tests; --- tools/copyright/sync-header-years.mjs | 15 ++++++++-- tools/copyright/sync-header-years.test.mjs | 34 +++++++++++++++------- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/tools/copyright/sync-header-years.mjs b/tools/copyright/sync-header-years.mjs index 9079f1cd6..b6ae0e026 100644 --- a/tools/copyright/sync-header-years.mjs +++ b/tools/copyright/sync-header-years.mjs @@ -101,13 +101,22 @@ export function updateCopyrightYears(content, year) { const start = Number.parseInt(startYear, 10); const end = endYear ? Number.parseInt(endYear, 10) : start; - if (Number.isNaN(start) || Number.isNaN(end) || end >= year) { + if (Number.isNaN(start) || Number.isNaN(end)) { return `${prefix}${startYear}${endYear ? `${separator}${endYear}` : ''}${suffix}`; } + + const resolvedEnd = end >= year ? end : year; + if (!endYear) { - return `${prefix}${startYear} - ${year}${suffix}`; + // Single year already current — no range needed + if (resolvedEnd === start) { + return `${prefix}${startYear}${suffix}`; + } + return `${prefix}${startYear} - ${resolvedEnd}${suffix}`; } - return `${prefix}${startYear}${separator}${year}${suffix}`; + + // Always normalize separator to ' - ' and bump end year when stale + return `${prefix}${startYear} - ${resolvedEnd}${suffix}`; }); } diff --git a/tools/copyright/sync-header-years.test.mjs b/tools/copyright/sync-header-years.test.mjs index f4f21ee1b..9697be391 100644 --- a/tools/copyright/sync-header-years.test.mjs +++ b/tools/copyright/sync-header-years.test.mjs @@ -4,28 +4,40 @@ import test from 'node:test'; import { hasInvalidPingCopyrightHeader, updateCopyrightYears } from './sync-header-years.mjs'; test('updates stale range end year and keeps start year', () => { - const input = '/* Copyright 2020-2026 Ping Identity. All Rights Reserved */'; + const input = '/* Copyright 2020 - 2026 Ping Identity. All Rights Reserved */'; const actual = updateCopyrightYears(input, 2026); - assert.equal(actual, '/* Copyright 2020-2026 Ping Identity. All Rights Reserved */'); + assert.equal(actual, '/* Copyright 2020 - 2026 Ping Identity. All Rights Reserved */'); }); -test('updates stale single year to a range preserving start year', () => { +test('normalizes separator on an already-current range', () => { + const input = '/* Copyright 2020 - 2026 Ping Identity. All Rights Reserved */'; + const actual = updateCopyrightYears(input, 2026); + assert.equal(actual, '/* Copyright 2020 - 2026 Ping Identity. All Rights Reserved */'); +}); + +test('expands stale single year to a range preserving start year', () => { + const input = '/* Copyright 2020 - 2026 Ping Identity. All Rights Reserved */'; + const actual = updateCopyrightYears(input, 2026); + assert.equal(actual, '/* Copyright 2020 - 2026 Ping Identity. All Rights Reserved */'); +}); + +test('does not change an already-current spaced range', () => { const input = '/* Copyright 2025 - 2026 Ping Identity. All Rights Reserved */'; const actual = updateCopyrightYears(input, 2026); - assert.equal(actual, '/* Copyright 2025 - 2026 Ping Identity. All Rights Reserved */'); + assert.equal(actual, input); }); test('supports © and © variants', () => { const input = [ - '/* © Copyright 2020-2026 Ping Identity. */', - '', + '/* © Copyright 2020 - 2026 Ping Identity. */', + '', ].join('\n'); const actual = updateCopyrightYears(input, 2026); assert.equal( actual, [ - '/* © Copyright 2020-2026 Ping Identity. */', - '', + '/* © Copyright 2020 - 2026 Ping Identity. */', + '', ].join('\n'), ); }); @@ -45,12 +57,12 @@ test('updates Ping Identity Corporation ranges with spaces and (c)', () => { ); }); -test('updates Ping Identity Corporation stale single year with (c) to range', () => { - const input = '/* Copyright (c) 2025 - 2026 Ping Identity Corporation. All right reserved. */'; +test('expands stale single year with (c) to a range for Ping Identity Corporation', () => { + const input = '/* Copyright (c) 2023 - 2026 Ping Identity Corporation. All right reserved. */'; const actual = updateCopyrightYears(input, 2026); assert.equal( actual, - '/* Copyright (c) 2025 - 2026 Ping Identity Corporation. All right reserved. */', + '/* Copyright (c) 2023 - 2026 Ping Identity Corporation. All right reserved. */', ); }); From 30eab18d67d198704f690c1ac525b5b1ecff1fc2 Mon Sep 17 00:00:00 2001 From: Gabriel Stein Date: Wed, 18 Mar 2026 11:49:34 -0700 Subject: [PATCH 3/5] fix: update test input dates From b780c49c91bfd81600d5bf081149ceb93fa5c1e3 Mon Sep 17 00:00:00 2001 From: Gabriel Stein Date: Wed, 18 Mar 2026 11:59:19 -0700 Subject: [PATCH 4/5] fix: add exclude paths to pre-commit date update; update test input dates; --- tools/copyright/sync-header-years.mjs | 14 ++++++++- tools/copyright/sync-header-years.test.mjs | 36 +++++++++++++++++----- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/tools/copyright/sync-header-years.mjs b/tools/copyright/sync-header-years.mjs index b6ae0e026..7036f33f5 100644 --- a/tools/copyright/sync-header-years.mjs +++ b/tools/copyright/sync-header-years.mjs @@ -20,7 +20,7 @@ function run() { const changedFiles = []; for (const file of stagedFiles) { - if (!isFile(file)) { + if (!isFile(file) || isExcluded(file)) { continue; } const absolutePath = resolve(process.cwd(), file); @@ -77,6 +77,18 @@ function getStagedFiles() { return output.split('\n').filter(Boolean); } +export function isExcluded(filePath) { + return EXCLUDE_PATTERNS.some((pattern) => pattern.test(filePath)); +} + +const EXCLUDE_PATTERNS = [ + /\.test\.[cm]?[jt]sx?$/i, + /\.spec\.[cm]?[jt]sx?$/i, + /(^|[/\\])dist[/\\]/, + /(^|[/\\])vendor[/\\]/, + /(^|[/\\])node_modules[/\\]/, +]; + function isFile(filePath) { try { return statSync(filePath).isFile(); diff --git a/tools/copyright/sync-header-years.test.mjs b/tools/copyright/sync-header-years.test.mjs index 9697be391..67b6cd3d2 100644 --- a/tools/copyright/sync-header-years.test.mjs +++ b/tools/copyright/sync-header-years.test.mjs @@ -1,22 +1,26 @@ import assert from 'node:assert/strict'; import test from 'node:test'; -import { hasInvalidPingCopyrightHeader, updateCopyrightYears } from './sync-header-years.mjs'; +import { + hasInvalidPingCopyrightHeader, + isExcluded, + updateCopyrightYears, +} from './sync-header-years.mjs'; test('updates stale range end year and keeps start year', () => { - const input = '/* Copyright 2020 - 2026 Ping Identity. All Rights Reserved */'; + const input = '/* Copyright 2020-2024 Ping Identity. All Rights Reserved */'; const actual = updateCopyrightYears(input, 2026); assert.equal(actual, '/* Copyright 2020 - 2026 Ping Identity. All Rights Reserved */'); }); test('normalizes separator on an already-current range', () => { - const input = '/* Copyright 2020 - 2026 Ping Identity. All Rights Reserved */'; + const input = '/* Copyright 2020-2026 Ping Identity. All Rights Reserved */'; const actual = updateCopyrightYears(input, 2026); assert.equal(actual, '/* Copyright 2020 - 2026 Ping Identity. All Rights Reserved */'); }); test('expands stale single year to a range preserving start year', () => { - const input = '/* Copyright 2020 - 2026 Ping Identity. All Rights Reserved */'; + const input = '/* Copyright 2020 Ping Identity. All Rights Reserved */'; const actual = updateCopyrightYears(input, 2026); assert.equal(actual, '/* Copyright 2020 - 2026 Ping Identity. All Rights Reserved */'); }); @@ -29,8 +33,8 @@ test('does not change an already-current spaced range', () => { test('supports © and © variants', () => { const input = [ - '/* © Copyright 2020 - 2026 Ping Identity. */', - '', + '/* © Copyright 2020-2024 Ping Identity. */', + '', ].join('\n'); const actual = updateCopyrightYears(input, 2026); assert.equal( @@ -49,7 +53,7 @@ test('does not update non-Ping headers', () => { }); test('updates Ping Identity Corporation ranges with spaces and (c)', () => { - const input = '/* Copyright (c) 2023 - 2026 Ping Identity Corporation. All right reserved. */'; + const input = '/* Copyright (c) 2023 - 2024 Ping Identity Corporation. All right reserved. */'; const actual = updateCopyrightYears(input, 2026); assert.equal( actual, @@ -58,7 +62,7 @@ test('updates Ping Identity Corporation ranges with spaces and (c)', () => { }); test('expands stale single year with (c) to a range for Ping Identity Corporation', () => { - const input = '/* Copyright (c) 2023 - 2026 Ping Identity Corporation. All right reserved. */'; + const input = '/* Copyright (c) 2023 Ping Identity Corporation. All right reserved. */'; const actual = updateCopyrightYears(input, 2026); assert.equal( actual, @@ -80,3 +84,19 @@ test('does not flag non-header Ping copyright text', () => { const input = 'This document is Copyright Ping Identity Corporation.'; assert.equal(hasInvalidPingCopyrightHeader(input), false); }); + +test('excludes test files from processing', () => { + assert.equal(isExcluded('src/foo.test.ts'), true); + assert.equal(isExcluded('src/foo.test.mjs'), true); + assert.equal(isExcluded('src/foo.spec.js'), true); +}); + +test('excludes dist and vendor paths from processing', () => { + assert.equal(isExcluded('dist/foo.js'), true); + assert.equal(isExcluded('vendor/lib.js'), true); +}); + +test('does not exclude regular source files', () => { + assert.equal(isExcluded('src/foo.ts'), false); + assert.equal(isExcluded('packages/sdk/src/index.ts'), false); +}); From 2d368311895c595fef409a12c8b7db73123b3845 Mon Sep 17 00:00:00 2001 From: Gabriel Stein Date: Wed, 8 Apr 2026 14:08:02 -0700 Subject: [PATCH 5/5] feat: enforce copyright headers via eslint-plugin-header --- .eslintrc.json | 34 ++++++++++- e2e/autoscript-apps/src/index.ts | 9 +++ e2e/autoscript-suites/config.ts | 5 ++ .../src/environments/environment.prod.ts | 5 ++ e2e/mock-api/src/environments/environment.ts | 5 ++ e2e/token-vault-suites/teardown.ts | 5 ++ package.json | 1 + .../device-client/mock-data/msw-mock-data.ts | 9 +++ .../script-text.mock.data.ts | 4 +- .../src/fr-webauthn/script-parser.ts | 4 +- pnpm-lock.yaml | 22 +++++++- tools/copyright/sync-header-years.mjs | 32 +++++++++++ tools/copyright/sync-header-years.test.mjs | 56 +++++++++++++++++++ 13 files changed, 184 insertions(+), 7 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index d56654ad3..09821ff5c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,7 +1,39 @@ { "root": true, - "plugins": ["@typescript-eslint", "@nx"], + "plugins": ["@typescript-eslint", "@nx", "header"], "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "excludedFiles": [ + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.test.ts", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.spec.jsx", + "**/*.test.js", + "**/*.test.jsx", + "**/*.test.mjs", + "**/*.spec.mjs", + "**/vite*.config.ts", + "**/vitest.setup.ts", + "**/playwright.config.ts", + "**/_polyfills/**", + "tools/**" + ], + "rules": { + "header/header": [ + "warn", + "block", + [ + { + "pattern": "[\\s\\S]*Copyright[\\s\\S]*Ping Identity[\\s\\S]*", + "template": "\n * Copyright (c) Ping Identity Corporation. All rights reserved.\n * This software may be modified and distributed under the terms\n * of the MIT license. See the LICENSE file for details.\n " + } + ] + ] + } + }, { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "rules": { diff --git a/e2e/autoscript-apps/src/index.ts b/e2e/autoscript-apps/src/index.ts index be15a9f01..4d153b792 100644 --- a/e2e/autoscript-apps/src/index.ts +++ b/e2e/autoscript-apps/src/index.ts @@ -1,2 +1,11 @@ +/* + * @forgerock/javascript-sdk + * + * index.ts + * + * Copyright (c) 2021 - 2026 Ping Identity Corporation. All rights reserved. + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ import 'core-js/stable'; import 'regenerator-runtime'; diff --git a/e2e/autoscript-suites/config.ts b/e2e/autoscript-suites/config.ts index 97fcc1e94..3d9c6bd98 100644 --- a/e2e/autoscript-suites/config.ts +++ b/e2e/autoscript-suites/config.ts @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2021 - 2026 Ping Identity Corporation. All rights reserved. + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ import { workspaceRoot } from '@nx/devkit'; import { PlaywrightTestConfig } from '@playwright/test'; import { baseConfig } from './playwright.config'; diff --git a/e2e/mock-api/src/environments/environment.prod.ts b/e2e/mock-api/src/environments/environment.prod.ts index c9669790b..95d059420 100644 --- a/e2e/mock-api/src/environments/environment.prod.ts +++ b/e2e/mock-api/src/environments/environment.prod.ts @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2021 - 2026 Ping Identity Corporation. All rights reserved. + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ export const environment = { production: true, }; diff --git a/e2e/mock-api/src/environments/environment.ts b/e2e/mock-api/src/environments/environment.ts index a24f6ba2c..cd0e59d7b 100644 --- a/e2e/mock-api/src/environments/environment.ts +++ b/e2e/mock-api/src/environments/environment.ts @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2021 - 2026 Ping Identity Corporation. All rights reserved. + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ export const environment = { AM_URL: 'https://openam-crbrl-01.forgeblocks.com/am/', REALM_PATH: 'alpha', diff --git a/e2e/token-vault-suites/teardown.ts b/e2e/token-vault-suites/teardown.ts index 09b49e70b..4290bcfb2 100644 --- a/e2e/token-vault-suites/teardown.ts +++ b/e2e/token-vault-suites/teardown.ts @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2021 - 2026 Ping Identity Corporation. All rights reserved. + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ export default () => { console.log('tests finished'); }; diff --git a/package.json b/package.json index bd3868bfb..0813b36e6 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "esbuild": "^0.19.2", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", + "eslint-plugin-header": "^3.1.1", "eslint-plugin-import": "2.27.5", "eslint-plugin-playwright": "^1.5.1", "eslint-plugin-prettier": "^5.1.3", diff --git a/packages/javascript-sdk/src/device-client/mock-data/msw-mock-data.ts b/packages/javascript-sdk/src/device-client/mock-data/msw-mock-data.ts index c204c0fa8..118e2f5d5 100644 --- a/packages/javascript-sdk/src/device-client/mock-data/msw-mock-data.ts +++ b/packages/javascript-sdk/src/device-client/mock-data/msw-mock-data.ts @@ -1,3 +1,12 @@ +/* + * @forgerock/javascript-sdk + * + * msw-mock-data.ts + * + * Copyright (c) 2025 - 2026 Ping Identity Corporation. All rights reserved. + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ import { GeneralResponse } from '../services/index.js'; import type { OathResponse, diff --git a/packages/javascript-sdk/src/fr-recovery-codes/script-text.mock.data.ts b/packages/javascript-sdk/src/fr-recovery-codes/script-text.mock.data.ts index f5eda7b2a..a9ceb18ec 100644 --- a/packages/javascript-sdk/src/fr-recovery-codes/script-text.mock.data.ts +++ b/packages/javascript-sdk/src/fr-recovery-codes/script-text.mock.data.ts @@ -1,13 +1,13 @@ -/* eslint-disable no-useless-escape */ /* * @forgerock/javascript-sdk * * script-text.mock.data.ts * - * Copyright (c) 2020 - 2025 Ping Identity Corporation. All rights reserved. + * Copyright (c) 2020 - 2026 Ping Identity Corporation. All rights reserved. * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ +/* eslint-disable no-useless-escape */ import type { CallbackType } from '../auth/enums'; diff --git a/packages/javascript-sdk/src/fr-webauthn/script-parser.ts b/packages/javascript-sdk/src/fr-webauthn/script-parser.ts index 2b58927c1..997dc76ef 100644 --- a/packages/javascript-sdk/src/fr-webauthn/script-parser.ts +++ b/packages/javascript-sdk/src/fr-webauthn/script-parser.ts @@ -1,13 +1,13 @@ -/* eslint-disable no-useless-escape */ /* * @forgerock/javascript-sdk * * script-parser.ts * - * Copyright (c) 2020 - 2025 Ping Identity Corporation. All rights reserved. + * Copyright (c) 2020 - 2026 Ping Identity Corporation. All rights reserved. * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ +/* eslint-disable no-useless-escape */ import { WebAuthnOutcomeType } from './enums'; import { ensureArray, getIndexOne, parsePubKeyArray, parseCredentials } from './helpers'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46583f484..308a09931 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -154,6 +154,9 @@ importers: eslint-config-prettier: specifier: 9.1.0 version: 9.1.0(eslint@8.57.0) + eslint-plugin-header: + specifier: ^3.1.1 + version: 3.1.1(eslint@8.57.0) eslint-plugin-import: specifier: 2.27.5 version: 2.27.5(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0) @@ -6522,6 +6525,14 @@ packages: eslint-import-resolver-webpack: optional: true + eslint-plugin-header@3.1.1: + resolution: + { + integrity: sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg==, + } + peerDependencies: + eslint: '>=7.7.0' + eslint-plugin-import@2.27.5: resolution: { @@ -7259,6 +7270,7 @@ packages: integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==, } engines: { node: '>=16' } + deprecated: This package is no longer maintained. For the JavaScript API, please use @conventional-changelog/git-client instead. hasBin: true glob-parent@5.1.2: @@ -7286,6 +7298,7 @@ packages: { integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==, } + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@6.0.4: @@ -7293,14 +7306,14 @@ packages: { integrity: sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==, } - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@7.2.3: resolution: { integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==, } - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-agent@3.0.0: resolution: @@ -12918,6 +12931,7 @@ packages: integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==, } engines: { node: '>=12' } + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-mimetype@3.0.0: resolution: @@ -17704,6 +17718,10 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-plugin-header@3.1.1(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + eslint-plugin-import@2.27.5(@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0): dependencies: array-includes: 3.1.8 diff --git a/tools/copyright/sync-header-years.mjs b/tools/copyright/sync-header-years.mjs index 7036f33f5..d1831af3f 100644 --- a/tools/copyright/sync-header-years.mjs +++ b/tools/copyright/sync-header-years.mjs @@ -42,6 +42,21 @@ function run() { process.exit(1); } + const missingHeaderFiles = []; + for (const { file, original } of stagedFileData) { + if (SOURCE_FILE_PATTERN.test(file) && !hasPingCopyrightHeader(original)) { + missingHeaderFiles.push(file); + } + } + + if (missingHeaderFiles.length > 0) { + console.error('Missing Ping copyright header in staged files:'); + for (const file of missingHeaderFiles) { + console.error(`- ${file}`); + } + process.exit(1); + } + for (const { file, absolutePath, original } of stagedFileData) { const updated = updateCopyrightYears(original, currentYear); if (updated === original) { @@ -87,6 +102,11 @@ const EXCLUDE_PATTERNS = [ /(^|[/\\])dist[/\\]/, /(^|[/\\])vendor[/\\]/, /(^|[/\\])node_modules[/\\]/, + /(^|[/\\])tools[/\\]/, + /(^|[/\\])_polyfills[/\\]/, + /(^|[/\\])vite[^/\\]*\.config\.[cm]?[jt]sx?$/i, + /(^|[/\\])vitest\.setup\.[cm]?[jt]sx?$/i, + /(^|[/\\])playwright\.config\.[cm]?[jt]sx?$/i, ]; function isFile(filePath) { @@ -148,6 +168,18 @@ export function hasInvalidPingCopyrightHeader(content) { return false; } +export function hasPingCopyrightHeader(content) { + const lines = content.split(/\r?\n/); + for (const line of lines) { + if (MAYBE_PING_COPYRIGHT_LINE_REGEX.test(line) && HEADER_COMMENT_LINE_REGEX.test(line)) { + return true; + } + } + return false; +} + +const SOURCE_FILE_PATTERN = /\.[cm]?[jt]sx?$/i; + const MAYBE_PING_COPYRIGHT_LINE_REGEX = /(?:©\s*|©\s*)?Copyright(?:\s*\(c\))?.*Ping Identity(?: Corporation)?/i; const HEADER_COMMENT_LINE_REGEX = /^\s*(?:\/\*+|\*+|\/\/+|#+|