refactor: remove downloads page and bundle generation (#1577)
The downloads page offered bmad-sources.zip and bmad-prompts.zip, both redundant: GitHub provides source archives for every tag natively, and npx bmad-method install is the supported path for compiled prompts. Remove the downloads page, all bundle generation code, the archiver dependency, and nav links. The llms.txt and llms-full.txt files (the genuinely useful artifacts) continue to be generated as before.
This commit is contained in:
parent
97bfe0a485
commit
a8cda7c6fa
|
|
@ -1,74 +0,0 @@
|
||||||
---
|
|
||||||
title: Downloads
|
|
||||||
---
|
|
||||||
|
|
||||||
Download BMad Method resources for offline use, AI training, or integration.
|
|
||||||
|
|
||||||
## Source Bundles
|
|
||||||
|
|
||||||
Download these from the `downloads/` folder on the documentation site.
|
|
||||||
|
|
||||||
| File | Description |
|
|
||||||
| ------------------ | ------------------------------- |
|
|
||||||
| `bmad-sources.zip` | Complete BMad source files |
|
|
||||||
| `bmad-prompts.zip` | Agent and workflow prompts only |
|
|
||||||
|
|
||||||
## LLM-Optimized Files
|
|
||||||
|
|
||||||
These files are designed for AI consumption - perfect for loading into Claude, ChatGPT, or any LLM context window. See [API Access](#api-access) below for URLs.
|
|
||||||
|
|
||||||
| File | Description | Use Case |
|
|
||||||
| --------------- | ----------------------------------- | -------------------------- |
|
|
||||||
| `llms.txt` | Documentation index with summaries | Quick overview, navigation |
|
|
||||||
| `llms-full.txt` | Complete documentation concatenated | Full context loading |
|
|
||||||
|
|
||||||
### Using with LLMs
|
|
||||||
|
|
||||||
**Claude Projects:**
|
|
||||||
```
|
|
||||||
Upload llms-full.txt as project knowledge
|
|
||||||
```
|
|
||||||
|
|
||||||
**ChatGPT:**
|
|
||||||
```
|
|
||||||
Paste llms.txt for navigation, or sections from llms-full.txt as needed
|
|
||||||
```
|
|
||||||
|
|
||||||
**API Usage:**
|
|
||||||
```python
|
|
||||||
import requests
|
|
||||||
docs = requests.get("https://bmad-code-org.github.io/BMAD-METHOD/llms-full.txt").text
|
|
||||||
# Include in your system prompt or context
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation Options
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx bmad-method install
|
|
||||||
```
|
|
||||||
|
|
||||||
[More details](/docs/how-to/install-bmad.md)
|
|
||||||
|
|
||||||
## Version Information
|
|
||||||
|
|
||||||
- **Current Version:** See [CHANGELOG](https://github.com/bmad-code-org/BMAD-METHOD/blob/main/CHANGELOG.md)
|
|
||||||
- **Release Notes:** Available on [GitHub Releases](https://github.com/bmad-code-org/BMAD-METHOD/releases)
|
|
||||||
|
|
||||||
## API Access
|
|
||||||
|
|
||||||
For programmatic access to BMad documentation:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Get documentation index
|
|
||||||
curl https://bmad-code-org.github.io/BMAD-METHOD/llms.txt
|
|
||||||
|
|
||||||
# Get full documentation
|
|
||||||
curl https://bmad-code-org.github.io/BMAD-METHOD/llms-full.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Want to improve BMad Method? Check out:
|
|
||||||
|
|
||||||
- [Contributing Guide](https://github.com/bmad-code-org/BMAD-METHOD/blob/main/CONTRIBUTING.md)
|
|
||||||
- [GitHub Repository](https://github.com/bmad-code-org/BMAD-METHOD)
|
|
||||||
|
|
@ -42,8 +42,6 @@ Fetch `llms-full.txt` into your session:
|
||||||
https://bmad-code-org.github.io/BMAD-METHOD/llms-full.txt
|
https://bmad-code-org.github.io/BMAD-METHOD/llms-full.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
See the [Downloads page](/docs/downloads.md) for other downloadable resources.
|
|
||||||
|
|
||||||
### 3. Ask Your Question
|
### 3. Ask Your Question
|
||||||
|
|
||||||
:::note[Example]
|
:::note[Example]
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,6 @@
|
||||||
"@astrojs/sitemap": "^3.6.0",
|
"@astrojs/sitemap": "^3.6.0",
|
||||||
"@astrojs/starlight": "^0.37.5",
|
"@astrojs/starlight": "^0.37.5",
|
||||||
"@eslint/js": "^9.33.0",
|
"@eslint/js": "^9.33.0",
|
||||||
"archiver": "^7.0.1",
|
|
||||||
"astro": "^5.16.0",
|
"astro": "^5.16.0",
|
||||||
"c8": "^10.1.3",
|
"c8": "^10.1.3",
|
||||||
"eslint": "^9.33.0",
|
"eslint": "^9.33.0",
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
* BMAD Documentation Build Pipeline
|
* BMAD Documentation Build Pipeline
|
||||||
*
|
*
|
||||||
* Consolidates docs from multiple sources, generates LLM-friendly files,
|
* Consolidates docs from multiple sources, generates LLM-friendly files,
|
||||||
* creates downloadable bundles, and builds the Astro+Starlight site.
|
* and builds the Astro+Starlight site.
|
||||||
*
|
*
|
||||||
* Build outputs:
|
* Build outputs:
|
||||||
* build/artifacts/ - With llms.txt, llms-full.txt, ZIPs
|
* build/artifacts/ - With llms.txt, llms-full.txt
|
||||||
* build/site/ - Final Astro output (deployable)
|
* build/site/ - Final Astro output (deployable)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -13,7 +13,6 @@ import { execSync } from 'node:child_process';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import archiver from 'archiver';
|
|
||||||
import { getSiteUrl } from '../website/src/lib/site-url.mjs';
|
import { getSiteUrl } from '../website/src/lib/site-url.mjs';
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
@ -35,7 +34,6 @@ const LLM_EXCLUDE_PATTERNS = [
|
||||||
'changelog',
|
'changelog',
|
||||||
'ide-info/',
|
'ide-info/',
|
||||||
'v4-to-v6-upgrade',
|
'v4-to-v6-upgrade',
|
||||||
'downloads/',
|
|
||||||
'faq',
|
'faq',
|
||||||
'reference/glossary/',
|
'reference/glossary/',
|
||||||
'explanation/game-dev/',
|
'explanation/game-dev/',
|
||||||
|
|
@ -81,17 +79,16 @@ main().catch((error) => {
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Pipeline Stages
|
// Pipeline Stages
|
||||||
/**
|
/**
|
||||||
* Generate LLM files and downloadable bundles for the documentation pipeline.
|
* Generate LLM files for the documentation pipeline.
|
||||||
*
|
*
|
||||||
* Creates the build/artifacts directory, writes `llms.txt` and `llms-full.txt` (sourced from the provided docs directory),
|
* Creates the build/artifacts directory and writes `llms.txt` and `llms-full.txt` (sourced from the provided docs directory).
|
||||||
* and produces download ZIP bundles.
|
|
||||||
*
|
*
|
||||||
* @param {string} docsDir - Path to the source docs directory containing Markdown files.
|
* @param {string} docsDir - Path to the source docs directory containing Markdown files.
|
||||||
* @returns {string} Path to the created artifacts directory.
|
* @returns {string} Path to the created artifacts directory.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async function generateArtifacts(docsDir) {
|
async function generateArtifacts(docsDir) {
|
||||||
printHeader('Generating LLM files and download bundles');
|
printHeader('Generating LLM files');
|
||||||
|
|
||||||
const outputDir = path.join(BUILD_DIR, 'artifacts');
|
const outputDir = path.join(BUILD_DIR, 'artifacts');
|
||||||
fs.mkdirSync(outputDir, { recursive: true });
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
|
|
@ -99,7 +96,6 @@ async function generateArtifacts(docsDir) {
|
||||||
// Generate LLM files reading from docs/, output to artifacts/
|
// Generate LLM files reading from docs/, output to artifacts/
|
||||||
generateLlmsTxt(outputDir);
|
generateLlmsTxt(outputDir);
|
||||||
generateLlmsFullTxt(docsDir, outputDir);
|
generateLlmsFullTxt(docsDir, outputDir);
|
||||||
await generateDownloadBundles(outputDir);
|
|
||||||
|
|
||||||
console.log();
|
console.log();
|
||||||
console.log(` \u001B[32m✓\u001B[0m Artifact generation complete`);
|
console.log(` \u001B[32m✓\u001B[0m Artifact generation complete`);
|
||||||
|
|
@ -176,8 +172,6 @@ function generateLlmsTxt(outputDir) {
|
||||||
'## Quick Links',
|
'## Quick Links',
|
||||||
'',
|
'',
|
||||||
`- [Full Documentation (llms-full.txt)](${siteUrl}/llms-full.txt) - Complete docs for AI context`,
|
`- [Full Documentation (llms-full.txt)](${siteUrl}/llms-full.txt) - Complete docs for AI context`,
|
||||||
`- [Source Bundle](${siteUrl}/downloads/bmad-sources.zip) - Complete source code`,
|
|
||||||
`- [Prompts Bundle](${siteUrl}/downloads/bmad-prompts.zip) - Agent prompts and workflows`,
|
|
||||||
'',
|
'',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
|
|
@ -249,7 +243,6 @@ function compareLlmDocs(a, b) {
|
||||||
|
|
||||||
function getLlmSortKey(filePath) {
|
function getLlmSortKey(filePath) {
|
||||||
if (filePath === 'index.md') return 0;
|
if (filePath === 'index.md') return 0;
|
||||||
if (filePath === 'downloads.md') return 1;
|
|
||||||
if (filePath.startsWith(`tutorials${path.sep}`) || filePath.startsWith('tutorials/')) return 2;
|
if (filePath.startsWith(`tutorials${path.sep}`) || filePath.startsWith('tutorials/')) return 2;
|
||||||
if (filePath.startsWith(`how-to${path.sep}`) || filePath.startsWith('how-to/')) return 3;
|
if (filePath.startsWith(`how-to${path.sep}`) || filePath.startsWith('how-to/')) return 3;
|
||||||
if (filePath.startsWith(`explanation${path.sep}`) || filePath.startsWith('explanation/')) return 4;
|
if (filePath.startsWith(`explanation${path.sep}`) || filePath.startsWith('explanation/')) return 4;
|
||||||
|
|
@ -322,48 +315,6 @@ function validateLlmSize(content) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Download Bundle Generation
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
async function generateDownloadBundles(outputDir) {
|
|
||||||
console.log(' → Generating download bundles...');
|
|
||||||
|
|
||||||
const downloadsDir = path.join(outputDir, 'downloads');
|
|
||||||
fs.mkdirSync(downloadsDir, { recursive: true });
|
|
||||||
|
|
||||||
await generateSourcesBundle(downloadsDir);
|
|
||||||
await generatePromptsBundle(downloadsDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateSourcesBundle(downloadsDir) {
|
|
||||||
const srcDir = path.join(PROJECT_ROOT, 'src');
|
|
||||||
if (!fs.existsSync(srcDir)) return;
|
|
||||||
|
|
||||||
const zipPath = path.join(downloadsDir, 'bmad-sources.zip');
|
|
||||||
await createZipArchive(srcDir, zipPath, ['__pycache__', '.pyc', '.DS_Store', 'node_modules']);
|
|
||||||
|
|
||||||
const size = (fs.statSync(zipPath).size / 1024 / 1024).toFixed(1);
|
|
||||||
console.log(` bmad-sources.zip (${size}M)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a zip archive of the project's prompts modules and place it in the downloads directory.
|
|
||||||
*
|
|
||||||
* Creates bmad-prompts.zip from src/modules, excluding common unwanted paths, writes it to the provided downloads directory, and logs the resulting file size. If the modules directory does not exist, the function returns without creating a bundle.
|
|
||||||
* @param {string} downloadsDir - Destination directory where bmad-prompts.zip will be written.
|
|
||||||
*/
|
|
||||||
async function generatePromptsBundle(downloadsDir) {
|
|
||||||
const modulesDir = path.join(PROJECT_ROOT, 'src', 'modules');
|
|
||||||
if (!fs.existsSync(modulesDir)) return;
|
|
||||||
|
|
||||||
const zipPath = path.join(downloadsDir, 'bmad-prompts.zip');
|
|
||||||
await createZipArchive(modulesDir, zipPath, ['docs', '.DS_Store', '__pycache__', 'node_modules']);
|
|
||||||
|
|
||||||
const size = Math.floor(fs.statSync(zipPath).size / 1024);
|
|
||||||
console.log(` bmad-prompts.zip (${size}K)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Astro Build
|
// Astro Build
|
||||||
/**
|
/**
|
||||||
|
|
@ -384,7 +335,6 @@ function runAstroBuild() {
|
||||||
* Copy generated artifact files into the built site directory.
|
* Copy generated artifact files into the built site directory.
|
||||||
*
|
*
|
||||||
* Copies llms.txt and llms-full.txt from the artifacts directory into the site directory.
|
* Copies llms.txt and llms-full.txt from the artifacts directory into the site directory.
|
||||||
* If a downloads subdirectory exists under artifacts, copies it into siteDir/downloads.
|
|
||||||
*
|
*
|
||||||
* @param {string} artifactsDir - Path to the build artifacts directory containing generated files.
|
* @param {string} artifactsDir - Path to the build artifacts directory containing generated files.
|
||||||
* @param {string} siteDir - Path to the target site directory where artifacts should be placed.
|
* @param {string} siteDir - Path to the target site directory where artifacts should be placed.
|
||||||
|
|
@ -394,11 +344,6 @@ function copyArtifactsToSite(artifactsDir, siteDir) {
|
||||||
|
|
||||||
fs.copyFileSync(path.join(artifactsDir, 'llms.txt'), path.join(siteDir, 'llms.txt'));
|
fs.copyFileSync(path.join(artifactsDir, 'llms.txt'), path.join(siteDir, 'llms.txt'));
|
||||||
fs.copyFileSync(path.join(artifactsDir, 'llms-full.txt'), path.join(siteDir, 'llms-full.txt'));
|
fs.copyFileSync(path.join(artifactsDir, 'llms-full.txt'), path.join(siteDir, 'llms-full.txt'));
|
||||||
|
|
||||||
const downloadsDir = path.join(artifactsDir, 'downloads');
|
|
||||||
if (fs.existsSync(downloadsDir)) {
|
|
||||||
copyDirectory(downloadsDir, path.join(siteDir, 'downloads'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
@ -407,7 +352,7 @@ function copyArtifactsToSite(artifactsDir, siteDir) {
|
||||||
* Prints a concise end-of-build summary and displays a sample listing of the final site directory.
|
* Prints a concise end-of-build summary and displays a sample listing of the final site directory.
|
||||||
*
|
*
|
||||||
* @param {string} docsDir - Path to the source documentation directory used for the build.
|
* @param {string} docsDir - Path to the source documentation directory used for the build.
|
||||||
* @param {string} artifactsDir - Path to the directory containing generated artifacts (e.g., llms.txt, downloads).
|
* @param {string} artifactsDir - Path to the directory containing generated artifacts (e.g., llms.txt).
|
||||||
* @param {string} siteDir - Path to the final built site directory whose contents will be listed.
|
* @param {string} siteDir - Path to the final built site directory whose contents will be listed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -526,35 +471,6 @@ function copyDirectory(src, dest, exclude = []) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a ZIP archive of a directory, optionally excluding entries that match given substrings.
|
|
||||||
* @param {string} sourceDir - Path to the source directory to archive.
|
|
||||||
* @param {string} outputPath - Path to write the resulting ZIP file.
|
|
||||||
* @param {string[]} [exclude=[]] - Array of substrings; any entry whose path includes one of these substrings will be omitted.
|
|
||||||
* @returns {Promise<void>} Resolves when the archive has been fully written and closed, rejects on error.
|
|
||||||
*/
|
|
||||||
function createZipArchive(sourceDir, outputPath, exclude = []) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const output = fs.createWriteStream(outputPath);
|
|
||||||
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
||||||
|
|
||||||
output.on('close', resolve);
|
|
||||||
archive.on('error', reject);
|
|
||||||
|
|
||||||
archive.pipe(output);
|
|
||||||
|
|
||||||
const baseName = path.basename(sourceDir);
|
|
||||||
archive.directory(sourceDir, baseName, (entry) => {
|
|
||||||
for (const pattern of exclude) {
|
|
||||||
if (entry.name.includes(pattern)) return false;
|
|
||||||
}
|
|
||||||
return entry;
|
|
||||||
});
|
|
||||||
|
|
||||||
archive.finalize();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Console Output Formatting
|
// Console Output Formatting
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
|
||||||
|
|
@ -73,4 +73,3 @@ Note: If copying, remember to keep the copy in sync with changes to `docs/`.
|
||||||
The build pipeline (`npm run docs:build`) produces:
|
The build pipeline (`npm run docs:build`) produces:
|
||||||
- Static HTML site in `build/site/`
|
- Static HTML site in `build/site/`
|
||||||
- LLM-friendly files: `llms.txt`, `llms-full.txt`
|
- LLM-friendly files: `llms.txt`, `llms-full.txt`
|
||||||
- Downloadable ZIP bundles in `downloads/`
|
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,6 @@ const shouldRenderSearch =
|
||||||
{shouldRenderSearch && <Search {...Astro.props} />}
|
{shouldRenderSearch && <Search {...Astro.props} />}
|
||||||
</div>
|
</div>
|
||||||
<div class="sl-hidden md:sl-flex print:hidden right-group">
|
<div class="sl-hidden md:sl-flex print:hidden right-group">
|
||||||
<nav class="sl-flex nav-links">
|
|
||||||
<a href={`${import.meta.env.BASE_URL}downloads/`}>Downloads</a>
|
|
||||||
</nav>
|
|
||||||
<div class="sl-flex social-icons">
|
<div class="sl-flex social-icons">
|
||||||
<SocialIcons {...Astro.props} />
|
<SocialIcons {...Astro.props} />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -55,33 +52,11 @@ const shouldRenderSearch =
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-group,
|
.right-group,
|
||||||
.social-icons,
|
.social-icons {
|
||||||
.nav-links {
|
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-links a {
|
|
||||||
color: var(--sl-color-white);
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 500;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: color 0.15s ease, background-color 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-links a:hover {
|
|
||||||
color: var(--sl-color-accent);
|
|
||||||
background-color: rgba(140, 140, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-links::after {
|
|
||||||
content: '';
|
|
||||||
height: 2rem;
|
|
||||||
border-inline-end: 1px solid var(--sl-color-gray-5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-icons::after {
|
.social-icons::after {
|
||||||
content: '';
|
content: '';
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,6 @@ import type { Props } from '@astrojs/starlight/props';
|
||||||
<div class="sl-flex social-icons">
|
<div class="sl-flex social-icons">
|
||||||
<SocialIcons {...Astro.props} />
|
<SocialIcons {...Astro.props} />
|
||||||
</div>
|
</div>
|
||||||
<nav class="sl-flex nav-links">
|
|
||||||
<a href={`${import.meta.env.BASE_URL}downloads/`}>Downloads</a>
|
|
||||||
</nav>
|
|
||||||
<ThemeSelect {...Astro.props} />
|
<ThemeSelect {...Astro.props} />
|
||||||
<LanguageSelect {...Astro.props} />
|
<LanguageSelect {...Astro.props} />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -33,21 +30,4 @@ import type { Props } from '@astrojs/starlight/props';
|
||||||
padding: 0.5rem 0;
|
padding: 0.5rem 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.nav-links {
|
|
||||||
gap: 1rem;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.nav-links a {
|
|
||||||
color: var(--sl-color-white);
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 500;
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: color 0.15s ease, background-color 0.15s ease;
|
|
||||||
}
|
|
||||||
.nav-links a:hover {
|
|
||||||
color: var(--sl-color-accent);
|
|
||||||
background-color: rgba(140, 140, 255, 0.1);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
*
|
*
|
||||||
* Transforms:
|
* Transforms:
|
||||||
* /img/foo.png → /BMAD-METHOD/img/foo.png (when base is /BMAD-METHOD/)
|
* /img/foo.png → /BMAD-METHOD/img/foo.png (when base is /BMAD-METHOD/)
|
||||||
* /downloads/file.zip → /BMAD-METHOD/downloads/file.zip
|
|
||||||
* /llms.txt → /BMAD-METHOD/llms.txt
|
* /llms.txt → /BMAD-METHOD/llms.txt
|
||||||
*
|
*
|
||||||
* Only affects absolute paths (/) - relative paths and external URLs are unchanged.
|
* Only affects absolute paths (/) - relative paths and external URLs are unchanged.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue