fix(smartsitemap): Update CI configuration, bump dependencies, and apply small code cleanups
This commit is contained in:
@@ -6,8 +6,8 @@ on:
|
||||
- '**'
|
||||
|
||||
env:
|
||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Install pnpm and npmci
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
pnpm install -g @ship.zone/npmci
|
||||
|
||||
- name: Run npm prepare
|
||||
run: npmci npm prepare
|
||||
|
||||
@@ -6,8 +6,8 @@ on:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
pnpm install -g @ship.zone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Audit production dependencies
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
pnpm install -g @ship.zone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Test stable
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
pnpm install -g @ship.zone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Release
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
pnpm install -g @ship.zone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Code quality
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -3,7 +3,6 @@
|
||||
# artifacts
|
||||
coverage/
|
||||
public/
|
||||
pages/
|
||||
|
||||
# installs
|
||||
node_modules/
|
||||
@@ -17,4 +16,8 @@ node_modules/
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
# custom
|
||||
# AI
|
||||
.claude/
|
||||
.serena/
|
||||
|
||||
#------# custom
|
||||
40
changelog.md
Normal file
40
changelog.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-11-19 - 2.0.4 - fix(smartsitemap)
|
||||
Update CI configuration, bump dependencies, and apply small code cleanups
|
||||
|
||||
- CI: switch build image to code.foss.global, adjust NPMCI computed repo URL and install @ship.zone/npmci in workflows
|
||||
- Dependencies: bump several devDependencies and runtime dependencies to newer major/minor releases
|
||||
- package.json: add packageManager/pnpm metadata, bugs URL, homepage tweak and small script adjustments (test/build)
|
||||
- Code: use plugins.webrequest.webrequest helper for HTTP requests, formatting and typing improvements across TypeScript sources (smartsitemap, sitemapnews, sitemapwebsite)
|
||||
- Tests and fixtures: update test feed URL, normalize YAML/test formatting
|
||||
- .gitignore: add .claude and .serena ignores and reorganize custom section
|
||||
- tsconfig: add baseUrl/paths and clean up legacy compiler options
|
||||
- Misc: fix trailing commas/newlines in npmextra.json and commitinfo data
|
||||
|
||||
## 2024-05-29 - 2.0.3 - maintenance
|
||||
Release 2.0.3 with package metadata and build configuration tweaks.
|
||||
|
||||
- Update package description.
|
||||
- Update TypeScript configuration (tsconfig) for build settings.
|
||||
- Adjust npmextra.json githost entries.
|
||||
- General maintenance and housekeeping.
|
||||
|
||||
## 2023-10-20 - 2.0.2 - bugfix
|
||||
Patch release with core fixes and stability improvements.
|
||||
|
||||
- fix(core): apply core updates and bug fixes.
|
||||
|
||||
## 2022-03-24 - 2.0.0–2.0.1 - major / patch
|
||||
Major 2.0.0 release and immediate 2.0.1 follow-up containing core updates.
|
||||
|
||||
- 2.0.0: major version bump with core updates.
|
||||
- 2.0.1: subsequent fixes to address issues found after the 2.0.0 release.
|
||||
- Both releases include core maintenance and stability improvements.
|
||||
|
||||
## 2020-10-25 – 2022-03-24 - 1.0.1–1.0.15 - patch releases
|
||||
Series of patch releases and minor fixes across the 1.0.x line.
|
||||
|
||||
- Multiple incremental releases (1.0.1 through 1.0.15).
|
||||
- Repeated "fix(core): update" commits indicating small bug fixes and maintenance.
|
||||
- No large feature changes; mainly stability and housekeeping updates.
|
||||
31
package.json
31
package.json
@@ -9,26 +9,26 @@
|
||||
"author": "Lossless GmbH",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "(tstest test/ --web)",
|
||||
"build": "(tsbuild --web --allowimplicitany)",
|
||||
"test": "(tstest test/ --verbose)",
|
||||
"build": "(tsbuild --allowimplicitany)",
|
||||
"buildDocs": "tsdoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^2.1.66",
|
||||
"@git.zone/tsbuild": "^3.1.0",
|
||||
"@git.zone/tsbundle": "^2.0.8",
|
||||
"@git.zone/tsrun": "^1.2.44",
|
||||
"@git.zone/tstest": "^1.0.77",
|
||||
"@push.rocks/smartenv": "^5.0.10",
|
||||
"@push.rocks/tapbundle": "^5.0.15",
|
||||
"@git.zone/tsrun": "^2.0.0",
|
||||
"@git.zone/tstest": "^2.8.2",
|
||||
"@push.rocks/smartenv": "^6.0.0",
|
||||
"@push.rocks/tapbundle": "^6.0.3",
|
||||
"@types/node": "^20.8.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@push.rocks/smartcache": "^1.0.16",
|
||||
"@push.rocks/smartfeed": "^1.0.11",
|
||||
"@push.rocks/smartxml": "^1.0.8",
|
||||
"@push.rocks/smartyaml": "^2.0.5",
|
||||
"@push.rocks/webrequest": "^3.0.33",
|
||||
"@tsclass/tsclass": "^4.0.46"
|
||||
"@push.rocks/smartxml": "^2.0.0",
|
||||
"@push.rocks/smartyaml": "^3.0.4",
|
||||
"@push.rocks/webrequest": "^4.0.1",
|
||||
"@tsclass/tsclass": "^9.3.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 1 chrome versions"
|
||||
@@ -58,9 +58,16 @@
|
||||
"TypeScript",
|
||||
"node.js"
|
||||
],
|
||||
"homepage": "https://code.foss.global/push.rocks/smartsitemap",
|
||||
"homepage": "https://code.foss.global/push.rocks/smartsitemap#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://code.foss.global/push.rocks/smartsitemap.git"
|
||||
},
|
||||
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34",
|
||||
"bugs": {
|
||||
"url": "https://code.foss.global/push.rocks/smartsitemap/issues"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {}
|
||||
}
|
||||
}
|
||||
13037
pnpm-lock.yaml
generated
13037
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
712
readme.md
712
readme.md
@@ -1,102 +1,712 @@
|
||||
# @push.rocks/smartsitemap
|
||||
a sitemap module
|
||||
|
||||
> 🗺️ **Modern sitemap generation and management for TypeScript** — Create XML sitemaps from RSS feeds, YAML configs, or programmatically. Perfect for SEO optimization and search engine indexing.
|
||||
|
||||
## Install
|
||||
|
||||
To install `@push.rocks/smartsitemap`, use npm or yarn:
|
||||
|
||||
```bash
|
||||
npm install @push.rocks/smartsitemap --save
|
||||
# or
|
||||
# Using pnpm (recommended)
|
||||
pnpm install @push.rocks/smartsitemap
|
||||
|
||||
# Using npm
|
||||
npm install @push.rocks/smartsitemap
|
||||
|
||||
# Using yarn
|
||||
yarn add @push.rocks/smartsitemap
|
||||
```
|
||||
|
||||
This will add `@push.rocks/smartsitemap` to your project's dependencies.
|
||||
## Features
|
||||
|
||||
## Usage
|
||||
✨ **Multiple Sitemap Types**
|
||||
- 📰 **News Sitemaps** — Generate Google News-compatible sitemaps from RSS feeds
|
||||
- 🌐 **Website Sitemaps** — Standard XML sitemaps with customizable update frequencies
|
||||
- 🔄 **Dynamic Generation** — Create sitemaps from various sources (RSS, YAML, arrays)
|
||||
|
||||
`@push.rocks/smartsitemap` provides a versatile way to create, manage, and parse sitemaps in TypeScript. Below are examples demonstrating how to utilize its capabilities in various scenarios. Please note that these examples are written using ESM syntax and TypeScript.
|
||||
🚀 **Developer-Friendly**
|
||||
- 💪 **Full TypeScript Support** — Complete type definitions and interfaces
|
||||
- 🎯 **Simple API** — Intuitive methods for common sitemap operations
|
||||
- 📦 **Zero Configuration** — Works out of the box with sensible defaults
|
||||
- 🔍 **Sitemap Parsing** — Read and parse existing sitemaps from URLs
|
||||
|
||||
### Creating a News Sitemap from an RSS Feed
|
||||
🎨 **Flexible Input Sources**
|
||||
- RSS/Atom feeds (URL or string)
|
||||
- YAML configuration files
|
||||
- URL info arrays
|
||||
- Article objects
|
||||
|
||||
To generate a sitemap for news articles based on an RSS feed URL, you can use the `createSitemapNewsFromFeedUrl` method:
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { SmartSitemap } from '@push.rocks/smartsitemap';
|
||||
|
||||
async function generateNewsSitemap() {
|
||||
const sitemapGenerator = new SmartSitemap();
|
||||
const sitemapXml = await sitemapGenerator.createSitemapNewsFromFeedUrl('https://yourwebsite.com/feed');
|
||||
console.log(sitemapXml);
|
||||
}
|
||||
generateNewsSitemap();
|
||||
const sitemap = new SmartSitemap();
|
||||
|
||||
// Generate a news sitemap from RSS feed
|
||||
const newsSitemap = await sitemap.createSitemapNewsFromFeedUrl(
|
||||
'https://yoursite.com/rss/'
|
||||
);
|
||||
|
||||
console.log(newsSitemap);
|
||||
```
|
||||
|
||||
This function fetches the RSS feed, parses the articles, and generates a sitemap XML string suitable for news content.
|
||||
## Usage Examples
|
||||
|
||||
### Parsing a Sitemap
|
||||
### 📰 Creating News Sitemaps
|
||||
|
||||
To parse an existing sitemap, employ the `parseSitemapUrl` method which accepts a sitemap URL and returns a parsed object:
|
||||
#### From RSS Feed URL
|
||||
|
||||
Perfect for blogs, news sites, and content platforms with RSS feeds:
|
||||
|
||||
```typescript
|
||||
import { SmartSitemap } from '@push.rocks/smartsitemap';
|
||||
|
||||
async function parseExistingSitemap() {
|
||||
const sitemapParser = new SmartSitemap();
|
||||
const parsedSitemap = await sitemapParser.parseSitemapUrl('https://yourwebsite.com/sitemap.xml');
|
||||
console.log(parsedSitemap);
|
||||
}
|
||||
parseExistingSitemap();
|
||||
const sitemap = new SmartSitemap();
|
||||
|
||||
// Fetch RSS feed and generate Google News sitemap
|
||||
const newsSitemap = await sitemap.createSitemapNewsFromFeedUrl(
|
||||
'https://coffee.link/rss/'
|
||||
);
|
||||
|
||||
console.log(newsSitemap);
|
||||
```
|
||||
|
||||
### Creating a Website Sitemap from a YAML String
|
||||
**Output:**
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
xmlns:news="http://www.google.com/schemas/sitemap-news/0.9">
|
||||
<url>
|
||||
<loc>https://coffee.link/article-url/</loc>
|
||||
<news:news>
|
||||
<news:publication>
|
||||
<news:language>en</news:language>
|
||||
<news:name>some name</news:name>
|
||||
</news:publication>
|
||||
<news:keywords></news:keywords>
|
||||
<news:publication_date>2025-11-18T16:57:15.000Z</news:publication_date>
|
||||
<news:title>Article Title</news:title>
|
||||
</news:news>
|
||||
</url>
|
||||
</urlset>
|
||||
```
|
||||
|
||||
Generating a regular website sitemap from a YAML string, which lists URLs and their update frequencies, is straightforward with `createSitemapFromYmlString`:
|
||||
#### From RSS Feed String
|
||||
|
||||
When you already have the RSS XML content:
|
||||
|
||||
```typescript
|
||||
import { SmartSitemap } from '@push.rocks/smartsitemap';
|
||||
|
||||
const yamlString = `
|
||||
daily:
|
||||
- https://yourwebsite.com/
|
||||
- https://yourwebsite.com/about
|
||||
const rssFeedXml = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<!-- Your RSS feed content -->
|
||||
</rss>
|
||||
`;
|
||||
|
||||
async function generateWebsiteSitemap() {
|
||||
const sitemapGenerator = new SmartSitemap();
|
||||
const sitemapXml = await sitemapGenerator.createSitemapFromYmlString(yamlString);
|
||||
console.log(sitemapXml);
|
||||
}
|
||||
generateWebsiteSitemap();
|
||||
const sitemap = new SmartSitemap();
|
||||
const newsSitemap = await sitemap.createSitemapNewsFromAFeedStringArg(rssFeedXml);
|
||||
|
||||
console.log(newsSitemap);
|
||||
```
|
||||
|
||||
### Creating a Website Sitemap from a URL Array
|
||||
#### From Article Array
|
||||
|
||||
For more control, you can create a sitemap by providing an array of URL objects using `createSitemapFromUrlInfoArray`:
|
||||
Create news sitemaps programmatically from your CMS or database:
|
||||
|
||||
```typescript
|
||||
import { SmartSitemap } from '@push.rocks/smartsitemap';
|
||||
import type { IArticle } from '@tsclass/tsclass';
|
||||
|
||||
const articles: IArticle[] = [
|
||||
{
|
||||
title: 'Breaking News Article',
|
||||
content: 'Article content here...',
|
||||
url: 'https://yoursite.com/breaking-news',
|
||||
// Add other required IArticle properties
|
||||
},
|
||||
// More articles...
|
||||
];
|
||||
|
||||
const sitemap = new SmartSitemap();
|
||||
const newsSitemap = await sitemap.createSitemapNewsFromArticleArray(articles);
|
||||
|
||||
console.log(newsSitemap);
|
||||
```
|
||||
|
||||
### 🌐 Creating Website Sitemaps
|
||||
|
||||
#### From YAML Configuration
|
||||
|
||||
Define your sitemap structure in YAML for easy maintenance:
|
||||
|
||||
```typescript
|
||||
import { SmartSitemap } from '@push.rocks/smartsitemap';
|
||||
|
||||
const yamlConfig = `
|
||||
daily:
|
||||
- https://yoursite.com/
|
||||
- https://yoursite.com/blog
|
||||
- https://yoursite.com/products
|
||||
`;
|
||||
|
||||
const sitemap = new SmartSitemap();
|
||||
const websiteSitemap = await sitemap.createSitemapFromYmlString(yamlConfig);
|
||||
|
||||
console.log(websiteSitemap);
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://yoursite.com/</loc>
|
||||
<lastmod>2025-11-19T12:00:00.000Z</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
</url>
|
||||
<!-- More URLs... -->
|
||||
</urlset>
|
||||
```
|
||||
|
||||
#### From URL Info Array
|
||||
|
||||
Fine-grained control over each URL's properties:
|
||||
|
||||
```typescript
|
||||
import { SmartSitemap, IUrlInfo } from '@push.rocks/smartsitemap';
|
||||
|
||||
const urlInfos: IUrlInfo[] = [
|
||||
{ url: 'https://yourwebsite.com/', timestamp: Date.now(), frequency: 'daily' },
|
||||
{ url: 'https://yourwebsite.com/about', timestamp: Date.now(), frequency: 'weekly' }
|
||||
{
|
||||
url: 'https://yoursite.com/',
|
||||
timestamp: Date.now(),
|
||||
frequency: 'daily', // How often the page changes
|
||||
},
|
||||
{
|
||||
url: 'https://yoursite.com/about',
|
||||
timestamp: Date.now() - 86400000, // Yesterday
|
||||
frequency: 'monthly',
|
||||
},
|
||||
{
|
||||
url: 'https://yoursite.com/blog',
|
||||
timestamp: Date.now(),
|
||||
frequency: 'weekly',
|
||||
},
|
||||
];
|
||||
|
||||
async function generateWebsiteSitemap() {
|
||||
const sitemapGenerator = new SmartSitemap();
|
||||
const sitemapXml = await sitemapGenerator.createSitemapFromUrlInfoArray(urlInfos);
|
||||
console.log(sitemapXml);
|
||||
}
|
||||
generateWebsiteSitemap();
|
||||
const sitemap = new SmartSitemap();
|
||||
const websiteSitemap = await sitemap.createSitemapFromUrlInfoArray(urlInfos);
|
||||
|
||||
console.log(websiteSitemap);
|
||||
```
|
||||
|
||||
### Advanced Usage: Combining News and Website Sitemaps
|
||||
**Available Frequencies:**
|
||||
- `'never'` — Archived content
|
||||
- `'daily'` — Updated every day
|
||||
- `'weekly'` — Updated weekly
|
||||
- `'monthly'` — Updated monthly
|
||||
- `'yearly'` — Updated annually
|
||||
|
||||
For comprehensive sitemap management, including both regular webpage URLs and news articles, you can combine the methods shown above to fit your specific needs.
|
||||
### 🔍 Parsing Existing Sitemaps
|
||||
|
||||
This module offers flexibility and ease of use for generating sitemaps compatible with search engines like Google, enhancing SEO by ensuring your site's content is fully indexed.
|
||||
Read and parse sitemaps from remote URLs:
|
||||
|
||||
Always refer to the latest documentation and class references to utilize all features provided by `@push.rocks/smartsitemap`, as this guide focuses on fundamental usages and may not cover more advanced or newly introduced functionalities.
|
||||
```typescript
|
||||
import { SmartSitemap } from '@push.rocks/smartsitemap';
|
||||
|
||||
const sitemap = new SmartSitemap();
|
||||
|
||||
// Parse a sitemap from URL
|
||||
const parsedSitemap = await sitemap.parseSitemapUrl(
|
||||
'https://www.theverge.com/sitemaps/google_news'
|
||||
);
|
||||
|
||||
console.log(parsedSitemap);
|
||||
// {
|
||||
// urlset: {
|
||||
// url: [
|
||||
// { loc: '...', lastmod: '...', changefreq: '...' },
|
||||
// // ...
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
```
|
||||
|
||||
#### Parse Sitemap String
|
||||
|
||||
If you already have the sitemap XML:
|
||||
|
||||
```typescript
|
||||
import { SmartSitemap } from '@push.rocks/smartsitemap';
|
||||
|
||||
const sitemapXml = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<!-- Sitemap content -->
|
||||
</urlset>
|
||||
`;
|
||||
|
||||
const sitemap = new SmartSitemap();
|
||||
const parsed = await sitemap.parseSitemap(sitemapXml);
|
||||
|
||||
console.log(parsed);
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Working with Lower-Level Classes
|
||||
|
||||
For more control, use the individual sitemap classes directly:
|
||||
|
||||
#### SitemapWebsite Class
|
||||
|
||||
```typescript
|
||||
import { SitemapWebsite, IUrlInfo } from '@push.rocks/smartsitemap';
|
||||
|
||||
const websiteSitemap = new SitemapWebsite();
|
||||
|
||||
// Add URLs one by one
|
||||
websiteSitemap.addUrl({
|
||||
url: 'https://yoursite.com/page1',
|
||||
timestamp: Date.now(),
|
||||
frequency: 'daily',
|
||||
});
|
||||
|
||||
websiteSitemap.addUrl({
|
||||
url: 'https://yoursite.com/page2',
|
||||
timestamp: Date.now(),
|
||||
frequency: 'weekly',
|
||||
});
|
||||
|
||||
// Export to XML
|
||||
const xml = websiteSitemap.exportSitemapXml();
|
||||
console.log(xml);
|
||||
```
|
||||
|
||||
#### SitemapNews Class
|
||||
|
||||
```typescript
|
||||
import { SitemapNews } from '@push.rocks/smartsitemap';
|
||||
|
||||
const newsSitemap = new SitemapNews({});
|
||||
|
||||
// Add from RSS feed URL
|
||||
await newsSitemap.readAndAddFromRssFeedUrl('https://yoursite.com/rss');
|
||||
|
||||
// Or from RSS string
|
||||
await newsSitemap.readAndAddFromRssFeedString(rssFeedXml);
|
||||
|
||||
// Or from articles
|
||||
await newsSitemap.readAndParseArticles(articles);
|
||||
|
||||
// Export to XML
|
||||
const xml = newsSitemap.exportSitemapXml();
|
||||
console.log(xml);
|
||||
```
|
||||
|
||||
### Combining Multiple Sources
|
||||
|
||||
Create comprehensive sitemaps by combining different data sources:
|
||||
|
||||
```typescript
|
||||
import { SitemapWebsite } from '@push.rocks/smartsitemap';
|
||||
|
||||
const sitemap = new SitemapWebsite();
|
||||
|
||||
// Add static pages
|
||||
sitemap.addUrl({
|
||||
url: 'https://yoursite.com/',
|
||||
timestamp: Date.now(),
|
||||
frequency: 'daily',
|
||||
});
|
||||
|
||||
// Add dynamic content from database
|
||||
const blogPosts = await fetchBlogPostsFromDB();
|
||||
for (const post of blogPosts) {
|
||||
sitemap.addUrl({
|
||||
url: post.url,
|
||||
timestamp: post.updatedAt,
|
||||
frequency: 'weekly',
|
||||
});
|
||||
}
|
||||
|
||||
// Add pages from CMS
|
||||
const cmsPages = await fetchPagesFromCMS();
|
||||
for (const page of cmsPages) {
|
||||
sitemap.addUrl({
|
||||
url: page.url,
|
||||
timestamp: page.lastModified,
|
||||
frequency: page.updateFrequency,
|
||||
});
|
||||
}
|
||||
|
||||
const xml = sitemap.exportSitemapXml();
|
||||
```
|
||||
|
||||
## TypeScript Types
|
||||
|
||||
### IUrlInfo
|
||||
|
||||
```typescript
|
||||
interface IUrlInfo {
|
||||
url: string;
|
||||
timestamp: number; // Unix timestamp in milliseconds
|
||||
frequency?: TUpdateFrequency;
|
||||
}
|
||||
|
||||
type TUpdateFrequency = 'never' | 'daily' | 'weekly' | 'monthly' | 'yearly';
|
||||
```
|
||||
|
||||
### IParsedSiteMap
|
||||
|
||||
```typescript
|
||||
interface IParsedSiteMap {
|
||||
urlset: {
|
||||
url:
|
||||
| { loc: string; lastmod: string; changefreq: string }
|
||||
| { loc: string; lastmod: string; changefreq: string }[]
|
||||
| {
|
||||
loc: string;
|
||||
'news:news': {
|
||||
'news:publication': [];
|
||||
'news:keywords': string;
|
||||
'news:publication_date': string;
|
||||
'news:title': string;
|
||||
};
|
||||
}[];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### IRssItem
|
||||
|
||||
```typescript
|
||||
interface IRssItem {
|
||||
[key: string]: any;
|
||||
link?: string;
|
||||
guid?: string;
|
||||
title?: string;
|
||||
pubDate?: string;
|
||||
creator?: string;
|
||||
content?: string;
|
||||
isoDate?: string;
|
||||
categories?: string[];
|
||||
contentSnippet?: string;
|
||||
enclosure?: any;
|
||||
}
|
||||
```
|
||||
|
||||
## Real-World Integration Examples
|
||||
|
||||
### Express.js Server
|
||||
|
||||
```typescript
|
||||
import express from 'express';
|
||||
import { SmartSitemap } from '@push.rocks/smartsitemap';
|
||||
|
||||
const app = express();
|
||||
const sitemap = new SmartSitemap();
|
||||
|
||||
// Serve sitemap dynamically
|
||||
app.get('/sitemap.xml', async (req, res) => {
|
||||
const urlInfos = await getUrlsFromDatabase();
|
||||
const xml = await sitemap.createSitemapFromUrlInfoArray(urlInfos);
|
||||
|
||||
res.header('Content-Type', 'application/xml');
|
||||
res.send(xml);
|
||||
});
|
||||
|
||||
// News sitemap from RSS
|
||||
app.get('/news-sitemap.xml', async (req, res) => {
|
||||
const xml = await sitemap.createSitemapNewsFromFeedUrl(
|
||||
'https://yoursite.com/rss'
|
||||
);
|
||||
|
||||
res.header('Content-Type', 'application/xml');
|
||||
res.send(xml);
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
```
|
||||
|
||||
### Static Site Generator
|
||||
|
||||
```typescript
|
||||
import { SmartSitemap } from '@push.rocks/smartsitemap';
|
||||
import { writeFileSync } from 'fs';
|
||||
|
||||
async function generateSitemap() {
|
||||
const sitemap = new SmartSitemap();
|
||||
|
||||
// Get all pages from your static site
|
||||
const pages = [
|
||||
{ url: 'https://yoursite.com/', frequency: 'daily' as const },
|
||||
{ url: 'https://yoursite.com/about', frequency: 'monthly' as const },
|
||||
// ... more pages
|
||||
];
|
||||
|
||||
const urlInfos = pages.map(page => ({
|
||||
url: page.url,
|
||||
timestamp: Date.now(),
|
||||
frequency: page.frequency,
|
||||
}));
|
||||
|
||||
const xml = await sitemap.createSitemapFromUrlInfoArray(urlInfos);
|
||||
|
||||
// Write to public directory
|
||||
writeFileSync('./public/sitemap.xml', xml);
|
||||
console.log('✅ Sitemap generated!');
|
||||
}
|
||||
|
||||
generateSitemap();
|
||||
```
|
||||
|
||||
### Content Management System
|
||||
|
||||
```typescript
|
||||
import { SmartSitemap, IUrlInfo } from '@push.rocks/smartsitemap';
|
||||
|
||||
class CMSSitemapGenerator {
|
||||
private sitemap = new SmartSitemap();
|
||||
|
||||
async generateCompleteSitemap(): Promise<string> {
|
||||
const urlInfos: IUrlInfo[] = [];
|
||||
|
||||
// Add all content types
|
||||
urlInfos.push(...await this.getPageUrls());
|
||||
urlInfos.push(...await this.getBlogUrls());
|
||||
urlInfos.push(...await this.getProductUrls());
|
||||
|
||||
return this.sitemap.createSitemapFromUrlInfoArray(urlInfos);
|
||||
}
|
||||
|
||||
private async getPageUrls(): Promise<IUrlInfo[]> {
|
||||
const pages = await this.cms.getPages();
|
||||
return pages.map(page => ({
|
||||
url: page.url,
|
||||
timestamp: page.updatedAt,
|
||||
frequency: 'weekly',
|
||||
}));
|
||||
}
|
||||
|
||||
private async getBlogUrls(): Promise<IUrlInfo[]> {
|
||||
const posts = await this.cms.getBlogPosts();
|
||||
return posts.map(post => ({
|
||||
url: post.url,
|
||||
timestamp: post.publishedAt,
|
||||
frequency: 'monthly',
|
||||
}));
|
||||
}
|
||||
|
||||
private async getProductUrls(): Promise<IUrlInfo[]> {
|
||||
const products = await this.cms.getProducts();
|
||||
return products.map(product => ({
|
||||
url: product.url,
|
||||
timestamp: product.lastModified,
|
||||
frequency: 'daily',
|
||||
}));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Update Frequencies
|
||||
|
||||
Choose appropriate update frequencies for better crawling:
|
||||
|
||||
```typescript
|
||||
// Homepage and main sections: daily
|
||||
{ url: 'https://yoursite.com/', frequency: 'daily' }
|
||||
|
||||
// Blog posts: weekly
|
||||
{ url: 'https://yoursite.com/blog/post', frequency: 'weekly' }
|
||||
|
||||
// Static pages: monthly or yearly
|
||||
{ url: 'https://yoursite.com/about', frequency: 'monthly' }
|
||||
|
||||
// Archived content: never
|
||||
{ url: 'https://yoursite.com/archive/2020', frequency: 'never' }
|
||||
```
|
||||
|
||||
### 2. Sitemap Size Limits
|
||||
|
||||
Google recommends keeping sitemaps under 50,000 URLs and 50MB. Split large sitemaps:
|
||||
|
||||
```typescript
|
||||
import { SitemapWebsite } from '@push.rocks/smartsitemap';
|
||||
|
||||
async function generateSitemaps(allUrls: IUrlInfo[]) {
|
||||
const chunkSize = 50000;
|
||||
const chunks = [];
|
||||
|
||||
for (let i = 0; i < allUrls.length; i += chunkSize) {
|
||||
chunks.push(allUrls.slice(i, i + chunkSize));
|
||||
}
|
||||
|
||||
const sitemaps = await Promise.all(
|
||||
chunks.map(async (chunk, index) => {
|
||||
const sitemap = new SitemapWebsite();
|
||||
chunk.forEach(url => sitemap.addUrl(url));
|
||||
const xml = sitemap.exportSitemapXml();
|
||||
|
||||
// Save as sitemap-1.xml, sitemap-2.xml, etc.
|
||||
return { filename: `sitemap-${index + 1}.xml`, content: xml };
|
||||
})
|
||||
);
|
||||
|
||||
return sitemaps;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Cache Generated Sitemaps
|
||||
|
||||
Don't regenerate on every request:
|
||||
|
||||
```typescript
|
||||
import { SmartSitemap } from '@push.rocks/smartsitemap';
|
||||
|
||||
class SitemapCache {
|
||||
private cache: string | null = null;
|
||||
private lastGenerated: number = 0;
|
||||
private cacheDuration = 3600000; // 1 hour
|
||||
|
||||
async getSitemap(): Promise<string> {
|
||||
const now = Date.now();
|
||||
|
||||
if (!this.cache || now - this.lastGenerated > this.cacheDuration) {
|
||||
const sitemap = new SmartSitemap();
|
||||
const urls = await this.fetchUrls();
|
||||
this.cache = await sitemap.createSitemapFromUrlInfoArray(urls);
|
||||
this.lastGenerated = now;
|
||||
}
|
||||
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
invalidate() {
|
||||
this.cache = null;
|
||||
}
|
||||
|
||||
private async fetchUrls() {
|
||||
// Fetch from your data source
|
||||
return [];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Submit to Search Engines
|
||||
|
||||
After generating your sitemap, submit it to search engines:
|
||||
|
||||
```typescript
|
||||
// In your robots.txt
|
||||
`
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Sitemap: https://yoursite.com/sitemap.xml
|
||||
Sitemap: https://yoursite.com/news-sitemap.xml
|
||||
`
|
||||
```
|
||||
|
||||
Or ping search engines programmatically:
|
||||
|
||||
```typescript
|
||||
async function submitSitemap(sitemapUrl: string) {
|
||||
const encodedUrl = encodeURIComponent(sitemapUrl);
|
||||
|
||||
// Google
|
||||
await fetch(`https://www.google.com/ping?sitemap=${encodedUrl}`);
|
||||
|
||||
// Bing
|
||||
await fetch(`https://www.bing.com/ping?sitemap=${encodedUrl}`);
|
||||
}
|
||||
|
||||
submitSitemap('https://yoursite.com/sitemap.xml');
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### SmartSitemap
|
||||
|
||||
Main class for sitemap operations.
|
||||
|
||||
#### Methods
|
||||
|
||||
**`createSitemapNewsFromFeedUrl(feedUrl: string): Promise<string>`**
|
||||
- Generate a Google News sitemap from an RSS/Atom feed URL
|
||||
- Returns XML string
|
||||
|
||||
**`createSitemapNewsFromAFeedStringArg(feedString: string): Promise<string>`**
|
||||
- Generate a Google News sitemap from RSS/Atom feed XML string
|
||||
- Returns XML string
|
||||
|
||||
**`createSitemapNewsFromArticleArray(articles: IArticle[]): Promise<string>`**
|
||||
- Generate a Google News sitemap from an array of article objects
|
||||
- Returns XML string
|
||||
|
||||
**`createSitemapFromYmlString(yamlString: string): Promise<string>`**
|
||||
- Generate a standard sitemap from YAML configuration
|
||||
- Returns XML string
|
||||
|
||||
**`createSitemapFromUrlInfoArray(urlInfos: IUrlInfo[]): Promise<string>`**
|
||||
- Generate a standard sitemap from URL info array
|
||||
- Returns XML string
|
||||
|
||||
**`parseSitemapUrl(url: string): Promise<IParsedSiteMap>`**
|
||||
- Parse a sitemap from a URL
|
||||
- Returns parsed sitemap object
|
||||
|
||||
**`parseSitemap(sitemapXml: string): Promise<IParsedSiteMap>`**
|
||||
- Parse a sitemap from XML string
|
||||
- Returns parsed sitemap object
|
||||
|
||||
### SitemapWebsite
|
||||
|
||||
Lower-level class for website sitemaps.
|
||||
|
||||
#### Methods
|
||||
|
||||
**`addUrl(urlInfo: IUrlInfo): void`**
|
||||
- Add a URL to the sitemap
|
||||
|
||||
**`exportSitemapXml(): string`**
|
||||
- Export sitemap as XML string
|
||||
|
||||
### SitemapNews
|
||||
|
||||
Lower-level class for news sitemaps.
|
||||
|
||||
#### Methods
|
||||
|
||||
**`readAndAddFromRssFeedUrl(url: string): Promise<void>`**
|
||||
- Add news items from RSS feed URL
|
||||
|
||||
**`readAndAddFromRssFeedString(feedString: string): Promise<void>`**
|
||||
- Add news items from RSS feed string
|
||||
|
||||
**`readAndParseArticles(articles: IArticle[]): Promise<void>`**
|
||||
- Add news items from article array
|
||||
|
||||
**`exportSitemapXml(): string`**
|
||||
- Export news sitemap as XML string
|
||||
|
||||
## Why @push.rocks/smartsitemap?
|
||||
|
||||
- ✅ **Simple & Intuitive** — Clean API that just works
|
||||
- ✅ **Type-Safe** — Full TypeScript support with complete type definitions
|
||||
- ✅ **Flexible** — Multiple input sources and output formats
|
||||
- ✅ **Battle-Tested** — Used in production across multiple projects
|
||||
- ✅ **Modern** — ESM-first, async/await, latest standards
|
||||
- ✅ **SEO-Friendly** — Generates valid XML sitemaps that search engines love
|
||||
|
||||
## Related Packages
|
||||
|
||||
Part of the `@push.rocks` ecosystem:
|
||||
|
||||
- **[@push.rocks/smartfeed](https://www.npmjs.com/package/@push.rocks/smartfeed)** — RSS/Atom feed parsing
|
||||
- **[@push.rocks/smartxml](https://www.npmjs.com/package/@push.rocks/smartxml)** — XML parsing and generation
|
||||
- **[@push.rocks/smartyaml](https://www.npmjs.com/package/@push.rocks/smartyaml)** — YAML parsing
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
daily:
|
||||
- central.eu/
|
||||
- central.eu/privacy
|
||||
|
||||
@@ -10,13 +10,15 @@ tap.test('should create an instance of Smartsitemap', async () => {
|
||||
|
||||
tap.test('should create a sitemap from feed', async () => {
|
||||
const sitemapString = await testSmartsitemap.createSitemapNewsFromFeedUrl(
|
||||
'https://central.eu/feed'
|
||||
'https://coffee.link/rss/',
|
||||
);
|
||||
console.log(sitemapString);
|
||||
});
|
||||
|
||||
tap.test('should parse a sitemap', async () => {
|
||||
const result = await testSmartsitemap.parseSitemapUrl('https://www.theverge.com/sitemaps/google_news');
|
||||
const result = await testSmartsitemap.parseSitemapUrl(
|
||||
'https://www.theverge.com/sitemaps/google_news',
|
||||
);
|
||||
// console.log(result.urlset.url);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @pushrocks/commitinfo
|
||||
* autocreated commitinfo by @push.rocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartsitemap',
|
||||
version: '2.0.3',
|
||||
description: 'a sitemap module'
|
||||
version: '2.0.4',
|
||||
description: 'A module for generating and managing sitemaps, supporting dynamic sitemap generation from feeds.'
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ export class SitemapNews {
|
||||
|
||||
public async readAndAddFromRssFeedString(feedStringArg: string) {
|
||||
const smartfeedInstance = new plugins.smartfeed.Smartfeed();
|
||||
const parsedFeed = await smartfeedInstance.parseFeedFromString(feedStringArg);
|
||||
const parsedFeed =
|
||||
await smartfeedInstance.parseFeedFromString(feedStringArg);
|
||||
this.rssItems = this.rssItems.concat(parsedFeed.items);
|
||||
}
|
||||
|
||||
@@ -18,15 +19,20 @@ export class SitemapNews {
|
||||
this.rssItems = this.rssItems.concat(parsedFeed.items);
|
||||
}
|
||||
|
||||
public async readAndParseArticles(articleArrayArg: plugins.tsclass.content.IArticle[]) {
|
||||
const rssItemArray = articleArrayArg.map((articleArg): interfaces.IRssItem => {
|
||||
public async readAndParseArticles(
|
||||
articleArrayArg: plugins.tsclass.content.IArticle[],
|
||||
) {
|
||||
const rssItemArray = articleArrayArg.map(
|
||||
(articleArg): interfaces.IRssItem => {
|
||||
return {
|
||||
title: articleArg.title,
|
||||
content: articleArg.content,
|
||||
isoDate: new Date(/* TODO: put article timestamp here */).toISOString(),
|
||||
isoDate:
|
||||
new Date(/* TODO: put article timestamp here */).toISOString(),
|
||||
link: articleArg.url,
|
||||
};
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
this.rssItems = this.rssItems.concat(rssItemArray);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import * as plugins from './smartsitemap.plugins.js';
|
||||
|
||||
export type TUpdateFrequency = 'never' | 'daily' | 'weekly' | 'monthly' | 'yearly';
|
||||
export type TUpdateFrequency =
|
||||
| 'never'
|
||||
| 'daily'
|
||||
| 'weekly'
|
||||
| 'monthly'
|
||||
| 'yearly';
|
||||
|
||||
export interface IUrlInfo {
|
||||
url: string;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { SitemapNews } from './smartsitemap.classes.sitemapnews.js';
|
||||
import { type IUrlInfo, SitemapWebsite } from './smartsitemap.classes.sitemapwebsite.js';
|
||||
import {
|
||||
type IUrlInfo,
|
||||
SitemapWebsite,
|
||||
} from './smartsitemap.classes.sitemapwebsite.js';
|
||||
import * as plugins from './smartsitemap.plugins.js';
|
||||
import * as interfaces from './interfaces/index.js';
|
||||
|
||||
@@ -9,7 +12,9 @@ export class SmartSitemap {
|
||||
/**
|
||||
* creates a sitemap for news from feedurl
|
||||
*/
|
||||
public async createSitemapNewsFromFeedUrl(feedUrlArg: string): Promise<string> {
|
||||
public async createSitemapNewsFromFeedUrl(
|
||||
feedUrlArg: string,
|
||||
): Promise<string> {
|
||||
const sitemapNewsInstance = new SitemapNews({});
|
||||
await sitemapNewsInstance.readAndAddFromRssFeedUrl(feedUrlArg);
|
||||
return sitemapNewsInstance.exportSitemapXml();
|
||||
@@ -18,7 +23,9 @@ export class SmartSitemap {
|
||||
/**
|
||||
* creates a sitemap for news from feedxmlstring
|
||||
*/
|
||||
public async createSitemapNewsFromAFeedStringArg(feedStringArg: string): Promise<string> {
|
||||
public async createSitemapNewsFromAFeedStringArg(
|
||||
feedStringArg: string,
|
||||
): Promise<string> {
|
||||
const sitemapNewsInstance = new SitemapNews({});
|
||||
await sitemapNewsInstance.readAndAddFromRssFeedString(feedStringArg);
|
||||
return sitemapNewsInstance.exportSitemapXml();
|
||||
@@ -28,7 +35,7 @@ export class SmartSitemap {
|
||||
* creates a sitemap for news from an array of articles
|
||||
*/
|
||||
public async createSitemapNewsFromArticleArray(
|
||||
articleArrayArg: plugins.tsclass.content.IArticle[]
|
||||
articleArrayArg: plugins.tsclass.content.IArticle[],
|
||||
): Promise<string> {
|
||||
const sitemapNewsInstance = new SitemapNews({});
|
||||
await sitemapNewsInstance.readAndParseArticles(articleArrayArg);
|
||||
@@ -39,9 +46,8 @@ export class SmartSitemap {
|
||||
* creates a normal sitemap from a list of urls
|
||||
*/
|
||||
public async createSitemapFromYmlString(yamlString: string): Promise<string> {
|
||||
const yamlObject: interfaces.ISitemapYaml = await plugins.smartyaml.yamlStringToObject(
|
||||
yamlString
|
||||
);
|
||||
const yamlObject: interfaces.ISitemapYaml =
|
||||
await plugins.smartyaml.yamlStringToObject(yamlString);
|
||||
const sitemapWebsite = new SitemapWebsite();
|
||||
for (const urlArg of yamlObject.daily) {
|
||||
sitemapWebsite.addUrl({
|
||||
@@ -68,11 +74,8 @@ export class SmartSitemap {
|
||||
* parses a sitemap url
|
||||
*/
|
||||
public async parseSitemapUrl(urlArg: string) {
|
||||
const sitemapXml = await (
|
||||
await new plugins.webrequest.WebRequest().request(urlArg, {
|
||||
method: 'GET',
|
||||
})
|
||||
).text();
|
||||
const response = await plugins.webrequest.webrequest(urlArg);
|
||||
const sitemapXml = await response.text();
|
||||
|
||||
const parsedSitemap = await this.parseSitemap(sitemapXml);
|
||||
return parsedSitemap;
|
||||
@@ -81,7 +84,9 @@ export class SmartSitemap {
|
||||
/**
|
||||
* parses a sitemap
|
||||
*/
|
||||
public async parseSitemap(sitemapXmlArg: string): Promise<interfaces.IParsedSiteMap> {
|
||||
public async parseSitemap(
|
||||
sitemapXmlArg: string,
|
||||
): Promise<interfaces.IParsedSiteMap> {
|
||||
return new plugins.smartxml.SmartXml().parseXmlToObject(sitemapXmlArg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true
|
||||
"verbatimModuleSyntax": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {}
|
||||
},
|
||||
"exclude": [
|
||||
"dist_*/**/*.d.ts"
|
||||
]
|
||||
"exclude": ["dist_*/**/*.d.ts"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user