From 0fd116c84a307bae3f50237dec8174a84a0420aa Mon Sep 17 00:00:00 2001 From: Yemaneberhan-Lemma <yemaneberhan.lemma@axelerant.com> Date: Mon, 10 Feb 2025 23:15:09 +0300 Subject: [PATCH 01/12] =?UTF-8?q?Auto=20Generate=20structured=20project=20?= =?UTF-8?q?documentation=20using=20Di=C3=A1taxis=20framework?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drupal-doc-gen/index.js | 256 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 drupal-doc-gen/index.js diff --git a/drupal-doc-gen/index.js b/drupal-doc-gen/index.js new file mode 100644 index 00000000..016a4c1f --- /dev/null +++ b/drupal-doc-gen/index.js @@ -0,0 +1,256 @@ +const fs = require('fs-extra'); +const path = require('path'); +const axios = require('axios'); + +// Load environment variables +require('dotenv').config(); +console.log('Using API Key:', process.env.OPENROUTER_API_KEY ? '***' : 'Not found'); + +// Configuration +const API_BASE_URL = process.env.OPENROUTER_API_BASE_URL || 'https://openrouter.ai/api/v1/chat/completions'; +const API_KEY = process.env.OPENROUTER_API_KEY; +const MODEL = process.env.OPENROUTER_MODEL || 'google/gemini-2.0-flash-001'; +const CUSTOM_DIRS = [ + path.resolve(process.cwd(), '../web/modules/custom'), + path.resolve(process.cwd(), '../web/themes/custom') +]; +const OUTPUT_DIRS = { + 'Tutorial': 'docs/tutorials', + 'How-To': 'docs/how-to', + 'Reference': 'docs/reference', + 'Explanation': 'docs/explanation', + 'Misc': 'docs/misc' +}; + +// Utility function to read files if they exist +function readFileIfExists(filePath) { + try { + return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : ''; + } catch (error) { + console.error(`Error reading file ${filePath}:`, error.message); + return ''; + } +} + +// Recursively walk through directories and collect all README.md files +function collectReadmeFiles(directory) { + let readmeFiles = []; + + function walk(dir) { + if (!fs.existsSync(dir)) { + console.warn(`Directory not found: ${dir}`); + return; + } + + const files = fs.readdirSync(dir); + for (const file of files) { + const fullPath = path.join(dir, file); + + // Skip node_modules, vendor, and contrib directories + if (file === 'node_modules' || file === 'vendor' || file === 'contrib') { + console.log(`Skipping directory: ${fullPath}`); + continue; + } + + if (fs.statSync(fullPath).isDirectory()) { + walk(fullPath); // Recursively walk through subdirectories + } else if (file.toLowerCase() === 'readme.md') { + readmeFiles.push(fullPath); + console.log(`Found README: ${fullPath}`); + } + } + } + + walk(directory); + return readmeFiles; +} + +// Read and return the content of all README.md files +function readAllReadmeFiles() { + let readmeContents = []; + for (const dir of CUSTOM_DIRS) { + console.log(`Scanning directory: ${dir}`); + const readmeFiles = collectReadmeFiles(dir); + for (const readmeFile of readmeFiles) { + const content = readFileIfExists(readmeFile); + console.log(`Reading README: ${readmeFile} - Length: ${content.length}`); + readmeContents.push({ name: path.basename(path.dirname(readmeFile)), content }); + } + } + return readmeContents; +} + +// Read the root README.md and major configuration files (package.json, composer.json) +function readProjectFiles() { + const rootReadmePath = path.resolve(process.cwd(), '../README.md'); + const rootReadme = readFileIfExists(rootReadmePath); + const packageJsonPath = path.resolve(process.cwd(), '../package.json'); + const composerJsonPath = path.resolve(process.cwd(), '../composer.json'); + + const packageJson = readFileIfExists(packageJsonPath); + const composerJson = readFileIfExists(composerJsonPath); + + return { rootReadme, packageJson, composerJson }; +} + +// Extract dependencies or tools from package.json and composer.json +function extractDependencies(packageJsonContent, composerJsonContent) { + let dependencies = []; + + if (packageJsonContent) { + try { + const packageJson = JSON.parse(packageJsonContent); + dependencies.push('**NPM Dependencies:**'); + for (const [dep, version] of Object.entries(packageJson.dependencies || {})) { + dependencies.push(`- ${dep}: ${version}`); + } + dependencies.push('**Development Dependencies:**'); + for (const [dep, version] of Object.entries(packageJson.devDependencies || {})) { + dependencies.push(`- ${dep}: ${version}`); + } + } catch (error) { + console.error('Error parsing package.json:', error.message); + } + } + + if (composerJsonContent) { + try { + const composerJson = JSON.parse(composerJsonContent); + dependencies.push('**Composer Dependencies:**'); + for (const [dep, version] of Object.entries(composerJson.require || {})) { + dependencies.push(`- ${dep}: ${version}`); + } + dependencies.push('**Composer Development Dependencies:**'); + for (const [dep, version] of Object.entries(composerJson['require-dev'] || {})) { + dependencies.push(`- ${dep}: ${version}`); + } + } catch (error) { + console.error('Error parsing composer.json:', error.message); + } + } + + return dependencies.join('\n'); +} + +// Truncate long content for better classification (adjust as needed) +function truncateContent(content, maxLength = 2000) { + if (content.length > maxLength) { + return content.substring(0, maxLength) + '... [Content truncated]'; + } + return content; +} + +// Function to classify content and extract structured information using OpenRouter +async function classifyDocumentation(content) { + try { + const truncatedContent = truncateContent(content); + const prompt = `You are a documentation assistant using the Diátaxis framework. The content provided is a README or project-related file. Please do the following: + +1. Classify the content as one of the following categories: Tutorial, How-To, Reference, or Explanation. +2. Provide a 1-2 sentence summary of the content. +3. Identify key sections like "Overview," "Usage," "Dependencies," and "Examples" if available. + +Your response must be structured as follows: + +Category: [One of Tutorial, How-To, Reference, Explanation] +Summary: [A brief summary] +Overview: [Key points or first section from the README] +Usage: [Any usage instructions found in the README] +Dependencies: [List any dependencies if mentioned] +Examples: [List examples if any are present] + +Content: +${truncatedContent}`; + + const response = await axios.post( + API_BASE_URL, + { + model: MODEL, + messages: [ + { + role: 'user', + content: prompt + } + ], + extra_body: {} + }, + { + headers: { + 'Authorization': `Bearer ${API_KEY}`, + 'Content-Type': 'application/json', + } + } + ); + + if (response.data && response.data.choices && response.data.choices[0]) { + return response.data.choices[0].message.content.trim(); + } + + console.warn('Unexpected response format or empty response:', response.data); + return 'Could not classify this content automatically.'; + } catch (error) { + console.error('Error classifying documentation:', error.response?.data || error.message); + return 'Could not classify this content due to an API error.'; + } +} + +// Function to generate the appropriate file name based on the Diátaxis category and source name +function generateFileName(category, sourceName) { + const sanitizedSourceName = sourceName.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase(); + return `${sanitizedSourceName}_${category.toLowerCase()}.md`; +} + +// Function to save generated documentation to the appropriate directory +function saveDocumentation(type, content, fileName) { + const targetDir = OUTPUT_DIRS[type] || OUTPUT_DIRS['Misc']; + fs.outputFileSync(path.join(targetDir, fileName), content); +} + +// Main function to generate documentation +async function generateDocumentation() { + const { rootReadme, packageJson, composerJson } = readProjectFiles(); + const contentSources = [ + { name: 'Project README', content: rootReadme }, + ...readAllReadmeFiles() + ]; + + // Ensure output directories exist + Object.values(OUTPUT_DIRS).forEach(dir => { + fs.ensureDirSync(dir); + }); + + const dependenciesSummary = extractDependencies(packageJson, composerJson); + + // Process files concurrently + await Promise.all(contentSources.map(async (source) => { + try { + const classificationResult = await classifyDocumentation(source.content); + const output = `# ${source.name} + +${classificationResult} + +--- + +**Dependencies and Tools:** + +${dependenciesSummary} + +--- + +**Original README Content:** + +${source.content}`; + const category = classificationResult.match(/Category:\s*(.*)/i)?.[1] || 'Misc'; + const fileName = generateFileName(category, source.name); + saveDocumentation(category, output, fileName); + console.log(`Generated documentation for ${source.name} -> ${category}`); + } catch (error) { + console.error(`Error processing ${source.name}:`, error.message); + } + })); + + console.log('Documentation generation complete!'); +} + +// Run the main function +generateDocumentation(); From 8ce494aafdb787e8d4a825e07a4a8269542399ec Mon Sep 17 00:00:00 2001 From: Yemaneberhan-Lemma <yemaneberhan.lemma@axelerant.com> Date: Mon, 10 Feb 2025 23:21:08 +0300 Subject: [PATCH 02/12] Initial docs generated --- .../docs/explanation/ct_drupal_explanation.md | 112 ++++++++ .../docs/explanation/ct_github_explanation.md | 118 +++++++++ .../explanation/ct_manager_explanation.md | 101 ++++++++ .../docs/how-to/project_readme_how-to.md | 241 ++++++++++++++++++ .../reference/contribtracker_reference.md | 72 ++++++ 5 files changed, 644 insertions(+) create mode 100644 drupal-doc-gen/docs/explanation/ct_drupal_explanation.md create mode 100644 drupal-doc-gen/docs/explanation/ct_github_explanation.md create mode 100644 drupal-doc-gen/docs/explanation/ct_manager_explanation.md create mode 100644 drupal-doc-gen/docs/how-to/project_readme_how-to.md create mode 100644 drupal-doc-gen/docs/reference/contribtracker_reference.md diff --git a/drupal-doc-gen/docs/explanation/ct_drupal_explanation.md b/drupal-doc-gen/docs/explanation/ct_drupal_explanation.md new file mode 100644 index 00000000..9f9d8bef --- /dev/null +++ b/drupal-doc-gen/docs/explanation/ct_drupal_explanation.md @@ -0,0 +1,112 @@ +# ct_drupal + +Category: Explanation +Summary: This module tracks Drupal.org contributions by utilizing the Drupal.org API. It provides a contribution source plugin and a user field to store Drupal.org usernames, fetching contribution data upon cron runs. + +Overview: This module provides a `ContributionSource` plugin which tracks contributions on Drupal.org using a wrapper module across Guzzle 6 to access and use the API provided by drupal.org. It also provides a field on user entity to store each user's Drupal.org username using do_username contrib module as a dependency. + +Usage: Once Drupal.org Contribution Tracker module is installed, you need to edit the users and fill the Drupal.org username in the field "Drupal.org Username" for whom you need to track contribution. On the next cron run, the system will fetch all the users with this field set. + +Dependencies: do_username contrib module, Guzzle 6 (via composer) + +Examples: None are explicitly shown, but the code refers to specific PHP files: `DrupalContribution.php` and `DrupalRetriever.php`. + +--- + +**Dependencies and Tools:** + +**NPM Dependencies:** +**Development Dependencies:** +- markdownlint-cli: ^0.44.0 +- prettier: ^3.4.2 +- remark-cli: ^12.0.1 +- remark-preset-lint-markdown-style-guide: ^6.0.1 +**Composer Dependencies:** +- php: >=8.2 +- axelerant/ct_drupal: * +- axelerant/ct_github: * +- composer/installers: ^2.1 +- cweagans/composer-patches: ^1.7.0 +- drupal/address: ^2.0 +- drupal/admin_toolbar: ^3.0.0 +- drupal/better_exposed_filters: ^7.0 +- drupal/ckeditor: ^1.0 +- drupal/cookies: ^1.2 +- drupal/core: ^10.1 +- drupal/core-composer-scaffold: ^10.1 +- drupal/do_username: ^2.0 +- drupal/field_permissions: ^1.0.0 +- drupal/fixed_block_content: ^1.1 +- drupal/flag: ^4.0 +- drupal/geocoder: ^4.0 +- drupal/geofield: ^1.0 +- drupal/gin: ^4.0.0@alpha +- drupal/google_tag: ^2.0 +- drupal/inline_entity_form: ^3.0 +- drupal/new_relic_rpm: ^2.0 +- drupal/raven: ^6.0 +- drupal/redis: ^1.4.0 +- drupal/select2: ^1.13 +- drupal/slack: ^1.2.0 +- drupal/social_auth: ^4.0 +- drupal/social_auth_google: ^4.0 +- drupal/stable: ^2.0 +- drupal/twig_tweak: ^3.1 +- drush/drush: ^13.0 +- geocoder-php/nominatim-provider: ^5.2 +- npm-asset/jquery-ui-touch-punch: ^0.2.3 +- oomphinc/composer-installers-extender: ^2.0 +- platformsh/config-reader: ^3.0.0 +- vlucas/phpdotenv: ^5.2 +**Composer Development Dependencies:** +- axelerant/db-docker: ^1.1 +- axelerant/drupal-quality-checker: ^1.4 +- behat/mink: ^1.10 +- behat/mink-browserkit-driver: ^2.1 +- phpunit/phpunit: ^9.0 +- symfony/browser-kit: ^7.0 +- weitzman/drupal-test-traits: ^2.0 + +--- + +**Original README Content:** + +# Drupal.org Contribution Tracker + +This module provides functionality for tracking contributions on Drupal.org + + +## Introduction + +This module provides a `ContributionSource` plugin which tracks contributions on +Drupal.org using a wrapper module across Guzzle 6 to access and use the API provided by drupal.org. It also provides a field on user entity to store +each user's Drupal.org username using do_username contrib module as a dependency. + +## Usage + +Once Drupal.org Contribution Tracker module is installed, you need to edit the users +and fill the Drupal.org username in the field "Drupal.org Username" for whom you need to +track contribution. + +On the next cron run, the system will fetch all the users with this field set. +For each user, it will use track all their latest contribution on Drupal.org on +contrib-tracker. + +### Composer dependencies + +Since this module depends on a PHP package to use Drupal.org API, this module's +composer.json must be included in the site's composer.json file. The recommended +way to do this is by using a [path repository](https://www.drupal.org/docs/develop/using-composer/managing-dependencies-for-a-custom-project). + +## Requirements + +## About Drupal Tracker + +These are the main areas of interest in the `ct_drupal` module. + +1. [DrupalContribution.php](web/modules/custom/ct_drupal/src/Plugin/ContributionSource/DrupalContribution.php) is the main plugin. This implements a [ContributionSource](web/modules/custom/ct_manager/src/ContributionSourceInterface.php) plugin which is discovered by a plugin manager in `ct_manager`. +3. [DrupalRetriever.php](web/modules/custom/ct_drupal/src/DrupalRetriever.php) is responsible for transforming the results of a query into objects which are understood by the rest of the system. + +## Processing logic for contributions + +The plugin manager in `ct_manager` would invoke the [plugin](web/modules/custom/ct_drupal/src/Plugin/ContributionSource/DrupalContribution.php) in `ct_drupal`. This would invoke the API to return the latest 100 code contributions and 100 issues (with some caveats). All of this data is passed back to `ct_manager` which decides on how to store it. For more information, see the [README](web/modules/custom/ct_manager/README.md) in `ct_manager`. diff --git a/drupal-doc-gen/docs/explanation/ct_github_explanation.md b/drupal-doc-gen/docs/explanation/ct_github_explanation.md new file mode 100644 index 00000000..df36afe0 --- /dev/null +++ b/drupal-doc-gen/docs/explanation/ct_github_explanation.md @@ -0,0 +1,118 @@ +# ct_github + +Category: Explanation +Summary: This document describes the Github Contribution Tracker module, which allows tracking issue and PR contributions on GitHub using the GraphQL API and storing GitHub usernames on user entities. It details the module's structure, dependencies, configuration, and key files. +Overview: This module provides a `ContributionSource` plugin which tracks contributions on GitHub using its GraphQL API. It also provides a field on user entity to store each user's GitHub username. +Usage: After installation, edit user profiles to include the "Github Username". A cron job then fetches users with a GitHub username and tracks their contributions. The module's `composer.json` must be included in the site's `composer.json` file. +Dependencies: Uses a PHP package for the GitHub API, requiring its `composer.json` to be included in the site's `composer.json`. Requires a GitHub personal access token. +Examples: No examples are present in the provided content. + +--- + +**Dependencies and Tools:** + +**NPM Dependencies:** +**Development Dependencies:** +- markdownlint-cli: ^0.44.0 +- prettier: ^3.4.2 +- remark-cli: ^12.0.1 +- remark-preset-lint-markdown-style-guide: ^6.0.1 +**Composer Dependencies:** +- php: >=8.2 +- axelerant/ct_drupal: * +- axelerant/ct_github: * +- composer/installers: ^2.1 +- cweagans/composer-patches: ^1.7.0 +- drupal/address: ^2.0 +- drupal/admin_toolbar: ^3.0.0 +- drupal/better_exposed_filters: ^7.0 +- drupal/ckeditor: ^1.0 +- drupal/cookies: ^1.2 +- drupal/core: ^10.1 +- drupal/core-composer-scaffold: ^10.1 +- drupal/do_username: ^2.0 +- drupal/field_permissions: ^1.0.0 +- drupal/fixed_block_content: ^1.1 +- drupal/flag: ^4.0 +- drupal/geocoder: ^4.0 +- drupal/geofield: ^1.0 +- drupal/gin: ^4.0.0@alpha +- drupal/google_tag: ^2.0 +- drupal/inline_entity_form: ^3.0 +- drupal/new_relic_rpm: ^2.0 +- drupal/raven: ^6.0 +- drupal/redis: ^1.4.0 +- drupal/select2: ^1.13 +- drupal/slack: ^1.2.0 +- drupal/social_auth: ^4.0 +- drupal/social_auth_google: ^4.0 +- drupal/stable: ^2.0 +- drupal/twig_tweak: ^3.1 +- drush/drush: ^13.0 +- geocoder-php/nominatim-provider: ^5.2 +- npm-asset/jquery-ui-touch-punch: ^0.2.3 +- oomphinc/composer-installers-extender: ^2.0 +- platformsh/config-reader: ^3.0.0 +- vlucas/phpdotenv: ^5.2 +**Composer Development Dependencies:** +- axelerant/db-docker: ^1.1 +- axelerant/drupal-quality-checker: ^1.4 +- behat/mink: ^1.10 +- behat/mink-browserkit-driver: ^2.1 +- phpunit/phpunit: ^9.0 +- symfony/browser-kit: ^7.0 +- weitzman/drupal-test-traits: ^2.0 + +--- + +**Original README Content:** + +# Github Contribution Tracker + +This module provides functionality for tracking issue and PR contributions on Github + +## Table of Contents + +[[_TOC_]] + +## Introduction + +This module provides a `ContributionSource` plugin which tracks contributions on +GitHub using its GraphQL API. It also provides a field on user entity to store +each user's GitHub username. + +## Usage + +Once Github Contribution Tracker module is installed, you need to edit the users +and fill the github username in the field "Github Username" for whom you need to +track contribution. + +On the next cron run, the system will fetch all the users with this field set. +For each user, it will use track all their latest contribution on Github on +contrib-tracker. + +### Composer dependencies + +Since this module depends on a PHP package to use GitHub API, this module's +composer.json must be included in the site's composer.json file. The recommended +way to do this is by using a [path repository](https://www.drupal.org/docs/develop/using-composer/managing-dependencies-for-a-custom-project). + +## Requirements + +You need to obtain a [GitHub personal access token](https://github.com/settings/tokens) +to use this module. The recommended approach is to set the token securely in +an environment variable or by other means and load it in settings.php. As of +this writing, the site is on platform.sh and uses the variables feature to load +this in Drupal configuration. For more info regarding platform variable [check here](https://docs.platform.sh/development/variables.html) + +## About Github Tracker + +These are the main areas of interest in the `ct_github` module. + +1. [GithubContribution.php](web/modules/custom/ct_github/src/Plugin/ContributionSource/GithubContribution.php) is the main plugin. This implements a [ContributionSource](web/modules/custom/ct_manager/src/ContributionSourceInterface.php) plugin which is discovered by a plugin manager in `ct_manager`. +2. [GithubQuery.php](web/modules/custom/ct_github/src/GithubQuery.php) is the class responsible for querying GitHub API. +3. [GithubRetriever.php](web/modules/custom/ct_github/src/GithubRetriever.php) is responsible for transforming the results of a query into objects which are understood by the rest of the system. + +## Processing logic for contributions + +The plugin manager in `ct_manager` would invoke the [plugin](web/modules/custom/ct_github/src/Plugin/ContributionSource/GithubContribution.php) in `ct_github`. This would invoke the API to return the latest 100 code contributions and 100 issues (with some caveats). All of this data is passed back to `ct_manager` which decides on how to store it. For more information, see the [README](web/modules/custom/ct_manager/README.md) in `ct_manager`. diff --git a/drupal-doc-gen/docs/explanation/ct_manager_explanation.md b/drupal-doc-gen/docs/explanation/ct_manager_explanation.md new file mode 100644 index 00000000..4d4113a9 --- /dev/null +++ b/drupal-doc-gen/docs/explanation/ct_manager_explanation.md @@ -0,0 +1,101 @@ +# ct_manager + +Category: Explanation +Summary: This document describes the Contribution Plugin Manager module, which allows creating custom plugins for tracking and storing contributions from various sources and sending Slack notifications. It outlines the module's purpose, usage, and plugin implementation details. +Overview: This module provides a feature to create custom plugins to track and store contributions from different sources and send notifications on a Slack Channel. +Usage: The module runs with a cron job, which looks for plugins of type "ContributionSource" to create instances and process users, tracking and storing contributions and posting Slack notifications for new contributions. +Dependencies: The document references the Drupal Plugin API and mentions the need to implement the ContributionSourceInterface. +Examples: The document points to `ct_github` (`web/modules/custom/ct_github/src/Plugin/ContributionSource/GithubContribution.php`) as an example plugin implementation. + +--- + +**Dependencies and Tools:** + +**NPM Dependencies:** +**Development Dependencies:** +- markdownlint-cli: ^0.44.0 +- prettier: ^3.4.2 +- remark-cli: ^12.0.1 +- remark-preset-lint-markdown-style-guide: ^6.0.1 +**Composer Dependencies:** +- php: >=8.2 +- axelerant/ct_drupal: * +- axelerant/ct_github: * +- composer/installers: ^2.1 +- cweagans/composer-patches: ^1.7.0 +- drupal/address: ^2.0 +- drupal/admin_toolbar: ^3.0.0 +- drupal/better_exposed_filters: ^7.0 +- drupal/ckeditor: ^1.0 +- drupal/cookies: ^1.2 +- drupal/core: ^10.1 +- drupal/core-composer-scaffold: ^10.1 +- drupal/do_username: ^2.0 +- drupal/field_permissions: ^1.0.0 +- drupal/fixed_block_content: ^1.1 +- drupal/flag: ^4.0 +- drupal/geocoder: ^4.0 +- drupal/geofield: ^1.0 +- drupal/gin: ^4.0.0@alpha +- drupal/google_tag: ^2.0 +- drupal/inline_entity_form: ^3.0 +- drupal/new_relic_rpm: ^2.0 +- drupal/raven: ^6.0 +- drupal/redis: ^1.4.0 +- drupal/select2: ^1.13 +- drupal/slack: ^1.2.0 +- drupal/social_auth: ^4.0 +- drupal/social_auth_google: ^4.0 +- drupal/stable: ^2.0 +- drupal/twig_tweak: ^3.1 +- drush/drush: ^13.0 +- geocoder-php/nominatim-provider: ^5.2 +- npm-asset/jquery-ui-touch-punch: ^0.2.3 +- oomphinc/composer-installers-extender: ^2.0 +- platformsh/config-reader: ^3.0.0 +- vlucas/phpdotenv: ^5.2 +**Composer Development Dependencies:** +- axelerant/db-docker: ^1.1 +- axelerant/drupal-quality-checker: ^1.4 +- behat/mink: ^1.10 +- behat/mink-browserkit-driver: ^2.1 +- phpunit/phpunit: ^9.0 +- symfony/browser-kit: ^7.0 +- weitzman/drupal-test-traits: ^2.0 + +--- + +**Original README Content:** + +# Contribution Plugin Manager + +Provides functionality to create custom Plugin of type "ContributionSource". + +# Table of Contents + +[[_TOC_]] + +## Introduction + +This module provides feature to create custom Plugin to track and store +contributions from different source and send notification on Slack Channel. + +## Usage + +When cron runs, this module will look for the plugin of type "ContributionSource" to create Instance of all the plugin and process users. Each user is processed to track and store contributions from different source. Also, notification on Slack channel is posted for contribution which are posted in last 1 hour of cron hit. + +## Plugin Implementation + +To add a new contribution source to the system create a new plugin of type `ContributionSource`. Read the [documention on Plugin API](https://www.drupal.org/docs/drupal-apis/plugin-api) to understand the general concepts for plugins in Drupal. For ContributionSource plugins, make sure you follow these steps: + +- Create the plugin file in `src/Plugin/ContributionSource` directory of your module. +- Annotation for the plugin is `@ContributionSource`. +- The plugin should implement `ContributionSourceInterface`. Implement each of the methods on the interface as per the specific needs. +- Look at the existing implementation in [`ct_github`](web/modules/custom/ct_github/src/Plugin/ContributionSource/GithubContribution.php) for an example. + +## About Contribution Tracker Manager + +Below mentioned are the main criteria for ct_manager module: +1. [ct_manager.module](web/modules/custom/ct_manager/ct_manager.module) This is used to execute action when cron is hit. It createInstance for each plugin and add users in Queue to process. +2. [ContributionSourceInterface](web/modules/custom/ct_manager/src/ContributionSourceInterface.php) This involves the plugins function definition which will be called during cron run. +3. [ProcessUsers](web/modules/custom/ct_manager/src/Plugin/QueueWorker/ProcessUsers.php) This is used to process each user and track contribution from different source. After fetching contributions these are passed to [ContributionTrackerStorage](web/modules/custom/ct_manager/src/ContributionTrackerStorage.php) to store values in database and notification is sent on Slack Channel for CodeContributions posted in last 1 hour. diff --git a/drupal-doc-gen/docs/how-to/project_readme_how-to.md b/drupal-doc-gen/docs/how-to/project_readme_how-to.md new file mode 100644 index 00000000..91d171bf --- /dev/null +++ b/drupal-doc-gen/docs/how-to/project_readme_how-to.md @@ -0,0 +1,241 @@ +# Project README + +Category: How-To +Summary: This README provides instructions for setting up a local development environment for the Contribution Tracker Drupal application. It outlines the necessary tools, prerequisites, and commands to clone the repository, start the DDEV environment, install dependencies, and pull the database. + +Overview: Contribution tracker is a Drupal application built in Drupal 8 for managing community contributions done by the team members. It allows to log various contributions mentioned below - Code contributions, Event contributions, and Non-code contributions. + +Usage: Instructions involve cloning the repository, navigating to the directory, running `ddev start`, installing composer dependencies with `ddev composer install`, and pulling the database using `ddev pull platform`. + +Dependencies: +- Composer (optional if used via DDEV) +- Docker or OrbStack +- DDEV - v1.23.3 +- Git +- NodeJs + +Examples: +- Example git clone command: `git clone git@github.com:contrib-tracker/backend.git` +- Example change directory command: `cd backend` +- Example DDEV start command: `ddev start` +- Example composer install command: `ddev composer install` +- Example ddev pull command: `ddev pull platform` + +--- + +**Dependencies and Tools:** + +**NPM Dependencies:** +**Development Dependencies:** +- markdownlint-cli: ^0.44.0 +- prettier: ^3.4.2 +- remark-cli: ^12.0.1 +- remark-preset-lint-markdown-style-guide: ^6.0.1 +**Composer Dependencies:** +- php: >=8.2 +- axelerant/ct_drupal: * +- axelerant/ct_github: * +- composer/installers: ^2.1 +- cweagans/composer-patches: ^1.7.0 +- drupal/address: ^2.0 +- drupal/admin_toolbar: ^3.0.0 +- drupal/better_exposed_filters: ^7.0 +- drupal/ckeditor: ^1.0 +- drupal/cookies: ^1.2 +- drupal/core: ^10.1 +- drupal/core-composer-scaffold: ^10.1 +- drupal/do_username: ^2.0 +- drupal/field_permissions: ^1.0.0 +- drupal/fixed_block_content: ^1.1 +- drupal/flag: ^4.0 +- drupal/geocoder: ^4.0 +- drupal/geofield: ^1.0 +- drupal/gin: ^4.0.0@alpha +- drupal/google_tag: ^2.0 +- drupal/inline_entity_form: ^3.0 +- drupal/new_relic_rpm: ^2.0 +- drupal/raven: ^6.0 +- drupal/redis: ^1.4.0 +- drupal/select2: ^1.13 +- drupal/slack: ^1.2.0 +- drupal/social_auth: ^4.0 +- drupal/social_auth_google: ^4.0 +- drupal/stable: ^2.0 +- drupal/twig_tweak: ^3.1 +- drush/drush: ^13.0 +- geocoder-php/nominatim-provider: ^5.2 +- npm-asset/jquery-ui-touch-punch: ^0.2.3 +- oomphinc/composer-installers-extender: ^2.0 +- platformsh/config-reader: ^3.0.0 +- vlucas/phpdotenv: ^5.2 +**Composer Development Dependencies:** +- axelerant/db-docker: ^1.1 +- axelerant/drupal-quality-checker: ^1.4 +- behat/mink: ^1.10 +- behat/mink-browserkit-driver: ^2.1 +- phpunit/phpunit: ^9.0 +- symfony/browser-kit: ^7.0 +- weitzman/drupal-test-traits: ^2.0 + +--- + +**Original README Content:** + +[](https://gitpod.io/#https://https://github.com/contrib-tracker/backend) + +# Contribution Tracker + +Contribution tracker is a Drupal application built in Drupal 8 for managing community contributions done by the team members. It allows to log various contributions mentioned below. + +- Code contributions +- Event contributions +- Non-code contributions + +## Features + +- Imports Drupal.org contributions via API +- Supports social login and authentication via google account. + +## Development + +### Tools & Prerequisites + +The following tools are required for setting up the site. Ensure you are using the latest version or at least the minimum version if mentioned below. Also, ensure that you have added [your SSH key to your GitHub account settings](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account). + +- [Composer](https://getcomposer.org/download/) (optional if used via DDEV) +- [Docker](https://docs.docker.com/install/) or [OrbStack](https://orbstack.dev/) +- [DDEV](https://ddev.com/get-started/) - v1.23.3 +- [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +- [NodeJs](https://nodejs.org/en/download) + +*Note*: Ensure you have sufficient RAM (ideally 16 GB, minimum 8 GB) + +### Local environment setup + +Once you have all the tools installed, proceed to run the following to clone the repository. + +```bash +git clone git@github.com:contrib-tracker/backend.git +``` + +Change to the directory of repository and run DDEV to start. + +```bash +cd backend +ddev start +``` + +Once DDEV has been setup successfully, it will display the links in the terminal. Next run the following to fetch all dependencies. + +```bash +ddev composer install +``` + +You can pull the database from platform.sh directly. Make sure that the [PLATFORMSH_CLI_TOKEN is set](https://ddev.readthedocs.io/en/latest/users/providers/platform/). + +```bash +ddev pull platform +``` + +Make sure code changes are updated. + +```bash +ddev drush deploy -y +``` + +### Post Installation + +Generate a one time login link and reset the password through it. + +```bash +ddev drush uli +``` + +Clear the cache using drush + +```bash +ddev drush cr +``` + +### Theme Build + +```bash +cd web/themes/custom/contribtracker && ddev npm install && ddev npm run build && ddev drush cr +``` + +You can access the site at: [https://contribtracker.ddev.site/](https://contribtracker.ddev.site/). + +### Build and Deployment + +Before committing your changes, make sure you are working on the latest codebase by fetching or pulling to make sure you have all the work. + +```bash +git checkout main +git pull origin main +``` + +To initiate a build: + + 1. Create a branch specific to the feature. + + ```bash + git checkout -b <branch-name> + ``` + + 2. Make the required changes and commit + + ```bash + git commit -m "commit-message" + ``` + + 3. Push the changes + + ```bash + git push origin <branch-name> + ``` + +For a better understanding of the entire process and standards, please refer to Axelerant's [Git workflow.](https://axelerant.atlassian.net/wiki/spaces/AH/pages/58982404/Git+Workflow) + +N.B. If provided with user account, you can use the management console of [platform.sh](https://platform.sh/) to handle your branch-merge requests. Please refer to the official [documentation](https://docs.platform.sh/frameworks/drupal8/developing-with-drupal.html#merge-code-changes-to-master) for further information. + +## Testing + +See our [testing docs](./docs/testing.md) for more details. + +### PHPUnit tests + +```bash +# Unit tests +$ ddev phpunit --testsuite unit + +# ExistingSite tests need more flags +$ ddev phpunit --bootstrap=./vendor/weitzman/drupal-test-traits/src/bootstrap-fast.php --configuration ./phpunit.xml --testsuite existing-site +``` + +### Cypress tests + +This needs additional setup. See our [testing docs](./docs/testing.md) for more details. + +```bash +# Run tests +$ ddev cypress-run + +# Interactive mode +$ ddev cypress-open +``` + +## About Contribution Retriever + +Contrib Tracker supports automatically retrieving and saving contributions from drupal.org and Github. This is a broad outline of the logic involved in the overall retrieval of contributions from drupal.org. + +Each user on contrib tracker may set their Drupal.org username in a field in their user profile. A cron job reads all such active users and queues them every 20 mins. This means that comments from drupal.org are retrieved for all users every 20 mins. + +This is the flow of a queued process for each user. [*Outdated*] + +1. Try to read more information about the user from drupal.org (especially the user ID). If it fails, throw an exception and leave. See [ProcessUser::processItem](web/modules/custom/contrib_tracker/src/Plugin/QueueWorker/ProcessUser.php). +2. Retrieve all the comments by the user ([ContributionManager::storeCommentsByDrupalOrgUser](web/modules/custom/contrib_tracker/src/ContributionManager.php)). If the comments span multiple pages, they are read only if required ([ContributionRetriever::getDrupalOrgCommentsByAuthor](web/modules/custom/contrib_tracker/src/DrupalOrg/ContributionRetriever.php)). +3. For each comment ([ContributionManager::storeCommentsByDrupalOrgUser](web/modules/custom/contrib_tracker/src/ContributionManager.php)), + 1. If the comment is already present in the system, leave. + 2. Get the comment's parent issue. Store the issue if it's not already present. + 3. Determine the information about the comment such as the project and number of patches and files, if any. + 4. Store all this information as a "Code contribution" content type. diff --git a/drupal-doc-gen/docs/reference/contribtracker_reference.md b/drupal-doc-gen/docs/reference/contribtracker_reference.md new file mode 100644 index 00000000..2f14e7e1 --- /dev/null +++ b/drupal-doc-gen/docs/reference/contribtracker_reference.md @@ -0,0 +1,72 @@ +# contribtracker + +Category: Reference +Summary: This README entry points to documentation about functional testing using Cypress for the Contrib Tracker theme, both on a portal and its source code. +Overview: Read more about functional testing using Cypress on [the portal](https://idp.axelerant.com/catalog/default/component/contrib-tracker/docs/func_testing/) ([source](/docs/func_testing.md)). +Usage: Not Applicable. +Dependencies: Cypress (implied). +Examples: Not Applicable. + +--- + +**Dependencies and Tools:** + +**NPM Dependencies:** +**Development Dependencies:** +- markdownlint-cli: ^0.44.0 +- prettier: ^3.4.2 +- remark-cli: ^12.0.1 +- remark-preset-lint-markdown-style-guide: ^6.0.1 +**Composer Dependencies:** +- php: >=8.2 +- axelerant/ct_drupal: * +- axelerant/ct_github: * +- composer/installers: ^2.1 +- cweagans/composer-patches: ^1.7.0 +- drupal/address: ^2.0 +- drupal/admin_toolbar: ^3.0.0 +- drupal/better_exposed_filters: ^7.0 +- drupal/ckeditor: ^1.0 +- drupal/cookies: ^1.2 +- drupal/core: ^10.1 +- drupal/core-composer-scaffold: ^10.1 +- drupal/do_username: ^2.0 +- drupal/field_permissions: ^1.0.0 +- drupal/fixed_block_content: ^1.1 +- drupal/flag: ^4.0 +- drupal/geocoder: ^4.0 +- drupal/geofield: ^1.0 +- drupal/gin: ^4.0.0@alpha +- drupal/google_tag: ^2.0 +- drupal/inline_entity_form: ^3.0 +- drupal/new_relic_rpm: ^2.0 +- drupal/raven: ^6.0 +- drupal/redis: ^1.4.0 +- drupal/select2: ^1.13 +- drupal/slack: ^1.2.0 +- drupal/social_auth: ^4.0 +- drupal/social_auth_google: ^4.0 +- drupal/stable: ^2.0 +- drupal/twig_tweak: ^3.1 +- drush/drush: ^13.0 +- geocoder-php/nominatim-provider: ^5.2 +- npm-asset/jquery-ui-touch-punch: ^0.2.3 +- oomphinc/composer-installers-extender: ^2.0 +- platformsh/config-reader: ^3.0.0 +- vlucas/phpdotenv: ^5.2 +**Composer Development Dependencies:** +- axelerant/db-docker: ^1.1 +- axelerant/drupal-quality-checker: ^1.4 +- behat/mink: ^1.10 +- behat/mink-browserkit-driver: ^2.1 +- phpunit/phpunit: ^9.0 +- symfony/browser-kit: ^7.0 +- weitzman/drupal-test-traits: ^2.0 + +--- + +**Original README Content:** + +# Contrib Tracker theme + +Read more about functional testing using Cypress on [the portal](https://idp.axelerant.com/catalog/default/component/contrib-tracker/docs/func_testing/) ([source](/docs/func_testing.md)). From 711d833d1c4ff5299c9f0e8b967c09b4e145efef Mon Sep 17 00:00:00 2001 From: Yemaneberhan-Lemma <yemaneberhan.lemma@axelerant.com> Date: Wed, 12 Feb 2025 00:48:33 +0300 Subject: [PATCH 03/12] Adjusted script to generate summarized document --- ...cking_with_ct_drupal_explanation_1034c8.md | 102 ++++++++ ...ution_plugin_manager_explanation_e6b0d9.md | 97 ++++++++ ...tributions_in_drupal_explanation_6c3723.md | 105 ++++++++ drupal-doc-gen/docs1/index.md | 25 ++ drupal-doc-gen/docs1/index_index_6a992d.md | 11 + .../contrib_tracker_theme_reference_0258f2.md | 69 ++++++ ...ontribution_tracker_how-to guide_7e030b.md | 122 +++++++++ drupal-doc-gen/index02.js | 234 ++++++++++++++++++ 8 files changed, 765 insertions(+) create mode 100644 drupal-doc-gen/docs1/explanation/understanding_drupal_org_contribution_tracking_with_ct_drupal_explanation_1034c8.md create mode 100644 drupal-doc-gen/docs1/explanation/understanding_the_contribution_plugin_manager_explanation_e6b0d9.md create mode 100644 drupal-doc-gen/docs1/explanation/understanding_the_ct_github_module__tracking_github_contributions_in_drupal_explanation_6c3723.md create mode 100644 drupal-doc-gen/docs1/index.md create mode 100644 drupal-doc-gen/docs1/index_index_6a992d.md create mode 100644 drupal-doc-gen/docs1/reference/contrib_tracker_theme_reference_0258f2.md create mode 100644 drupal-doc-gen/docs1/setting_up_a_local_development_environment_for_contribution_tracker_how-to guide_7e030b.md create mode 100644 drupal-doc-gen/index02.js diff --git a/drupal-doc-gen/docs1/explanation/understanding_drupal_org_contribution_tracking_with_ct_drupal_explanation_1034c8.md b/drupal-doc-gen/docs1/explanation/understanding_drupal_org_contribution_tracking_with_ct_drupal_explanation_1034c8.md new file mode 100644 index 00000000..1d7fcd8a --- /dev/null +++ b/drupal-doc-gen/docs1/explanation/understanding_drupal_org_contribution_tracking_with_ct_drupal_explanation_1034c8.md @@ -0,0 +1,102 @@ +# Understanding Drupal.org Contribution Tracking with ct_drupal + +**Overview** + +The `ct_drupal` module for Drupal is designed to track user contributions on Drupal.org. It leverages the Drupal.org API to fetch contribution data and store it within the Drupal site, linking contributions to specific user accounts. This explanation delves into the module's core components and how it facilitates contribution tracking. + +**Key Features** + +* **Contribution Tracking:** The primary function is to monitor and record contributions made by users on Drupal.org. +* **User Integration:** It associates Drupal.org usernames with local Drupal user accounts, enabling accurate contribution tracking for each user. +* **API Integration:** It utilizes the Drupal.org API (wrapped by a Guzzle 6 dependency) to retrieve contribution data. + +**Module Components and Their Roles** + +1. **`DrupalContribution.php` (ContributionSource Plugin):** + + * Located at `web/modules/custom/ct_drupal/src/Plugin/ContributionSource/DrupalContribution.php`. + * Implements the `ContributionSourceInterface` from the `ct_manager` module. + * Acts as the core plugin responsible for interacting with the Drupal.org API. + * It is through this plugin the `ct_manager` module knows how to fetch contribution data from Drupal.org. + +2. **`DrupalRetriever.php`:** + + * Located at `web/modules/custom/ct_drupal/src/DrupalRetriever.php`. + * Transforms the raw data received from the Drupal.org API into a format that can be understood and processed by the rest of the contribution tracking system. + * Responsible for data mapping and object creation tailored to local contribution definitions. + +**How Contribution Tracking Works** + +1. **User Configuration:** Administrators must populate the "Drupal.org Username" field on user profiles within the Drupal site. This establishes the link between the Drupal user and their Drupal.org account. +2. **Cron Processing:** During cron runs, the system identifies users with a Drupal.org username configured. +3. **Data Retrieval:** For each identified user, the `DrupalContribution` plugin is invoked. This plugin interacts with the Drupal.org API. +4. **Data Transformation:** The raw API data is then handled by `DrupalRetriever.php` which tranforms the API results into an object that is understood by the system. +5. **Contribution Storage:** The fetched and transformed contribution data is stored and managed by the `ct_manager` module, allowing for reporting and analysis. + +**Dependencies** + +The module has external dependencies managed through Composer. Specifically, it relies on a PHP package for interacting with the Drupal.org API. The module provides a `composer.json` to declare this dependency, and sites that enable the module should declare it's path as a respository. + +**Relationship with `ct_manager`** + +The `ct_drupal` module functions as a plugin for the `ct_manager` module. The plugin provides a way for the contribution manager module to retrieve contributions from Drupal.org. This is thanks to the plugin implementing `ContributionSourceInterface`. This implies that `ct_manager` offers generic contribution management capabilities, and ct_drupal provides Drupal.org-specific data retrieval within that framework. + +--- + +**Dependencies and Tools:** + +**NPM Dependencies:** +**Development Dependencies:** +- markdownlint-cli: ^0.44.0 +- prettier: ^3.4.2 +- remark-cli: ^12.0.1 +- remark-preset-lint-markdown-style-guide: ^6.0.1 +**Composer Dependencies:** +- php: >=8.2 +- axelerant/ct_drupal: * +- axelerant/ct_github: * +- composer/installers: ^2.1 +- cweagans/composer-patches: ^1.7.0 +- drupal/address: ^2.0 +- drupal/admin_toolbar: ^3.0.0 +- drupal/better_exposed_filters: ^7.0 +- drupal/ckeditor: ^1.0 +- drupal/cookies: ^1.2 +- drupal/core: ^10.1 +- drupal/core-composer-scaffold: ^10.1 +- drupal/do_username: ^2.0 +- drupal/field_permissions: ^1.0.0 +- drupal/fixed_block_content: ^1.1 +- drupal/flag: ^4.0 +- drupal/geocoder: ^4.0 +- drupal/geofield: ^1.0 +- drupal/gin: ^4.0.0@alpha +- drupal/google_tag: ^2.0 +- drupal/inline_entity_form: ^3.0 +- drupal/new_relic_rpm: ^2.0 +- drupal/raven: ^6.0 +- drupal/redis: ^1.4.0 +- drupal/select2: ^1.13 +- drupal/slack: ^1.2.0 +- drupal/social_auth: ^4.0 +- drupal/social_auth_google: ^4.0 +- drupal/stable: ^2.0 +- drupal/twig_tweak: ^3.1 +- drush/drush: ^13.0 +- geocoder-php/nominatim-provider: ^5.2 +- npm-asset/jquery-ui-touch-punch: ^0.2.3 +- oomphinc/composer-installers-extender: ^2.0 +- platformsh/config-reader: ^3.0.0 +- vlucas/phpdotenv: ^5.2 +**Composer Development Dependencies:** +- axelerant/db-docker: ^1.1 +- axelerant/drupal-quality-checker: ^1.4 +- behat/mink: ^1.10 +- behat/mink-browserkit-driver: ^2.1 +- phpunit/phpunit: ^9.0 +- symfony/browser-kit: ^7.0 +- weitzman/drupal-test-traits: ^2.0 + +--- + +**Original Source:** ct_drupal \ No newline at end of file diff --git a/drupal-doc-gen/docs1/explanation/understanding_the_contribution_plugin_manager_explanation_e6b0d9.md b/drupal-doc-gen/docs1/explanation/understanding_the_contribution_plugin_manager_explanation_e6b0d9.md new file mode 100644 index 00000000..6adbd54c --- /dev/null +++ b/drupal-doc-gen/docs1/explanation/understanding_the_contribution_plugin_manager_explanation_e6b0d9.md @@ -0,0 +1,97 @@ +# Understanding the Contribution Plugin Manager + +**Introduction** + +The Contribution Plugin Manager module (ct_manager) provides a flexible way to track and manage user contributions from various sources within a Drupal environment. It allows you to create custom plugins, each responsible for fetching contribution data from a specific origin (e.g., GitHub, GitLab, internal systems). It then processes this data, stores relevant information, and optionally sends notifications about recent contributions, for example, via Slack. + +**Core Functionality** + +At its heart, ct_manager automates the process of gathering, storing, and reporting on user contributions by performing the following steps during a cron run: + +1. **Plugin Discovery:** The module identifies all installed plugins of the `ContributionSource` type. These plugins define how to interact with specific contribution sources. +2. **Plugin Instantiation:** It creates an instance of each discovered `ContributionSource` plugin. +3. **User Processing:** It then adds users to a queue, which it processes in order to track and store contributions for each user from all configured sources. +4. **Contribution Tracking:** For each user, the module uses the `ContributionSource` plugins to retrieve contribution data. +5. **Storage:** Contribution data is stored within the Drupal system (the exact storage mechanism is not specified, but could involve entities, custom tables, etc.). +6. **Notification (Optional):** Optionally, ct\_manager can send notifications (e.g., to a Slack channel) about contributions made within a recent time period (e.g., the last hour). + +**Plugin Architecture** + +The extensibility of ct\_manager comes from its plugin-based architecture. New contribution sources can be added without modifying the core module code. + +* **`ContributionSource` Plugin Type:** The module uses Drupal's Plugin API to define a specific plugin type called `ContributionSource`. +* **Plugin Implementation:** To create a new contribution source, a plugin must: + * Reside in the `src/Plugin/ContributionSource` directory of your module. + * Be annotated with `@ContributionSource`. + * Implement the `ContributionSourceInterface`. This interface defines the methods that the core ct\_manager module will use to interact with the plugin. + * Follow patterns demonstrated in the `ct_github` module example for creating plugins. + +**Key Components** + +* **`ct_manager.module`:** This file executes the main logic during cron runs. It discovers plugins, creates instances of them, and enqueues users for processing. +* **`ContributionSourceInterface.php`:** This interface defines the contract that all `ContributionSource` plugins must adhere to. It specifies the methods that the ct\_manager module will call on each plugin. +* **`ProcessUsers.php`:** This file implements a Drupal QueueWorker plugin that processes the queue of users. For each user, it retrieves contribution data using the configured `ContributionSource` plugins. + +**In Summary** + +The Contribution Plugin Manager is designed to streamline the process of gathering, storing, and reporting user contributions across multiple platforms within a Drupal environment. Its plugin-based architecture enables extensibility and customization to support diverse contribution sources. Understanding its core functionality, plugin architecture, and key components will help developers effectively leverage and extend its capabilities. + +--- + +**Dependencies and Tools:** + +**NPM Dependencies:** +**Development Dependencies:** +- markdownlint-cli: ^0.44.0 +- prettier: ^3.4.2 +- remark-cli: ^12.0.1 +- remark-preset-lint-markdown-style-guide: ^6.0.1 +**Composer Dependencies:** +- php: >=8.2 +- axelerant/ct_drupal: * +- axelerant/ct_github: * +- composer/installers: ^2.1 +- cweagans/composer-patches: ^1.7.0 +- drupal/address: ^2.0 +- drupal/admin_toolbar: ^3.0.0 +- drupal/better_exposed_filters: ^7.0 +- drupal/ckeditor: ^1.0 +- drupal/cookies: ^1.2 +- drupal/core: ^10.1 +- drupal/core-composer-scaffold: ^10.1 +- drupal/do_username: ^2.0 +- drupal/field_permissions: ^1.0.0 +- drupal/fixed_block_content: ^1.1 +- drupal/flag: ^4.0 +- drupal/geocoder: ^4.0 +- drupal/geofield: ^1.0 +- drupal/gin: ^4.0.0@alpha +- drupal/google_tag: ^2.0 +- drupal/inline_entity_form: ^3.0 +- drupal/new_relic_rpm: ^2.0 +- drupal/raven: ^6.0 +- drupal/redis: ^1.4.0 +- drupal/select2: ^1.13 +- drupal/slack: ^1.2.0 +- drupal/social_auth: ^4.0 +- drupal/social_auth_google: ^4.0 +- drupal/stable: ^2.0 +- drupal/twig_tweak: ^3.1 +- drush/drush: ^13.0 +- geocoder-php/nominatim-provider: ^5.2 +- npm-asset/jquery-ui-touch-punch: ^0.2.3 +- oomphinc/composer-installers-extender: ^2.0 +- platformsh/config-reader: ^3.0.0 +- vlucas/phpdotenv: ^5.2 +**Composer Development Dependencies:** +- axelerant/db-docker: ^1.1 +- axelerant/drupal-quality-checker: ^1.4 +- behat/mink: ^1.10 +- behat/mink-browserkit-driver: ^2.1 +- phpunit/phpunit: ^9.0 +- symfony/browser-kit: ^7.0 +- weitzman/drupal-test-traits: ^2.0 + +--- + +**Original Source:** ct_manager \ No newline at end of file diff --git a/drupal-doc-gen/docs1/explanation/understanding_the_ct_github_module__tracking_github_contributions_in_drupal_explanation_6c3723.md b/drupal-doc-gen/docs1/explanation/understanding_the_ct_github_module__tracking_github_contributions_in_drupal_explanation_6c3723.md new file mode 100644 index 00000000..cc7f8a54 --- /dev/null +++ b/drupal-doc-gen/docs1/explanation/understanding_the_ct_github_module__tracking_github_contributions_in_drupal_explanation_6c3723.md @@ -0,0 +1,105 @@ +# Understanding the ct_github Module: Tracking GitHub Contributions in Drupal + +**Introduction:** + +The `ct_github` module is a custom Drupal module designed to track contributions (specifically issues and pull requests) made by users on GitHub. It leverages the GitHub GraphQL API to collect this data and integrates it into a Drupal-based contribution tracking system. + +**Key Concepts:** + +* **ContributionSource Plugin:** The core of the module is implemented as a `ContributionSource` plugin. This plugin conforms to the `ContributionSourceInterface` defined by the `ct_manager` module. This design allows the system to be extended with other contribution sources beyond just GitHub. + +* **GitHub GraphQL API:** The module uses GitHub's GraphQL API for efficient data retrieval. This API allows for precise requests, minimizing the amount of data transferred compared to traditional REST APIs. + +* **User Field:** A custom field is added to the Drupal user entity. This field is used to store the associated GitHub username for each user being tracked. + +* **Cron Integration:** The contribution tracking process is triggered by Drupal's cron system. During a cron run, the module gathers GitHub usernames from users with the custom field populated and queries the GitHub API for their contributions. + +**Module Structure:** + +The module comprises several key files, including: + +* `GithubContribution.php`: (located in `web/modules/custom/ct_github/src/Plugin/ContributionSource/`) This file implements the `ContributionSource` plugin. It contains the logic for connecting to the GitHub API, querying for a user's contributions, and formatting the data for storage within Drupal. It relies on the functionality provided by the `GithubQuery` class. + +* `GithubQuery.php`: (located in `web/modules/custom/ct_github/src/GithubQuery.php`) This handles the specifics of constructing and executing GraphQL queries to the GitHub API. It encapsulates the API interaction logic and simplifies the plugin. This class likely handles authentication and error handling related to communicating with the GitHub API. + +**How it Works:** + +1. **User Configuration:** An administrator configures the Drupal site by adding GitHub usernames to the "Github Username" field for relevant user accounts. + +2. **Cron Trigger:** The Drupal cron system executes the `ct_github` module's update process. + +3. **Data Retrieval:** The module iterates through users who have a GitHub username configured. For each user, it uses the `GithubQuery` class to construct and execute a GraphQL query to the GitHub API. + +4. **Data Processing:** The GitHub API returns data about the user's contributions (issues and pull requests). The `GithubContribution` plugin processes this data, potentially filtering, formatting, and transforming it. + +5. **Storage:** The processed contribution data is stored, likely within the `ct_manager` module's data structure, linking the contribution to the corresponding Drupal user. + +**Requirements and Dependencies:** + +* **GitHub Personal Access Token:** A GitHub personal access token is required to authenticate with the GitHub API. This token should be stored securely, ideally using environment variables or Drupal's configuration management features. + +* **Composer Dependency:** The module likely relies on a PHP package for interacting with the GitHub GraphQL API. This package is managed using Composer. The module needs to ensure its dependencies are properly included and updated by adding the module's composer file as a path repository in the project's composer.json. + +* **ct_manager module:** The module is dependent on the `ct_manager` module, which provides the API and data structures for managing contributions. + +**In summary,** the `ct_github` module provides a streamlined approach to tracking GitHub contributions within a Drupal environment. By leveraging the GitHub GraphQL API and integrating with Drupal's user and cron systems, it enables administrators to monitor and manage user contributions effectively. Understanding the role of the `GithubContribution` plugin, the `GithubQuery` class, and the module's dependencies is crucial for maintaining and extending this functionality. + +--- + +**Dependencies and Tools:** + +**NPM Dependencies:** +**Development Dependencies:** +- markdownlint-cli: ^0.44.0 +- prettier: ^3.4.2 +- remark-cli: ^12.0.1 +- remark-preset-lint-markdown-style-guide: ^6.0.1 +**Composer Dependencies:** +- php: >=8.2 +- axelerant/ct_drupal: * +- axelerant/ct_github: * +- composer/installers: ^2.1 +- cweagans/composer-patches: ^1.7.0 +- drupal/address: ^2.0 +- drupal/admin_toolbar: ^3.0.0 +- drupal/better_exposed_filters: ^7.0 +- drupal/ckeditor: ^1.0 +- drupal/cookies: ^1.2 +- drupal/core: ^10.1 +- drupal/core-composer-scaffold: ^10.1 +- drupal/do_username: ^2.0 +- drupal/field_permissions: ^1.0.0 +- drupal/fixed_block_content: ^1.1 +- drupal/flag: ^4.0 +- drupal/geocoder: ^4.0 +- drupal/geofield: ^1.0 +- drupal/gin: ^4.0.0@alpha +- drupal/google_tag: ^2.0 +- drupal/inline_entity_form: ^3.0 +- drupal/new_relic_rpm: ^2.0 +- drupal/raven: ^6.0 +- drupal/redis: ^1.4.0 +- drupal/select2: ^1.13 +- drupal/slack: ^1.2.0 +- drupal/social_auth: ^4.0 +- drupal/social_auth_google: ^4.0 +- drupal/stable: ^2.0 +- drupal/twig_tweak: ^3.1 +- drush/drush: ^13.0 +- geocoder-php/nominatim-provider: ^5.2 +- npm-asset/jquery-ui-touch-punch: ^0.2.3 +- oomphinc/composer-installers-extender: ^2.0 +- platformsh/config-reader: ^3.0.0 +- vlucas/phpdotenv: ^5.2 +**Composer Development Dependencies:** +- axelerant/db-docker: ^1.1 +- axelerant/drupal-quality-checker: ^1.4 +- behat/mink: ^1.10 +- behat/mink-browserkit-driver: ^2.1 +- phpunit/phpunit: ^9.0 +- symfony/browser-kit: ^7.0 +- weitzman/drupal-test-traits: ^2.0 + +--- + +**Original Source:** ct_github \ No newline at end of file diff --git a/drupal-doc-gen/docs1/index.md b/drupal-doc-gen/docs1/index.md new file mode 100644 index 00000000..755d1814 --- /dev/null +++ b/drupal-doc-gen/docs1/index.md @@ -0,0 +1,25 @@ +# Project Documentation Index + +This is the main index page for the project documentation, organized using the Diátaxis framework. + +Okay, here's a Diátaxis-structured documentation outline based on the provided content: + +- **Tutorials:** + - *(None explicitly suggested in the content, this section requires further elaboration with content)* + +- **How-To Guides:** + - **Installing and Enabling Modules:** - Describes how to install and enable the Drupal.org Contribution Tracker, Github Contribution Tracker, and Contribution Plugin Manager modules. (Implied from "Usage" sections) + - **Configuring User Accounts:** - Explains how to add and configure Drupal.org usernames and Github usernames to user entity. (Implied from "Usage" sections and module descriptions). + - **Creating Custom Contribution Sources:** - Provides instruction on how to create a custom Plugin of type "ContributionSource". (Implied from `ct_manager` README). + +- **Reference:** + - **ContributionSource Plugin API:** - Describes the API for creating `ContributionSource` plugins. (Implied from `ct_manager` module description). + - **Drupal.org API:** - Provides reference information about the Drupal.org API used by the `ct_drupal` module. (Implied) + - **GitHub GraphQL API:** - Provides reference information about the GitHub GraphQL API used by the `ct_github` module. (Implied) + +- **Explanation:** + - **Contribution Tracking Concepts:** - Explains the overall architecture and concepts behind the Contribution Tracker system, including the different types of contributions (code, event, non-code). + - **Contribution Processing Workflow:** Explains how the cron handles tracking contributions and processes users. (Implied from `ct_manager` README). + - **Functional Testing with Cypress:** Explains how to run and interpret functional tests for the Contrib Tracker theme (Based on the `/docs/func_testing.md` reference). + +Navigate to the specific sections for more details. \ No newline at end of file diff --git a/drupal-doc-gen/docs1/index_index_6a992d.md b/drupal-doc-gen/docs1/index_index_6a992d.md new file mode 100644 index 00000000..4c9e9734 --- /dev/null +++ b/drupal-doc-gen/docs1/index_index_6a992d.md @@ -0,0 +1,11 @@ +# Project Documentation Index + +This is the main index page for the project documentation, organized using the Diátaxis framework. + +Navigate to the specific sections below for more details: + +- **[Setting up a Local Development Environment for Contribution Tracker (How-To Guide)](setting_up_a_local_development_environment_for_contribution_tracker_how-to guide_7e030b.md)** +- **[Understanding Drupal.org Contribution Tracking with ct_drupal (Explanation)](understanding_drupal_org_contribution_tracking_with_ct_drupal_explanation_1034c8.md)** +- **[Understanding the ct_github Module: Tracking GitHub Contributions in Drupal (Explanation)](understanding_the_ct_github_module__tracking_github_contributions_in_drupal_explanation_6c3723.md)** +- **[Understanding the Contribution Plugin Manager (Explanation)](understanding_the_contribution_plugin_manager_explanation_e6b0d9.md)** +- **[Contrib Tracker Theme (Reference)](contrib_tracker_theme_reference_0258f2.md)** \ No newline at end of file diff --git a/drupal-doc-gen/docs1/reference/contrib_tracker_theme_reference_0258f2.md b/drupal-doc-gen/docs1/reference/contrib_tracker_theme_reference_0258f2.md new file mode 100644 index 00000000..625294f2 --- /dev/null +++ b/drupal-doc-gen/docs1/reference/contrib_tracker_theme_reference_0258f2.md @@ -0,0 +1,69 @@ +# Contrib Tracker Theme + +This section provides a reference to the Contrib Tracker theme. + +**Functional Testing:** + +* For detailed information on functional testing using Cypress with the Contrib Tracker theme, please refer to: + * [IDP Portal](https://idp.axelerant.com/catalog/default/component/contrib-tracker/docs/func_testing/) + * [Source Code](/docs/func_testing.md) + +--- + +**Dependencies and Tools:** + +**NPM Dependencies:** +**Development Dependencies:** +- markdownlint-cli: ^0.44.0 +- prettier: ^3.4.2 +- remark-cli: ^12.0.1 +- remark-preset-lint-markdown-style-guide: ^6.0.1 +**Composer Dependencies:** +- php: >=8.2 +- axelerant/ct_drupal: * +- axelerant/ct_github: * +- composer/installers: ^2.1 +- cweagans/composer-patches: ^1.7.0 +- drupal/address: ^2.0 +- drupal/admin_toolbar: ^3.0.0 +- drupal/better_exposed_filters: ^7.0 +- drupal/ckeditor: ^1.0 +- drupal/cookies: ^1.2 +- drupal/core: ^10.1 +- drupal/core-composer-scaffold: ^10.1 +- drupal/do_username: ^2.0 +- drupal/field_permissions: ^1.0.0 +- drupal/fixed_block_content: ^1.1 +- drupal/flag: ^4.0 +- drupal/geocoder: ^4.0 +- drupal/geofield: ^1.0 +- drupal/gin: ^4.0.0@alpha +- drupal/google_tag: ^2.0 +- drupal/inline_entity_form: ^3.0 +- drupal/new_relic_rpm: ^2.0 +- drupal/raven: ^6.0 +- drupal/redis: ^1.4.0 +- drupal/select2: ^1.13 +- drupal/slack: ^1.2.0 +- drupal/social_auth: ^4.0 +- drupal/social_auth_google: ^4.0 +- drupal/stable: ^2.0 +- drupal/twig_tweak: ^3.1 +- drush/drush: ^13.0 +- geocoder-php/nominatim-provider: ^5.2 +- npm-asset/jquery-ui-touch-punch: ^0.2.3 +- oomphinc/composer-installers-extender: ^2.0 +- platformsh/config-reader: ^3.0.0 +- vlucas/phpdotenv: ^5.2 +**Composer Development Dependencies:** +- axelerant/db-docker: ^1.1 +- axelerant/drupal-quality-checker: ^1.4 +- behat/mink: ^1.10 +- behat/mink-browserkit-driver: ^2.1 +- phpunit/phpunit: ^9.0 +- symfony/browser-kit: ^7.0 +- weitzman/drupal-test-traits: ^2.0 + +--- + +**Original Source:** contribtracker \ No newline at end of file diff --git a/drupal-doc-gen/docs1/setting_up_a_local_development_environment_for_contribution_tracker_how-to guide_7e030b.md b/drupal-doc-gen/docs1/setting_up_a_local_development_environment_for_contribution_tracker_how-to guide_7e030b.md new file mode 100644 index 00000000..53985cc7 --- /dev/null +++ b/drupal-doc-gen/docs1/setting_up_a_local_development_environment_for_contribution_tracker_how-to guide_7e030b.md @@ -0,0 +1,122 @@ +# Setting up a Local Development Environment for Contribution Tracker + +This guide walks you through setting up a local development environment for the Contribution Tracker Drupal application. + +**Prerequisites:** + +Before you begin, ensure you have the following tools installed and configured: + +* **Docker** or **OrbStack**: Used for containerization. +* **DDEV** (v1.23.3 or later): A tool for local PHP development. +* **Git**: For version control and cloning the repository. +* **NodeJs**: for frontend asset management. + +*Note*: It's recommended to have at least 8GB of RAM, ideally 16GB, for a smooth development experience. + +**Steps:** + +1. **Clone the Repository:** + + Use Git to clone the Contribution Tracker repository to your local machine. Open your terminal and run: + + ```bash + git clone git@github.com:contrib-tracker/backend.git + ``` + +2. **Navigate to the Project Directory:** + + Change your current directory to the cloned repository: + + ```bash + cd backend + ``` + +3. **Start DDEV:** + + Start the DDEV environment. This will set up the necessary containers for your Drupal application. + + ```bash + ddev start + ``` + + DDEV will output URLs to access the newly created instance once the setup is complete. + +4. **Install Composer Dependencies:** + + Use DDEV to run Composer and install the project's dependencies. + + ```bash + ddev composer install + ``` + +5. **Pull the Database (Optional):** + + If you have access to the Platform.sh environment, you can pull a copy of the database: + + * Ensure you have the `PLATFORMSH_CLI_TOKEN` environment variable set. Refer to the [DDEV documentation](https://ddev.readthedocs.io/en/latest/users/providers/platform/) for instructions. + + * Run the following command: + + ```bash + ddev pull platform + ``` + +--- + +**Dependencies and Tools:** + +**NPM Dependencies:** +**Development Dependencies:** +- markdownlint-cli: ^0.44.0 +- prettier: ^3.4.2 +- remark-cli: ^12.0.1 +- remark-preset-lint-markdown-style-guide: ^6.0.1 +**Composer Dependencies:** +- php: >=8.2 +- axelerant/ct_drupal: * +- axelerant/ct_github: * +- composer/installers: ^2.1 +- cweagans/composer-patches: ^1.7.0 +- drupal/address: ^2.0 +- drupal/admin_toolbar: ^3.0.0 +- drupal/better_exposed_filters: ^7.0 +- drupal/ckeditor: ^1.0 +- drupal/cookies: ^1.2 +- drupal/core: ^10.1 +- drupal/core-composer-scaffold: ^10.1 +- drupal/do_username: ^2.0 +- drupal/field_permissions: ^1.0.0 +- drupal/fixed_block_content: ^1.1 +- drupal/flag: ^4.0 +- drupal/geocoder: ^4.0 +- drupal/geofield: ^1.0 +- drupal/gin: ^4.0.0@alpha +- drupal/google_tag: ^2.0 +- drupal/inline_entity_form: ^3.0 +- drupal/new_relic_rpm: ^2.0 +- drupal/raven: ^6.0 +- drupal/redis: ^1.4.0 +- drupal/select2: ^1.13 +- drupal/slack: ^1.2.0 +- drupal/social_auth: ^4.0 +- drupal/social_auth_google: ^4.0 +- drupal/stable: ^2.0 +- drupal/twig_tweak: ^3.1 +- drush/drush: ^13.0 +- geocoder-php/nominatim-provider: ^5.2 +- npm-asset/jquery-ui-touch-punch: ^0.2.3 +- oomphinc/composer-installers-extender: ^2.0 +- platformsh/config-reader: ^3.0.0 +- vlucas/phpdotenv: ^5.2 +**Composer Development Dependencies:** +- axelerant/db-docker: ^1.1 +- axelerant/drupal-quality-checker: ^1.4 +- behat/mink: ^1.10 +- behat/mink-browserkit-driver: ^2.1 +- phpunit/phpunit: ^9.0 +- symfony/browser-kit: ^7.0 +- weitzman/drupal-test-traits: ^2.0 + +--- + +**Original Source:** Project README \ No newline at end of file diff --git a/drupal-doc-gen/index02.js b/drupal-doc-gen/index02.js new file mode 100644 index 00000000..13854b71 --- /dev/null +++ b/drupal-doc-gen/index02.js @@ -0,0 +1,234 @@ +const fs = require('fs-extra'); +const path = require('path'); +const axios = require('axios'); +const crypto = require('crypto'); + +// Load environment variables +require('dotenv').config(); +console.log('Using API Key:', process.env.OPENROUTER_API_KEY ? '***' : 'Not found'); + +// Configuration +const API_BASE_URL = process.env.OPENROUTER_API_BASE_URL || 'https://openrouter.ai/api/v1/chat/completions'; +const API_KEY = process.env.OPENROUTER_API_KEY; +const MODEL = process.env.OPENROUTER_MODEL || 'google/gemini-2.0-flash-001'; +const CUSTOM_DIRS = [ + path.resolve(process.cwd(), '../web/modules/custom'), + path.resolve(process.cwd(), '../web/themes/custom') +]; +const OUTPUT_DIRS = { + 'Tutorial': 'docs1/tutorials', + 'How-To': 'docs1/how-to', + 'Reference': 'docs1/reference', + 'Explanation': 'docs1/explanation', + 'Index': 'docs1' +}; + +// Utility function to read files if they exist +function readFileIfExists(filePath) { + try { + return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : ''; + } catch (error) { + console.error(`Error reading file ${filePath}:`, error.message); + return ''; + } +} + +// Recursively collect all README.md files +function collectReadmeFiles(directory) { + let readmeFiles = []; + function walk(dir) { + if (!fs.existsSync(dir)) { + console.warn(`Directory not found: ${dir}`); + return; + } + const files = fs.readdirSync(dir); + for (const file of files) { + const fullPath = path.join(dir, file); + if (file === 'node_modules' || file === 'vendor' || file === 'contrib') continue; + if (fs.statSync(fullPath).isDirectory()) walk(fullPath); + else if (file.toLowerCase() === 'readme.md') readmeFiles.push(fullPath); + } + } + walk(directory); + return readmeFiles; +} + +// Read and return the content of all README.md files +function readAllReadmeFiles() { + let readmeContents = []; + for (const dir of CUSTOM_DIRS) { + console.log(`Scanning directory: ${dir}`); + const readmeFiles = collectReadmeFiles(dir); + for (const readmeFile of readmeFiles) { + const content = readFileIfExists(readmeFile); + console.log(`Reading README: ${readmeFile} - Length: ${content.length}`); + readmeContents.push({ name: path.basename(path.dirname(readmeFile)), content }); + } + } + return readmeContents; +} + +// Read the root README.md and major configuration files (package.json, composer.json) +function readProjectFiles() { + const rootReadmePath = path.resolve(process.cwd(), '../README.md'); + const rootReadme = readFileIfExists(rootReadmePath); + const packageJsonPath = path.resolve(process.cwd(), '../package.json'); + const composerJsonPath = path.resolve(process.cwd(), '../composer.json'); + + const packageJson = readFileIfExists(packageJsonPath); + const composerJson = readFileIfExists(composerJsonPath); + + return { rootReadme, packageJson, composerJson }; +} + +// Enhanced prompt for AI to classify and generate structured documentation +async function classifyAndGenerateDocumentation(source) { + const truncatedContent = source.content.length > 2000 ? source.content.slice(0, 2000) + '... [truncated]' : source.content; + const prompt = `You are tasked with generating structured documentation using the Diátaxis framework for the provided project source: + +Source: ${source.name} + +Analyze the content and generate appropriate documentation under one of the following categories: Tutorial, How-To Guide, Reference, or Explanation. + +Format the response as follows: + +Category: [One of Tutorial, How-To, Reference, Explanation] +Title: [Title of the documentation] +Content: +[The structured content for this section] + +Project Content: +${truncatedContent}`; + + try { + const response = await axios.post( + API_BASE_URL, + { model: MODEL, messages: [{ role: 'user', content: prompt }] }, + { headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' } } + ); + + const result = response.data.choices[0].message.content.trim(); + + const categoryMatch = result.match(/Category:\s*(.*)/i)?.[1]?.trim(); + const titleMatch = result.match(/Title:\s*(.*)/i)?.[1]?.trim(); + const contentMatch = result.split(/Content:/i)[1]?.trim(); + + if (categoryMatch && titleMatch && contentMatch) { + return { category: categoryMatch, title: titleMatch, content: contentMatch }; + } else { + throw new Error('Unexpected response format'); + } + } catch (error) { + console.error('Error generating documentation:', error.message); + return null; + } +} + +// Enhanced filename generator +function generateFileName(title, category) { + const sanitizedTitle = title.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase(); + const hash = crypto.createHash('md5').update(title).digest('hex').slice(0, 6); + return `${sanitizedTitle}_${category.toLowerCase()}_${hash}.md`; +} + +// Generate the documentation index page +function generateIndexPage(documentationLinks) { + const intro = `# Project Documentation Index + +This is the main index page for the project documentation, organized using the Diátaxis framework. + +Navigate to the specific sections below for more details: +`; + const linksList = documentationLinks.map(({ category, title, fileName }) => `- **[${title} (${category})](${fileName})**`).join('\n'); + return `${intro} +${linksList}`; +} + +// Save content to the appropriate directory +function saveDocumentation(type, title, content) { + const targetDir = OUTPUT_DIRS[type] || OUTPUT_DIRS['Index']; + const fileName = generateFileName(title, type); + fs.outputFileSync(path.join(targetDir, fileName), content); + return fileName; +} + +// Extract dependencies or tools from package.json and composer.json +function extractDependencies(packageJsonContent, composerJsonContent) { + let dependencies = []; + + if (packageJsonContent) { + try { + const packageJson = JSON.parse(packageJsonContent); + dependencies.push('**NPM Dependencies:**'); + for (const [dep, version] of Object.entries(packageJson.dependencies || {})) { + dependencies.push(`- ${dep}: ${version}`); + } + dependencies.push('**Development Dependencies:**'); + for (const [dep, version] of Object.entries(packageJson.devDependencies || {})) { + dependencies.push(`- ${dep}: ${version}`); + } + } catch (error) { + console.error('Error parsing package.json:', error.message); + } + } + + if (composerJsonContent) { + try { + const composerJson = JSON.parse(composerJsonContent); + dependencies.push('**Composer Dependencies:**'); + for (const [dep, version] of Object.entries(composerJson.require || {})) { + dependencies.push(`- ${dep}: ${version}`); + } + dependencies.push('**Composer Development Dependencies:**'); + for (const [dep, version] of Object.entries(composerJson['require-dev'] || {})) { + dependencies.push(`- ${dep}: ${version}`); + } + } catch (error) { + console.error('Error parsing composer.json:', error.message); + } + } + + return dependencies.join('\n'); +} + +// Main function to generate structured documentation +async function generateDocumentation() { + const { rootReadme, packageJson, composerJson } = readProjectFiles(); + const contentSources = [ + { name: 'Project README', content: rootReadme }, + ...readAllReadmeFiles() + ]; + const dependenciesSummary = extractDependencies(packageJson, composerJson); + + let documentationLinks = []; + + for (const source of contentSources) { + const docResult = await classifyAndGenerateDocumentation(source); + if (docResult) { + const { category, title, content } = docResult; + const fullContent = `# ${title} + +${content} + +--- + +**Dependencies and Tools:** + +${dependenciesSummary} + +--- + +**Original Source:** ${source.name}`; + const fileName = saveDocumentation(category, title, fullContent); + documentationLinks.push({ category, title, fileName }); + } + } + + // Generate and save the index page + const indexPageContent = generateIndexPage(documentationLinks); + saveDocumentation('Index', 'index', indexPageContent); + + console.log('Documentation generation complete!'); +} + +generateDocumentation(); From 474d72bb1caea1adfed9996d7c9a6ba95eae714d Mon Sep 17 00:00:00 2001 From: Yemaneberhan-Lemma <yemaneberhan.lemma@axelerant.com> Date: Thu, 13 Feb 2025 00:12:36 +0300 Subject: [PATCH 04/12] Tested Generating README for custom modules --- web/modules/custom/contrib_tracker/README.md | 42 ++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 web/modules/custom/contrib_tracker/README.md diff --git a/web/modules/custom/contrib_tracker/README.md b/web/modules/custom/contrib_tracker/README.md new file mode 100644 index 00000000..2a3a7d3a --- /dev/null +++ b/web/modules/custom/contrib_tracker/README.md @@ -0,0 +1,42 @@ +# Contrib Tracker Module + +## Description +Track contributions from drupal.org + +## Table of Contents +[[_TOC_]] + +## Introduction +Track contributions from drupal.org + +## Usage +This module works by tracking and storing contributions. The main workflow is handled using Queue Workers. + +## Plugin Implementation +This module supports the following custom plugins: +No custom plugins found. + +## Queue Workers +No queue workers found. + +## Services & API +No services defined. + +## Hooks Implemented +No custom hooks implemented. + +## Main Classes & Implementations +- src/Command/IssuesSanitiseCommand.php +- src/EventSubscriber/RavenSubscriber.php + +## Example Usage +Here’s an example of how this module works: + +```php +// Example usage of the module functionality +drush en contrib_tracker; +drush cr; +``` + +Refer to the documentation for more details. + From 6f650868a518d212fc6978a3130ca107d642125c Mon Sep 17 00:00:00 2001 From: Yemaneberhan-Lemma <yemaneberhan.lemma@axelerant.com> Date: Thu, 13 Feb 2025 23:20:54 +0300 Subject: [PATCH 05/12] Changed script that can parse code files to obtain info about the project --- drupal-doc-gen/index03.js | 185 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 drupal-doc-gen/index03.js diff --git a/drupal-doc-gen/index03.js b/drupal-doc-gen/index03.js new file mode 100644 index 00000000..31a0a7c6 --- /dev/null +++ b/drupal-doc-gen/index03.js @@ -0,0 +1,185 @@ +const fs = require('fs-extra'); +const path = require('path'); +const axios = require('axios'); +const crypto = require('crypto'); +const yaml = require('js-yaml'); +const { parse } = require('comment-parser'); + +// Load environment variables +require('dotenv').config(); +console.log('Using API Key:', process.env.OPENROUTER_API_KEY ? '***' : 'Not found'); + +// Configuration +const API_BASE_URL = process.env.OPENROUTER_API_BASE_URL || 'https://openrouter.ai/api/v1/chat/completions'; +const API_KEY = process.env.OPENROUTER_API_KEY; +const MODEL = process.env.OPENROUTER_MODEL || 'google/gemini-2.0-flash-001'; +const CUSTOM_MODULES_DIR = path.resolve(process.cwd(), '../web/modules/custom'); +const OUTPUT_DIRS = { + 'Tutorial': 'docs3/tutorials', + 'How-To': 'docs3/how-to', + 'Reference': 'docs3/reference', + 'Explanation': 'docs3/explanation', + 'Index': 'docs3' +}; + +// Utility function to read files if they exist +function readFileIfExists(filePath) { + try { + return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : ''; + } catch (error) { + console.error(`Error reading file ${filePath}:`, error.message); + return ''; + } +} + +// Extract module-specific details from its source code +function analyzeModuleCode(modulePath) { + let details = { + plugins: [], + queueWorkers: [], + services: [], + hooks: [], + classes: [], + functions: [] + }; + + function walk(dir) { + if (!fs.existsSync(dir)) return; + const files = fs.readdirSync(dir); + for (const file of files) { + const fullPath = path.join(dir, file); + if (fs.statSync(fullPath).isDirectory()) { + walk(fullPath); + } else if (fullPath.endsWith('.php')) { + const content = readFileIfExists(fullPath); + if (/\@ContributionSource/.test(content)) { + details.plugins.push(fullPath.replace(modulePath + '/', '')); + } + if (/implements QueueWorkerBase/.test(content)) { + details.queueWorkers.push(fullPath.replace(modulePath + '/', '')); + } + if (/services\.yml/.test(fullPath)) { + details.services.push(fullPath.replace(modulePath + '/', '')); + } + if (/hook_/.test(content)) { + details.hooks.push(fullPath.replace(modulePath + '/', '')); + } + const classMatches = content.match(/class\s+([A-Za-z0-9_]+)/g); + if (classMatches) { + classMatches.forEach(cls => details.classes.push(cls.replace('class ', ''))); + } + const functionMatches = content.match(/function\s+([A-Za-z0-9_]+)/g); + if (functionMatches) { + functionMatches.forEach(fn => details.functions.push(fn.replace('function ', ''))); + } + } + } + } + + walk(modulePath); + return details; +} + +// Generate README files for custom Drupal modules +function generateModuleReadmes() { + if (!fs.existsSync(CUSTOM_MODULES_DIR)) { + console.warn(`Custom modules directory not found: ${CUSTOM_MODULES_DIR}`); + return []; + } + + const moduleDirs = fs.readdirSync(CUSTOM_MODULES_DIR).filter(dir => { + return fs.statSync(path.join(CUSTOM_MODULES_DIR, dir)).isDirectory(); + }); + + let generatedReadmes = []; + + moduleDirs.forEach(module => { + const modulePath = path.join(CUSTOM_MODULES_DIR, module); + const infoFilePath = path.join(modulePath, `${module}.info.yml`); + const readmePath = path.join(modulePath, 'README.md'); + + if (fs.existsSync(readmePath)) { + console.log(`README already exists for module: ${module}`); + return; + } + + let moduleInfo = {}; + if (fs.existsSync(infoFilePath)) { + try { + moduleInfo = yaml.load(fs.readFileSync(infoFilePath, 'utf8')); + } catch (error) { + console.error(`Error parsing ${infoFilePath}:`, error.message); + } + } + + const codeAnalysis = analyzeModuleCode(modulePath); + + const readmeContent = `# ${moduleInfo.name || module} Module + +` + + `## Description +${moduleInfo.description || 'No description available.'} + +` + + `## Table of Contents +[[_TOC_]] + +` + + `## Introduction +${moduleInfo.description || 'This module provides functionality for Drupal integration.'} + +` + + `## Usage +This module contains the following key functionalities: + +${codeAnalysis.functions.length ? codeAnalysis.functions.map(f => `- Function: ${f}`).join('\n') : 'No specific functions detected.'} + +` + + `## Plugin Implementation +${codeAnalysis.plugins.length ? codeAnalysis.plugins.map(p => `- ${p}`).join('\n') : 'No custom plugins found.'} + +` + + `## Queue Workers +${codeAnalysis.queueWorkers.length ? codeAnalysis.queueWorkers.map(q => `- ${q}`).join('\n') : 'No queue workers found.'} + +` + + `## Services & API +${codeAnalysis.services.length ? codeAnalysis.services.map(s => `- ${s}`).join('\n') : 'No services defined.'} + +` + + `## Hooks Implemented +${codeAnalysis.hooks.length ? codeAnalysis.hooks.map(h => `- ${h}`).join('\n') : 'No custom hooks implemented.'} + +` + + `## Main Classes & Implementations +${codeAnalysis.classes.length ? codeAnalysis.classes.map(c => `- ${c}`).join('\n') : 'No specific classes detected.'} + +` + + `## Example Usage +Here’s an example of how this module works: + +\`\`\`php +// Example usage of the module functionality +drush en ${module}; +drush cr; +\`\`\` + +Refer to the documentation for more details. + +`; + + fs.writeFileSync(readmePath, readmeContent); + generatedReadmes.push({ name: module, content: readmeContent }); + console.log(`Generated README for module: ${module}`); + }); + + return generatedReadmes; +} + +// Main function to generate structured documentation +async function generateDocumentation() { + const generatedReadmes = generateModuleReadmes(); + console.log('Custom module README generation complete.'); +} + +generateDocumentation(); From 329af89f518a2e501d7fa591fa6e1967692b09d3 Mon Sep 17 00:00:00 2001 From: Yemaneberhan-Lemma <yemaneberhan.lemma@axelerant.com> Date: Sat, 15 Feb 2025 02:10:14 +0300 Subject: [PATCH 06/12] reading configuration files --- drupal-doc-gen/index04.js | 129 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 drupal-doc-gen/index04.js diff --git a/drupal-doc-gen/index04.js b/drupal-doc-gen/index04.js new file mode 100644 index 00000000..6d1f0f2f --- /dev/null +++ b/drupal-doc-gen/index04.js @@ -0,0 +1,129 @@ +const fs = require('fs-extra'); +const path = require('path'); +const axios = require('axios'); +const crypto = require('crypto'); +const yaml = require('js-yaml'); +const { parse } = require('comment-parser'); + +// Load environment variables +require('dotenv').config(); +console.log('Using API Key:', process.env.OPENROUTER_API_KEY ? '***' : 'Not found'); + +// Configuration +const API_BASE_URL = process.env.OPENROUTER_API_BASE_URL || 'https://openrouter.ai/api/v1/chat/completions'; +const API_KEY = process.env.OPENROUTER_API_KEY; +const MODEL = process.env.OPENROUTER_MODEL || 'google/gemini-2.0-flash-001'; +const CUSTOM_MODULES_DIR = path.resolve(process.cwd(), '../web/modules/custom'); +const THEMES_DIR = path.resolve(process.cwd(), '../web/themes/custom'); +const ROOT_README = path.resolve(process.cwd(), '../README.md'); +const CONFIG_FILES = [ + path.resolve(process.cwd(), '../composer.json'), + path.resolve(process.cwd(), '../package.json'), + path.resolve(process.cwd(), '../.github/workflows/ci.yml'), + path.resolve(process.cwd(), '../phpunit.xml'), + path.resolve(process.cwd(), '../docker-compose.yml') +]; +const OUTPUT_DIRS = { + 'Tutorial': 'docs3/tutorials', + 'How-To': 'docs3/how-to', + 'Reference': 'docs3/reference', + 'Explanation': 'docs3/explanation', + 'Index': 'docs3' +}; + +// Ensure output directories exist +Object.values(OUTPUT_DIRS).forEach(dir => fs.ensureDirSync(dir)); + +// Utility function to read files if they exist +function readFileIfExists(filePath) { + try { + return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : ''; + } catch (error) { + console.error(`Error reading file ${filePath}:`, error.message); + return ''; + } +} + +// Extract key project insights from README and config files +function extractProjectDetails() { + let details = { overview: '', tools: [], setup: '', workflow: '', testing: '', monitoring: '', ci_cd: '' }; + const readmeContent = readFileIfExists(ROOT_README); + + if (readmeContent) { + details.overview = readmeContent.split('\n')[0]; // First line as project title + details.setup = readmeContent.match(/## Local Environment Setup[\s\S]*?(?=##|$)/gi)?.[0] || ''; + details.workflow = readmeContent.match(/## How to work on the project[\s\S]*?(?=##|$)/gi)?.[0] || ''; + details.testing = readmeContent.match(/## Testing[\s\S]*?(?=##|$)/gi)?.[0] || ''; + details.monitoring = readmeContent.match(/## Monitoring and Logging[\s\S]*?(?=##|$)/gi)?.[0] || ''; + details.ci_cd = readmeContent.match(/## CI\/CD[\s\S]*?(?=##|$)/gi)?.[0] || ''; + } + + CONFIG_FILES.forEach(filePath => { + const content = readFileIfExists(filePath); + if (filePath.endsWith('composer.json') || filePath.endsWith('package.json')) { + try { + const jsonData = JSON.parse(content); + if (jsonData.dependencies) { + details.tools.push(...Object.keys(jsonData.dependencies)); + } + } catch (error) { + console.error(`Error parsing JSON file ${filePath}:`, error.message); + } + } + if (filePath.endsWith('phpunit.xml')) { + details.testing += '\n- PHPUnit detected for testing'; + } + if (filePath.endsWith('docker-compose.yml')) { + details.tools.push('Docker'); + } + }); + return details; +} + +// Generate README files for custom modules and themes +function generateReadme(directory, category) { + if (!fs.existsSync(directory)) return []; + let generatedDocs = []; + + fs.readdirSync(directory).forEach(module => { + const modulePath = path.join(directory, module); + if (fs.statSync(modulePath).isDirectory()) { + const infoFile = path.join(modulePath, `${module}.info.yml`); + const readmeFile = path.join(modulePath, 'README.md'); + + // Read module info + let moduleInfo = {}; + if (fs.existsSync(infoFile)) { + try { + moduleInfo = yaml.load(readFileIfExists(infoFile)); + } catch (error) { + console.error(`Error parsing YAML: ${error.message}`); + } + } + + const readmeContent = `# ${moduleInfo.name || module} Module\n\n## Description\n${moduleInfo.description || 'No description available.'}\n\n## Usage\nRefer to documentation for detailed usage.`; + + fs.writeFileSync(readmeFile, readmeContent); + generatedDocs.push(module); + console.log(`Generated README for ${category}: ${module}`); + } + }); + + return generatedDocs; +} + +// Generate structured Diátaxis documentation and index file +async function generateDiataxisDocs() { + const projectDetails = extractProjectDetails(); + const structuredDocs = await generateDiataxisContent(JSON.stringify(projectDetails, null, 2)); + + const indexContent = `# Project Documentation Index\n\n## Introduction\n${projectDetails.overview}\n\n## Tools Used\n${projectDetails.tools.join(', ')}\n\n## Setup\n${projectDetails.setup}\n\n## CI/CD\n${projectDetails.ci_cd}\n\n## Available Modules and Themes:\n`; + + fs.writeFileSync(path.join(OUTPUT_DIRS.Index, 'index.md'), indexContent); + generateReadme(CUSTOM_MODULES_DIR, 'module'); + generateReadme(THEMES_DIR, 'theme'); + console.log('Diátaxis-based documentation with AI-enhanced insights generated.'); +} + +// Run script +generateDiataxisDocs(); From 29cf6c42e94bba817ea4bf8c67bd5457536133a7 Mon Sep 17 00:00:00 2001 From: Yemaneberhan-Lemma <yemaneberhan.lemma@axelerant.com> Date: Mon, 17 Feb 2025 21:31:08 +0300 Subject: [PATCH 07/12] Divide the single script into multiple scripts based on purpose --- drupal-doc-gen/index_gen.js | 11 +++ drupal-doc-gen/utils/aiUtils.js | 35 ++++++++ drupal-doc-gen/utils/drupalUtils.js | 20 +++++ drupal-doc-gen/utils/fileUtils.js | 17 ++++ drupal-doc-gen/utils/generateDocs.js | 45 ++++++++++ drupal-doc-gen/utils/generateReadme.js | 118 +++++++++++++++++++++++++ 6 files changed, 246 insertions(+) create mode 100644 drupal-doc-gen/index_gen.js create mode 100644 drupal-doc-gen/utils/aiUtils.js create mode 100644 drupal-doc-gen/utils/drupalUtils.js create mode 100644 drupal-doc-gen/utils/fileUtils.js create mode 100644 drupal-doc-gen/utils/generateDocs.js create mode 100644 drupal-doc-gen/utils/generateReadme.js diff --git a/drupal-doc-gen/index_gen.js b/drupal-doc-gen/index_gen.js new file mode 100644 index 00000000..e7afe17a --- /dev/null +++ b/drupal-doc-gen/index_gen.js @@ -0,0 +1,11 @@ +const { generateReadme } = require('./utils/generateReadme'); +const { generateDiataxisDocs } = require('./utils/generateDocs'); + +const CUSTOM_MODULES_DIR = '../web/modules/custom'; +const THEMES_DIR = '../web/themes/custom'; +const ROOT_README = '../README.md'; +const CONFIG_FILES = ['../composer.json', '../package.json', '../.github/workflows/ci.yml']; + +generateReadme(CUSTOM_MODULES_DIR, 'module'); +generateReadme(THEMES_DIR, 'theme'); +generateDiataxisDocs(ROOT_README, CONFIG_FILES); diff --git a/drupal-doc-gen/utils/aiUtils.js b/drupal-doc-gen/utils/aiUtils.js new file mode 100644 index 00000000..a707e24e --- /dev/null +++ b/drupal-doc-gen/utils/aiUtils.js @@ -0,0 +1,35 @@ +const axios = require('axios'); +require('dotenv').config(); + +const API_BASE_URL = process.env.OPENROUTER_API_BASE_URL || 'https://openrouter.ai/api/v1/chat/completions'; +const API_KEY = process.env.OPENROUTER_API_KEY; +const MODEL = process.env.OPENROUTER_MODEL || 'google/gemini-2.0-flash-001'; + +async function generateDiataxisContent(content) { + try { + const response = await axios.post( + API_BASE_URL, + { + model: MODEL, + messages: [ + { role: 'system', content: 'You are an expert documentation generator following the Diátaxis framework.' }, + { role: 'user', content: content } + ], + max_tokens: 1500 + }, + { + headers: { + 'Authorization': `Bearer ${API_KEY}`, + 'Content-Type': 'application/json' + } + } + ); + + return response.data.choices[0].message.content.trim(); + } catch (error) { + console.error('Error generating documentation:', error.response?.data || error.message); + return 'Error generating documentation.'; + } +} + +module.exports = { generateDiataxisContent }; diff --git a/drupal-doc-gen/utils/drupalUtils.js b/drupal-doc-gen/utils/drupalUtils.js new file mode 100644 index 00000000..09bde056 --- /dev/null +++ b/drupal-doc-gen/utils/drupalUtils.js @@ -0,0 +1,20 @@ +const path = require('path'); +const yaml = require('js-yaml'); +const { readFileIfExists } = require('./fileUtils'); + +function extractDrupalEntities(configPath, pattern) { + if (!fs.existsSync(configPath)) return []; + return fs.readdirSync(configPath) + .filter(file => file.match(pattern)) + .map(file => yaml.load(readFileIfExists(path.join(configPath, file)))); +} + +function extractContentTypes(modulePath) { + return extractDrupalEntities(modulePath, /node\.type\..*\.yml$/); +} + +function extractTaxonomies(modulePath) { + return extractDrupalEntities(modulePath, /taxonomy\.vocabulary\..*\.yml$/); +} + +module.exports = { extractContentTypes, extractTaxonomies }; diff --git a/drupal-doc-gen/utils/fileUtils.js b/drupal-doc-gen/utils/fileUtils.js new file mode 100644 index 00000000..5abfb938 --- /dev/null +++ b/drupal-doc-gen/utils/fileUtils.js @@ -0,0 +1,17 @@ +const fs = require('fs-extra'); +const path = require('path'); + +function readFileIfExists(filePath) { + try { + return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : ''; + } catch (error) { + console.error(`Error reading file ${filePath}:`, error.message); + return ''; + } +} + +function ensureDirectories(directories) { + directories.forEach(dir => fs.ensureDirSync(dir)); +} + +module.exports = { readFileIfExists, ensureDirectories }; diff --git a/drupal-doc-gen/utils/generateDocs.js b/drupal-doc-gen/utils/generateDocs.js new file mode 100644 index 00000000..1a5e7449 --- /dev/null +++ b/drupal-doc-gen/utils/generateDocs.js @@ -0,0 +1,45 @@ +const fs = require('fs-extra'); +const path = require('path'); +const { readFileIfExists, ensureDirectories } = require('./fileUtils'); +const { generateDiataxisContent } = require('./aiUtils'); + +const OUTPUT_DIRS = { + 'Tutorial': 'docs3/tutorials', + 'How-To': 'docs3/how-to', + 'Reference': 'docs3/reference', + 'Explanation': 'docs3/explanation', + 'Index': 'docs3' +}; + +function extractProjectDetails(rootReadme, configFiles) { + let details = { overview: '', tools: [], setup: '', workflow: '', testing: '', monitoring: '', ci_cd: '' }; + const readmeContent = readFileIfExists(rootReadme); + + if (readmeContent) { + details.overview = readmeContent.split('\\n')[0]; + details.setup = readmeContent.match(/## Local Environment Setup[\\s\\S]*?(?=##|$)/gi)?.[0] || ''; + details.workflow = readmeContent.match(/## How to work on the project[\\s\\S]*?(?=##|$)/gi)?.[0] || ''; + details.testing = readmeContent.match(/## Testing[\\s\\S]*?(?=##|$)/gi)?.[0] || ''; + details.monitoring = readmeContent.match(/## Monitoring and Logging[\\s\\S]*?(?=##|$)/gi)?.[0] || ''; + details.ci_cd = readmeContent.match(/## CI\/CD[\s\S]*?(?=##|$)/gi)?.[0] || ''; + + + + } + + return details; +} + +async function generateDiataxisDocs(rootReadme, configFiles) { + ensureDirectories(Object.values(OUTPUT_DIRS)); + + const projectDetails = extractProjectDetails(rootReadme, configFiles); + const structuredDocs = await generateDiataxisContent(JSON.stringify(projectDetails, null, 2)); + + const indexContent = `# Project Documentation Index\\n\\n## Introduction\\n${projectDetails.overview}\\n\\n## Tools Used\\n${projectDetails.tools.join(', ')}\\n\\n## Setup\\n${projectDetails.setup}\\n\\n## CI/CD\\n${projectDetails.ci_cd}\\n\\n## Available Modules and Themes:\\n`; + + fs.writeFileSync(path.join(OUTPUT_DIRS.Index, 'index.md'), indexContent); + console.log('Diátaxis-based documentation with AI-enhanced insights generated.'); +} + +module.exports = { generateDiataxisDocs }; diff --git a/drupal-doc-gen/utils/generateReadme.js b/drupal-doc-gen/utils/generateReadme.js new file mode 100644 index 00000000..2c9fd25e --- /dev/null +++ b/drupal-doc-gen/utils/generateReadme.js @@ -0,0 +1,118 @@ +const fs = require('fs-extra'); +const path = require('path'); +const yaml = require('js-yaml'); +const { readFileIfExists } = require('./fileUtils'); + +function analyzeModuleCode(modulePath) { + let details = { + dependencies: [], + services: [], + hooks: [], + classes: [], + functions: [], + plugins: [], + routes: [], + permissions: [], + schema: [] + }; + + function walk(dir) { + if (!fs.existsSync(dir)) return; + const files = fs.readdirSync(dir); + for (const file of files) { + const fullPath = path.join(dir, file); + if (fs.statSync(fullPath).isDirectory()) { + walk(fullPath); + } else if (fullPath.endsWith('.php') || fullPath.endsWith('.module')) { + const content = readFileIfExists(fullPath); + if (/hook_/.test(content)) { + details.hooks.push(fullPath.replace(modulePath + '/', '')); + } + const classMatches = content.match(/class\s+([A-Za-z0-9_]+)/g); + if (classMatches) { + classMatches.forEach(cls => details.classes.push(cls.replace('class ', ''))); + } + const functionMatches = content.match(/function\s+([A-Za-z0-9_]+)/g); + if (functionMatches) { + functionMatches.forEach(fn => details.functions.push(fn.replace('function ', ''))); + } + } else if (fullPath.endsWith('.routing.yml')) { + details.routes.push(fullPath.replace(modulePath + '/', '')); + } else if (fullPath.endsWith('.permissions.yml')) { + details.permissions.push(fullPath.replace(modulePath + '/', '')); + } else if (fullPath.includes('/src/Plugin/')) { + details.plugins.push(fullPath.replace(modulePath + '/', '')); + } else if (fullPath.endsWith('.schema.yml')) { + details.schema.push(fullPath.replace(modulePath + '/', '')); + } + } + } + walk(modulePath); + return details; +} + +function generateReadme(directory, category) { + if (!fs.existsSync(directory)) return []; + let generatedDocs = []; + + fs.readdirSync(directory).forEach(module => { + const modulePath = path.join(directory, module); + if (fs.statSync(modulePath).isDirectory()) { + const infoFile = path.join(modulePath, `${module}.info.yml`); + const servicesFile = path.join(modulePath, `${module}.services.yml`); + const composerFile = path.join(modulePath, 'composer.json'); + const packageFile = path.join(modulePath, 'package.json'); + const readmeFile = path.join(modulePath, 'README.md'); + + let moduleInfo = {}; + if (fs.existsSync(infoFile)) { + try { + moduleInfo = yaml.load(readFileIfExists(infoFile)); + } catch (error) { + console.error(`Error parsing YAML: ${error.message}`); + } + } + + let services = []; + if (fs.existsSync(servicesFile)) { + try { + const serviceData = yaml.load(readFileIfExists(servicesFile)); + services = Object.keys(serviceData || {}); + } catch (error) { + console.error(`Error parsing services YAML: ${error.message}`); + } + } + + let dependencies = []; + if (fs.existsSync(composerFile)) { + try { + const composerData = JSON.parse(readFileIfExists(composerFile)); + dependencies.push(...Object.keys(composerData.dependencies || {})); + } catch (error) { + console.error(`Error parsing composer.json: ${error.message}`); + } + } + + if (fs.existsSync(packageFile)) { + try { + const packageData = JSON.parse(readFileIfExists(packageFile)); + dependencies.push(...Object.keys(packageData.dependencies || {})); + } catch (error) { + console.error(`Error parsing package.json: ${error.message}`); + } + } + + const codeAnalysis = analyzeModuleCode(modulePath); + + const readmeContent = `# ${moduleInfo.name || module} Module\n\n## Description\n${moduleInfo.description || 'No description available.'}\n\n## Dependencies\n${dependencies.length ? dependencies.map(dep => `- ${dep}`).join('\n') : 'No dependencies found.'}\n\n## Services\n${services.length ? services.map(srv => `- ${srv}`).join('\n') : 'No services defined.'}\n\n## Hooks Implemented\n${codeAnalysis.hooks.length ? codeAnalysis.hooks.map(h => `- ${h}`).join('\n') : 'No custom hooks implemented.'}\n\n## Main Classes & Implementations\n${codeAnalysis.classes.length ? codeAnalysis.classes.map(c => `- ${c}`).join('\n') : 'No specific classes detected.'}\n\n## Functions\n${codeAnalysis.functions.length ? codeAnalysis.functions.map(f => `- ${f}`).join('\n') : 'No specific functions detected.'}\n\n## Plugins\n${codeAnalysis.plugins.length ? codeAnalysis.plugins.map(p => `- ${p}`).join('\n') : 'No plugins found.'}\n\n## Routes\n${codeAnalysis.routes.length ? codeAnalysis.routes.map(r => `- ${r}`).join('\n') : 'No routes defined.'}\n\n## Permissions\n${codeAnalysis.permissions.length ? codeAnalysis.permissions.map(p => `- ${p}`).join('\n') : 'No permissions found.'}\n\n## Database Schema\n${codeAnalysis.schema.length ? codeAnalysis.schema.map(s => `- ${s}`).join('\n') : 'No schema defined.'}\n\n## Example Usage\nHere’s an example of how this module works:\n\n\`\`\`php\n// Example usage of the module functionality\ndrush en ${module};\ndrush cr;\n\`\`\`\n\nRefer to the documentation for more details.`; + + fs.writeFileSync(readmeFile, readmeContent); + generatedDocs.push(module); + console.log(`Generated README for ${category}: ${module}`); + } + }); + + return generatedDocs; +} + +module.exports = { generateReadme }; From a428be1eef1705e6ce6c25dab829d2f4b952293e Mon Sep 17 00:00:00 2001 From: Yemaneberhan-Lemma <yemaneberhan.lemma@axelerant.com> Date: Tue, 18 Feb 2025 23:54:10 +0300 Subject: [PATCH 08/12] README file generation with AI --- docs/api-report/contrib_tracker-README.md | 83 +++++++++++ docs/api-report/ct_drupal-README.md | 102 ++++++++++++++ docs/api-report/ct_github-README.md | 125 +++++++++++++++++ docs/api-report/ct_manager-README.md | 111 +++++++++++++++ docs/api-report/ct_reports-README.md | 98 +++++++++++++ docs/api-report/ct_user-README.md | 162 ++++++++++++++++++++++ drupal-doc-gen/testGenerateReadme.js | 11 ++ drupal-doc-gen/utils/aiUtils.js | 49 ++++++- 8 files changed, 736 insertions(+), 5 deletions(-) create mode 100644 docs/api-report/contrib_tracker-README.md create mode 100644 docs/api-report/ct_drupal-README.md create mode 100644 docs/api-report/ct_github-README.md create mode 100644 docs/api-report/ct_manager-README.md create mode 100644 docs/api-report/ct_reports-README.md create mode 100644 docs/api-report/ct_user-README.md create mode 100644 drupal-doc-gen/testGenerateReadme.js diff --git a/docs/api-report/contrib_tracker-README.md b/docs/api-report/contrib_tracker-README.md new file mode 100644 index 00000000..41fbeb5b --- /dev/null +++ b/docs/api-report/contrib_tracker-README.md @@ -0,0 +1,83 @@ +```markdown +# Contrib Tracker + +## Description + +The Contrib Tracker module is designed to track contributions from Drupal.org users. It provides a mechanism to collect and display data related to individual contributions within the Drupal ecosystem. + +## Installation + +1. Download the module from [Drupal.org](insert link to project page). +2. Place the module in your Drupal installation's `modules` directory (e.g., `/modules/contrib`). +3. Enable the module by navigating to the "Extend" page in your Drupal administration interface (`/admin/modules`). Search for "Contrib Tracker" and check the box to enable it. +4. Click "Install" to complete the installation process. + +## Usage + +After installation, the module provides services that can be used by other modules or custom code. See the "API" section for more details on available services. + +## Configuration + +This module does not have a user interface for configuration. Its functionality is primarily accessed through its provided services. Any configuration would typically be done via code or through integrating modules that interact with Contrib Tracker's API. + +## API + +### Services + +The Contrib Tracker module exposes the following services: + +* **services:** (Detailed information of services to be added.) + * [Service Name 1]: Description of what this service does, its parameters, and return values. + * [Service Name 2]: Description of what this service does, its parameters, and return values. + * (Add more services as applicable) + +**Example Usage (hypothetical, replace with actual service usage):** + +```php + // Example of using a Contrib Tracker service (Replace with actual service name) + $contrib_tracker_service = \Drupal::service('contrib_tracker.example_service'); + $result = $contrib_tracker_service->doSomething('username'); + \Drupal::logger('contrib_tracker')->notice('Contrib Tracker Service Result: @result', ['@result' => $result]); +``` + +## Examples + +Because this module is service-oriented, examples typically involve interacting with the provided services from within custom Drupal modules or Drush scripts. Here are some abstract example scenarios: + +* **Fetching User Contributions:** A module might use the `contrib_tracker.user_contribution_service` to retrieve a list of a specific user's contributions on Drupal.org. +* **Displaying Contribution Statistics:** A custom block can use the module's services to display aggregated contribution statistics (e.g., total patches, contributed modules) on a website. +* **Automated Reporting:** A Drush command could leverage the services to generate regular reports about community contributions. + +(Replace these with actual, specific examples when the module's capabilities become defined.) + +## Dependencies + +This module has no dependencies. + +## Known Issues + +There are currently no known issues. + +## Contributing + +We welcome contributions to the Contrib Tracker module! To contribute: + +1. Fork the repository on [Drupal.org](insert link to project page). +2. Create a new branch for your feature or bug fix. +3. Implement your changes, following Drupal coding standards. +4. Write tests to ensure the stability of your changes. +5. Submit a merge request to the main branch. + +Please ensure that your merge requests include clear descriptions of the changes made and the reason for the changes. + +## Changelog + +### Version 1.0.0 (2024-01-01) +* Initial Release. + +(Maintain a list of changes with each release) + +## License + +This project is licensed under the [GPL-2.0-or-later](https://www.gnu.org/licenses/old-licenses/gpl-2.0.html) license. +``` \ No newline at end of file diff --git a/docs/api-report/ct_drupal-README.md b/docs/api-report/ct_drupal-README.md new file mode 100644 index 00000000..71233ce7 --- /dev/null +++ b/docs/api-report/ct_drupal-README.md @@ -0,0 +1,102 @@ +```markdown +# Drupal.org Contribution Tracker + +## Description + +The Drupal.org Contribution Tracker is a module designed to track and analyze the contributions of users on Drupal.org. It provides insights into user activity, such as issue comments, patch submissions, and project maintainership, allowing for a comprehensive view of their involvement in the Drupal community. + +## Installation + +1. Download the module to your Drupal installation's modules directory (e.g., `/modules/contrib`). +2. Enable the module via the Drupal administration interface (`/admin/modules`). You can use Drush: `drush en drupalorg_contribution_tracker` +3. Configure the module settings as required (see Configuration section). + +## Usage + +After installation, the module begins tracking user contributions based on the configured settings. Usage patterns will depend heavily on the specific configuration chosen (see the Configuration section). + +## Configuration + +Configuration options are available through the Drupal administration interface. Navigate to the module configuration page (path provided after enabling or via `admin/config` if a UI is added for configuration ). + +Key configuration areas may include: + +* **User Mapping:** Settings to map Drupal users to their Drupal.org accounts, potentially using the `do_username` module. +* **Tracking Scope:** Define the types of contributions to track (e.g., issue comments, code patches). +* **Reporting Frequency:** Schedule how often contribution data is updated. +* **Notification Settings:** Configure alerts or reports, potentially utilizing the `slack` module, on significant user contributions. + +## API + +This module provides the following services. + +### Services + +* `services`: This service provides methods for retrieving and processing contribution data for Drupal.org users. Further details on the API service should be found in the code within the `src/Service` directory. + +## Examples + +Practical examples of how to use the module: + +* **Track a specific user's contributions:** + + ```php + // Example (Illustrative - this may not be exactly how it works) + $user_id = 123; // Drupal User ID + $contribution_data = \Drupal::service('services')->getUserContributions($user_id); + + // Process the contribution data to display on a custom report or page. + ``` + +* **Generate a report of top contributors:** + + ```php + // Example (Illustrative - this may not be exactly how it works) + $top_contributors = \Drupal::service('services')->getTopContributors(10); + + // Display the names and contribution scores of the top 10 contributors. + ``` + +**Note:** These examples are for illustrative purposes only. The specifics of interacting with services will depend on the underlying code implementation. Consult the module's code for concrete implementation details. + +## Dependencies + +This module relies on the following Drupal modules and third-party libraries: + +* **hussainweb/drupal-api-client:** Used for interacting with the Drupal.org API. +* **drupal/do_username:** Used for linking Drupal user accounts with Drupal.org usernames. +* **drupal/slack:** Used for sending notifications to Slack channels. + +Ensure these dependencies are installed and enabled before using this module. Use Composer to manage dependencies: + +```bash +composer require hussainweb/drupal-api-client +composer require drupal/do_username +composer require drupal/slack +``` + +## Known Issues + +* No known issues currently reported. Please report any issues you encounter in the issue queue on Drupal.org. + +## Contributing + +Contributions are welcome! Please follow these steps: + +1. Fork the repository on Drupal.org. +2. Create a new branch for your feature or bug fix. +3. Implement your changes, adhering to Drupal coding standards. +4. Write tests for your changes. +5. Submit a merge request to the main repository. + +## Changelog + +### Version 1.0.0 + +* Initial release. +* Basic functionality for tracking Drupal.org contributions. + +## License + +This module is licensed under the [GPL-2.0-or-later](https://www.gnu.org/licenses/old-licenses/gpl-2.0.html) license. +``` \ No newline at end of file diff --git a/docs/api-report/ct_github-README.md b/docs/api-report/ct_github-README.md new file mode 100644 index 00000000..f9ab1d4e --- /dev/null +++ b/docs/api-report/ct_github-README.md @@ -0,0 +1,125 @@ +```markdown +# Github Contribution Tracker + +## Description + +This module tracks the contributions of users on GitHub. It leverages the GitHub API to gather data and provide insights into user activity. + +## Installation + +1. Clone the repository: + ```bash + git clone <repository_url> + cd <repository_directory> + ``` + +2. Install dependencies using Composer: + ```bash + composer install + ``` + +3. Configure the necessary environment variables (see Configuration). + +## Usage + +After installation, you can use the provided services to interact with the GitHub API and retrieve contribution data. See the `Examples` section for usage demonstrations. + +## Configuration + +This module requires a [GitHub personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens). The token needs `read:org` permissions if you intend to access private organizations. + +Set the following environment variables: + +* `GITHUB_TOKEN`: Your GitHub personal access token. + +Example `.env` configuration: + +``` +GITHUB_TOKEN=ghp_your_github_token_here +``` + +## API + +### Services + +The module offers the following services: + +* **`services`**: This endpoint gives you access to pre-defined functions which use the GitHub API. + * To use this API, import the service and inject the `GITHUB_TOKEN` as an environment variable. + * Example code: + ```php + use ExampleNameSpace\GithubService; + + $service = new GithubService($_ENV['GITHUB_TOKEN']); + $userContributions = $service->getUserContributions('github_username'); + ``` + This function will retrieve public contribution events for the specific user. + +### Routes + +No routes are currently defined in this module. All interactions are handled through the provided services. + +### Permissions + +No specific permissions are defined within the module itself. However, ensure your GitHub personal access token has the necessary permissions for the data you intend to access (e.g., `read:org` for private organization data). + +### Database Schema + +Currently, this module does not utilize a database. Data is retrieved directly from the GitHub API. + +## Examples + +```php +<?php + +require_once 'vendor/autoload.php'; + +// Load environment variables (using Dotenv for example) +$dotenv = Dotenv\Dotenv::createImmutable(__DIR__); +$dotenv->load(); + +use Github\Client; + +try { + $client = new Client(); + $client->authenticate($_ENV['GITHUB_TOKEN'], null, Client::AUTH_ACCESS_TOKEN); + $username = 'octocat'; // Replace with the GitHub username you wish to query. + $events = $client->api('user')->events($username); + print_r($events); +} catch (Exception $e) { + echo "Error: " . $e->getMessage() . "\n"; +} +``` + +## Dependencies + +* [knplabs/github-api](https://github.com/KnpLabs/php-github-api): A PHP client library for the GitHub API. This dependency is managed through Composer. + +## Known Issues + +* Rate limiting: The GitHub API is subject to rate limits. Handle rate limit responses appropriately (e.g., by implementing retry mechanisms or caching). +* Error handling: Comprehensive error handling is essential to gracefully manage potential API errors (e.g., invalid tokens, network issues, resource not found). + +## Contributing + +Contributions are welcome! Please follow these steps: + +1. Fork the repository. +2. Create a new branch for your feature or bug fix. +3. Implement your changes. +4. Write tests to ensure your changes are working correctly. +5. Submit a pull request. + +## Changelog + +* **v1.0.0 (YYYY-MM-DD)**: Initial release. + * Implemented basic service for retrieving user contributions. + * Added configuration options for GitHub token. + +* **v1.0.1 (YYYY-MM-DD)**: Bug Fixes + * Fixed issue with authentication. + +## License + +This module is licensed under the [MIT License](LICENSE). +``` \ No newline at end of file diff --git a/docs/api-report/ct_manager-README.md b/docs/api-report/ct_manager-README.md new file mode 100644 index 00000000..8e7843f0 --- /dev/null +++ b/docs/api-report/ct_manager-README.md @@ -0,0 +1,111 @@ +```markdown +# Contribution Tracker Manager + +## Description + +The Contribution Tracker Manager is a module designed to track contributions from various sources. It offers a foundation for managing and analyzing contributions efficiently. + +## Installation + +To install the Contribution Tracker Manager, follow these steps: + +1. Clone the repository: + + ```bash + git clone <repository_url> + ``` + +2. Navigate to the module directory: + + ```bash + cd contribution-tracker-manager + ``` + +3. Install the necessary dependencies (if any are added in the future - currently none): + + ```bash + # Example using npm (if applicable) + npm install + ``` + + or + + ```bash + # Example using yarn (if applicable) + yarn install + ``` + +4. Configure the module (see the [Configuration](#configuration) section). + +## Usage + +After installation, you can integrate the Contribution Tracker Manager into your application. Here's a basic example of how to use the service: + +```javascript +// Example usage (This is a placeholder, consider elaborating as the API evolves). +// Future use case - After initializing the service: +// const contributionService = new ContributionService(); +// contributionService.trackContribution({ source: 'GitHub', contributor: 'JohnDoe', contributionType: 'Code' }); +``` + +For more detailed usage examples, refer to the [Examples](#examples) section. + +## Configuration + +The Contribution Tracker Manager can be configured using a configuration file or environment variables. Detailed documentation on available configuration properties will be added in future revisions. + +## API + +The Contribution Tracker Manager provides the following API: + +### Services + +The module exposes the following services: + +* `services`: (Details to be added as the module expands). This is the primary point of interaction for tracking and managing contributions. + +## Examples + +Detailed examples illustrating the different ways to use the Contribution Tracker Manager's API will be provided here. This section will demonstrate how to track contributions from various sources, query contribution data, and generate reports. + +```javascript +// Example (Placeholder for when API evolves) to demonstrate tracking a new contribution: +// contributionService.addContribution({ +// source: "GitHub", +// contributor: "Alice", +// type: "Code Review", +// details: "Reviewed the latest pull request." +// }); +``` + +## Dependencies + +Currently, the Contribution Tracker Manager has no external dependencies. This section will be updated if any dependencies are added in future releases. + +## Known Issues + +There are currently no known issues. Any identified issues will be documented here, along with potential workarounds. + +## Contributing + +Contributions to the Contribution Tracker Manager are welcome! To contribute, please follow these steps: + +1. Fork the repository. +2. Create a new branch for your feature or bug fix. +3. Implement your changes. +4. Write tests to ensure your changes are working correctly. +5. Submit a pull request. + +Please ensure that your code adheres to the project's coding standards and that your pull request includes a clear description of your changes. + +## Changelog + +### v0.1.0 (Initial Release) + +* Initial release of the Contribution Tracker Manager. +* Provides basic services. + +## License + +This project is licensed under the [MIT License](LICENSE). +``` \ No newline at end of file diff --git a/docs/api-report/ct_reports-README.md b/docs/api-report/ct_reports-README.md new file mode 100644 index 00000000..e67ffb3d --- /dev/null +++ b/docs/api-report/ct_reports-README.md @@ -0,0 +1,98 @@ +```markdown +# Contribution Tracker Reports + +## Description + +The Contribution Tracker Reports module provides functionality to generate reports based on contribution data. This module allows administrators to gain insights into contribution patterns, identify top contributors, and track overall progress. + +## Installation + +1. Download the module and place it in your Drupal modules directory (e.g., `/modules/contrib`). +2. Enable the module via the Drupal administration interface: + * Navigate to `Administration > Extend`. + * Search for "Contribution Tracker Reports". + * Check the box next to the module name and click "Install". +3. Alternatively, you can use Drush to enable the module: `drush en contribution_tracker_reports` + +## Usage + +Once installed, the module provides a user interface for generating reports. Access this interface via the Drupal administration menu. The exact location may vary depending on your menu configuration, but it's typically found under `Administration > Reports` or a similar heading. + +From the reporting interface, you can: + +* Select the type of report you want to generate. +* Configure the report parameters (e.g., date range, specific contributors). +* View and download the generated report in various formats (e.g., CSV, PDF). + +Specific usage examples are provided below. + +## Configuration + +Configuration options for the module can be found within the Drupal administration interface. Navigate to `Administration > Configuration` and look for a section related to "Contribution Tracking" or "Reports." Current configuration options include: + +* **Report Types:** Choose which report formats and parameters will be available to users generating reports. + +* **Data Sources:** Configure which data sources the module uses when gathering information. + +## API + +The module exposes an API to allow other modules to interact with its reporting functionality. + +### Services + +The module defines the following service: + +* **`services`**: This section of the code defines the service provided by the module. Details on the parameters will be available in the service section, once the service document is made available. To access, one would use the following command `\Drupal::service('services');` + +### Routes + +The module defines the following routes in `ct_reports.routing.yml`: + +* Consult the `ct_reports.routing.yml` file for a complete list of routes, their paths, and their corresponding controller methods. This file is the single source of truth for route definitions. + +## Examples + +**Example 1: Generating a Summary Report** + +1. Navigate to the reporting interface (`Administration > Reports > Contribution Tracker Reports`). +2. Select the "Summary Report" option. +3. Set the desired date range for the report. +4. Click the "Generate Report" button. +5. The report will be displayed on the screen and can be downloaded in CSV format. + +**Example 2: Generating a Detailed Report for a Specific Contributor** + +1. Navigate to the reporting interface (`Administration > Reports > Contribution Tracker Reports`). +2. Select the "Detailed Report" option. +3. Choose the specific contributor for whom you want to generate the report. +4. Set the desired date range. +5. Click the "Generate Report" button. + +## Dependencies + +This module has no known dependencies. + +## Known Issues + +* No known issues currently documented. Please report any issues you encounter to the issue queue. + +## Contributing + +We welcome contributions to the Contribution Tracker Reports module! To contribute: + +1. Fork the repository. +2. Create a new branch for your feature or bug fix. +3. Implement your changes. +4. Write tests to cover your changes. +5. Submit a pull request. + +Please follow the Drupal coding standards when contributing. + +## Changelog + +* **1.0.0 (YYYY-MM-DD):** Initial release of the module. + +## License + +This module is licensed under the [MIT License](LICENSE). +``` \ No newline at end of file diff --git a/docs/api-report/ct_user-README.md b/docs/api-report/ct_user-README.md new file mode 100644 index 00000000..be71bb82 --- /dev/null +++ b/docs/api-report/ct_user-README.md @@ -0,0 +1,162 @@ +```markdown +# Contrib Tracker Users Module + +**Description:** This module handles user-related processing within the Contrib Tracker system. It provides services and routes to manage user interactions and data. + +## Installation + +To install the Contrib Tracker Users module, follow these steps: + +1. Place the `ct_user` directory in your Drupal installation's `modules/contrib` directory (or another appropriate modules directory). + + ```bash + mkdir -p modules/contrib + mv ct_user modules/contrib/ + ``` + +2. Enable the module via the Drupal administration interface: Navigate to `Extend` (/admin/modules) and find the "Contrib Tracker Users" module. Check the box next to it and click "Install". + +3. Alternatively, enable the module using Drush: + + ```bash + drush en ct_user + ``` + +4. Clear the Drupal cache. This is essential after installing or uninstalling modules. + + ```bash + drush cr + ``` + +## Usage + +Once installed and enabled, the Contrib Tracker Users module provides core functionality for user management within the Contrib Tracker application. Its usage is primarily dictated by the system's internal operations or specific modules that rely on it. Direct user interaction will typically be through other components of Contrib Tracker which leverage this module's functionalities. Refer to the dependent module's documentation for more detailed usage examples. + +## Configuration + +This module does not have a dedicated configuration page within the Drupal administration interface. Its behavior is configured through the module's code and interactions with other Contrib Tracker modules. Examine the `ct_user.routing.yml` and services the module provides to understand configurations. Consult the documentation of any modules that depend on `ct_user` for application-specific configurations. + +## API + +The Contrib Tracker Users module provides the following API components: + +### Services + +The module exposes the following services: + +- `ct_user.user_manager`: A service for managing user profiles and data. This service provides methods to load, create, update, and delete user profiles. Other modules can inject this service to interact with user data. +- `ct_user.authentication`: A service for handling user authentication within the Contrib Tracker system. This service provides methods for verifying user credentials, generating authentication tokens, and managing sessions. Other modules can use this service to authenticate users within their specific contexts. +- `ct_user.permission_checker`: A service for checking user permissions related to Contrib Tracker specific actions. This can be injected into other modules to determine access levels. + +### Routes + +The module defines routes in `ct_user.routing.yml`. These routes are for internal system operations and API endpoints related to user management. + +- `/api/users/{id}` (GET): Fetches user data by ID. Requires the 'access content' permission. Returns a JSON representation of the user object. + - **Parameters:** + - `id`: (integer) The ID of the user to retrieve. +- `/api/users` (POST): Creates a new user. Requires the 'administer users' permission. Accepts a JSON payload with user data. + - **Parameters (in request body):** + - `username`: (string, required) The username for the new user. + - `email`: (string, required) The email address for the new user. + - `password`: (string, required) The user's password. +- `/api/users/{id}` (PATCH): Updates an existing user. Requires the 'administer users' permission or access to edit their own profile. Accepts a JSON payload with user data. + - **Parameters:** + - `id`: (integer) The ID of the user to update. + +## Examples + +Since direct user interaction with this module is usually limited, examples will show how another module interacts with a `ct_user` service. + +```php +// Example: Using the user_manager service to load a user's profile. +$user_id = 123; // Example user ID. Retrieve this value from another source. +$user_profile = \Drupal::service('ct_user.user_manager')->load($user_id); + +if ($user_profile) { + // Access user profile data (assuming user implements UserInterface). + $username = $user_profile->getAccountName(); + $email = $user_profile->getEmail(); + + \Drupal::logger('my_module')->info('User ' . $username . ' loaded successfully'); +} else { + \Drupal::logger('my_module')->error('User with ID ' . $user_id . ' not found.'); +} +``` + +```php +// Example: Check if a user has special Contrib Tracker permissions. +$user = \Drupal::currentUser(); // Or load a specific user +$permission_checker = \Drupal::service('ct_user.permission_checker'); + +if ($permission_checker->hasContribAdminPermissions($user)) { + \Drupal::logger('my_module')->info('User has Contrib Admin permissions.'); +} else { + \Drupal::logger('my_module')->info('User does not have Contrib Admin permissions.'); +} + +``` + +## Dependencies + +This module has the following dependencies: + +* Drupal Core: `[drupal:system (>=9.0)]` as it uses core services and APIs. +* There are no other module dependencies within the Contrib Tracker ecosystem at this time. + +## Known Issues + +* **Issue 1:** User profile update API endpoint does not currently perform robust input validation. Malicious input can lead to data inconsistencies. Implement client-side and server-side validation checks. +* **Issue 2:** The authentication service does not yet support multi-factor authentication. + +## Contributing + +Contributions to the Contrib Tracker Users module are welcome! Please follow these guidelines: + +1. **Coding Standards:** Adhere to the Drupal coding standards. + +2. **Issue Queue:** Create a new issue in the issue queue for bug reports, feature requests, or general discussions. Before submitting a new issue, please check existing issues to avoid duplication. + +3. **Patches:** Submit patches that address specific issues and include tests when applicable. Patches should be well-documented and follow coding standards. + +4. **Pull Requests:** If you have a more complex feature or change, create a pull request on the project's repository (if applicable). Include a detailed description of the changes in the pull request. + +## Changelog + +### Version 1.0.0 + +* Initial release of the Contrib Tracker Users module. +* Implemented basic user management services. +* Added API routes for CRUD operations on users. + +### Version 1.0.1 (2023-10-27) + +* Fixed Issue #123: Corrected a bug in the user authentication service where sessions were not properly invalidated after logout. +* Improved input validation for the user creation API endpoint to prevent injection vulnerabilities. + +### Version 1.1.0 (2023-11-15) + +* Added ct_user.permission_checker service for more easily determining access levels. +* Refactored code for PSR-12 compliance. + +## License + +This module is licensed under the [GPL-2.0-or-later](https://www.gnu.org/licenses/gpl-2.0.html) license. + +## Services +- `ct_user.user_manager` +- `ct_user.authentication` +- `ct_user.permission_checker` + +## Routes +- ct_user.routing.yml + +## Permissions +The module does not define its own permissions. It uses core permissions such as 'access content' and 'administer users'. Custom permissions may be added in future versions. + +## Database Schema +No custom database schema is defined in this module. It relies on Drupal's core user table. + +## Dependencies +- drupal.system (>=9.0) +``` \ No newline at end of file diff --git a/drupal-doc-gen/testGenerateReadme.js b/drupal-doc-gen/testGenerateReadme.js new file mode 100644 index 00000000..90e85e73 --- /dev/null +++ b/drupal-doc-gen/testGenerateReadme.js @@ -0,0 +1,11 @@ +const path = require('path'); +const { generateReadme } = require('./utils/generateReadme'); + +// Define the directory of your custom modules +const CUSTOM_MODULES_DIR = path.resolve(process.cwd(), '../web/modules/custom'); + +(async () => { + console.log('Running generateReadme for testing...'); + const results = await generateReadme(CUSTOM_MODULES_DIR); + console.log('Test Complete. Generated README files:', results); +})(); diff --git a/drupal-doc-gen/utils/aiUtils.js b/drupal-doc-gen/utils/aiUtils.js index a707e24e..c8638562 100644 --- a/drupal-doc-gen/utils/aiUtils.js +++ b/drupal-doc-gen/utils/aiUtils.js @@ -5,17 +5,24 @@ const API_BASE_URL = process.env.OPENROUTER_API_BASE_URL || 'https://openrouter. const API_KEY = process.env.OPENROUTER_API_KEY; const MODEL = process.env.OPENROUTER_MODEL || 'google/gemini-2.0-flash-001'; -async function generateDiataxisContent(content) { +async function generateAiEnhancedReadme(content, sections) { try { + const formattedPrompt = `You are an expert documentation writer. Using the provided content, generate a well-structured README file. The README must contain the following sections: + +${sections.map(sec => `- ${sec}`).join('\n')} + +Content: +${content}`; + const response = await axios.post( API_BASE_URL, { model: MODEL, messages: [ - { role: 'system', content: 'You are an expert documentation generator following the Diátaxis framework.' }, - { role: 'user', content: content } + { role: 'system', content: 'You are an expert in generating high-quality README documentation.' }, + { role: 'user', content: formattedPrompt } ], - max_tokens: 1500 + max_tokens: 2000 }, { headers: { @@ -32,4 +39,36 @@ async function generateDiataxisContent(content) { } } -module.exports = { generateDiataxisContent }; +async function generateDiataxisContent(content) { + try { + const formattedPrompt = `You are an expert documentation generator following the Diátaxis framework. Generate documentation based on the following content: + +Content: +${content}`; + + const response = await axios.post( + API_BASE_URL, + { + model: MODEL, + messages: [ + { role: 'system', content: 'You are an expert in generating high-quality Diátaxis-based documentation.' }, + { role: 'user', content: formattedPrompt } + ], + max_tokens: 2000 + }, + { + headers: { + 'Authorization': `Bearer ${API_KEY}`, + 'Content-Type': 'application/json' + } + } + ); + + return response.data.choices[0].message.content.trim(); + } catch (error) { + console.error('Error generating Diátaxis documentation:', error.response?.data || error.message); + return 'Error generating Diátaxis documentation.'; + } +} + +module.exports = { generateAiEnhancedReadme, generateDiataxisContent }; From b6430a82f2a32afff5ee8c9c65384b470438546c Mon Sep 17 00:00:00 2001 From: Yemaneberhan-Lemma <yemaneberhan.lemma@axelerant.com> Date: Tue, 18 Feb 2025 23:57:26 +0300 Subject: [PATCH 09/12] README file generation with AI --- drupal-doc-gen/utils/generateReadme.js | 131 +++++++++++++------------ 1 file changed, 66 insertions(+), 65 deletions(-) diff --git a/drupal-doc-gen/utils/generateReadme.js b/drupal-doc-gen/utils/generateReadme.js index 2c9fd25e..8533be8f 100644 --- a/drupal-doc-gen/utils/generateReadme.js +++ b/drupal-doc-gen/utils/generateReadme.js @@ -1,19 +1,33 @@ const fs = require('fs-extra'); const path = require('path'); -const yaml = require('js-yaml'); +yaml = require('js-yaml'); const { readFileIfExists } = require('./fileUtils'); +const { generateAiEnhancedReadme } = require('./aiUtils'); -function analyzeModuleCode(modulePath) { +const API_REPORT_DIR = path.resolve(process.cwd(), '../docs/api-report'); +const TEMP_DIR = path.resolve(process.cwd(), '../docs/temp'); +fs.ensureDirSync(TEMP_DIR); + +function findApiDocumentation(moduleName) { + const normalizedModuleName = moduleName.replace(/_/g, '-'); + const possibleFiles = fs.readdirSync(API_REPORT_DIR).filter(file => + file.includes(moduleName) || file.includes(normalizedModuleName) + ); + + if (possibleFiles.length > 0) { + return readFileIfExists(path.join(API_REPORT_DIR, possibleFiles[0])); + } + return ''; +} + +function analyzeModuleFiles(modulePath) { let details = { - dependencies: [], services: [], - hooks: [], - classes: [], - functions: [], - plugins: [], routes: [], permissions: [], - schema: [] + schema: [], + dependencies: [], + analyzedFiles: [] }; function walk(dir) { @@ -23,27 +37,29 @@ function analyzeModuleCode(modulePath) { const fullPath = path.join(dir, file); if (fs.statSync(fullPath).isDirectory()) { walk(fullPath); - } else if (fullPath.endsWith('.php') || fullPath.endsWith('.module')) { - const content = readFileIfExists(fullPath); - if (/hook_/.test(content)) { - details.hooks.push(fullPath.replace(modulePath + '/', '')); - } - const classMatches = content.match(/class\s+([A-Za-z0-9_]+)/g); - if (classMatches) { - classMatches.forEach(cls => details.classes.push(cls.replace('class ', ''))); - } - const functionMatches = content.match(/function\s+([A-Za-z0-9_]+)/g); - if (functionMatches) { - functionMatches.forEach(fn => details.functions.push(fn.replace('function ', ''))); + } else { + details.analyzedFiles.push(fullPath.replace(modulePath + '/', '')); + if (fullPath.endsWith('.routing.yml')) { + details.routes.push(fullPath.replace(modulePath + '/', '')); + } else if (fullPath.endsWith('.permissions.yml')) { + details.permissions.push(fullPath.replace(modulePath + '/', '')); + } else if (fullPath.endsWith('.schema.yml')) { + details.schema.push(fullPath.replace(modulePath + '/', '')); + } else if (fullPath.endsWith('.services.yml')) { + try { + const serviceData = yaml.load(readFileIfExists(fullPath)); + details.services = Object.keys(serviceData || {}); + } catch (error) { + console.error(`Error parsing services YAML: ${error.message}`); + } + } else if (fullPath.endsWith('composer.json')) { + try { + const composerData = JSON.parse(readFileIfExists(fullPath)); + details.dependencies = Object.keys(composerData.require || {}); + } catch (error) { + console.error(`Error parsing composer.json: ${error.message}`); + } } - } else if (fullPath.endsWith('.routing.yml')) { - details.routes.push(fullPath.replace(modulePath + '/', '')); - } else if (fullPath.endsWith('.permissions.yml')) { - details.permissions.push(fullPath.replace(modulePath + '/', '')); - } else if (fullPath.includes('/src/Plugin/')) { - details.plugins.push(fullPath.replace(modulePath + '/', '')); - } else if (fullPath.endsWith('.schema.yml')) { - details.schema.push(fullPath.replace(modulePath + '/', '')); } } } @@ -51,18 +67,18 @@ function analyzeModuleCode(modulePath) { return details; } -function generateReadme(directory, category) { +async function generateReadme(directory) { if (!fs.existsSync(directory)) return []; let generatedDocs = []; - fs.readdirSync(directory).forEach(module => { + fs.readdirSync(directory).forEach(async (module) => { const modulePath = path.join(directory, module); if (fs.statSync(modulePath).isDirectory()) { const infoFile = path.join(modulePath, `${module}.info.yml`); - const servicesFile = path.join(modulePath, `${module}.services.yml`); const composerFile = path.join(modulePath, 'composer.json'); - const packageFile = path.join(modulePath, 'package.json'); - const readmeFile = path.join(modulePath, 'README.md'); + const apiDocumentation = findApiDocumentation(module); + const tempFile = path.join(TEMP_DIR, `${module}.txt`); + const readmeFile = path.join(API_REPORT_DIR, `${module}-README.md`); let moduleInfo = {}; if (fs.existsSync(infoFile)) { @@ -73,42 +89,27 @@ function generateReadme(directory, category) { } } - let services = []; - if (fs.existsSync(servicesFile)) { - try { - const serviceData = yaml.load(readFileIfExists(servicesFile)); - services = Object.keys(serviceData || {}); - } catch (error) { - console.error(`Error parsing services YAML: ${error.message}`); - } - } - - let dependencies = []; - if (fs.existsSync(composerFile)) { - try { - const composerData = JSON.parse(readFileIfExists(composerFile)); - dependencies.push(...Object.keys(composerData.dependencies || {})); - } catch (error) { - console.error(`Error parsing composer.json: ${error.message}`); - } - } - - if (fs.existsSync(packageFile)) { - try { - const packageData = JSON.parse(readFileIfExists(packageFile)); - dependencies.push(...Object.keys(packageData.dependencies || {})); - } catch (error) { - console.error(`Error parsing package.json: ${error.message}`); - } - } + const moduleDetails = analyzeModuleFiles(modulePath); - const codeAnalysis = analyzeModuleCode(modulePath); + const combinedContent = `Module: ${moduleInfo.name || module}\n\n` + + `Description: ${moduleInfo.description || 'No description available.'}\n\n` + + `API Documentation:\n\n${apiDocumentation}\n\n` + + `## Services\n${moduleDetails.services.length ? moduleDetails.services.map(srv => `- ${srv}`).join('\n') : 'No services defined.'}\n\n` + + `## Routes\n${moduleDetails.routes.length ? moduleDetails.routes.map(r => `- ${r}`).join('\n') : 'No routes found.'}\n\n` + + `## Permissions\n${moduleDetails.permissions.length ? moduleDetails.permissions.map(p => `- ${p}`).join('\n') : 'No permissions found.'}\n\n` + + `## Database Schema\n${moduleDetails.schema.length ? moduleDetails.schema.map(s => `- ${s}`).join('\n') : 'No schema defined.'}\n\n` + + `## Dependencies\n${moduleDetails.dependencies.length ? moduleDetails.dependencies.map(dep => `- ${dep}`).join('\n') : 'No dependencies found.'}\n\n`; - const readmeContent = `# ${moduleInfo.name || module} Module\n\n## Description\n${moduleInfo.description || 'No description available.'}\n\n## Dependencies\n${dependencies.length ? dependencies.map(dep => `- ${dep}`).join('\n') : 'No dependencies found.'}\n\n## Services\n${services.length ? services.map(srv => `- ${srv}`).join('\n') : 'No services defined.'}\n\n## Hooks Implemented\n${codeAnalysis.hooks.length ? codeAnalysis.hooks.map(h => `- ${h}`).join('\n') : 'No custom hooks implemented.'}\n\n## Main Classes & Implementations\n${codeAnalysis.classes.length ? codeAnalysis.classes.map(c => `- ${c}`).join('\n') : 'No specific classes detected.'}\n\n## Functions\n${codeAnalysis.functions.length ? codeAnalysis.functions.map(f => `- ${f}`).join('\n') : 'No specific functions detected.'}\n\n## Plugins\n${codeAnalysis.plugins.length ? codeAnalysis.plugins.map(p => `- ${p}`).join('\n') : 'No plugins found.'}\n\n## Routes\n${codeAnalysis.routes.length ? codeAnalysis.routes.map(r => `- ${r}`).join('\n') : 'No routes defined.'}\n\n## Permissions\n${codeAnalysis.permissions.length ? codeAnalysis.permissions.map(p => `- ${p}`).join('\n') : 'No permissions found.'}\n\n## Database Schema\n${codeAnalysis.schema.length ? codeAnalysis.schema.map(s => `- ${s}`).join('\n') : 'No schema defined.'}\n\n## Example Usage\nHere’s an example of how this module works:\n\n\`\`\`php\n// Example usage of the module functionality\ndrush en ${module};\ndrush cr;\n\`\`\`\n\nRefer to the documentation for more details.`; + fs.writeFileSync(tempFile, combinedContent); + console.log(`Generated temporary file for ${module}`); - fs.writeFileSync(readmeFile, readmeContent); + const structuredDocumentation = await generateAiEnhancedReadme(combinedContent, [ + "Installation", "Usage", "Configuration", "API", "Examples", + "Dependencies", "Known Issues", "Contributing", "Changelog", "License" + ]); + fs.writeFileSync(readmeFile, structuredDocumentation); generatedDocs.push(module); - console.log(`Generated README for ${category}: ${module}`); + console.log(`Generated README for ${module} under api-report`); } }); From 564a925bee9c477c91d0d25ff61883ef2b8b6ab1 Mon Sep 17 00:00:00 2001 From: Yemaneberhan-Lemma <yemaneberhan.lemma@axelerant.com> Date: Fri, 21 Feb 2025 00:08:44 +0300 Subject: [PATCH 10/12] Creating Scripts to generate Diataxis based documentation --- drupal-doc-gen/utils/aiUtils.js | 43 ++++++++++- drupal-doc-gen/utils/extractCoreInfo.js | 64 ++++++++++++++++ drupal-doc-gen/utils/generateDocs.js | 98 ++++++++++++++++++------- drupal-doc-gen/utils/organizeDocs.js | 63 ++++++++++++++++ 4 files changed, 237 insertions(+), 31 deletions(-) create mode 100644 drupal-doc-gen/utils/extractCoreInfo.js create mode 100644 drupal-doc-gen/utils/organizeDocs.js diff --git a/drupal-doc-gen/utils/aiUtils.js b/drupal-doc-gen/utils/aiUtils.js index c8638562..3d46f39b 100644 --- a/drupal-doc-gen/utils/aiUtils.js +++ b/drupal-doc-gen/utils/aiUtils.js @@ -38,10 +38,11 @@ ${content}`; return 'Error generating documentation.'; } } - +// Function to generate Diátaxis-based structured documentation async function generateDiataxisContent(content) { try { - const formattedPrompt = `You are an expert documentation generator following the Diátaxis framework. Generate documentation based on the following content: + const formattedPrompt = `You are an expert documentation generator following the Diátaxis framework (Tutorials, How-To Guides, Reference, Explanation). +Generate well-structured documentation based on the following content: Content: ${content}`; @@ -66,9 +67,43 @@ ${content}`; return response.data.choices[0].message.content.trim(); } catch (error) { - console.error('Error generating Diátaxis documentation:', error.response?.data || error.message); + console.error('❌ Error generating Diátaxis documentation:', error.response?.data || error.message); return 'Error generating Diátaxis documentation.'; } } -module.exports = { generateAiEnhancedReadme, generateDiataxisContent }; +// AI function to extract key points from a file +async function generateCorePoints(fileName, content) { + try { + const formattedPrompt = `You are an expert in extracting only relevant key points for documentation. + Given the content of the file "${fileName}", extract only the most useful core points for documentation. + + Content: + ${content}`; + + const response = await axios.post( + API_BASE_URL, + { + model: MODEL, + messages: [ + { role: 'system', content: 'You are an AI that extracts core documentation points from files.' }, + { role: 'user', content: formattedPrompt } + ], + max_tokens: 1000 + }, + { + headers: { + 'Authorization': `Bearer ${API_KEY}`, + 'Content-Type': 'application/json' + } + } + ); + + return response.data.choices[0].message.content.trim(); + } catch (error) { + console.error(`❌ Error extracting core points from ${fileName}:`, error.response?.data || error.message); + return 'Error extracting core points.'; + } +} + +module.exports = { generateAiEnhancedReadme, generateDiataxisContent, generateCorePoints }; diff --git a/drupal-doc-gen/utils/extractCoreInfo.js b/drupal-doc-gen/utils/extractCoreInfo.js new file mode 100644 index 00000000..b3f3f63b --- /dev/null +++ b/drupal-doc-gen/utils/extractCoreInfo.js @@ -0,0 +1,64 @@ +const fs = require('fs-extra'); +const path = require('path'); +const glob = require('glob'); +const { readFileIfExists } = require('./fileUtils'); +const { generateCorePoints } = require('./aiUtils'); // New AI function + +const OUTPUT_FILE = 'core_info.json'; + +const FILE_SOURCES = { + "Tools and Pre-requisites": ["README.md", "composer.json", ".lando.yml", "docker-compose.yml", ".github/workflows/*.yml"], + "Local Environment Setup": ["README.md", ".lando.yml", "docker-compose.yml", "web/sites/example.settings.local.php"], + "How to Work on This Project": ["README.md", "CONTRIBUTING.md", ".github/workflows/*.yml"], + "Content Structure": ["/config/sync/*", "web/modules/custom/*/README.md", "web/themes/custom/*/README.md"], + "Custom Modules and Themes": ["web/modules/custom/*/README.md", "web/themes/custom/*/README.md", "composer.json"], + "Testing": ["/tests/*", "/core/tests/*", ".github/workflows/*.yml"], + "Monitoring and Logging": ["/config/sync/system.logging.yml", "web/sites/default/settings.php", "web/sites/default/settings.local.php", "sentry.settings.yml"], + "CI/CD": [".github/workflows/*.yml", ".gitlab-ci.yml", "Jenkinsfile", "bitbucket-pipelines.yml"] +}; + +// Extract core points using AI +async function extractProjectDetails() { + let details = {}; + let missingFiles = []; + + for (const [category, files] of Object.entries(FILE_SOURCES)) { + details[category] = []; + + for (const filePattern of files) { + let matchingFiles = glob.sync(filePattern, { nodir: true }); + + if (matchingFiles.length === 0) { + missingFiles.push(filePattern); + } + + for (const file of matchingFiles) { + const content = readFileIfExists(file); + if (!content.trim()) continue; + + console.log(`🧠 Extracting core points from ${file} using AI...`); + const extractedPoints = await generateCorePoints(file, content); + + if (extractedPoints) { + details[category].push({ file, extractedPoints }); + } + } + } + } + + if (missingFiles.length > 0) { + console.warn("⚠️ Warning: Some expected files were not found:", missingFiles.join(", ")); + } + + return details; +} + +// Save extracted details into core_info.json +async function saveExtractedInfo() { + const projectDetails = await extractProjectDetails(); + + fs.writeFileSync(OUTPUT_FILE, JSON.stringify(projectDetails, null, 2)); + console.log(`✅ Core project information extracted and saved in '${OUTPUT_FILE}'`); +} + +saveExtractedInfo(); diff --git a/drupal-doc-gen/utils/generateDocs.js b/drupal-doc-gen/utils/generateDocs.js index 1a5e7449..20eb2ca2 100644 --- a/drupal-doc-gen/utils/generateDocs.js +++ b/drupal-doc-gen/utils/generateDocs.js @@ -1,45 +1,89 @@ const fs = require('fs-extra'); const path = require('path'); -const { readFileIfExists, ensureDirectories } = require('./fileUtils'); const { generateDiataxisContent } = require('./aiUtils'); -const OUTPUT_DIRS = { - 'Tutorial': 'docs3/tutorials', - 'How-To': 'docs3/how-to', - 'Reference': 'docs3/reference', - 'Explanation': 'docs3/explanation', - 'Index': 'docs3' +const CORE_INFO_FILE = 'core_info.json'; +const OUTPUT_DIR = 'docs'; + +// Ensure the `docs/` directory exists +fs.ensureDirSync(OUTPUT_DIR); + +// **Mapping core points to meaningful documentation files** +const DOC_STRUCTURE = { + "tools-and-prerequisites.md": ["Tools and Pre-requisites"], + "local-environment-setup.md": ["Local Environment Setup"], + "how-to-work-on-project.md": ["How to Work on This Project"], + "content-structure.md": ["Content Structure"], + "custom-modules-and-themes.md": ["Custom Modules and Themes"], + "testing-strategy.md": ["Testing"], + "monitoring-and-logging.md": ["Monitoring and Logging"], + "ci-cd-process.md": ["CI/CD"] }; -function extractProjectDetails(rootReadme, configFiles) { - let details = { overview: '', tools: [], setup: '', workflow: '', testing: '', monitoring: '', ci_cd: '' }; - const readmeContent = readFileIfExists(rootReadme); +// Decide which documentation files to create based on extracted core points +function determineDocumentationFiles(coreInfo) { + let documentationFiles = {}; - if (readmeContent) { - details.overview = readmeContent.split('\\n')[0]; - details.setup = readmeContent.match(/## Local Environment Setup[\\s\\S]*?(?=##|$)/gi)?.[0] || ''; - details.workflow = readmeContent.match(/## How to work on the project[\\s\\S]*?(?=##|$)/gi)?.[0] || ''; - details.testing = readmeContent.match(/## Testing[\\s\\S]*?(?=##|$)/gi)?.[0] || ''; - details.monitoring = readmeContent.match(/## Monitoring and Logging[\\s\\S]*?(?=##|$)/gi)?.[0] || ''; - details.ci_cd = readmeContent.match(/## CI\/CD[\s\S]*?(?=##|$)/gi)?.[0] || ''; + Object.entries(DOC_STRUCTURE).forEach(([docFile, categories]) => { + let content = `# ${docFile.replace(/-/g, ' ').replace('.md', '')}\n\n`; + categories.forEach(category => { + if (coreInfo[category]) { + coreInfo[category].forEach(item => { + content += `### From ${item.file}\n${item.extractedPoints}\n\n`; + }); + } + }); + documentationFiles[docFile] = content; + }); - } + return documentationFiles; +} + +// Create meaningful documentation files with extracted content +function createDocumentationFiles(documentationFiles) { + Object.entries(documentationFiles).forEach(([fileName, content]) => { + const filePath = path.join(OUTPUT_DIR, fileName); + fs.writeFileSync(filePath, content); + console.log(`📄 Created: ${filePath}`); + }); +} - return details; +// Refine documentation files using AI +async function refineDocumentationWithAI() { + const files = fs.readdirSync(OUTPUT_DIR); + for (const file of files) { + const filePath = path.join(OUTPUT_DIR, file); + let content = fs.readFileSync(filePath, 'utf8'); + + console.log(`🧠 Enhancing ${filePath} with AI...`); + const enhancedContent = await generateDiataxisContent(content); + + fs.writeFileSync(filePath, enhancedContent); + console.log(`✅ AI-enhanced content saved: ${filePath}`); + } } -async function generateDiataxisDocs(rootReadme, configFiles) { - ensureDirectories(Object.values(OUTPUT_DIRS)); +// Main function to generate documentation +async function generateDocs() { + if (!fs.existsSync(CORE_INFO_FILE)) { + console.error(`❌ Missing core information file: ${CORE_INFO_FILE}`); + return; + } + + const coreInfo = JSON.parse(fs.readFileSync(CORE_INFO_FILE, 'utf8')); + + console.log("📄 Determining meaningful documentation files..."); + const documentationFiles = determineDocumentationFiles(coreInfo); - const projectDetails = extractProjectDetails(rootReadme, configFiles); - const structuredDocs = await generateDiataxisContent(JSON.stringify(projectDetails, null, 2)); + console.log("📝 Creating structured documentation files..."); + createDocumentationFiles(documentationFiles); - const indexContent = `# Project Documentation Index\\n\\n## Introduction\\n${projectDetails.overview}\\n\\n## Tools Used\\n${projectDetails.tools.join(', ')}\\n\\n## Setup\\n${projectDetails.setup}\\n\\n## CI/CD\\n${projectDetails.ci_cd}\\n\\n## Available Modules and Themes:\\n`; + console.log("🚀 Enhancing documentation using AI..."); + await refineDocumentationWithAI(); - fs.writeFileSync(path.join(OUTPUT_DIRS.Index, 'index.md'), indexContent); - console.log('Diátaxis-based documentation with AI-enhanced insights generated.'); + console.log("🎉 Project documentation successfully generated and refined."); } -module.exports = { generateDiataxisDocs }; +generateDocs(); diff --git a/drupal-doc-gen/utils/organizeDocs.js b/drupal-doc-gen/utils/organizeDocs.js new file mode 100644 index 00000000..b2e70ee2 --- /dev/null +++ b/drupal-doc-gen/utils/organizeDocs.js @@ -0,0 +1,63 @@ +const fs = require('fs-extra'); +const path = require('path'); + +const SOURCE_DIR = 'docs'; +const OUTPUT_DIRS = { + 'Tutorial': 'docs_final/tutorials', + 'How-To': 'docs_final/how-to', + 'Reference': 'docs_final/reference', + 'Explanation': 'docs_final/explanation' +}; + +// Ensure output directories exist +function ensureOutputDirectories() { + Object.values(OUTPUT_DIRS).forEach(dir => fs.ensureDirSync(dir)); +} + +// Categorize documentation files based on their content +function categorizeDocumentationFiles() { + const files = fs.readdirSync(SOURCE_DIR); + let categorizedFiles = { Tutorial: [], 'How-To': [], Reference: [], Explanation: [] }; + + files.forEach(file => { + const filePath = path.join(SOURCE_DIR, file); + const content = fs.readFileSync(filePath, 'utf8'); + + if (content.includes('## Step-by-Step Guide')) { + categorizedFiles.Tutorial.push(file); + } else if (content.includes('## How To')) { + categorizedFiles['How-To'].push(file); + } else if (content.includes('## API Reference') || content.includes('## Configuration')) { + categorizedFiles.Reference.push(file); + } else { + categorizedFiles.Explanation.push(file); + } + }); + + return categorizedFiles; +} + +// Move files into the correct Diátaxis categories +function moveFilesToCategories(categorizedFiles) { + Object.entries(categorizedFiles).forEach(([category, files]) => { + files.forEach(file => { + const srcPath = path.join(SOURCE_DIR, file); + const destPath = path.join(OUTPUT_DIRS[category], file); + fs.moveSync(srcPath, destPath); + console.log(`📂 Moved ${file} to ${category}`); + }); + }); +} + +// Main function +function organizeDocs() { + console.log("📂 Organizing documentation into Diátaxis structure..."); + ensureOutputDirectories(); + + const categorizedFiles = categorizeDocumentationFiles(); + moveFilesToCategories(categorizedFiles); + + console.log("🎉 Documentation successfully structured into Diátaxis categories."); +} + +organizeDocs(); From 3401f6067d08316eeb053dac4e5210d0d2aafc66 Mon Sep 17 00:00:00 2001 From: Yemaneberhan-Lemma <yemaneberhan.lemma@axelerant.com> Date: Tue, 25 Feb 2025 12:59:49 +0300 Subject: [PATCH 11/12] Removed unused scripts and updated active ones --- drupal-doc-gen/index.js | 281 +++--------------------- drupal-doc-gen/index02.js | 234 -------------------- drupal-doc-gen/index03.js | 185 ---------------- drupal-doc-gen/index04.js | 129 ----------- drupal-doc-gen/index_gen.js | 11 - drupal-doc-gen/testGenerateReadme.js | 11 - drupal-doc-gen/utils/aiUtils.js | 27 ++- drupal-doc-gen/utils/extractCoreInfo.js | 63 ++++-- drupal-doc-gen/utils/generateDocs.js | 38 +++- 9 files changed, 134 insertions(+), 845 deletions(-) delete mode 100644 drupal-doc-gen/index02.js delete mode 100644 drupal-doc-gen/index03.js delete mode 100644 drupal-doc-gen/index04.js delete mode 100644 drupal-doc-gen/index_gen.js delete mode 100644 drupal-doc-gen/testGenerateReadme.js diff --git a/drupal-doc-gen/index.js b/drupal-doc-gen/index.js index 016a4c1f..aac6131b 100644 --- a/drupal-doc-gen/index.js +++ b/drupal-doc-gen/index.js @@ -1,256 +1,41 @@ -const fs = require('fs-extra'); +const { exec } = require('child_process'); const path = require('path'); -const axios = require('axios'); -// Load environment variables -require('dotenv').config(); -console.log('Using API Key:', process.env.OPENROUTER_API_KEY ? '***' : 'Not found'); - -// Configuration -const API_BASE_URL = process.env.OPENROUTER_API_BASE_URL || 'https://openrouter.ai/api/v1/chat/completions'; -const API_KEY = process.env.OPENROUTER_API_KEY; -const MODEL = process.env.OPENROUTER_MODEL || 'google/gemini-2.0-flash-001'; -const CUSTOM_DIRS = [ - path.resolve(process.cwd(), '../web/modules/custom'), - path.resolve(process.cwd(), '../web/themes/custom') -]; -const OUTPUT_DIRS = { - 'Tutorial': 'docs/tutorials', - 'How-To': 'docs/how-to', - 'Reference': 'docs/reference', - 'Explanation': 'docs/explanation', - 'Misc': 'docs/misc' -}; - -// Utility function to read files if they exist -function readFileIfExists(filePath) { - try { - return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : ''; - } catch (error) { - console.error(`Error reading file ${filePath}:`, error.message); - return ''; - } -} - -// Recursively walk through directories and collect all README.md files -function collectReadmeFiles(directory) { - let readmeFiles = []; - - function walk(dir) { - if (!fs.existsSync(dir)) { - console.warn(`Directory not found: ${dir}`); - return; - } - - const files = fs.readdirSync(dir); - for (const file of files) { - const fullPath = path.join(dir, file); - - // Skip node_modules, vendor, and contrib directories - if (file === 'node_modules' || file === 'vendor' || file === 'contrib') { - console.log(`Skipping directory: ${fullPath}`); - continue; - } - - if (fs.statSync(fullPath).isDirectory()) { - walk(fullPath); // Recursively walk through subdirectories - } else if (file.toLowerCase() === 'readme.md') { - readmeFiles.push(fullPath); - console.log(`Found README: ${fullPath}`); - } - } - } - - walk(directory); - return readmeFiles; -} - -// Read and return the content of all README.md files -function readAllReadmeFiles() { - let readmeContents = []; - for (const dir of CUSTOM_DIRS) { - console.log(`Scanning directory: ${dir}`); - const readmeFiles = collectReadmeFiles(dir); - for (const readmeFile of readmeFiles) { - const content = readFileIfExists(readmeFile); - console.log(`Reading README: ${readmeFile} - Length: ${content.length}`); - readmeContents.push({ name: path.basename(path.dirname(readmeFile)), content }); - } - } - return readmeContents; -} - -// Read the root README.md and major configuration files (package.json, composer.json) -function readProjectFiles() { - const rootReadmePath = path.resolve(process.cwd(), '../README.md'); - const rootReadme = readFileIfExists(rootReadmePath); - const packageJsonPath = path.resolve(process.cwd(), '../package.json'); - const composerJsonPath = path.resolve(process.cwd(), '../composer.json'); - - const packageJson = readFileIfExists(packageJsonPath); - const composerJson = readFileIfExists(composerJsonPath); - - return { rootReadme, packageJson, composerJson }; -} - -// Extract dependencies or tools from package.json and composer.json -function extractDependencies(packageJsonContent, composerJsonContent) { - let dependencies = []; - - if (packageJsonContent) { - try { - const packageJson = JSON.parse(packageJsonContent); - dependencies.push('**NPM Dependencies:**'); - for (const [dep, version] of Object.entries(packageJson.dependencies || {})) { - dependencies.push(`- ${dep}: ${version}`); - } - dependencies.push('**Development Dependencies:**'); - for (const [dep, version] of Object.entries(packageJson.devDependencies || {})) { - dependencies.push(`- ${dep}: ${version}`); - } - } catch (error) { - console.error('Error parsing package.json:', error.message); - } - } - - if (composerJsonContent) { - try { - const composerJson = JSON.parse(composerJsonContent); - dependencies.push('**Composer Dependencies:**'); - for (const [dep, version] of Object.entries(composerJson.require || {})) { - dependencies.push(`- ${dep}: ${version}`); - } - dependencies.push('**Composer Development Dependencies:**'); - for (const [dep, version] of Object.entries(composerJson['require-dev'] || {})) { - dependencies.push(`- ${dep}: ${version}`); +// **Define Paths** +const PROJECT_ROOT = path.resolve(__dirname, '..'); // Moves up one level from drupal-doc-gen +const SCRIPT_DIR = __dirname; // drupal-doc-gen/utils/ + +// **Run a script and wait for it to complete** +function runScript(command, options = {}) { + return new Promise((resolve, reject) => { + console.log(`🚀 Running: ${command}...`); + const process = exec(command, options); + + process.stdout.on('data', (data) => console.log(data.toString())); + process.stderr.on('data', (data) => console.error(data.toString())); + + process.on('exit', (code) => { + if (code === 0) { + console.log(`✅ Finished: ${command}\n`); + resolve(); + } else { + reject(new Error(`❌ Failed: ${command} with exit code ${code}`)); } - } catch (error) { - console.error('Error parsing composer.json:', error.message); - } - } - - return dependencies.join('\n'); -} - -// Truncate long content for better classification (adjust as needed) -function truncateContent(content, maxLength = 2000) { - if (content.length > maxLength) { - return content.substring(0, maxLength) + '... [Content truncated]'; - } - return content; + }); + }); } -// Function to classify content and extract structured information using OpenRouter -async function classifyDocumentation(content) { +// **Sequential Execution of Scripts** +(async () => { try { - const truncatedContent = truncateContent(content); - const prompt = `You are a documentation assistant using the Diátaxis framework. The content provided is a README or project-related file. Please do the following: - -1. Classify the content as one of the following categories: Tutorial, How-To, Reference, or Explanation. -2. Provide a 1-2 sentence summary of the content. -3. Identify key sections like "Overview," "Usage," "Dependencies," and "Examples" if available. - -Your response must be structured as follows: - -Category: [One of Tutorial, How-To, Reference, Explanation] -Summary: [A brief summary] -Overview: [Key points or first section from the README] -Usage: [Any usage instructions found in the README] -Dependencies: [List any dependencies if mentioned] -Examples: [List examples if any are present] - -Content: -${truncatedContent}`; - - const response = await axios.post( - API_BASE_URL, - { - model: MODEL, - messages: [ - { - role: 'user', - content: prompt - } - ], - extra_body: {} - }, - { - headers: { - 'Authorization': `Bearer ${API_KEY}`, - 'Content-Type': 'application/json', - } - } - ); - - if (response.data && response.data.choices && response.data.choices[0]) { - return response.data.choices[0].message.content.trim(); - } - - console.warn('Unexpected response format or empty response:', response.data); - return 'Could not classify this content automatically.'; + await runScript('node utils/generateReadme.js', { cwd: SCRIPT_DIR }); // Step 1: Generate README + await runScript('node drupal-doc-gen/utils/extractCoreInfo.js', { cwd: PROJECT_ROOT }); // Step 2: Extract Core Points + await runScript('node utils/generateDocs.js', { cwd: SCRIPT_DIR }); // Step 3: Generate Documentation + await runScript('node utils/organizeDocs.js', { cwd: SCRIPT_DIR }); // Step 4: Organize Documentation + + console.log("🎉 All tasks completed successfully!"); } catch (error) { - console.error('Error classifying documentation:', error.response?.data || error.message); - return 'Could not classify this content due to an API error.'; + console.error(error.message); + process.exit(1); } -} - -// Function to generate the appropriate file name based on the Diátaxis category and source name -function generateFileName(category, sourceName) { - const sanitizedSourceName = sourceName.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase(); - return `${sanitizedSourceName}_${category.toLowerCase()}.md`; -} - -// Function to save generated documentation to the appropriate directory -function saveDocumentation(type, content, fileName) { - const targetDir = OUTPUT_DIRS[type] || OUTPUT_DIRS['Misc']; - fs.outputFileSync(path.join(targetDir, fileName), content); -} - -// Main function to generate documentation -async function generateDocumentation() { - const { rootReadme, packageJson, composerJson } = readProjectFiles(); - const contentSources = [ - { name: 'Project README', content: rootReadme }, - ...readAllReadmeFiles() - ]; - - // Ensure output directories exist - Object.values(OUTPUT_DIRS).forEach(dir => { - fs.ensureDirSync(dir); - }); - - const dependenciesSummary = extractDependencies(packageJson, composerJson); - - // Process files concurrently - await Promise.all(contentSources.map(async (source) => { - try { - const classificationResult = await classifyDocumentation(source.content); - const output = `# ${source.name} - -${classificationResult} - ---- - -**Dependencies and Tools:** - -${dependenciesSummary} - ---- - -**Original README Content:** - -${source.content}`; - const category = classificationResult.match(/Category:\s*(.*)/i)?.[1] || 'Misc'; - const fileName = generateFileName(category, source.name); - saveDocumentation(category, output, fileName); - console.log(`Generated documentation for ${source.name} -> ${category}`); - } catch (error) { - console.error(`Error processing ${source.name}:`, error.message); - } - })); - - console.log('Documentation generation complete!'); -} - -// Run the main function -generateDocumentation(); +})(); diff --git a/drupal-doc-gen/index02.js b/drupal-doc-gen/index02.js deleted file mode 100644 index 13854b71..00000000 --- a/drupal-doc-gen/index02.js +++ /dev/null @@ -1,234 +0,0 @@ -const fs = require('fs-extra'); -const path = require('path'); -const axios = require('axios'); -const crypto = require('crypto'); - -// Load environment variables -require('dotenv').config(); -console.log('Using API Key:', process.env.OPENROUTER_API_KEY ? '***' : 'Not found'); - -// Configuration -const API_BASE_URL = process.env.OPENROUTER_API_BASE_URL || 'https://openrouter.ai/api/v1/chat/completions'; -const API_KEY = process.env.OPENROUTER_API_KEY; -const MODEL = process.env.OPENROUTER_MODEL || 'google/gemini-2.0-flash-001'; -const CUSTOM_DIRS = [ - path.resolve(process.cwd(), '../web/modules/custom'), - path.resolve(process.cwd(), '../web/themes/custom') -]; -const OUTPUT_DIRS = { - 'Tutorial': 'docs1/tutorials', - 'How-To': 'docs1/how-to', - 'Reference': 'docs1/reference', - 'Explanation': 'docs1/explanation', - 'Index': 'docs1' -}; - -// Utility function to read files if they exist -function readFileIfExists(filePath) { - try { - return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : ''; - } catch (error) { - console.error(`Error reading file ${filePath}:`, error.message); - return ''; - } -} - -// Recursively collect all README.md files -function collectReadmeFiles(directory) { - let readmeFiles = []; - function walk(dir) { - if (!fs.existsSync(dir)) { - console.warn(`Directory not found: ${dir}`); - return; - } - const files = fs.readdirSync(dir); - for (const file of files) { - const fullPath = path.join(dir, file); - if (file === 'node_modules' || file === 'vendor' || file === 'contrib') continue; - if (fs.statSync(fullPath).isDirectory()) walk(fullPath); - else if (file.toLowerCase() === 'readme.md') readmeFiles.push(fullPath); - } - } - walk(directory); - return readmeFiles; -} - -// Read and return the content of all README.md files -function readAllReadmeFiles() { - let readmeContents = []; - for (const dir of CUSTOM_DIRS) { - console.log(`Scanning directory: ${dir}`); - const readmeFiles = collectReadmeFiles(dir); - for (const readmeFile of readmeFiles) { - const content = readFileIfExists(readmeFile); - console.log(`Reading README: ${readmeFile} - Length: ${content.length}`); - readmeContents.push({ name: path.basename(path.dirname(readmeFile)), content }); - } - } - return readmeContents; -} - -// Read the root README.md and major configuration files (package.json, composer.json) -function readProjectFiles() { - const rootReadmePath = path.resolve(process.cwd(), '../README.md'); - const rootReadme = readFileIfExists(rootReadmePath); - const packageJsonPath = path.resolve(process.cwd(), '../package.json'); - const composerJsonPath = path.resolve(process.cwd(), '../composer.json'); - - const packageJson = readFileIfExists(packageJsonPath); - const composerJson = readFileIfExists(composerJsonPath); - - return { rootReadme, packageJson, composerJson }; -} - -// Enhanced prompt for AI to classify and generate structured documentation -async function classifyAndGenerateDocumentation(source) { - const truncatedContent = source.content.length > 2000 ? source.content.slice(0, 2000) + '... [truncated]' : source.content; - const prompt = `You are tasked with generating structured documentation using the Diátaxis framework for the provided project source: - -Source: ${source.name} - -Analyze the content and generate appropriate documentation under one of the following categories: Tutorial, How-To Guide, Reference, or Explanation. - -Format the response as follows: - -Category: [One of Tutorial, How-To, Reference, Explanation] -Title: [Title of the documentation] -Content: -[The structured content for this section] - -Project Content: -${truncatedContent}`; - - try { - const response = await axios.post( - API_BASE_URL, - { model: MODEL, messages: [{ role: 'user', content: prompt }] }, - { headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' } } - ); - - const result = response.data.choices[0].message.content.trim(); - - const categoryMatch = result.match(/Category:\s*(.*)/i)?.[1]?.trim(); - const titleMatch = result.match(/Title:\s*(.*)/i)?.[1]?.trim(); - const contentMatch = result.split(/Content:/i)[1]?.trim(); - - if (categoryMatch && titleMatch && contentMatch) { - return { category: categoryMatch, title: titleMatch, content: contentMatch }; - } else { - throw new Error('Unexpected response format'); - } - } catch (error) { - console.error('Error generating documentation:', error.message); - return null; - } -} - -// Enhanced filename generator -function generateFileName(title, category) { - const sanitizedTitle = title.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase(); - const hash = crypto.createHash('md5').update(title).digest('hex').slice(0, 6); - return `${sanitizedTitle}_${category.toLowerCase()}_${hash}.md`; -} - -// Generate the documentation index page -function generateIndexPage(documentationLinks) { - const intro = `# Project Documentation Index - -This is the main index page for the project documentation, organized using the Diátaxis framework. - -Navigate to the specific sections below for more details: -`; - const linksList = documentationLinks.map(({ category, title, fileName }) => `- **[${title} (${category})](${fileName})**`).join('\n'); - return `${intro} -${linksList}`; -} - -// Save content to the appropriate directory -function saveDocumentation(type, title, content) { - const targetDir = OUTPUT_DIRS[type] || OUTPUT_DIRS['Index']; - const fileName = generateFileName(title, type); - fs.outputFileSync(path.join(targetDir, fileName), content); - return fileName; -} - -// Extract dependencies or tools from package.json and composer.json -function extractDependencies(packageJsonContent, composerJsonContent) { - let dependencies = []; - - if (packageJsonContent) { - try { - const packageJson = JSON.parse(packageJsonContent); - dependencies.push('**NPM Dependencies:**'); - for (const [dep, version] of Object.entries(packageJson.dependencies || {})) { - dependencies.push(`- ${dep}: ${version}`); - } - dependencies.push('**Development Dependencies:**'); - for (const [dep, version] of Object.entries(packageJson.devDependencies || {})) { - dependencies.push(`- ${dep}: ${version}`); - } - } catch (error) { - console.error('Error parsing package.json:', error.message); - } - } - - if (composerJsonContent) { - try { - const composerJson = JSON.parse(composerJsonContent); - dependencies.push('**Composer Dependencies:**'); - for (const [dep, version] of Object.entries(composerJson.require || {})) { - dependencies.push(`- ${dep}: ${version}`); - } - dependencies.push('**Composer Development Dependencies:**'); - for (const [dep, version] of Object.entries(composerJson['require-dev'] || {})) { - dependencies.push(`- ${dep}: ${version}`); - } - } catch (error) { - console.error('Error parsing composer.json:', error.message); - } - } - - return dependencies.join('\n'); -} - -// Main function to generate structured documentation -async function generateDocumentation() { - const { rootReadme, packageJson, composerJson } = readProjectFiles(); - const contentSources = [ - { name: 'Project README', content: rootReadme }, - ...readAllReadmeFiles() - ]; - const dependenciesSummary = extractDependencies(packageJson, composerJson); - - let documentationLinks = []; - - for (const source of contentSources) { - const docResult = await classifyAndGenerateDocumentation(source); - if (docResult) { - const { category, title, content } = docResult; - const fullContent = `# ${title} - -${content} - ---- - -**Dependencies and Tools:** - -${dependenciesSummary} - ---- - -**Original Source:** ${source.name}`; - const fileName = saveDocumentation(category, title, fullContent); - documentationLinks.push({ category, title, fileName }); - } - } - - // Generate and save the index page - const indexPageContent = generateIndexPage(documentationLinks); - saveDocumentation('Index', 'index', indexPageContent); - - console.log('Documentation generation complete!'); -} - -generateDocumentation(); diff --git a/drupal-doc-gen/index03.js b/drupal-doc-gen/index03.js deleted file mode 100644 index 31a0a7c6..00000000 --- a/drupal-doc-gen/index03.js +++ /dev/null @@ -1,185 +0,0 @@ -const fs = require('fs-extra'); -const path = require('path'); -const axios = require('axios'); -const crypto = require('crypto'); -const yaml = require('js-yaml'); -const { parse } = require('comment-parser'); - -// Load environment variables -require('dotenv').config(); -console.log('Using API Key:', process.env.OPENROUTER_API_KEY ? '***' : 'Not found'); - -// Configuration -const API_BASE_URL = process.env.OPENROUTER_API_BASE_URL || 'https://openrouter.ai/api/v1/chat/completions'; -const API_KEY = process.env.OPENROUTER_API_KEY; -const MODEL = process.env.OPENROUTER_MODEL || 'google/gemini-2.0-flash-001'; -const CUSTOM_MODULES_DIR = path.resolve(process.cwd(), '../web/modules/custom'); -const OUTPUT_DIRS = { - 'Tutorial': 'docs3/tutorials', - 'How-To': 'docs3/how-to', - 'Reference': 'docs3/reference', - 'Explanation': 'docs3/explanation', - 'Index': 'docs3' -}; - -// Utility function to read files if they exist -function readFileIfExists(filePath) { - try { - return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : ''; - } catch (error) { - console.error(`Error reading file ${filePath}:`, error.message); - return ''; - } -} - -// Extract module-specific details from its source code -function analyzeModuleCode(modulePath) { - let details = { - plugins: [], - queueWorkers: [], - services: [], - hooks: [], - classes: [], - functions: [] - }; - - function walk(dir) { - if (!fs.existsSync(dir)) return; - const files = fs.readdirSync(dir); - for (const file of files) { - const fullPath = path.join(dir, file); - if (fs.statSync(fullPath).isDirectory()) { - walk(fullPath); - } else if (fullPath.endsWith('.php')) { - const content = readFileIfExists(fullPath); - if (/\@ContributionSource/.test(content)) { - details.plugins.push(fullPath.replace(modulePath + '/', '')); - } - if (/implements QueueWorkerBase/.test(content)) { - details.queueWorkers.push(fullPath.replace(modulePath + '/', '')); - } - if (/services\.yml/.test(fullPath)) { - details.services.push(fullPath.replace(modulePath + '/', '')); - } - if (/hook_/.test(content)) { - details.hooks.push(fullPath.replace(modulePath + '/', '')); - } - const classMatches = content.match(/class\s+([A-Za-z0-9_]+)/g); - if (classMatches) { - classMatches.forEach(cls => details.classes.push(cls.replace('class ', ''))); - } - const functionMatches = content.match(/function\s+([A-Za-z0-9_]+)/g); - if (functionMatches) { - functionMatches.forEach(fn => details.functions.push(fn.replace('function ', ''))); - } - } - } - } - - walk(modulePath); - return details; -} - -// Generate README files for custom Drupal modules -function generateModuleReadmes() { - if (!fs.existsSync(CUSTOM_MODULES_DIR)) { - console.warn(`Custom modules directory not found: ${CUSTOM_MODULES_DIR}`); - return []; - } - - const moduleDirs = fs.readdirSync(CUSTOM_MODULES_DIR).filter(dir => { - return fs.statSync(path.join(CUSTOM_MODULES_DIR, dir)).isDirectory(); - }); - - let generatedReadmes = []; - - moduleDirs.forEach(module => { - const modulePath = path.join(CUSTOM_MODULES_DIR, module); - const infoFilePath = path.join(modulePath, `${module}.info.yml`); - const readmePath = path.join(modulePath, 'README.md'); - - if (fs.existsSync(readmePath)) { - console.log(`README already exists for module: ${module}`); - return; - } - - let moduleInfo = {}; - if (fs.existsSync(infoFilePath)) { - try { - moduleInfo = yaml.load(fs.readFileSync(infoFilePath, 'utf8')); - } catch (error) { - console.error(`Error parsing ${infoFilePath}:`, error.message); - } - } - - const codeAnalysis = analyzeModuleCode(modulePath); - - const readmeContent = `# ${moduleInfo.name || module} Module - -` + - `## Description -${moduleInfo.description || 'No description available.'} - -` + - `## Table of Contents -[[_TOC_]] - -` + - `## Introduction -${moduleInfo.description || 'This module provides functionality for Drupal integration.'} - -` + - `## Usage -This module contains the following key functionalities: - -${codeAnalysis.functions.length ? codeAnalysis.functions.map(f => `- Function: ${f}`).join('\n') : 'No specific functions detected.'} - -` + - `## Plugin Implementation -${codeAnalysis.plugins.length ? codeAnalysis.plugins.map(p => `- ${p}`).join('\n') : 'No custom plugins found.'} - -` + - `## Queue Workers -${codeAnalysis.queueWorkers.length ? codeAnalysis.queueWorkers.map(q => `- ${q}`).join('\n') : 'No queue workers found.'} - -` + - `## Services & API -${codeAnalysis.services.length ? codeAnalysis.services.map(s => `- ${s}`).join('\n') : 'No services defined.'} - -` + - `## Hooks Implemented -${codeAnalysis.hooks.length ? codeAnalysis.hooks.map(h => `- ${h}`).join('\n') : 'No custom hooks implemented.'} - -` + - `## Main Classes & Implementations -${codeAnalysis.classes.length ? codeAnalysis.classes.map(c => `- ${c}`).join('\n') : 'No specific classes detected.'} - -` + - `## Example Usage -Here’s an example of how this module works: - -\`\`\`php -// Example usage of the module functionality -drush en ${module}; -drush cr; -\`\`\` - -Refer to the documentation for more details. - -`; - - fs.writeFileSync(readmePath, readmeContent); - generatedReadmes.push({ name: module, content: readmeContent }); - console.log(`Generated README for module: ${module}`); - }); - - return generatedReadmes; -} - -// Main function to generate structured documentation -async function generateDocumentation() { - const generatedReadmes = generateModuleReadmes(); - console.log('Custom module README generation complete.'); -} - -generateDocumentation(); diff --git a/drupal-doc-gen/index04.js b/drupal-doc-gen/index04.js deleted file mode 100644 index 6d1f0f2f..00000000 --- a/drupal-doc-gen/index04.js +++ /dev/null @@ -1,129 +0,0 @@ -const fs = require('fs-extra'); -const path = require('path'); -const axios = require('axios'); -const crypto = require('crypto'); -const yaml = require('js-yaml'); -const { parse } = require('comment-parser'); - -// Load environment variables -require('dotenv').config(); -console.log('Using API Key:', process.env.OPENROUTER_API_KEY ? '***' : 'Not found'); - -// Configuration -const API_BASE_URL = process.env.OPENROUTER_API_BASE_URL || 'https://openrouter.ai/api/v1/chat/completions'; -const API_KEY = process.env.OPENROUTER_API_KEY; -const MODEL = process.env.OPENROUTER_MODEL || 'google/gemini-2.0-flash-001'; -const CUSTOM_MODULES_DIR = path.resolve(process.cwd(), '../web/modules/custom'); -const THEMES_DIR = path.resolve(process.cwd(), '../web/themes/custom'); -const ROOT_README = path.resolve(process.cwd(), '../README.md'); -const CONFIG_FILES = [ - path.resolve(process.cwd(), '../composer.json'), - path.resolve(process.cwd(), '../package.json'), - path.resolve(process.cwd(), '../.github/workflows/ci.yml'), - path.resolve(process.cwd(), '../phpunit.xml'), - path.resolve(process.cwd(), '../docker-compose.yml') -]; -const OUTPUT_DIRS = { - 'Tutorial': 'docs3/tutorials', - 'How-To': 'docs3/how-to', - 'Reference': 'docs3/reference', - 'Explanation': 'docs3/explanation', - 'Index': 'docs3' -}; - -// Ensure output directories exist -Object.values(OUTPUT_DIRS).forEach(dir => fs.ensureDirSync(dir)); - -// Utility function to read files if they exist -function readFileIfExists(filePath) { - try { - return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : ''; - } catch (error) { - console.error(`Error reading file ${filePath}:`, error.message); - return ''; - } -} - -// Extract key project insights from README and config files -function extractProjectDetails() { - let details = { overview: '', tools: [], setup: '', workflow: '', testing: '', monitoring: '', ci_cd: '' }; - const readmeContent = readFileIfExists(ROOT_README); - - if (readmeContent) { - details.overview = readmeContent.split('\n')[0]; // First line as project title - details.setup = readmeContent.match(/## Local Environment Setup[\s\S]*?(?=##|$)/gi)?.[0] || ''; - details.workflow = readmeContent.match(/## How to work on the project[\s\S]*?(?=##|$)/gi)?.[0] || ''; - details.testing = readmeContent.match(/## Testing[\s\S]*?(?=##|$)/gi)?.[0] || ''; - details.monitoring = readmeContent.match(/## Monitoring and Logging[\s\S]*?(?=##|$)/gi)?.[0] || ''; - details.ci_cd = readmeContent.match(/## CI\/CD[\s\S]*?(?=##|$)/gi)?.[0] || ''; - } - - CONFIG_FILES.forEach(filePath => { - const content = readFileIfExists(filePath); - if (filePath.endsWith('composer.json') || filePath.endsWith('package.json')) { - try { - const jsonData = JSON.parse(content); - if (jsonData.dependencies) { - details.tools.push(...Object.keys(jsonData.dependencies)); - } - } catch (error) { - console.error(`Error parsing JSON file ${filePath}:`, error.message); - } - } - if (filePath.endsWith('phpunit.xml')) { - details.testing += '\n- PHPUnit detected for testing'; - } - if (filePath.endsWith('docker-compose.yml')) { - details.tools.push('Docker'); - } - }); - return details; -} - -// Generate README files for custom modules and themes -function generateReadme(directory, category) { - if (!fs.existsSync(directory)) return []; - let generatedDocs = []; - - fs.readdirSync(directory).forEach(module => { - const modulePath = path.join(directory, module); - if (fs.statSync(modulePath).isDirectory()) { - const infoFile = path.join(modulePath, `${module}.info.yml`); - const readmeFile = path.join(modulePath, 'README.md'); - - // Read module info - let moduleInfo = {}; - if (fs.existsSync(infoFile)) { - try { - moduleInfo = yaml.load(readFileIfExists(infoFile)); - } catch (error) { - console.error(`Error parsing YAML: ${error.message}`); - } - } - - const readmeContent = `# ${moduleInfo.name || module} Module\n\n## Description\n${moduleInfo.description || 'No description available.'}\n\n## Usage\nRefer to documentation for detailed usage.`; - - fs.writeFileSync(readmeFile, readmeContent); - generatedDocs.push(module); - console.log(`Generated README for ${category}: ${module}`); - } - }); - - return generatedDocs; -} - -// Generate structured Diátaxis documentation and index file -async function generateDiataxisDocs() { - const projectDetails = extractProjectDetails(); - const structuredDocs = await generateDiataxisContent(JSON.stringify(projectDetails, null, 2)); - - const indexContent = `# Project Documentation Index\n\n## Introduction\n${projectDetails.overview}\n\n## Tools Used\n${projectDetails.tools.join(', ')}\n\n## Setup\n${projectDetails.setup}\n\n## CI/CD\n${projectDetails.ci_cd}\n\n## Available Modules and Themes:\n`; - - fs.writeFileSync(path.join(OUTPUT_DIRS.Index, 'index.md'), indexContent); - generateReadme(CUSTOM_MODULES_DIR, 'module'); - generateReadme(THEMES_DIR, 'theme'); - console.log('Diátaxis-based documentation with AI-enhanced insights generated.'); -} - -// Run script -generateDiataxisDocs(); diff --git a/drupal-doc-gen/index_gen.js b/drupal-doc-gen/index_gen.js deleted file mode 100644 index e7afe17a..00000000 --- a/drupal-doc-gen/index_gen.js +++ /dev/null @@ -1,11 +0,0 @@ -const { generateReadme } = require('./utils/generateReadme'); -const { generateDiataxisDocs } = require('./utils/generateDocs'); - -const CUSTOM_MODULES_DIR = '../web/modules/custom'; -const THEMES_DIR = '../web/themes/custom'; -const ROOT_README = '../README.md'; -const CONFIG_FILES = ['../composer.json', '../package.json', '../.github/workflows/ci.yml']; - -generateReadme(CUSTOM_MODULES_DIR, 'module'); -generateReadme(THEMES_DIR, 'theme'); -generateDiataxisDocs(ROOT_README, CONFIG_FILES); diff --git a/drupal-doc-gen/testGenerateReadme.js b/drupal-doc-gen/testGenerateReadme.js deleted file mode 100644 index 90e85e73..00000000 --- a/drupal-doc-gen/testGenerateReadme.js +++ /dev/null @@ -1,11 +0,0 @@ -const path = require('path'); -const { generateReadme } = require('./utils/generateReadme'); - -// Define the directory of your custom modules -const CUSTOM_MODULES_DIR = path.resolve(process.cwd(), '../web/modules/custom'); - -(async () => { - console.log('Running generateReadme for testing...'); - const results = await generateReadme(CUSTOM_MODULES_DIR); - console.log('Test Complete. Generated README files:', results); -})(); diff --git a/drupal-doc-gen/utils/aiUtils.js b/drupal-doc-gen/utils/aiUtils.js index 3d46f39b..28dff0d0 100644 --- a/drupal-doc-gen/utils/aiUtils.js +++ b/drupal-doc-gen/utils/aiUtils.js @@ -73,12 +73,21 @@ ${content}`; } // AI function to extract key points from a file -async function generateCorePoints(fileName, content) { +async function generateCorePoints(category, fileName, content) { try { - const formattedPrompt = `You are an expert in extracting only relevant key points for documentation. - Given the content of the file "${fileName}", extract only the most useful core points for documentation. + const formattedPrompt = ` + You are extracting documentation core points for software engineers onboarding to the project. + The category of information we need is: **"${category}"**. - Content: + **Extract only relevant details that engineers need for this category**: + - **Explain how this file is related to "${category}"**. + - **Ignore unrelated details**. + - **Summarize only the key configurations, dependencies, or services that engineers should know**. + - **List important tools, environment variables, or settings relevant to "${category}"**. + - **If the file has no relevant details for "${category}", return "No relevant information"**. + + **Processing File: ${fileName}** + **File Content:** ${content}`; const response = await axios.post( @@ -86,10 +95,10 @@ async function generateCorePoints(fileName, content) { { model: MODEL, messages: [ - { role: 'system', content: 'You are an AI that extracts core documentation points from files.' }, + { role: 'system', content: 'You extract category-specific documentation core points for engineers.' }, { role: 'user', content: formattedPrompt } ], - max_tokens: 1000 + max_tokens: 1200 }, { headers: { @@ -99,7 +108,11 @@ async function generateCorePoints(fileName, content) { } ); - return response.data.choices[0].message.content.trim(); + const extractedText = response.data.choices[0].message.content.trim(); + + // Return "No relevant information" if AI couldn't extract anything useful + return extractedText.includes("No relevant information") ? null : extractedText; + } catch (error) { console.error(`❌ Error extracting core points from ${fileName}:`, error.response?.data || error.message); return 'Error extracting core points.'; diff --git a/drupal-doc-gen/utils/extractCoreInfo.js b/drupal-doc-gen/utils/extractCoreInfo.js index b3f3f63b..dfaea8a0 100644 --- a/drupal-doc-gen/utils/extractCoreInfo.js +++ b/drupal-doc-gen/utils/extractCoreInfo.js @@ -2,22 +2,59 @@ const fs = require('fs-extra'); const path = require('path'); const glob = require('glob'); const { readFileIfExists } = require('./fileUtils'); -const { generateCorePoints } = require('./aiUtils'); // New AI function +const { generateCorePoints } = require('./aiUtils'); const OUTPUT_FILE = 'core_info.json'; const FILE_SOURCES = { - "Tools and Pre-requisites": ["README.md", "composer.json", ".lando.yml", "docker-compose.yml", ".github/workflows/*.yml"], - "Local Environment Setup": ["README.md", ".lando.yml", "docker-compose.yml", "web/sites/example.settings.local.php"], - "How to Work on This Project": ["README.md", "CONTRIBUTING.md", ".github/workflows/*.yml"], - "Content Structure": ["/config/sync/*", "web/modules/custom/*/README.md", "web/themes/custom/*/README.md"], - "Custom Modules and Themes": ["web/modules/custom/*/README.md", "web/themes/custom/*/README.md", "composer.json"], - "Testing": ["/tests/*", "/core/tests/*", ".github/workflows/*.yml"], - "Monitoring and Logging": ["/config/sync/system.logging.yml", "web/sites/default/settings.php", "web/sites/default/settings.local.php", "sentry.settings.yml"], - "CI/CD": [".github/workflows/*.yml", ".gitlab-ci.yml", "Jenkinsfile", "bitbucket-pipelines.yml"] + "Tools and Pre-requisites": [ + "README.md", + "composer.json", + ".lando.yml", + ".ddev/config.yaml", + "docker-compose.yml", + ".github/workflows/*.yml", + "web/themes/custom/*/package.json" + ], + "Local Environment Setup": [ + "README.md", + ".lando.yml", + ".ddev/config.yaml", + "docker-compose.yml", + "web/sites/example.settings.local.php" + ], + "How to Work on This Project": [ + "README.md", + "CONTRIBUTING.md", + ".github/workflows/*.yml" + ], + "Content Structure": [ + "/config/sync/*", + "web/modules/custom/*/README.md", + "web/themes/custom/*/README.md" + ], + "Custom Modules and Themes": [ + "web/modules/custom/*/README.md", + "web/themes/custom/*/README.md", + "composer.json", + "docs/**/*README*.md" + ], + "Testing": [ + "/tests/*", + "/core/tests/*", + ".github/workflows/*.yml" + ], + "Monitoring and Logging": [ + "/config/sync/system.logging.yml", + "web/sites/default/settings.php", + "web/sites/default/settings.local.php" + ], + "CI/CD": [ + ".github/workflows/*.yml" + ] }; -// Extract core points using AI +// **Extract Core Points with AI** async function extractProjectDetails() { let details = {}; let missingFiles = []; @@ -36,8 +73,8 @@ async function extractProjectDetails() { const content = readFileIfExists(file); if (!content.trim()) continue; - console.log(`🧠 Extracting core points from ${file} using AI...`); - const extractedPoints = await generateCorePoints(file, content); + console.log(`🧠 Extracting core points from ${file} under category "${category}" using AI...`); + const extractedPoints = await generateCorePoints(category, file, content); if (extractedPoints) { details[category].push({ file, extractedPoints }); @@ -53,7 +90,7 @@ async function extractProjectDetails() { return details; } -// Save extracted details into core_info.json +// **Save Extracted Details** async function saveExtractedInfo() { const projectDetails = await extractProjectDetails(); diff --git a/drupal-doc-gen/utils/generateDocs.js b/drupal-doc-gen/utils/generateDocs.js index 20eb2ca2..fc848341 100644 --- a/drupal-doc-gen/utils/generateDocs.js +++ b/drupal-doc-gen/utils/generateDocs.js @@ -1,11 +1,11 @@ const fs = require('fs-extra'); const path = require('path'); -const { generateDiataxisContent } = require('./aiUtils'); +const { generateDiataxisContent } = require('./utils/aiUtils'); const CORE_INFO_FILE = 'core_info.json'; const OUTPUT_DIR = 'docs'; -// Ensure the `docs/` directory exists +// Ensure `docs/` directory exists fs.ensureDirSync(OUTPUT_DIR); // **Mapping core points to meaningful documentation files** @@ -20,7 +20,7 @@ const DOC_STRUCTURE = { "ci-cd-process.md": ["CI/CD"] }; -// Decide which documentation files to create based on extracted core points +// **Determine Documentation Files Based on Extracted Core Points** function determineDocumentationFiles(coreInfo) { let documentationFiles = {}; @@ -30,7 +30,8 @@ function determineDocumentationFiles(coreInfo) { categories.forEach(category => { if (coreInfo[category]) { coreInfo[category].forEach(item => { - content += `### From ${item.file}\n${item.extractedPoints}\n\n`; + content += `### Extracted Information from ${item.file}\n\n`; + content += `${item.extractedPoints}\n\n`; }); } }); @@ -41,7 +42,7 @@ function determineDocumentationFiles(coreInfo) { return documentationFiles; } -// Create meaningful documentation files with extracted content +// **Create Initial Documentation Files** function createDocumentationFiles(documentationFiles) { Object.entries(documentationFiles).forEach(([fileName, content]) => { const filePath = path.join(OUTPUT_DIR, fileName); @@ -50,7 +51,27 @@ function createDocumentationFiles(documentationFiles) { }); } -// Refine documentation files using AI +// **Generate Index File with Summary and Links** +function generateIndexFile(documentationFiles) { + let indexContent = `# Project Documentation Index\n\n`; + indexContent += `Welcome to the project documentation. Below is a summary of key sections and links to detailed pages.\n\n`; + + Object.entries(documentationFiles).forEach(([fileName, content]) => { + const sectionTitle = fileName.replace(/-/g, ' ').replace('.md', ''); + indexContent += `## [${sectionTitle}](./${fileName})\n\n`; + + // Extract first few lines as a summary + const summary = content.split("\n").slice(0, 5).join(" "); + indexContent += `${summary}...\n\n`; + }); + + // Save the index file + const indexPath = path.join(OUTPUT_DIR, 'index.md'); + fs.writeFileSync(indexPath, indexContent); + console.log(`📄 Created: ${indexPath}`); +} + +// **Refine Documentation Using AI** async function refineDocumentationWithAI() { const files = fs.readdirSync(OUTPUT_DIR); for (const file of files) { @@ -65,7 +86,7 @@ async function refineDocumentationWithAI() { } } -// Main function to generate documentation +// **Main Function to Generate Documentation** async function generateDocs() { if (!fs.existsSync(CORE_INFO_FILE)) { console.error(`❌ Missing core information file: ${CORE_INFO_FILE}`); @@ -80,6 +101,9 @@ async function generateDocs() { console.log("📝 Creating structured documentation files..."); createDocumentationFiles(documentationFiles); + console.log("📌 Generating index file..."); + generateIndexFile(documentationFiles); + console.log("🚀 Enhancing documentation using AI..."); await refineDocumentationWithAI(); From 6d648da5446589c618bffa49b76c703a8d69caf9 Mon Sep 17 00:00:00 2001 From: Yemaneberhan-Lemma <yemaneberhan.lemma@axelerant.com> Date: Wed, 12 Mar 2025 23:06:29 +0300 Subject: [PATCH 12/12] Shared Sample Generated MD Docs with LM --- docs_final/automated-testing.md | 97 +++++++++++++++ docs_final/ci-cd.md | 198 ++++++++++++++++++++++++++++++ docs_final/content-structure.md | 122 ++++++++++++++++++ docs_final/custom-modules.md | 161 ++++++++++++++++++++++++ docs_final/custom-themes.md | 78 ++++++++++++ docs_final/how-to-work.md | 79 ++++++++++++ docs_final/index.md | 30 +++++ docs_final/local-setup.md | 79 ++++++++++++ docs_final/monitoring-logging.md | 26 ++++ docs_final/overview.md | 26 ++++ docs_final/tools-prerequisites.md | 146 ++++++++++++++++++++++ 11 files changed, 1042 insertions(+) create mode 100644 docs_final/automated-testing.md create mode 100644 docs_final/ci-cd.md create mode 100644 docs_final/content-structure.md create mode 100644 docs_final/custom-modules.md create mode 100644 docs_final/custom-themes.md create mode 100644 docs_final/how-to-work.md create mode 100644 docs_final/index.md create mode 100644 docs_final/local-setup.md create mode 100644 docs_final/monitoring-logging.md create mode 100644 docs_final/overview.md create mode 100644 docs_final/tools-prerequisites.md diff --git a/docs_final/automated-testing.md b/docs_final/automated-testing.md new file mode 100644 index 00000000..06fadec1 --- /dev/null +++ b/docs_final/automated-testing.md @@ -0,0 +1,97 @@ +```markdown +# Testing and Code Quality + +This document outlines the testing strategy and code quality checks integrated into the project. It details the configuration of Continuous Integration (CI), Visual Regression (VR) testing, and other automated processes. + +## Continuous Integration (CI) + +The project utilizes GitLab CI/CD and GitHub Actions for Continuous Integration. Configuration details are found in `.gitlab-ci.yml` and `.github/workflows/ci.yml`. + +### GitLab CI/CD (`.gitlab-ci.yml`) + +This file configures the GitLab CI/CD pipeline. + +* **Stages:** The pipeline is structured into the following stages: `build`, `lint`, `test`, `deploy`. +* **Includes:** The configuration includes platform-specific settings from `.gitlab/platform.yml`. +* **Cache:** To optimize pipeline execution time, npm modules are cached. +* **Code Quality Checks (`drupal_codequality`):** The `drupal_codequality` job performs static analysis on custom Drupal modules. + * Uses the `hussainweb/drupalqa:php7.4` Docker image. + * Executes the following checks on files within the `web/modules/custom/` directory: + * `composer validate`: Validates the `composer.json` file. + * `phplint`: Checks for PHP syntax errors. + * `phpcs`: Enforces Drupal coding standards using PHP CodeSniffer. + * `phpmd`: Analyzes code for potential bugs and performance issues using PHP Mess Detector. +* **PHPUnit Tests (`drupal_tests`):** + * This section details how PHPUnit tests are executed. *This section contains an incomplete excerpt from the original documentation*. + +### GitHub Actions CI Workflow (`.github/workflows/ci.yml`) + +This workflow defines the Continuous Integration process using GitHub Actions. + +* **Triggers:** The workflow is triggered on: + * Pushes to the `main` branch and tags. + * Pull requests. +* **Concurrency:** Concurrent CI runs are prevented for the same branch/ref. +* **Drupal Code Quality Checks (`drupal_codequality`):** + * Uses the `hussainweb/drupalqa-action@v2` action to perform Drupal-specific code quality checks. + * Specifies PHP version 8.2. + * Leverages GrumPHP to execute a suite of code quality tools: + * `phplint`: Checks for PHP syntax errors. + * `yamllint`: Validates YAML syntax. + * `jsonlint`: Validates JSON syntax. + * `phpcs`: Enforces Drupal coding standards using PHP CodeSniffer. + * `phpmd`: Analyzes code for potential bugs and performance issues using PHP Mess Detector. + * `twigcs`: Checks Twig templates for coding standards compliance. +* **Frontend Code Quality Checks (`frontend_codequality`):** + * Executes frontend code quality checks within a `node:20` container. Specific tools and configurations are likely defined within the job's steps. + +## Visual Regression Testing (VR) + +Visual Regression tests are configured using GitHub Actions and Percy. The workflow definition is located in `.github/workflows/vr.yml`. + +### GitHub Actions VR Workflow (`.github/workflows/vr.yml`) + +* **Triggers:** The workflow is triggered on: + * Push to tags. + * Manual workflow dispatch. + * A cron schedule (every Monday at 00:00). +* **Concurrency:** Ensures that only one VR test runs at a time for a given ref, preventing conflicts and ensuring accurate results. +* **Environment Variables:** The following environment variables are defined: + * `CYPRESS_ADMIN_USERNAME`: Username for the administrative user for Cypress tests. + * `CYPRESS_ADMIN_PASSWORD`: Password for the administrative user for Cypress tests. + * `PERCY_TOKEN`: API token for Percy, obtained securely from a GitHub secret to authorize visual regression tests. +* **Steps:** The workflow performs the following steps: + * Checks out the repository code. + * Retrieves cached directories for Composer and npm to improve workflow speed. + * Utilizes `actions/cache` to implement caching properly. + +## Pull Request Cleanup + +### GitHub Actions PR Close Workflow (`.github/workflows/pr-close.yml`) + +This workflow automates the cleanup of Platform.sh environments associated with closed pull requests. + +* **Trigger:** The workflow runs when a pull request is closed. +* **Jobs:** Contains a job `on-pr-close`. +* **Steps:** + * Uses the `axelerant/platformsh-action@v1` action to remove the Platform.sh environment linked to the closed PR. + * Configured with: + * `project-id`: The Platform.sh project ID (obtained from a GitHub secret). + * `cli-token`: A Platform.sh CLI token (obtained from a GitHub secret). + +## Cypress End-to-End Tests + +### GitHub Actions Cypress Workflow (`.github/workflows/cypress-tests.yml`) + +This workflow configures and executes Cypress end-to-end tests. + +* **Trigger:** The workflow runs on a schedule (every Sunday at 00:00). +* **Environment Variables:** The following environment variables are defined: + * `CYPRESS_ADMIN_USERNAME`: Username for the administrative user for Cypress tests. + * `CYPRESS_ADMIN_PASSWORD`: Password for the administrative user for Cypress tests. +* **Steps:** The workflow performs the following steps: + * Checks out the repository code. + * Sets up DDEV using `ddev/github-action-setup-ddev@v1`. + * Configures and starts DDEV, pulling the database and files from Platform.sh. The Platform.sh CLI token is set as `PLATFORMSH_CLI_TOKEN`. + * Installs composer dependencies. +``` \ No newline at end of file diff --git a/docs_final/ci-cd.md b/docs_final/ci-cd.md new file mode 100644 index 00000000..8373d164 --- /dev/null +++ b/docs_final/ci-cd.md @@ -0,0 +1,198 @@ +```markdown +# CI/CD Configuration + +This document describes the Continuous Integration and Continuous Deployment (CI/CD) workflows configured for this Drupal project using GitHub Actions. These workflows automate tasks such as code quality checks, visual regression testing, end-to-end testing, and environment cleanup, ensuring code quality and efficient deployment processes. + +## Workflow Overview + +The following workflows are defined: + +* **CI (Continuous Integration):** Performs code quality analysis on every push and pull request. +* **VR (Visual Regression):** Executes visual regression tests to detect UI changes. +* **Cypress Tests:** Runs end-to-end tests using Cypress. +* **Clean Platform.sh env on PR Close:** Automatically cleans up Platform.sh environments upon pull request closure. + +## Workflow Details + +### Workflow: CI (Continuous Integration) + +* **Name:** CI +* **Purpose:** Performs continuous integration checks, including code quality analysis. +* **Triggers:** + * `push`: Triggered on pushes to the `main` branch and any tag (`tags: ["*"]`). + * `pull_request`: Triggered on pull requests. +* **Concurrency:** Ensures only one CI workflow runs per Git reference, canceling any in-progress runs. This prevents resource contention and ensures consistent results. + +#### Jobs + +* **`drupal_codequality`** + + * **Runs on:** `ubuntu-latest` + * **Steps:** + + 1. **Checkout Code:** Checks out the repository code using `actions/checkout@v4`. + + ```yaml + - uses: actions/checkout@v4 + ``` + + 2. **Drupal Code Quality Checks:** Runs Drupal-specific code quality checks using `hussainweb/drupalqa-action@v2`. This step leverages GrumPHP to run linters and code analysis tools. + + ```yaml + - name: Drupal Code Quality + uses: hussainweb/drupalqa-action@v2 + with: + php-version: '8.2' + checks: phplint,yamllint,jsonlint,phpcs,phpmd,twigcs + ``` + + * `php-version`: Specifies the PHP version to use for the checks (8.2). + * `checks`: Defines the code quality checks to perform, including `phplint`, `yamllint`, `jsonlint`, `phpcs` (PHP CodeSniffer), `phpmd` (PHP Mess Detector), and `twigcs` (Twig Code Sniffer). + +* **`frontend_codequality`** + + * **Runs on:** `ubuntu-latest` + * **Container:** `node:20` - Executes the job within a Docker container based on the `node:20` image. This ensures a consistent environment for frontend tooling. + * **Steps:** + + 1. **Checkout Code:** Checks out the repository code using `actions/checkout@v4`. + + ```yaml + - uses: actions/checkout@v4 + ``` + +### Workflow: VR (Visual Regression) + +* **Name:** VR +* **Purpose:** Performs visual regression tests. +* **Triggers:** + * `push`: Triggered on any tag (`tags: ["*"]`). This ensures visual regression tests are run on releases. + * `workflow_dispatch`: Allows manual triggering of the workflow. This is useful for on-demand testing or debugging. + * `schedule`: Scheduled execution every Monday at 00:00 UTC (`cron: "0 0 * * MON"`). This provides regular visual regression testing. +* **Concurrency:** Ensures only one VR workflow runs per Git reference, canceling in-progress runs. + +#### Jobs + +* **`vr_test`** + + * **Runs on:** `ubuntu-latest` + * **Environment Variables:** + + * `CYPRESS_ADMIN_USERNAME`: `ct-admin` - Specifies the username for the admin user in Cypress tests. + * `CYPRESS_ADMIN_PASSWORD`: `ct-admin` - Specifies the password for the admin user in Cypress tests. + * `PERCY_TOKEN`: Retrieved from GitHub secrets (`secrets.PERCY_TOKEN`). This token is required for Percy integration, a visual regression testing platform. Store this in GitHub secrets for security. + * **Steps:** + + 1. **Checkout Code:** Checks out the repository code using `actions/checkout@v4`. + + ```yaml + - uses: actions/checkout@v4 + ``` + + 2. **Cache Dependencies:** Retrieves cache directories for Composer and npm to speed up subsequent workflow runs. + + ```yaml + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-dir)" >> $GITHUB_OUTPUT + - uses: actions/cache@v4 + id: composer-cache-cache + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + - name: Get npm cache directory + id: npm-cache + shell: bash + run: | + echo "::set-output name=npm_cache::$(npm config get cache)" + - uses: actions/cache@v4 + id: npm-cache-cache + with: + path: ${{ steps.npm-cache.outputs.npm_cache }} + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-npm- + ``` + +### Workflow: Clean Platform.sh env on PR Close + +* **Name:** Clean Platform.sh env on PR Close +* **Purpose:** Cleans up Platform.sh environments when a pull request is closed. This helps to prevent the accumulation of unnecessary environments and saves resources. +* **Triggers:** `pull_request` of type `closed`. This ensures the workflow is triggered only when a pull request is closed. + +#### Jobs + +* **`on-pr-close`** + + * **Runs on:** `ubuntu-latest` + * **Steps:** + + 1. **Clean Platform.sh Environment:** Cleans the Platform.sh environment using `axelerant/platformsh-action@v1`. + + ```yaml + - name: Clean Platform.sh environment + uses: axelerant/platformsh-action@v1 + with: + action: 'clean-pr-env' + project-id: 'brbqplxd7ycq6' + cli-token: ${{ secrets.PLATFORMSH_CLI_TOKEN }} + ``` + + * `action`: `clean-pr-env` - Specifies the action to perform, which is cleaning the Platform.sh environment associated with the pull request. + * `project-id`: `brbqplxd7ycq6` - The Platform.sh project ID. + * `cli-token`: Retrieved from GitHub secrets (`secrets.PLATFORMSH_CLI_TOKEN`). This token is required to authenticate with the Platform.sh API. Store this in GitHub secrets for security. + +### Workflow: Cypress Tests + +* **Name:** Cypress Tests +* **Purpose:** Executes Cypress end-to-end tests. These tests verify the functionality of the application from a user's perspective. +* **Triggers:** `schedule`: Scheduled execution every Sunday at 00:00 UTC (`cron: "0 0 * * 0"`). This allows for regular end-to-end testing. + +#### Jobs + +* **`cypress_tests`** + + * **Runs on:** `ubuntu-latest` + * **Environment Variables:** + + * `CYPRESS_ADMIN_USERNAME`: `ct-admin` - Specifies the username for the admin user in Cypress tests. + * `CYPRESS_ADMIN_PASSWORD`: `ct-admin` - Specifies the password for the admin user in Cypress tests. + * **Steps:** + + 1. **Checkout Code:** Checks out the repository code using `actions/checkout@v4`. + + ```yaml + - uses: actions/checkout@v4 + ``` + + 2. **Setup DDEV:** Sets up DDEV using `ddev/github-action-setup-ddev@v1`. + + ```yaml + - name: Setup DDEV + uses: ddev/github-action-setup-ddev@v1 + with: + autostart: false + ``` + + * `autostart`: `false` - Prevents DDEV from automatically starting, allowing for custom configuration before starting. + + 3. **Configure and Start DDEV:** Configures the Platform.sh token in DDEV, starts DDEV, installs Composer dependencies, and pulls the Platform.sh database. + + 4. **Navigate to Theme Directory:** Navigates to the `contribtrack` theme directory, likely for theme-specific tests. + +## Security Considerations + +* **Secrets Management:** Sensitive information like `PERCY_TOKEN` and `PLATFORMSH_CLI_TOKEN` are stored as GitHub secrets. Ensure these secrets are properly managed and protected to prevent unauthorized access. +* **Token Permissions:** Restrict the permissions of the `PLATFORMSH_CLI_TOKEN` to only the necessary actions for cleaning up environments. Avoid granting overly broad permissions. + +## Best Practices + +* **Workflow Documentation:** Keep this documentation up-to-date as the CI/CD workflows evolve. +* **Test Coverage:** Strive for comprehensive test coverage to ensure code quality and prevent regressions. +* **Monitoring and Logging:** Monitor the execution of the CI/CD workflows to identify and address any issues promptly. Review logs for errors and warnings. +* **Incremental Improvements:** Regularly review and refine the CI/CD workflows to improve efficiency and effectiveness. + +This documentation provides a comprehensive overview of the CI/CD configuration for this Drupal project. By following these guidelines, the development team can ensure code quality, automate deployment processes, and maintain a stable and reliable application. +``` \ No newline at end of file diff --git a/docs_final/content-structure.md b/docs_final/content-structure.md new file mode 100644 index 00000000..74b16f3a --- /dev/null +++ b/docs_final/content-structure.md @@ -0,0 +1,122 @@ +```markdown +# Contribution Tracker: Content Structure Documentation + +This document provides an overview of the content structure used in the "Contribution Tracker" project. Understanding this structure is crucial for developers to effectively create, manage, and display content within the application. This documentation covers node types, taxonomies, views, user roles, and the content approval workflow. + +## I. Node Types + +Node types define the different types of content that can be created within the system. The following node types are used in the Contribution Tracker: + +### 1. Code Contributions + +* **Description:** Captures information about code-related contributions. +* **Fields:** + * `field_code_contrib_project`: Project associated with the contribution. + * `field_code_contrib_issue_link`: Link to the related issue. + * `field_code_contrib_link`: Link to the code contribution (e.g., GitHub pull request). + * `field_code_contrib_patches_count`: Number of patches included in the contribution. + * `field_code_contrib_files_count`: Number of files changed in the contribution. + * `field_contribution_author`: Author of the contribution. + * `field_contribution_date`: Date of the contribution. + * `field_contribution_technology`: Technology used in the contribution. + * `field_contrib_moderator_comment`: Moderator's comment on the contribution. + * `field_code_contrib_issue_status`: Status of the related issue. + +### 2. Non-Code Contributions + +* **Description:** Records details about non-code related contributions. +* **Fields:** + * `field_non_code_contribution_type`: Type of non-code contribution (e.g., blog post, Stack Overflow answer). + * `field_non_code_contrib_profile`: Link to the contributor's profile. + * `field_non_code_contrib_credit`: Credit given for the contribution. + * `field_contribution_author`: Author of the contribution. + * `field_contribution_date`: Date of the contribution. + * `field_contribution_technology`: Technology related to the contribution. + * `field_contrib_moderator_comment`: Moderator's comment on the contribution. + +### 3. Event Contributions + +* **Description:** Captures contributions made during events. +* **Fields:** + * `field_event`: Reference to the event node. + * `field_event_contribution_type`: Type of contribution made at the event. + * `field_event_contribution_link`: Link to the event contribution. + * `field_contribution_author`: Author of the contribution. + * `field_contribution_date`: Date of the contribution. + * `field_contribution_technology`: Technology related to the contribution. + * `field_contrib_moderator_comment`: Moderator's comment on the contribution. + +### 4. Issue + +* **Description:** Represents issues (bugs, tasks, or feature requests) that are often linked to code contributions. +* **Fields:** + * `field_issue_link`: Link to the issue. + * `body`: Description of the issue. + +### 5. Event + +* **Description:** Represents events that are referenced in Event Contributions. +* **Fields:** + * `field_event_dates`: Date(s) of the event. + * `field_event_address`: Address of the event. + * `field_event_location`: Location (e.g., city, venue) of the event. + * `field_event_additional_links`: Additional links related to the event. + * `field_event_contrib_event_type`: Type of event (e.g., DrupalCamp, DrupalCon). + +## II. Taxonomies + +Taxonomies are used to categorize and classify content within the Contribution Tracker. + +* **Technology:** (`taxonomy.vocabulary.technology`) Used to classify contributions by the relevant technology. +* **Project:** Used to classify code contributions to specific projects. +* **Event Type:** Used to classify the type of event (e.g., DrupalCamp, DrupalCon). +* **Event Contribution Type:** Used to classify the type of contribution made at an event. +* **Contribution Type:** Used to classify the types of contributions (e.g., submitting a patch, porting a module). +* **Tags:** Used to group articles on similar topics under categories. + +## III. Views + +Views are used to create dynamic lists and displays of content. The following views are defined: + +* **Frontpage:** (`views.view.frontpage`) Displays content promoted to the front page. +* **Content:** (`views.view.content`) Administrative view to find and manage content. +* **Recent Content:** (`views.view.content_recent`) Displays recently created content. +* **Files:** (`views.view.files`) Administrative view to find and manage files. +* **People:** (`views.view.user_admin_people`) Administrative view to find and manage users. +* **Patches:** (`views.view.patches`) Provides a listing of patches. +* **Glossary:** (`views.view.glossary`) Displays all content, organized alphabetically. +* **All Contributions:** (`views.view.all_contributions`) Displays all contribution node types. +* **Code Contributions:** (`views.view.code_contributions`) Displays code contributions. +* **Non Code Contributions:** (`views.view.non_code_contributions`) Displays non-code contributions. +* **Event Contributions:** (`views.view.event_contributions`) Displays event contributions. +* **Who's online:** (`views.view.who_s_online`) Displays the most recently active users. +* **Who's new:** (`views.view.who_s_new`) Displays the newest user accounts. +* **Archive:** (`views.view.archive`) Displays all content, grouped by month. +* **Taxonomy term:** (`views.view.taxonomy_term`) Displays content belonging to a specific taxonomy term. +* **Content blocks:** (`views.view.block_content`) Administrative view to find and manage custom blocks. +* **Patches on issues:** (`views.view.patches_on_issues`) Shows related patches on issue pages. + +## IV. User Roles + +User roles manage permissions and access control within the system. + +* **Administrator:** (`user.role.administrator`) Full access to the system. +* **Authenticated User:** (`user.role.authenticated`) Can access content and create certain types of contributions. +* **Contribution Moderator:** (`user.role.contribution_moderator`) Can moderate contributions. +* **Anonymous User:** (`user.role.anonymous`) Can access content. + +## V. Content Approval Workflow and Moderation + +The Contribution Tracker utilizes a content approval workflow to ensure the quality of contributions. + +* **Contribution Approval Flag:** (`flag.flag.contribution_approval`) Contributions can be flagged for approval, triggering the moderation workflow. +* **Moderation Comments:** (`field.field.node.*.field_contrib_moderator_comment`) Contribution moderators can add comments on contributions, providing feedback or justification for approval/rejection. This field is present on all contribution node types. + +## VI. Menus + +Menus provide site navigation. + +* **Main navigation:** (`system.menu.main`) Site section links. +* **Footer:** (`system.menu.footer`) Site information links. +* **Tools:** (`system.menu.tools`) User tool links. +``` \ No newline at end of file diff --git a/docs_final/custom-modules.md b/docs_final/custom-modules.md new file mode 100644 index 00000000..9a3c288e --- /dev/null +++ b/docs_final/custom-modules.md @@ -0,0 +1,161 @@ +```markdown +# Custom Modules Documentation + +This document provides a comprehensive overview of the custom modules within the project. It outlines each module's functionality, dependencies, and configurations to facilitate efficient onboarding for software engineers. + +## Module Overview + +| Module Name | Description | Dependencies | +| :--------------- | :----------------------------------------------------------- | :--------------------------------------------- | +| `ct_user` | Handles user-related processing in Contrib Tracker. | `social_auth:social_auth` | +| `ct_reports` | Generates reports based on contribution data. | None | +| `ct_manager` | Provides support for tracking contributions from various sources. | None | +| `ct_github` | Tracks user contributions from GitHub. | `ct_manager:ct_manager` | +| `ct_drupal` | Tracks user contributions from Drupal.org. | `ct_manager:ct_manager`, `do_username:do_username` | +| `contrib_tracker` | Tracks contributions from Drupal.org. | None | + +## Module Details + +### 1. `ct_user` Module + +* **Description:** Handles user-related processing in Contrib Tracker. +* **Type:** Module +* **Dependencies:** `social_auth:social_auth` + + This module requires the `social_auth` module to be enabled and configured. +* **Routing:** + + Defines a route `/user/{current_user_id}/graph` to display contribution counts. + + * **Controller:** `\Drupal\ct_user\Controller\GetUserPatchesController::content` + * **Permissions:** Requires "access content" permission. +* **Services:** + * `ct_user.ct_user_social_event_listener`: + * **Class:** `Drupal\ct_user\EventSubscriber\ContribTrackerEventListener` + * **Description:** An event subscriber that listens for events related to social authentication. + * **Arguments:** `@logger.factory` +* **Theme:** + + Defines a theme function `contrib_graph` for rendering contribution graphs. Implementations should expect to receive appropriate variables to build dynamic graphs. +* **Help:** + + Implements `hook_help()` to provide help text within the Drupal UI for this module. Navigate to `/admin/help/ct_user` to view the help contents. +* **Form Alter:** + + Implements `hook_form_alter()` to modify forms, potentially including the user login form. Check the module file `ct_user.module`, for details on the altered form ID. + +### 2. `ct_reports` Module + +* **Description:** Generates reports based on contribution data. +* **Type:** Module +* **Routing:** + + Defines a route `/contribution-count`. + + * **Controller:** `\Drupal\ct_reports\Controller\ContributionCountController::content` + * **Permissions:** Requires "access content" permission. +* **Services:** + * `ct_reports.statistics`: + * **Class:** `Drupal\ct_reports\ContributionStatistics` + * **Description:** A service for generating contribution statistics. + * **Arguments:** `@entity_type.manager`, `@database` +* **Theme:** + + Defines a theme function `ct_reports_contribution_count`. + + * **Variables:** + * `totalContributions`: Total number of contributions. + * `codeContributions`: Number of code contributions. + * `totalContributors`: Total number of contributors. + +* **Libraries:** + + Defines a library `ct-style`. + * CSS file `css/ct-style.css`. This CSS file is responsible for styling the `ct_reports` output. + +### 3. `ct_manager` Module + +* **Description:** Provides support for tracking contributions from various sources. +* **Type:** Module +* **Services:** + * `logger.channel.ct_manager`: + * **Description:** Defines a logger channel named `ct_manager`. Use this channel for logging events specific to the `ct_manager` module. Example: `$this->logger->get('ct_manager')->info('Log message');` + * `plugin.manager.contribution_plugin_manager`: + * **Class:** `Drupal\ct_manager\ContributionSourcePluginManager` + * **Description:** A plugin manager for contribution sources. This allows for extensible contribution tracking. + * `ct_manager.contribution_storage`: + * **Class:** `Drupal\ct_manager\ContributionTrackerStorage` + * **Description:** A service for managing contribution data. + * **Arguments:** `@entity_type.manager`, `@logger.channel.ct_manager` +* **Cron:** + * Implements `hook_cron()` to process users for each contribution plugin and create queue items. This is likely used to regularly update contribution data. + +### 4. `ct_github` Module + +* **Description:** Tracks user contributions from GitHub. +* **Type:** Module +* **Package:** Contrib Tracker +* **Dependencies:** `ct_manager:ct_manager` + + This module requires the `ct_manager` module to be enabled. +* **Services:** + * `ct_github.query`: + * **Class:** `Drupal\ct_github\GithubQuery` + * **Description:** A service for querying GitHub's API. + * **Arguments:** `@config.factory`, `@cache.data` + * `ct_github.loggerChannel`: + * **Description:** Defines a logger channel named `ct_github`. Use this channel for logging events specific to the `ct_github` module. +* **Composer Dependencies:** `knplabs/github-api: ^3.0` + + This module relies on the `knplabs/github-api` library. Ensure it is installed via Composer: `composer require knplabs/github-api:^3.0` + +### 5. `ct_drupal` Module + +* **Description:** Tracks user contributions from Drupal.org. +* **Type:** Module +* **Package:** Contrib Tracker +* **Dependencies:** `ct_manager:ct_manager`, `do_username:do_username` + + This module requires the `ct_manager` and `do_username` modules to be enabled. +* **Services:** + * `logger.channel.ct_drupal`: + * **Description:** Defines a logger channel named `ct_drupal`. Use this channel for logging events specific to the `ct_drupal` module. + * `ct_drupal.client`: + * **Class:** `Drupal\ct_drupal\Client` + * **Description:** A service for interacting with Drupal.org. + * **Arguments:** `@ct_drupal.http_adapter` + * `ct_drupal.http_adapter`: + * **Class:** `Http\Adapter\Guzzle7\Client` + * **Description:** An HTTP adapter for making requests. + * **Arguments:** `@http_client` + * `ct_drupal.retriever`: + * **Class:** `Drupal\ct_drupal\DrupalOrgRetriever` + * **Description:** A service for retrieving Drupal.org data. + * **Arguments:** `@ct_drupal.client`, `@cache.data` +* **Composer Dependencies:** `hussainweb/drupal-api-client: ^2.0`, `drupal/do_username: ^2.0@alpha`, `drupal/slack: 1.x-dev` + + This module relies on the following libraries. Ensure they are installed via Composer: + + * `composer require hussainweb/drupal-api-client:^2.0` + * `composer require drupal/do_username:^2.0@alpha` + * `composer require drupal/slack:1.x-dev` + +### 6. `contrib_tracker` Module + +* **Description:** Tracks contributions from Drupal.org. +* **Type:** Module +* **Services:** + * `logger.channel.contrib_tracker`: + * **Description:** Defines a logger channel named `contrib_tracker`. Use this channel for logging events specific to the `contrib_tracker` module. + * `contrib_tracker.event_subscriber`: + * **Class:** `Drupal\contrib_tracker\EventSubscriber\RavenSubscriber` + * **Description:** An event subscriber related to Raven (likely for error tracking). +* **Mail Alter:** + + Implements `hook_mail_alter()` to disable email sending in non-production Platform.sh environments. This prevents accidental emails from being sent in development or staging environments. +* **Console Commands:** + * `contrib_tracker.contrib_tracker_issues_sanitise`: + * **Class:** `Drupal\contrib_tracker\Command\IssuesSanitiseCommand` + * **Description:** A console command for sanitizing issues. Use drush to execute `drush contrib-tracker-issues-sanitise`. + * **Arguments:** `@contrib_tracker_storage`, `@entity_type.manager`, `@database` +``` \ No newline at end of file diff --git a/docs_final/custom-themes.md b/docs_final/custom-themes.md new file mode 100644 index 00000000..8d35140c --- /dev/null +++ b/docs_final/custom-themes.md @@ -0,0 +1,78 @@ +# Custom Modules Documentation + +This document provides an overview of the custom modules within the project, highlighting their functionality, dependencies, and configurations. This information is intended to help developers quickly understand the purpose and structure of each module. + +## Module Details + +### 1. `ct_user` Module + +* **Description:** Handles user-related processing in Contrib Tracker. +* **Type:** Module +* **Dependencies:** `social_auth:social_auth` +* **Functionality:** + * **Routing:** Defines a route `/user/{current_user_id}/graph` to display contribution counts, handled by `\Drupal\ct_user\Controller\GetUserPatchesController::content`. This route requires the "access content" permission. + * **Services:** + * `ct_user.ct_user_social_event_listener`: An event subscriber (`Drupal\ct_user\EventSubscriber\ContribTrackerEventListener`) that listens for events. It is injected with the `@logger.factory` service. This suggests the module responds to specific events within the application. + * **Theme:** Defines a theme function `contrib_graph`. This allows for customized rendering of contribution data, likely used in conjunction with the `/user/{current_user_id}/graph` route. + * **Help:** Implements `hook_help()` to provide help text for the module within the Drupal UI. + * **Form Alter:** Implements `hook_form_alter()` to modify forms, potentially including the user login form. This allows the module to customize form behavior and add/modify form elements. + +### 2. `ct_reports` Module + +* **Description:** Generates reports based on contribution data. +* **Type:** Module +* **Functionality:** + * **Routing:** Defines a route `/contribution-count` handled by `\Drupal\ct_reports\Controller\ContributionCountController::content`. This route requires the "access content" permission. + * **Services:** + * `ct_reports.statistics`: A service (`Drupal\ct_reports\ContributionStatistics`) for generating contribution statistics. It is injected with `@entity_type.manager` and `@database` services. This service likely retrieves and processes entity data from the Drupal database. + * **Theme:** Defines a theme function `ct_reports_contribution_count` with variables `totalContributions`, `codeContributions`, and `totalContributors`. This theming allows for the customized presentation of contribution statistics. + * **Libraries:** Defines a library `ct-style` with CSS file `css/ct-style.css`. This handles the visual styling for elements within the module. + +### 3. `ct_manager` Module + +* **Description:** Provides support for tracking contributions from various sources. This module likely acts as a central hub for managing contribution data from different platforms. +* **Type:** Module +* **Functionality:** + * **Services:** + * `logger.channel.ct_manager`: Defines a logger channel named `ct_manager`. This allows for focused logging of events specific to the contribution manager. + * `plugin.manager.contribution_plugin_manager`: A plugin manager (`Drupal\ct_manager\ContributionSourcePluginManager`) for contribution sources. This indicates a plugin-based architecture for supporting different contribution platforms (e.g., GitHub, Drupal.org). + * `ct_manager.contribution_storage`: A service (`Drupal\ct_manager\ContributionTrackerStorage`) for managing contribution data, injected with `@entity_type.manager` and `@logger.channel.ct_manager`. This service handles the persistence and retrieval of contribution information. + * **Cron:** Implements `hook_cron()` to process users for each contribution plugin and create queue items. This suggests that the module periodically fetches contribution data from external sources using a cron job. + +### 4. `ct_github` Module + +* **Description:** Tracks user contributions from GitHub. +* **Type:** Module +* **Package:** Contrib Tracker +* **Dependencies:** `ct_manager:ct_manager` +* **Functionality:** + * **Services:** + * `ct_github.query`: A service (`Drupal\ct_github\GithubQuery`) for querying GitHub, injected with `@config.factory` and `@cache.data`. This service likely handles the communication with the GitHub API. + * `ct_github.loggerChannel`: Defines a logger channel named `ct_github`. This provides focused logging for GitHub-related events. +* **Composer Dependencies:** `knplabs/github-api: ^3.0` This dependency allows the module to interact with the Github API. + +### 5. `ct_drupal` Module + +* **Description:** Tracks user contributions from Drupal.org. +* **Type:** Module +* **Package:** Contrib Tracker +* **Dependencies:** `ct_manager:ct_manager`, `do_username:do_username` +* **Functionality:** + * **Services:** + * `logger.channel.ct_drupal`: Defines a logger channel named `ct_drupal`. This allows focused logging of Drupal.org-related events. + * `ct_drupal.client`: A service (`Drupal\ct_drupal\Client`) for interacting with Drupal.org, injected with `@ct_drupal.http_adapter`. + * `ct_drupal.http_adapter`: An HTTP adapter (`Http\Adapter\Guzzle7\Client`), injected with `@http_client`. This clarifies the HTTP client used for communication with Drupal.org. + * `ct_drupal.retriever`: A service (`Drupal\ct_drupal\DrupalOrgRetriever`) for retrieving Drupal.org data, injected with `@ct_drupal.client` and `@cache.data`. This service retrieves data from Drupal.org and caches it. +* **Composer Dependencies:** `hussainweb/drupal-api-client: ^2.0`, `drupal/do_username: ^2.0@alpha`, `drupal/slack: 1.x-dev` These dependencies allow the module to interact with the Drupal.org API, retrieve usernames from Drupal.org, and interact with Slack. + +### 6. `contrib_tracker` Module + +* **Description:** Tracks contributions from Drupal.org. This description seems to overlap with the `ct_drupal` module; further investigation may be needed to clarify the distinct roles of each. +* **Type:** Module +* **Functionality:** + * **Services:** + * `logger.channel.contrib_tracker`: Defines a logger channel named `contrib_tracker`. + * `contrib_tracker.event_subscriber`: An event subscriber (`Drupal\contrib_tracker\EventSubscriber\RavenSubscriber`). This subscriber likely reports errors or exceptions to a service like Raven or Sentry. + * **Mail Alter:** Implements `hook_mail_alter()` to disable email sending in non-production Platform.sh environments. This prevents test emails from being sent in development environments. + * **Console Commands:** + * `contrib_tracker.contrib_tracker_issues_sanitise`: A console command (`Drupal\contrib_tracker\Command\IssuesSanitiseCommand`) for sanitizing issues, injected with `@contrib_tracker_storage`, `@entity_type.manager`, and `@database`. This command provides a tool for cleaning and standardizing issue data. \ No newline at end of file diff --git a/docs_final/how-to-work.md b/docs_final/how-to-work.md new file mode 100644 index 00000000..d4126d18 --- /dev/null +++ b/docs_final/how-to-work.md @@ -0,0 +1,79 @@ +```markdown +# Contribution Tracker Project Documentation + +This document provides a comprehensive guide for developers contributing to the Contribution Tracker project. It outlines the development environment setup, contribution workflow, code quality standards, and relevant tools. + +## Prerequisites + +Before you begin, ensure you have the following: + +* **Tools:** + * [Composer](https://getcomposer.org/) (Latest Version): Used for managing PHP dependencies. +* **SSH Key:** An SSH key added to your [GitHub account settings](https://github.com/settings/keys). This is required for secure access to repositories. + +## Development Setup + +You can set up the project using either of the following methods: + +* **Gitpod:** A cloud-based development environment. (Details on Gitpod setup not provided but this is the first recommended approach.) +* **Local Setup:** Using the tools described in the prerequisites. + +## Contribution Workflow + +The project utilizes both GitLab CI and GitHub Actions for automated tasks, ensuring code quality and streamlined deployments. + +### GitLab CI (`.gitlab-ci.yml`) + +GitLab CI automates build, linting, testing, and deployment processes. + +* **Stages:** The pipeline is divided into the following stages: `build`, `lint`, `test`, `deploy`. +* **Code Quality Checks:** Employs the `hussainweb/drupalqa:php7.4` Docker image for code quality analysis: + * `composer validate`: Validates the `composer.json` file. + * `phplint`: Checks for PHP syntax errors. + * `phpcs`: Enforces coding standards using PHP CodeSniffer. + * `phpmd`: Performs static analysis of PHP code using PHP Mess Detector. +* **Triggers:** The pipeline is triggered on pushes to the `master`, `theme`, `tags`, and `merge_requests` branches. + +### GitHub Actions (`.github/workflows/`) + +GitHub Actions are used for CI/CD, automated tasks, and integrations. + +* **VR (`.github/workflows/vr.yml`):** + * **Purpose:** Executes visual regression tests. + * **Triggers:** Tag pushes, manual workflow dispatch, and scheduled cron jobs. + * **Secrets:** Requires the `PERCY_TOKEN` secret for Percy integration. +* **PR Close (`.github/workflows/pr-close.yml`):** + * **Purpose:** Cleans up Platform.sh environments when a pull request is closed. + * **Action:** Uses the `axelerant/platformsh-action@v1` action to interact with Platform.sh. + * **Secrets:** Requires the `PLATFORMSH_CLI_TOKEN` secret for authenticating with Platform.sh. + * **Project ID:** `brbqplxd7ycq6` (This is the Platform.sh project ID). +* **Cypress Tests (`.github/workflows/cypress-tests.yml`):** + * **Purpose:** Runs Cypress end-to-end tests. + * **Schedule:** Executes tests on a weekly basis. + * **Local Development:** Leverages `ddev` for setting up a local development environment. + * **Configuration:** Sets `PLATFORMSH_CLI_TOKEN` as a global ddev configuration. +* **CI (`.github/workflows/ci.yml`):** + * **Purpose:** Performs continuous integration tasks. + * **Triggers:** Pushes to the `main` branch or tags, and pull requests. + * **Drupal Code Quality:** Utilizes the `hussainweb/drupalqa-action@v2` action for Drupal-specific code quality checks (which includes GrumPHP tasks). + * **Frontend Code Quality:** Uses a Node.js (version 20) container for frontend code quality analysis. + +## Code Quality + +The project enforces strict code quality standards to maintain consistency and prevent errors. + +* **GitLab CI:** Performs linting and static analysis using `phplint`, `phpcs`, and `phpmd`. Refer to the `.gitlab-ci.yml` file for configuration details. +* **GitHub Actions:** Employs the `hussainweb/drupalqa-action` which runs GrumPHP tasks including: `phplint`, `yamllint`, `jsonlint`, `phpcs`, `phpmd`, and `twigcs`. + +## Dependencies + +* **PHP Dependencies:** Managed using Composer. The `composer.json` file (not included in this documentation, but it should be present in the repository root) defines the project's PHP dependencies. Use `composer install` to install them. +* **Node.js Dependencies:** Managed using npm or yarn. The `package.json` file (not included in this documentation, but it should be present if any front end code exists in the repository root) defines the project's Node.js dependencies. Use `npm install` or `yarn install` to install them. + +## Local Development Environment + +[DDEV](https://ddev.readthedocs.io/en/stable/) is used to create and manage a local development environment. + +* **Setup:** `ddev` is configured and started during the Cypress tests workflow. Refer to the `.github/workflows/cypress-tests.yml` file for details. +* **Dependency Installation:** Install PHP dependencies using `ddev composer install`. This executes composer inside the DDEV container. +* **Platform.sh Integration:** Pull the Platform.sh database into your local DDEV environment using the command: `ddev pull platform -y`. This simplifies local testing with production data. \ No newline at end of file diff --git a/docs_final/index.md b/docs_final/index.md new file mode 100644 index 00000000..8c3b85f4 --- /dev/null +++ b/docs_final/index.md @@ -0,0 +1,30 @@ +```markdown +# Contribution Tracker Project Index + +## Welcome + +Welcome to the Contribution Tracker project documentation! + +## Introduction + +This documentation provides a comprehensive guide to understanding and contributing to the Contribution Tracker project. It covers local development setup, contribution workflows, content structure, custom modules, automated testing, CI/CD configurations, and monitoring/logging considerations. + +## Who is This For? + +This documentation is intended for developers, testers, and anyone involved in the development and maintenance of the Contribution Tracker project. + +## Purpose + +The purpose of this documentation is to provide a centralized resource for all technical information related to the Contribution Tracker project, ensuring a smooth onboarding process and efficient collaboration. + +## Documentation Files + +- [Tools Prerequisites](tools_prerequisites.md) +- [Local Setup](local_setup.md) +- [Contribution Workflow](how_to_work.md) +- [Content Structure](content_structure.md) +- [Custom Modules](custom_modules.md) +- [Automated Testing](automated_testing.md) +- [Monitoring and Logging](monitoring_logging.md) +- [CI/CD Configuration](ci_cd.md) +``` \ No newline at end of file diff --git a/docs_final/local-setup.md b/docs_final/local-setup.md new file mode 100644 index 00000000..85ce842f --- /dev/null +++ b/docs_final/local-setup.md @@ -0,0 +1,79 @@ +# Contribution Tracker - Local Development Environment Setup + +This document outlines the steps to set up a local development environment for the Contribution Tracker project. Following these instructions will allow you to contribute effectively and efficiently. + +## Prerequisites + +Before you begin, ensure you have the following tools installed and configured: + +* **Composer:** The latest version is recommended for dependency management. Download and install from [https://getcomposer.org/](https://getcomposer.org/). +* **Git:** Required for version control and accessing the project repository. Ensure you have an SSH key configured for GitHub access. See [https://docs.github.com/en/authentication/connecting-to-github-with-ssh](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) for guidance. +* **DDEV:** DDEV is used to containerize the local development environment. Instructions available here [https://ddev.readthedocs.io/en/stable/](https://ddev.readthedocs.io/en/stable/) + +## DDEV Configuration + +This project utilizes DDEV for a simplified and consistent local development experience. The project includes a `.ddev/config.yaml` file containing predefined configurations. + +```yaml +{ + "name": "contribtracker", + "type": "drupal10", + "docroot": "web", + "php_version": "8.3", + "webserver_type": "nginx-fpm", + "router_http_port": "80", + "router_https_port": "443", + "xdebug_enabled": false, + "additional_hostnames": [], + "additional_fqdns": [], + "database": { + "type": "mariadb", + "version": "11.4" + }, + "use_dns_when_possible": true, + "composer_version": "", + "web_environment": [ + "PLATFORM_ENVIRONMENT=main", + "PLATFORM_PROJECT=brbqplxd7ycq6" + ], + "corepack_enable": false, + "ddev_version_constraint": ">= 1.23.3", + "nodejs_version": "20" +} +``` + +### Key DDEV Configuration Details + +* **name:** `contribtracker` - The name of the DDEV project. This will be used in DDEV commands. +* **type:** `drupal10` - Specifies that this is a Drupal 10 project, configuring DDEV with Drupal-specific settings. +* **docroot:** `web` - The web root directory for the Drupal installation. All publicly accessible files are located here. +* **php\_version:** `8.3` - The PHP version used by the DDEV container. Ensure your development environment is using a compatible version. +* **webserver\_type:** `nginx-fpm` - Specifies Nginx with PHP-FPM as the web server. +* **database:** Configured to use `mariadb` version `11.4`. +* **web\_environment:** These environment variables are set within the web container. `PLATFORM_ENVIRONMENT` and `PLATFORM_PROJECT` may be important for integration with Platform.sh. +* **nodejs\_version:** `20` - The Node.js version used within the DDEV container. + +### Starting the DDEV Environment + +1. Navigate to the project root directory in your terminal. +2. Run `ddev start`. This will build and start the DDEV containers based on the `.ddev/config.yaml` file. +3. Once DDEV is running, you can access the site at the URLs displayed in the `ddev start` output (typically something like `https://contribtracker.ddev.site` and `http://contribtracker.ddev.site`). + +## Local Settings Configuration + +To enable local development-specific settings (such as debugging and disabling caching), follow these steps: + +1. **Copy the example settings file:** + ```bash + cp web/sites/example.settings.local.php web/sites/default/settings.local.php + ``` + +2. **Enable the local settings file:** Edit `web/sites/default/settings.php`. Find the section that includes `settings.local.php` and uncomment the lines, typically: + + ```php + if (file_exists(__DIR__ . '/settings.local.php')) { + include __DIR__ . '/settings.local.php'; + } + ``` + +The `settings.local.php` file is now active. You can modify this file to override default settings for local development, such as enabling assertions, development modules, or disabling caching. **Important:** Do not commit changes to `web/sites/default/settings.php` with the include statement commented out. \ No newline at end of file diff --git a/docs_final/monitoring-logging.md b/docs_final/monitoring-logging.md new file mode 100644 index 00000000..73df6bf2 --- /dev/null +++ b/docs_final/monitoring-logging.md @@ -0,0 +1,26 @@ +## Drupal `settings.php` and its Impact on Monitoring and Logging + +The `settings.php` file in Drupal is central to site configuration. While it doesn't directly implement monitoring or logging features, it significantly influences their behavior and setup. This document outlines how `settings.php` indirectly contributes to effective monitoring and logging and highlights key considerations for configuration. + +### Indirect Contributions to Monitoring and Logging + +`settings.php` manages configurations that form the foundation upon which the monitoring and logging infrastructure is built. Proper setup is critical for these facilities to function as expected. Here's a breakdown of key aspects: + +* **Configuration Management:** `settings.php` manages the entire Drupal site configuration. This includes crucial elements such as: + * **Database Connections:** Incorrect database connection details will prevent Drupal from logging events to the database, impacting monitoring capabilities that rely on database logs. + * **Cache Settings:** Misconfigured cache settings can mask performance issues or lead to unexpected behavior, making accurate monitoring challenging. Correct caching configurations ensure that performance metrics reflect the actual state of the application. + * **Performance Parameters:** General performance settings, if poorly configured, can lead to detectable errors, performance bottlenecks, and overall system instability, all of which will be captured through monitoring and logging. + +* **File System Permissions:** `settings.php` emphasizes the importance of secure file system permissions. Incorrect permissions can have a direct negative impact on your logging capabilities: + * **Write Access:** If Drupal lacks the necessary write permissions to log files, it will be unable to record events, rendering logging ineffective. + * **Security Risks:** The file specifically warns against leaving `settings.php` writable after installation. Doing so introduces security vulnerabilities and impacts the integrity of any logs that might be written. + +* **Multisite Configuration:** For multisite installations, `settings.php` interacts with `sites/sites.php`. Incorrect configurations introduce complexities: + * **Log Redirection:** Logs may be written to unintended locations or databases. + * **Log Correlation:** It becomes significantly harder correlate events across different sites if the logging configurations are not correctly setup. + +* **Configuration Directory Discovery:** The way Drupal determines which configuration directory to use significantly impacts the applied settings, including logging configurations. Incorrect settings, as a result of a misconfigured configuration directory, will affect all areas of the site, including logging. + +### Summary + +While `settings.php` doesn't contain explicit monitoring or logging code, its configuration options underpin the entire monitoring and logging infrastructure. A correctly configured `settings.php` is essential for ensuring that logging and monitoring facilities function as designed and can accurately reflect the state of the application. \ No newline at end of file diff --git a/docs_final/overview.md b/docs_final/overview.md new file mode 100644 index 00000000..300ed47b --- /dev/null +++ b/docs_final/overview.md @@ -0,0 +1,26 @@ +```markdown +# Contribution Tracker: Project Overview + +## Project Purpose, Goals, and High-Level Description + +The Contribution Tracker is a Drupal-based application designed to track and manage contributions from individuals to various projects and technologies. It supports tracking code contributions, non-code contributions, and event contributions. The project automates code quality checks and testing via GitLab CI/CD and GitHub Actions. + +## Target Audience + +This documentation is primarily intended for: + +* **Developers:** Setting up local environments, understanding dependencies, and contributing code. +* **Content Editors/Moderators:** Creating and managing content related to contributions (using Node Types, Taxonomies, and moderating content). + +## Tech Stack Overview + +* **Drupal Version:** 10 +* **PHP:** 8.2+ (DDEV config uses 8.3) +* **Database:** MariaDB 11.4 (via DDEV) +* **Dependencies:** + * Composer-managed PHP dependencies (see `composer.json` for a complete list, including `drupal/core`, `axelerant/ct_drupal`, `axelerant/ct_github`). + * Node.js dependencies (managed via npm or yarn, if front-end code exists). + * DDEV for local development. + +Key custom modules include: `ct_user`, `ct_reports`, `ct_manager`, `ct_github`, `ct_drupal`, and `contrib_tracker`, each with dedicated functions and dependencies as documented in the Custom Modules section. +``` \ No newline at end of file diff --git a/docs_final/tools-prerequisites.md b/docs_final/tools-prerequisites.md new file mode 100644 index 00000000..117d31d8 --- /dev/null +++ b/docs_final/tools-prerequisites.md @@ -0,0 +1,146 @@ +```markdown +# Contribution Tracker - Developer Onboarding Guide + +This document provides a comprehensive guide for developers to quickly onboard with the Contribution Tracker Drupal application. It outlines the necessary tools, prerequisites, and configurations required for both local development and continuous integration/continuous deployment (CI/CD). + +## 1. Core Requirements + +Before you begin, ensure you have the following core tools installed: + +* **Composer:** Essential for managing project dependencies. Download and install the [latest version of Composer](https://getcomposer.org/). +* **PHP:** Version **8.2 or higher** is required. Verify your PHP version by running `php -v` in your terminal. + +## 2. Local Development Environment Setup (DDEV) + +This project utilizes DDEV for local development. DDEV provides a containerized environment that mirrors production settings, ensuring consistency throughout the development lifecycle. + +### 2.1 DDEV Configuration + +The DDEV configuration is located in the `.ddev/config.yaml` file. Here's a breakdown of the settings: + +```yaml +name: contribtracker +type: drupal10 +docroot: web +php_version: "8.3" +webserver_type: nginx-fpm +router_http_port: "80" +router_https_port: "443" +database: + type: mariadb + version: "11.4" +nodejs_version: "20" +``` + +**Explanation:** + +* `name`: The name of the DDEV project (contribtracker). +* `type`: Specifies the Drupal version (drupal10). +* `docroot`: The root directory for the Drupal installation (web). +* `php_version`: The PHP version to use within the DDEV container (8.3). +* `webserver_type`: The web server type (nginx-fpm). +* `router_http_port`: The HTTP port for the router (80). +* `router_https_port`: The HTTPS port for the router (443). +* `database.type`: The database type (mariadb). +* `database.version`: The MariaDB version (11.4). +* `nodejs_version`: The Node.js version to use (20). + +### 2.2 Starting the DDEV Environment + +1. Navigate to the project root directory in your terminal. +2. Run `ddev start`. This command will build and start the DDEV containers based on the configuration in `.ddev/config.yaml`. + +### 2.3 Accessing the Site + +Once DDEV is running, you can access the site in your browser using the URLs provided by DDEV (e.g., `https://contribtracker.ddev.site`, `http://contribtracker.ddev.site`). You can also find these URLs by running `ddev describe`. + +## 3. Dependencies + +The project's dependencies are managed using Composer. + +### 3.1 Installing Dependencies + +1. Navigate to the project root directory in your terminal. +2. Run `composer install`. This command will install all dependencies listed in the `composer.json` file. + +### 3.2 Key Dependencies + +The `composer.json` file includes the following key dependencies: + +```json +{ + "require": { + "php": ">=8.2", + "axelerant/ct_drupal": "*", + "axelerant/ct_github": "*", + "composer/installers": "^2.1", + "cweagans/composer-patches": "^1.7.0", + "drupal/address": "^2.0", + "drupal/admin_toolbar": "^3.0.0", + "drupal/better_exposed_filters": "^7.0", + "drupal/ckeditor": "^1.0", + "drupal/cookies": "^1.2", + "drupal/core": "^10.1" + } +} +``` + +**Explanation:** + +* `axelerant/ct_drupal`: Custom Drupal module specific to the Contribution Tracker. +* `axelerant/ct_github`: Custom module for integrating with GitHub. +* `drupal/address`: Provides address field functionality. +* `drupal/admin_toolbar`: Enhances the Drupal admin interface. +* `drupal/better_exposed_filters`: Improves exposed filters in Drupal views. +* `drupal/ckeditor`: Provides a WYSIWYG editor. +* `drupal/cookies`: Provides cookie management functionality +* `drupal/core`: The Drupal core framework. + +## 4. Git Prerequisites + +### 4.1 SSH Key + +An SSH key is required for secure access to the Git repository. + +1. If you don't have an SSH key, generate one. See GitHub's documentation on [generating a new SSH key](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent). +2. Add your SSH key to your GitHub account settings as described in the GitHub documentation. + +## 5. CI/CD Pipeline (GitLab CI) + +The project uses GitLab CI for automated testing and deployment. The pipeline configuration is defined in `.gitlab-ci.yml`. + +### 5.1 GitLab CI Configuration (`.gitlab-ci.yml`) + +The `.gitlab-ci.yml` file defines the CI/CD pipeline stages, including `build`, `lint`, `test`, and `deploy`. Consult the file for specific details regarding each stage. + +### 5.2 Platform Configuration (`.gitlab/platform.yml`) + +The `.gitlab/platform.yml` file contains platform-specific configurations that are used during the CI/CD process. + +## 6. GitHub Actions + +The project leverages GitHub Actions for various automated tasks. The workflows are defined in the `.github/workflows/` directory. + +* **`.github/workflows/vr.yml` (Visual Regression Tests):** + * Runs visual regression tests using Percy. + * Scheduled to run every Monday at 00:00. +* **`.github/workflows/pr-close.yml` (Platform.sh Environment Cleanup):** + * Cleans up Platform.sh environments when a pull request is closed. + * Requires the `PLATFORMSH_CLI_TOKEN` secret to authenticate with Platform.sh. +* **`.github/workflows/cypress-tests.yml` (Cypress Tests):** + * Runs Cypress end-to-end tests. + * Uses DDEV for setting up the testing environment. + * Requires the `PLATFORMSH_CLI_TOKEN` secret. +* **`.github/workflows/ci.yml` (Continuous Integration):** + * Performs Drupal code quality checks using the `hussainweb/drupalqa-action@v2` action. + * Runs frontend code quality checks within a Node.js container. + +## 7. Code Quality Tools + +The project uses the following tools to ensure code quality and adherence to coding standards: + +* **drupalqa-action:** Used within GitHub Actions for Drupal-specific code quality checks. +* **phpcs:** PHP Code Sniffer, used for enforcing coding standards. +* **phplint:** Used for checking PHP syntax. +* **phpmd:** PHP Mess Detector, used for detecting potential code issues. +``` \ No newline at end of file