Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
node_modules/
.next/
out/
public/
*.config.js
*.config.mjs
*.config.cts
.storybook/
coverage/
netlify/
cypress/
dist/
build/
next-env.d.ts
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"react-hooks/rules-of-hooks": "error",
"import/no-extraneous-dependencies": [
"error",
{ "devDependencies": ["**/*.test.js", "**/*.spec.js", "**/setupTests.js"] }
{ "devDependencies": ["**/*.test.js", "**/*.test.ts", "**/*.spec.js", "**/setupTests.js", "tests/**/*"] }
],
"no-console": "error",
"no-debugger": "error",
Expand Down
19 changes: 16 additions & 3 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,22 @@ const config = {
coverageReporters: ['text', 'lcov', 'json-summary'],
coverageDirectory: 'coverage',
collectCoverageFrom: ['scripts/**/*.ts'],
coveragePathIgnorePatterns: ['scripts/compose.ts', 'scripts/tools/categorylist.ts', 'scripts/tools/tags-color.ts'],
testMatch: ['**/tests/**/*.test.*', '!**/netlify/**/*.test.*'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json']
coveragePathIgnorePatterns: [
'scripts/compose.ts',
'scripts/tools/categorylist.ts',
'scripts/tools/tags-color.ts',
'scripts/helpers/logger.ts'
],
// Default: all tests
testMatch: [
'**/tests/**/*.test.*',
'**/npm/integrationTests/**/*.test.*',
'!**/netlify/**/*.test.*'
],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1'
}
Comment on lines +17 to +25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid negation in testMatch; move exclusions to testPathIgnorePatterns.

Jest’s testMatch is for positive globs; use testPathIgnorePatterns for excludes to prevent accidental test discovery.

Apply:

   testMatch: [
-    '**/tests/**/*.test.*',
-    '**/npm/integrationTests/**/*.test.*',
-    '!**/netlify/**/*.test.*'
+    '**/tests/**/*.test.*',
+    '**/npm/integrationTests/**/*.test.*'
   ],
+  testPathIgnorePatterns: ['<rootDir>/netlify/'],
🤖 Prompt for AI Agents
In jest.config.js around lines 17 to 25, the testMatch array contains a negated
glob which should not be used for exclusions; remove the
'!**/netlify/**/*.test.*' entry from testMatch and instead add an appropriate
pattern to testPathIgnorePatterns (for example a pattern matching /netlify/ or
'**/netlify/**') so testMatch only contains positive globs and exclusions are
handled via testPathIgnorePatterns.

};

export default config;
7 changes: 7 additions & 0 deletions mdx-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import { mdxComponents } from '@/components/MDX/MDX';
// React component you want, including inline styles,
// components from other libraries, and more.

/**
* Customizes MDX components by merging default components with any provided overrides.
* This function is used by the MDX provider to determine which React components should be used to render MDX content.
*
* @param components - Custom MDX components to override the defaults
* @returns A merged object of default and custom MDX components
*/
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
// Allows customizing built-in components, e.g. to add styling.
Expand Down
35 changes: 35 additions & 0 deletions npm/runners/build-adopters-list-runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { buildAdoptersList } from '@/scripts/adopters';
import { logger } from '@/scripts/helpers/logger';
import { CustomError } from '@/types/errors/CustomError';

/**
* Runs the build adopters list process.
*
* This function invokes the buildAdoptersList script and handles errors,
* logging them with context and letting the top-level .catch handle process exit.
* @throws {CustomError} When the build process fails
*/
async function runBuildAdoptersList(): Promise<void> {
try {
await buildAdoptersList();
} catch (error) {
const customError = CustomError.fromError(error, {
category: 'script',
operation: 'runBuildAdoptersList',
detail: 'Build adopters list runner failed'
});

logger.error('Build adopters list runner failed', customError);

throw customError;
}
}

// Self-executing async function to handle top-level await
(async () => {
try {
await runBuildAdoptersList();
} catch {
process.exit(1);
}
})();
67 changes: 67 additions & 0 deletions npm/runners/build-dashboard-runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';

import { start as buildDashboard } from '@/scripts/dashboard/build-dashboard';
import { logger } from '@/scripts/helpers/logger';
import { CustomError } from '@/types/errors/CustomError';

const currentFilePath = fileURLToPath(import.meta.url);
const currentDirPath = dirname(currentFilePath);

interface BuildDashboardOptions {
outputPath?: string;
}

/**
* Runs the dashboard generation process with configurable options.
*
* This function resolves the path to the dashboard.json output file,
* then invokes the buildDashboard script. It handles errors, logging them with context
* and letting the top-level .catch handle process exit.
*
* @param options - Optional configuration for output path
* @throws {CustomError} If the build process fails or an error occurs in the runner
*/
async function runBuildDashboard(options: BuildDashboardOptions = {}): Promise<void> {
try {
const outputPath = options.outputPath || resolve(currentDirPath, '../../dashboard.json');

await buildDashboard(outputPath);
} catch (error) {
// Create or enhance the error with full context
const customError =
error instanceof CustomError
? error.updateContext({
operation: 'runBuildDashboard',
detail: `Runner failed with output path: ${options.outputPath}`
})
: CustomError.fromError(error, {
category: 'script',
operation: 'runBuildDashboard',
detail: `Build dashboard runner failed with output path: ${options.outputPath}`
});

// Log error with full context
logger.error('Build dashboard runner failed', customError);

throw customError;
}
}

// Run only in non-test environments
if (process.env.NODE_ENV === 'test') {
logger.info('Skipping dashboard build in test environment');
} else {
// Self-executing async function to handle top-level await
(async () => {
try {
await runBuildDashboard();
} catch (error) {
// Ensure we exit with error code
process.exit(1);
}
})();
}

// Export for testing purposes
export { runBuildDashboard };
99 changes: 99 additions & 0 deletions npm/runners/build-finance-info-list-runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import fs from 'fs';
import { resolve } from 'path';

import { buildFinanceInfoList } from '@/scripts/finance';
import { logger } from '@/scripts/helpers/logger';
import { CustomError } from '@/types/errors/CustomError';

interface BuildFinanceInfoOptions {
currentDir?: string;
configDir?: string;
financeDir?: string;
jsonDataDir?: string;
year?: string;
}

const DEFAULT_OPTIONS = {
currentDir: '.',
configDir: 'config',
financeDir: 'finance',
jsonDataDir: 'json-data'
};

/**
* Runs the build finance info list process with configurable options.
*
* This function locates the latest year in the finance directory,
* then invokes the buildFinanceInfoList script for that year.
* It handles errors, logging them with context and letting the top-level .catch handle process exit.
*
* @param options - Optional configuration for directories and year
* @throws {CustomError} If the build process fails or an error occurs in the runner
*/
async function runBuildFinanceInfoList(options: BuildFinanceInfoOptions = {}): Promise<void> {
const config = { ...DEFAULT_OPTIONS, ...options };

const financeDir = resolve(config.currentDir, config.configDir, config.financeDir);

try {
// If year is not provided, find the latest year
let targetYear = config.year;

if (!targetYear) {
const yearsList = fs
.readdirSync(financeDir)
// Filter out any files that are not numbers
.filter((file) => !Number.isNaN(parseFloat(file)))
// Sort the years in descending order
.sort((a, b) => parseFloat(b) - parseFloat(a));

Comment on lines +42 to +49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Robustly detect year directories (avoid parseFloat pitfalls and non-directories).

parseFloat will accept names like "2024-old" and even "2024.json". Filter by directory and a strict 4-digit year.

-      const yearsList = fs
-        .readdirSync(financeDir)
-        // Filter out any files that are not numbers
-        .filter((file) => !Number.isNaN(parseFloat(file)))
-        // Sort the years in descending order
-        .sort((a, b) => parseFloat(b) - parseFloat(a));
+      const yearsList = fs
+        .readdirSync(financeDir, { withFileTypes: true })
+        .filter((d) => d.isDirectory() && /^\d{4}$/.test(d.name))
+        .map((d) => d.name)
+        .sort((a, b) => Number(b) - Number(a));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!targetYear) {
const yearsList = fs
.readdirSync(financeDir)
// Filter out any files that are not numbers
.filter((file) => !Number.isNaN(parseFloat(file)))
// Sort the years in descending order
.sort((a, b) => parseFloat(b) - parseFloat(a));
if (!targetYear) {
const yearsList = fs
.readdirSync(financeDir, { withFileTypes: true })
.filter((d) => d.isDirectory() && /^\d{4}$/.test(d.name))
.map((d) => d.name)
.sort((a, b) => Number(b) - Number(a));
🤖 Prompt for AI Agents
In npm/runners/build-finance-info-list-runner.ts around lines 42 to 49, the code
uses parseFloat on filenames which wrongly accepts names like "2024-old" or
"2024.json" and doesn't ensure entries are directories; replace the current
readdirSync/filter/sort chain with readdirSync(financeDir, { withFileTypes: true
}) then filter Dirent objects by isDirectory() and name.match(/^\d{4}$/) to only
keep strict 4-digit year directories, then sort by numeric value descending
(e.g., (a, b) => Number(b) - Number(a)).

if (yearsList.length === 0) {
throw new CustomError('No finance data found in the finance directory', {
category: 'script',
operation: 'findLatestYear',
detail: `Finance directory: ${financeDir}, Available years: ${yearsList.join(', ')}`
});
}

const [latestYear] = yearsList;

targetYear = latestYear;
}

await buildFinanceInfoList({
currentDir: config.currentDir,
configDir: config.configDir,
financeDir: config.financeDir,
year: targetYear,
jsonDataDir: config.jsonDataDir
});
} catch (error) {
const customError = CustomError.fromError(error, {
category: 'script',
operation: 'runBuildFinanceInfoList',
detail: `Build finance info failed for year: ${config.year}, financeDir: ${financeDir}`
});

logger.error('Build finance info list runner failed', customError);

throw customError;
}
}

// Run only in non-test environments
if (process.env.NODE_ENV === 'test') {
logger.info('Skipping finance info list build in test environment');
} else {
// Self-executing async function to handle top-level await
(async () => {
try {
await runBuildFinanceInfoList();
} catch (error) {
// Ensure we exit with error code
process.exit(1);
}
})();
}

// Export for testing purposes
export { runBuildFinanceInfoList };
59 changes: 59 additions & 0 deletions npm/runners/build-meetings-runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';

import { buildMeetings } from '@/scripts/build-meetings';
import { logger } from '@/scripts/helpers/logger';
import { CustomError } from '@/types/errors/CustomError';

const currentFilePath = fileURLToPath(import.meta.url);
const currentDirPath = dirname(currentFilePath);

interface BuildMeetingsOptions {
outputPath?: string;
}

/**
* Runs the build meetings process with configurable options.
*
* This function resolves the path to the meetings.json configuration file,
* then invokes the buildMeetings script. It handles errors, logging them with context
* and letting the top-level .catch handle process exit.
*
* @param options - Optional configuration for output path
* @throws {CustomError} If the build process fails or an error occurs in the runner
*/
async function runBuildMeetings(options: BuildMeetingsOptions = {}): Promise<void> {
try {
const outputPath = options.outputPath || resolve(currentDirPath, '../../config', 'meetings.json');

await buildMeetings(outputPath);
} catch (error) {
const customError = CustomError.fromError(error, {
category: 'script',
operation: 'runBuildMeetings',
detail: `Build meetings failed with output path: ${options.outputPath}`
});

logger.error('Build meetings runner failed', customError);

throw customError;
}
}

// Run only in non-test environments
if (process.env.NODE_ENV === 'test') {
logger.info('Skipping meetings build in test environment');
} else {
// Self-executing async function to handle top-level await
(async () => {
try {
await runBuildMeetings();
} catch (error) {
// Ensure we exit with error code
process.exit(1);
}
})();
}

// Export for testing purposes
export { runBuildMeetings };
59 changes: 59 additions & 0 deletions npm/runners/build-newsroom-videos-runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';

import { buildNewsroomVideos } from '@/scripts/build-newsroom-videos';
import { logger } from '@/scripts/helpers/logger';
import { CustomError } from '@/types/errors/CustomError';

const currentFilePath = fileURLToPath(import.meta.url);
const currentDirPath = dirname(currentFilePath);

interface BuildNewsroomVideosOptions {
outputPath?: string;
}

/**
* Runs the build newsroom videos process with configurable options.
*
* This function resolves the path to the newsroom_videos.json configuration file,
* then invokes the buildNewsroomVideos script. It handles errors, logging them with context
* and letting the top-level .catch handle process exit.
*
* @param options - Optional configuration for output path
* @throws {CustomError} If the build process fails or an error occurs in the runner
*/
async function runBuildNewsroomVideos(options: BuildNewsroomVideosOptions = {}): Promise<void> {
try {
const outputPath = options.outputPath || resolve(currentDirPath, '../../config', 'newsroom_videos.json');

await buildNewsroomVideos(outputPath);
} catch (error) {
const customError = CustomError.fromError(error, {
category: 'script',
operation: 'runBuildNewsroomVideos',
detail: `Build newsroom videos failed with output path: ${options.outputPath}`
});

logger.error('Build newsroom videos runner failed', customError);

throw customError;
}
}

// Run only in non-test environments
if (process.env.NODE_ENV === 'test') {
logger.info('Skipping newsroom videos build in test environment');
} else {
// Self-executing async function to handle top-level await
(async () => {
try {
await runBuildNewsroomVideos();
} catch (error) {
// Ensure we exit with error code
process.exit(1);
}
})();
}

// Export for testing purposes
export { runBuildNewsroomVideos };
Loading
Loading