From 049a98da58bd99fe8297e8f14923746a580243eb Mon Sep 17 00:00:00 2001 From: Roland Golla Date: Wed, 18 Feb 2026 16:55:07 +0100 Subject: [PATCH 01/10] Remove healthcheck .gitlab-ci.yml --- docker-compose.conversis.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/docker-compose.conversis.yml b/docker-compose.conversis.yml index 65e8f76..821bbe8 100644 --- a/docker-compose.conversis.yml +++ b/docker-compose.conversis.yml @@ -6,18 +6,6 @@ services: restart: unless-stopped networks: - proxy - healthcheck: - test: - [ - 'CMD', - 'node', - '-e', - "require('http').get('http://localhost:4321', (r) => process.exit(r.statusCode === 200 ? 0 : 1))", - ] - interval: 30s - timeout: 5s - retries: 3 - start_period: 10s labels: - 'traefik.enable=true' - 'traefik.http.routers.$SHORTCODE1.entrypoints=http' From e7302a582046f5056bb94f15ef9e09b4d83f7854 Mon Sep 17 00:00:00 2001 From: Roland Golla Date: Sat, 21 Feb 2026 20:29:30 +0100 Subject: [PATCH 02/10] move article content path from src/content/articles/ to nca-ai-cms-content/ --- .gitignore | 2 +- .gitlab-ci.yml | 6 +++--- Dockerfile.conversis | 4 ++-- README.md | 2 +- nca-ai-cms-content/.gitignore | 3 +++ src/components/HeroImage.astro | 2 +- src/content.config.ts | 2 +- src/domain/entities/Article.test.ts | 4 ++-- src/domain/entities/Article.ts | 2 +- src/pages/api/article-image/[...path].ts | 2 +- src/pages/index.astro | 2 +- src/pages/sitemap.xml.test.ts | 2 +- src/services/ArticleService.ts | 2 +- src/services/FileWriter.test.ts | 2 +- 14 files changed, 20 insertions(+), 17 deletions(-) create mode 100644 nca-ai-cms-content/.gitignore diff --git a/.gitignore b/.gitignore index 49904d5..dc1cdc6 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,4 @@ coverage/ .idea/ # generated content (articles + co-located images) -src/content/articles/ +nca-ai-cms-content/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2507604..7f909a9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,16 +22,16 @@ build: - docker login -u $CI_REGISTRY_USER -p $CI_JOB_TOKEN $CI_REGISTRY script: # Backup content from old container - - docker cp $SHORTCODE1-web:/app/src/content/articles /tmp/articles 2>/dev/null || true + - docker cp $SHORTCODE1-web:/app/nca-ai-cms-content /tmp/articles 2>/dev/null || true - docker cp $SHORTCODE1-web:/app/.astro /tmp/astro-db 2>/dev/null || true # Remove old container and start new one - docker compose -f docker-compose.conversis.yml -p $SHORTCODE1 rm -sfv || true - docker compose -f docker-compose.conversis.yml -p $SHORTCODE1 up -d --remove-orphans # Restore content to new container - - docker cp /tmp/articles/. $SHORTCODE1-web:/app/src/content/articles/ 2>/dev/null || true + - docker cp /tmp/articles/. $SHORTCODE1-web:/app/nca-ai-cms-content/ 2>/dev/null || true - docker cp /tmp/astro-db/. $SHORTCODE1-web:/app/.astro/ 2>/dev/null || true # Fix ownership for non-root user - - docker exec -u root $SHORTCODE1-web chown -R astro:nodejs /app/src/content/articles 2>/dev/null || true + - docker exec -u root $SHORTCODE1-web chown -R astro:nodejs /app/nca-ai-cms-content 2>/dev/null || true - docker exec -u root $SHORTCODE1-web chown -R astro:nodejs /app/.astro 2>/dev/null || true # Rebuild to pick up restored content (creates DB on first deploy) - docker exec $SHORTCODE1-web npm run build diff --git a/Dockerfile.conversis b/Dockerfile.conversis index 18d3d83..9f66c66 100644 --- a/Dockerfile.conversis +++ b/Dockerfile.conversis @@ -9,7 +9,7 @@ FROM deps AS builder COPY . . # Build with empty articles folder and local database ENV ASTRO_DATABASE_FILE=/app/.astro/content.db -RUN mkdir -p .astro && rm -rf src/content/articles/* && npm run build +RUN mkdir -p .astro && rm -rf nca-ai-cms-content/* && npm run build FROM base AS runner RUN addgroup -g 1001 -S nodejs && \ @@ -24,7 +24,7 @@ COPY --from=builder /app/db ./db COPY --from=builder /app/astro.config.mjs ./ COPY --from=builder /app/tsconfig.json ./ -RUN mkdir -p src/content/articles .astro && \ +RUN mkdir -p nca-ai-cms-content .astro && \ chown -R astro:nodejs . USER astro diff --git a/README.md b/README.md index f80b840..5c6a820 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,7 @@ Set variables in **GitLab > Settings > CI/CD > Variables**. The pipeline builds Articles are stored in the filesystem, not the database: ``` -src/content/articles/{YEAR}/{MONTH}/{SLUG}/ +nca-ai-cms-content/{YEAR}/{MONTH}/{SLUG}/ ├── index.md # Article content (Markdown + frontmatter) └── hero.webp # Generated hero image ``` diff --git a/nca-ai-cms-content/.gitignore b/nca-ai-cms-content/.gitignore new file mode 100644 index 0000000..006ade9 --- /dev/null +++ b/nca-ai-cms-content/.gitignore @@ -0,0 +1,3 @@ +# Generated content — not tracked in git +* +!.gitignore diff --git a/src/components/HeroImage.astro b/src/components/HeroImage.astro index 2350777..af7dfc0 100644 --- a/src/components/HeroImage.astro +++ b/src/components/HeroImage.astro @@ -17,7 +17,7 @@ let mtime = ''; try { const heroPath = path.join( process.cwd(), - 'src/content/articles', + 'nca-ai-cms-content', articleId, 'hero.webp' ); diff --git a/src/content.config.ts b/src/content.config.ts index e678d97..4a7be5e 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -4,7 +4,7 @@ import { glob } from 'astro/loaders'; // Define articles to prevent auto-generation - but use live collection at runtime // This loader won't find anything since we use index.md in folders const articles = defineCollection({ - loader: glob({ pattern: '**/index.md', base: './src/content/articles' }), + loader: glob({ pattern: '**/index.md', base: './nca-ai-cms-content' }), schema: z.object({ title: z.string(), description: z.string(), diff --git a/src/domain/entities/Article.test.ts b/src/domain/entities/Article.test.ts index 6169eec..f003ed2 100644 --- a/src/domain/entities/Article.test.ts +++ b/src/domain/entities/Article.test.ts @@ -29,14 +29,14 @@ describe('Article', () => { it('generates correct folderPath for co-located content', () => { const article = new Article(defaultProps); expect(article.folderPath).toBe( - 'src/content/articles/2025/12/html-accessibility-grundlagen' + 'nca-ai-cms-content/2025/12/html-accessibility-grundlagen' ); }); it('generates correct filepath with index.md', () => { const article = new Article(defaultProps); expect(article.filepath).toBe( - 'src/content/articles/2025/12/html-accessibility-grundlagen/index.md' + 'nca-ai-cms-content/2025/12/html-accessibility-grundlagen/index.md' ); }); diff --git a/src/domain/entities/Article.ts b/src/domain/entities/Article.ts index 44a9252..8cdfbf5 100644 --- a/src/domain/entities/Article.ts +++ b/src/domain/entities/Article.ts @@ -53,7 +53,7 @@ export class Article { } get folderPath(): string { - return `src/content/articles/${this.year}/${this.month}/${this.slug.toString()}`; + return `nca-ai-cms-content/${this.year}/${this.month}/${this.slug.toString()}`; } get filepath(): string { diff --git a/src/pages/api/article-image/[...path].ts b/src/pages/api/article-image/[...path].ts index e6f9fec..6fabab3 100644 --- a/src/pages/api/article-image/[...path].ts +++ b/src/pages/api/article-image/[...path].ts @@ -25,7 +25,7 @@ export const GET: APIRoute = async ({ params }) => { const fullPath = path.join( process.cwd(), - 'src/content/articles', + 'nca-ai-cms-content', normalizedPath ); diff --git a/src/pages/index.astro b/src/pages/index.astro index 68d1719..a82f11d 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -39,7 +39,7 @@ const getArticleImageUrl = async (articleId: string) => { try { const heroPath = path.join( process.cwd(), - 'src/content/articles', + 'nca-ai-cms-content', articleId, 'hero.webp' ); diff --git a/src/pages/sitemap.xml.test.ts b/src/pages/sitemap.xml.test.ts index 189558f..dea84c6 100644 --- a/src/pages/sitemap.xml.test.ts +++ b/src/pages/sitemap.xml.test.ts @@ -14,7 +14,7 @@ describe('generateSitemapXml', () => { date: new Date('2026-01-15'), tags: ['test'], content: 'Test content', - folderPath: '/app/src/content/articles/2026/01/test-article', + folderPath: '/app/nca-ai-cms-content/2026/01/test-article', ...overrides, }); diff --git a/src/services/ArticleService.ts b/src/services/ArticleService.ts index 3e25a71..6259661 100644 --- a/src/services/ArticleService.ts +++ b/src/services/ArticleService.ts @@ -33,7 +33,7 @@ export class ArticleService { private readonly finder: ArticleFinder; private readonly basePath: string; - constructor(basePath: string = 'src/content/articles') { + constructor(basePath: string = 'nca-ai-cms-content') { this.finder = new ArticleFinder(basePath); this.basePath = basePath; } diff --git a/src/services/FileWriter.test.ts b/src/services/FileWriter.test.ts index fad1ff6..c0bb20b 100644 --- a/src/services/FileWriter.test.ts +++ b/src/services/FileWriter.test.ts @@ -45,7 +45,7 @@ describe('FileWriter', () => { const expectedFolder = path.join( tempDir, - 'src/content/articles/2025/12/test-article' + 'nca-ai-cms-content/2025/12/test-article' ); const stats = await fs.stat(expectedFolder); expect(stats.isDirectory()).toBe(true); From a2ba8d9be2820b0c7346cf2de92e8d21edae3f38 Mon Sep 17 00:00:00 2001 From: Roland Golla Date: Sat, 21 Feb 2026 22:49:14 +0100 Subject: [PATCH 03/10] add plugin link/unlink scripts with husky and lint guard --- package-lock.json | 1919 ++++++++++++++++++++++++++------------------- package.json | 8 +- 2 files changed, 1107 insertions(+), 820 deletions(-) diff --git a/package-lock.json b/package-lock.json index 796e96f..8621a4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "astro": "^5.16.4", "gray-matter": "^4.0.3", "marked": "^17.0.1", + "nca-ai-cms-astro-plugin": "^0.1.0", "react": "^19.2.1", "react-dom": "^19.2.1", "sanitize-html": "^2.17.0", @@ -31,12 +32,50 @@ "@types/sanitize-html": "^2.16.0", "@types/turndown": "^5.0.6", "@vitest/coverage-v8": "^4.0.15", + "husky": "^9.1.7", "prettier": "^3.7.4", "prettier-plugin-astro": "^0.14.1", "typescript": "^5.9.3", "vitest": "^4.0.15" } }, + "../nca-ai-cms-astro-plugin": { + "version": "0.1.0", + "devDependencies": { + "@astrojs/db": "^0.18.3", + "@astrojs/react": "^4.4.2", + "@google/genai": "^1.31.0", + "@google/generative-ai": "^0.24.1", + "@types/sanitize-html": "^2.16.0", + "@types/turndown": "^5.0.6", + "astro": "^5.16.4", + "gray-matter": "^4.0.3", + "marked": "^17.0.1", + "react": "^19.2.1", + "react-dom": "^19.2.1", + "sanitize-html": "^2.17.0", + "sharp": "^0.34.5", + "turndown": "^7.2.2", + "typescript": "^5.9.3", + "vitest": "^4.0.15", + "zod": "^3.25.76" + }, + "peerDependencies": { + "@astrojs/db": "^0.18.0", + "@astrojs/react": "^4.0.0", + "@google/genai": "^1.0.0", + "@google/generative-ai": "^0.24.0", + "astro": "^5.0.0", + "gray-matter": "^4.0.0", + "marked": "^17.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "sanitize-html": "^2.0.0", + "sharp": "^0.34.0", + "turndown": "^7.0.0", + "zod": "^3.0.0" + } + }, "node_modules/@anthropic-ai/sdk": { "version": "0.71.2", "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.71.2.tgz", @@ -75,9 +114,9 @@ } }, "node_modules/@astrojs/compiler": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.0.tgz", - "integrity": "sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw==" + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.1.tgz", + "integrity": "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==" }, "node_modules/@astrojs/db": { "version": "0.18.3", @@ -94,51 +133,34 @@ "zod": "^3.25.76" } }, - "node_modules/@astrojs/db/node_modules/nanoid": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", - "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, "node_modules/@astrojs/internal-helpers": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.5.tgz", "integrity": "sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==" }, "node_modules/@astrojs/language-server": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-2.16.2.tgz", - "integrity": "sha512-J3hVx/mFi3FwEzKf8ExYXQNERogD6RXswtbU+TyrxoXRBiQoBO5ooo7/lRWJ+rlUKUd7+rziMPI9jYB7TRlh0w==", + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-2.16.3.tgz", + "integrity": "sha512-yO5K7RYCMXUfeDlnU6UnmtnoXzpuQc0yhlaCNZ67k1C/MiwwwvMZz+LGa+H35c49w5QBfvtr4w4Zcf5PcH8uYA==", "dev": true, "dependencies": { - "@astrojs/compiler": "^2.10.3", + "@astrojs/compiler": "^2.13.0", "@astrojs/yaml2ts": "^0.2.2", - "@jridgewell/sourcemap-codec": "^1.4.15", - "@volar/kit": "~2.4.23", - "@volar/language-core": "~2.4.23", - "@volar/language-server": "~2.4.23", - "@volar/language-service": "~2.4.23", - "fast-glob": "^3.2.12", + "@jridgewell/sourcemap-codec": "^1.5.5", + "@volar/kit": "~2.4.27", + "@volar/language-core": "~2.4.27", + "@volar/language-server": "~2.4.27", + "@volar/language-service": "~2.4.27", "muggle-string": "^0.4.1", - "volar-service-css": "0.0.67", - "volar-service-emmet": "0.0.67", - "volar-service-html": "0.0.67", - "volar-service-prettier": "0.0.67", - "volar-service-typescript": "0.0.67", - "volar-service-typescript-twoslash-queries": "0.0.67", - "volar-service-yaml": "0.0.67", - "vscode-html-languageservice": "^5.5.2", + "tinyglobby": "^0.2.15", + "volar-service-css": "0.0.68", + "volar-service-emmet": "0.0.68", + "volar-service-html": "0.0.68", + "volar-service-prettier": "0.0.68", + "volar-service-typescript": "0.0.68", + "volar-service-typescript-twoslash-queries": "0.0.68", + "volar-service-yaml": "0.0.68", + "vscode-html-languageservice": "^5.6.1", "vscode-uri": "^3.1.0" }, "bin": { @@ -186,16 +208,16 @@ } }, "node_modules/@astrojs/node": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/@astrojs/node/-/node-9.5.1.tgz", - "integrity": "sha512-7k+SU877OUQylPr0mFcWrGvNuC78Lp9w+GInY8Rwc+LkHyDP9xls+nZAioK0WDWd+fyeQnlHbpDGURO3ZHuDVg==", + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/@astrojs/node/-/node-9.5.4.tgz", + "integrity": "sha512-AbPSZsMGu8hXPR2XxV79RaKy8h6wijhtoqZGeUf4OXg2w1mxXlx4VnIc1D+QvtsgauSz7P5PLhmvf6w/J41GJg==", "dependencies": { "@astrojs/internal-helpers": "0.7.5", - "send": "^1.2.0", + "send": "^1.2.1", "server-destroy": "^1.0.1" }, "peerDependencies": { - "astro": "^5.14.3" + "astro": "^5.17.3" } }, "node_modules/@astrojs/prism": { @@ -267,9 +289,9 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", @@ -280,27 +302,27 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", - "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", - "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -317,12 +339,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", - "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dependencies": { - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -427,11 +449,11 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", - "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dependencies": { - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -490,16 +512,16 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", - "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -507,9 +529,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", - "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" @@ -603,9 +625,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", "cpu": [ "ppc64" ], @@ -618,9 +640,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", "cpu": [ "arm" ], @@ -633,9 +655,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", "cpu": [ "arm64" ], @@ -648,9 +670,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", "cpu": [ "x64" ], @@ -663,9 +685,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", "cpu": [ "arm64" ], @@ -678,9 +700,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", "cpu": [ "x64" ], @@ -693,9 +715,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", "cpu": [ "arm64" ], @@ -708,9 +730,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", "cpu": [ "x64" ], @@ -723,9 +745,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", "cpu": [ "arm" ], @@ -738,9 +760,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", "cpu": [ "arm64" ], @@ -753,9 +775,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", "cpu": [ "ia32" ], @@ -768,9 +790,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", "cpu": [ "loong64" ], @@ -783,9 +805,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", "cpu": [ "mips64el" ], @@ -798,9 +820,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", "cpu": [ "ppc64" ], @@ -813,9 +835,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", "cpu": [ "riscv64" ], @@ -828,9 +850,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", "cpu": [ "s390x" ], @@ -843,9 +865,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", "cpu": [ "x64" ], @@ -858,9 +880,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", "cpu": [ "arm64" ], @@ -873,9 +895,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", "cpu": [ "x64" ], @@ -888,9 +910,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", "cpu": [ "arm64" ], @@ -903,9 +925,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", "cpu": [ "x64" ], @@ -918,9 +940,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", "cpu": [ "arm64" ], @@ -933,9 +955,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", "cpu": [ "x64" ], @@ -948,9 +970,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", "cpu": [ "arm64" ], @@ -963,9 +985,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", "cpu": [ "ia32" ], @@ -978,9 +1000,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", "cpu": [ "x64" ], @@ -993,11 +1015,12 @@ } }, "node_modules/@google/genai": { - "version": "1.36.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.36.0.tgz", - "integrity": "sha512-f4S31aVi5G6U1phMKTaNqpik+sfsU1Z25QW3sGyCjtzNj1/0SnCEfWU0Gyq0iR4pjCsaqeeZtSfzHYW3OcEWUQ==", + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.42.0.tgz", + "integrity": "sha512-+3nlMTcrQufbQ8IumGkOphxD5Pd5kKyJOzLcnY0/1IuE8upJk5aLmoexZ2BJhBp1zAjRJMEB4a2CJwKI9e2EYw==", "dependencies": { "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, @@ -1005,7 +1028,7 @@ "node": ">=20.0.0" }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.24.0" + "@modelcontextprotocol/sdk": "^1.25.2" }, "peerDependenciesMeta": { "@modelcontextprotocol/sdk": { @@ -1720,41 +1743,6 @@ "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==" }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@oslojs/encoding": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", @@ -1870,9 +1858,9 @@ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", - "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.58.0.tgz", + "integrity": "sha512-mr0tmS/4FoVk1cnaeN244A/wjvGDNItZKR8hRhnmCzygyRXYtKF5jVDSIILR1U97CTzAYmbgIj/Dukg62ggG5w==", "cpu": [ "arm" ], @@ -1882,9 +1870,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", - "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.58.0.tgz", + "integrity": "sha512-+s++dbp+/RTte62mQD9wLSbiMTV+xr/PeRJEc/sFZFSBRlHPNPVaf5FXlzAL77Mr8FtSfQqCN+I598M8U41ccQ==", "cpu": [ "arm64" ], @@ -1894,9 +1882,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", - "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.58.0.tgz", + "integrity": "sha512-MFWBwTcYs0jZbINQBXHfSrpSQJq3IUOakcKPzfeSznONop14Pxuqa0Kg19GD0rNBMPQI2tFtu3UzapZpH0Uc1Q==", "cpu": [ "arm64" ], @@ -1906,9 +1894,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", - "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.58.0.tgz", + "integrity": "sha512-yiKJY7pj9c9JwzuKYLFaDZw5gma3fI9bkPEIyofvVfsPqjCWPglSHdpdwXpKGvDeYDms3Qal8qGMEHZ1M/4Udg==", "cpu": [ "x64" ], @@ -1918,9 +1906,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", - "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.58.0.tgz", + "integrity": "sha512-x97kCoBh5MOevpn/CNK9W1x8BEzO238541BGWBc315uOlN0AD/ifZ1msg+ZQB05Ux+VF6EcYqpiagfLJ8U3LvQ==", "cpu": [ "arm64" ], @@ -1930,9 +1918,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", - "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.58.0.tgz", + "integrity": "sha512-Aa8jPoZ6IQAG2eIrcXPpjRcMjROMFxCt1UYPZZtCxRV68WkuSigYtQ/7Zwrcr2IvtNJo7T2JfDXyMLxq5L4Jlg==", "cpu": [ "x64" ], @@ -1942,9 +1930,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", - "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.58.0.tgz", + "integrity": "sha512-Ob8YgT5kD/lSIYW2Rcngs5kNB/44Q2RzBSPz9brf2WEtcGR7/f/E9HeHn1wYaAwKBni+bdXEwgHvUd0x12lQSA==", "cpu": [ "arm" ], @@ -1954,9 +1942,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", - "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.58.0.tgz", + "integrity": "sha512-K+RI5oP1ceqoadvNt1FecL17Qtw/n9BgRSzxif3rTL2QlIu88ccvY+Y9nnHe/cmT5zbH9+bpiJuG1mGHRVwF4Q==", "cpu": [ "arm" ], @@ -1966,9 +1954,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", - "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.58.0.tgz", + "integrity": "sha512-T+17JAsCKUjmbopcKepJjHWHXSjeW7O5PL7lEFaeQmiVyw4kkc5/lyYKzrv6ElWRX/MrEWfPiJWqbTvfIvjM1Q==", "cpu": [ "arm64" ], @@ -1978,9 +1966,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", - "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.58.0.tgz", + "integrity": "sha512-cCePktb9+6R9itIJdeCFF9txPU7pQeEHB5AbHu/MKsfH/k70ZtOeq1k4YAtBv9Z7mmKI5/wOLYjQ+B9QdxR6LA==", "cpu": [ "arm64" ], @@ -1990,9 +1978,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", - "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.58.0.tgz", + "integrity": "sha512-iekUaLkfliAsDl4/xSdoCJ1gnnIXvoNz85C8U8+ZxknM5pBStfZjeXgB8lXobDQvvPRCN8FPmmuTtH+z95HTmg==", "cpu": [ "loong64" ], @@ -2002,9 +1990,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", - "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.58.0.tgz", + "integrity": "sha512-68ofRgJNl/jYJbxFjCKE7IwhbfxOl1muPN4KbIqAIe32lm22KmU7E8OPvyy68HTNkI2iV/c8y2kSPSm2mW/Q9Q==", "cpu": [ "loong64" ], @@ -2014,9 +2002,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", - "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.58.0.tgz", + "integrity": "sha512-dpz8vT0i+JqUKuSNPCP5SYyIV2Lh0sNL1+FhM7eLC457d5B9/BC3kDPp5BBftMmTNsBarcPcoz5UGSsnCiw4XQ==", "cpu": [ "ppc64" ], @@ -2026,9 +2014,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", - "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.58.0.tgz", + "integrity": "sha512-4gdkkf9UJ7tafnweBCR/mk4jf3Jfl0cKX9Np80t5i78kjIH0ZdezUv/JDI2VtruE5lunfACqftJ8dIMGN4oHew==", "cpu": [ "ppc64" ], @@ -2038,9 +2026,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", - "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.58.0.tgz", + "integrity": "sha512-YFS4vPnOkDTD/JriUeeZurFYoJhPf9GQQEF/v4lltp3mVcBmnsAdjEWhr2cjUCZzZNzxCG0HZOvJU44UGHSdzw==", "cpu": [ "riscv64" ], @@ -2050,9 +2038,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", - "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.58.0.tgz", + "integrity": "sha512-x2xgZlFne+QVNKV8b4wwaCS8pwq3y14zedZ5DqLzjdRITvreBk//4Knbcvm7+lWmms9V9qFp60MtUd0/t/PXPw==", "cpu": [ "riscv64" ], @@ -2062,9 +2050,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", - "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.58.0.tgz", + "integrity": "sha512-jIhrujyn4UnWF8S+DHSkAkDEO3hLX0cjzxJZPLF80xFyzyUIYgSMRcYQ3+uqEoyDD2beGq7Dj7edi8OnJcS/hg==", "cpu": [ "s390x" ], @@ -2074,9 +2062,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", - "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.58.0.tgz", + "integrity": "sha512-+410Srdoh78MKSJxTQ+hZ/Mx+ajd6RjjPwBPNd0R3J9FtL6ZA0GqiiyNjCO9In0IzZkCNrpGymSfn+kgyPQocg==", "cpu": [ "x64" ], @@ -2086,9 +2074,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", - "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.58.0.tgz", + "integrity": "sha512-ZjMyby5SICi227y1MTR3VYBpFTdZs823Rs/hpakufleBoufoOIB6jtm9FEoxn/cgO7l6PM2rCEl5Kre5vX0QrQ==", "cpu": [ "x64" ], @@ -2098,9 +2086,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", - "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.58.0.tgz", + "integrity": "sha512-ds4iwfYkSQ0k1nb8LTcyXw//ToHOnNTJtceySpL3fa7tc/AsE+UpUFphW126A6fKBGJD5dhRvg8zw1rvoGFxmw==", "cpu": [ "x64" ], @@ -2110,9 +2098,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", - "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.58.0.tgz", + "integrity": "sha512-fd/zpJniln4ICdPkjWFhZYeY/bpnaN9pGa6ko+5WD38I0tTqk9lXMgXZg09MNdhpARngmxiCg0B0XUamNw/5BQ==", "cpu": [ "arm64" ], @@ -2122,9 +2110,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", - "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.58.0.tgz", + "integrity": "sha512-YpG8dUOip7DCz3nr/JUfPbIUo+2d/dy++5bFzgi4ugOGBIox+qMbbqt/JoORwvI/C9Kn2tz6+Bieoqd5+B1CjA==", "cpu": [ "arm64" ], @@ -2134,9 +2122,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", - "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.58.0.tgz", + "integrity": "sha512-b9DI8jpFQVh4hIXFr0/+N/TzLdpBIoPzjt0Rt4xJbW3mzguV3mduR9cNgiuFcuL/TeORejJhCWiAXe3E/6PxWA==", "cpu": [ "ia32" ], @@ -2146,9 +2134,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", - "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.58.0.tgz", + "integrity": "sha512-CSrVpmoRJFN06LL9xhkitkwUcTZtIotYAF5p6XOR2zW0Zz5mzb3IPpcoPhB02frzMHFNo1reQ9xSF5fFm3hUsQ==", "cpu": [ "x64" ], @@ -2158,9 +2146,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", - "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.58.0.tgz", + "integrity": "sha512-QFsBgQNTnh5K0t/sBsjJLq24YVqEIVkGpfN2VHsnN90soZyhaiA9UUHufcctVNL4ypJY0wrwad0wslx2KJQ1/w==", "cpu": [ "x64" ], @@ -2170,55 +2158,55 @@ ] }, "node_modules/@shikijs/core": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.21.0.tgz", - "integrity": "sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.22.0.tgz", + "integrity": "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==", "dependencies": { - "@shikijs/types": "3.21.0", + "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "node_modules/@shikijs/engine-javascript": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.21.0.tgz", - "integrity": "sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.22.0.tgz", + "integrity": "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==", "dependencies": { - "@shikijs/types": "3.21.0", + "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "node_modules/@shikijs/engine-oniguruma": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.21.0.tgz", - "integrity": "sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.22.0.tgz", + "integrity": "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==", "dependencies": { - "@shikijs/types": "3.21.0", + "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "node_modules/@shikijs/langs": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.21.0.tgz", - "integrity": "sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.22.0.tgz", + "integrity": "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==", "dependencies": { - "@shikijs/types": "3.21.0" + "@shikijs/types": "3.22.0" } }, "node_modules/@shikijs/themes": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.21.0.tgz", - "integrity": "sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.22.0.tgz", + "integrity": "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==", "dependencies": { - "@shikijs/types": "3.21.0" + "@shikijs/types": "3.22.0" } }, "node_modules/@shikijs/types": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.21.0.tgz", - "integrity": "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.22.0.tgz", + "integrity": "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" @@ -2331,17 +2319,17 @@ } }, "node_modules/@types/node": { - "version": "25.0.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.8.tgz", - "integrity": "sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==", + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", + "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/react": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz", - "integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "peer": true, "dependencies": { "csstype": "^3.2.2" @@ -2356,6 +2344,11 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, "node_modules/@types/sanitize-html": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.16.0.tgz", @@ -2409,13 +2402,13 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.17.tgz", - "integrity": "sha512-/6zU2FLGg0jsd+ePZcwHRy3+WpNTBBhDY56P4JTRqUN/Dp6CvOEa9HrikcQ4KfV2b2kAHUFB4dl1SuocWXSFEw==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz", + "integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.0.17", + "@vitest/utils": "4.0.18", "ast-v8-to-istanbul": "^0.3.10", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -2429,8 +2422,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.0.17", - "vitest": "4.0.17" + "@vitest/browser": "4.0.18", + "vitest": "4.0.18" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2439,15 +2432,15 @@ } }, "node_modules/@vitest/expect": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.17.tgz", - "integrity": "sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", "dev": true, "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.17", - "@vitest/utils": "4.0.17", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" }, @@ -2456,12 +2449,12 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.17.tgz", - "integrity": "sha512-+ZtQhLA3lDh1tI2wxe3yMsGzbp7uuJSWBM1iTIKCbppWTSBN09PUC+L+fyNlQApQoR+Ps8twt2pbSSXg2fQVEQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", "dev": true, "dependencies": { - "@vitest/spy": "4.0.17", + "@vitest/spy": "4.0.18", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -2482,9 +2475,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.17.tgz", - "integrity": "sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", "dev": true, "dependencies": { "tinyrainbow": "^3.0.3" @@ -2494,12 +2487,12 @@ } }, "node_modules/@vitest/runner": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.17.tgz", - "integrity": "sha512-JmuQyf8aMWoo/LmNFppdpkfRVHJcsgzkbCA+/Bk7VfNH7RE6Ut2qxegeyx2j3ojtJtKIbIGy3h+KxGfYfk28YQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", "dev": true, "dependencies": { - "@vitest/utils": "4.0.17", + "@vitest/utils": "4.0.18", "pathe": "^2.0.3" }, "funding": { @@ -2507,12 +2500,12 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.17.tgz", - "integrity": "sha512-npPelD7oyL+YQM2gbIYvlavlMVWUfNNGZPcu0aEUQXt7FXTuqhmgiYupPnAanhKvyP6Srs2pIbWo30K0RbDtRQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", "dev": true, "dependencies": { - "@vitest/pretty-format": "4.0.17", + "@vitest/pretty-format": "4.0.18", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -2521,21 +2514,21 @@ } }, "node_modules/@vitest/spy": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.17.tgz", - "integrity": "sha512-I1bQo8QaP6tZlTomQNWKJE6ym4SHf3oLS7ceNjozxxgzavRAgZDc06T7kD8gb9bXKEgcLNt00Z+kZO6KaJ62Ew==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", "dev": true, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.17.tgz", - "integrity": "sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", "dev": true, "dependencies": { - "@vitest/pretty-format": "4.0.17", + "@vitest/pretty-format": "4.0.18", "tinyrainbow": "^3.0.3" }, "funding": { @@ -2543,13 +2536,13 @@ } }, "node_modules/@volar/kit": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/kit/-/kit-2.4.27.tgz", - "integrity": "sha512-ilZoQDMLzqmSsImJRWx4YiZ4FcvvPrPnFVmL6hSsIWB6Bn3qc7k88J9yP32dagrs5Y8EXIlvvD/mAFaiuEOACQ==", + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/kit/-/kit-2.4.28.tgz", + "integrity": "sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg==", "dev": true, "dependencies": { - "@volar/language-service": "2.4.27", - "@volar/typescript": "2.4.27", + "@volar/language-service": "2.4.28", + "@volar/typescript": "2.4.28", "typesafe-path": "^0.2.2", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" @@ -2559,23 +2552,23 @@ } }, "node_modules/@volar/language-core": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.27.tgz", - "integrity": "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==", + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", "dev": true, "dependencies": { - "@volar/source-map": "2.4.27" + "@volar/source-map": "2.4.28" } }, "node_modules/@volar/language-server": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.27.tgz", - "integrity": "sha512-SymGNkErcHg8GjiG65iQN8sLkhqu1pwKhFySmxeBuYq5xFYagKBW36eiNITXQTdvT0tutI1GXcXdq/FdE/IyjA==", + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.28.tgz", + "integrity": "sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw==", "dev": true, "dependencies": { - "@volar/language-core": "2.4.27", - "@volar/language-service": "2.4.27", - "@volar/typescript": "2.4.27", + "@volar/language-core": "2.4.28", + "@volar/language-service": "2.4.28", + "@volar/typescript": "2.4.28", "path-browserify": "^1.0.1", "request-light": "^0.7.0", "vscode-languageserver": "^9.0.1", @@ -2585,30 +2578,30 @@ } }, "node_modules/@volar/language-service": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.27.tgz", - "integrity": "sha512-SxKZ8yLhpWa7Y5e/RDxtNfm7j7xsXp/uf2urijXEffRNpPSmVdfzQrFFy5d7l8PNpZy+bHg+yakmqBPjQN+MOw==", + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.28.tgz", + "integrity": "sha512-Rh/wYCZJrI5vCwMk9xyw/Z+MsWxlJY1rmMZPsxUoJKfzIRjS/NF1NmnuEcrMbEVGja00aVpCsInJfixQTMdvLw==", "dev": true, "dependencies": { - "@volar/language-core": "2.4.27", + "@volar/language-core": "2.4.28", "vscode-languageserver-protocol": "^3.17.5", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" } }, "node_modules/@volar/source-map": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.27.tgz", - "integrity": "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==", + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", "dev": true }, "node_modules/@volar/typescript": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.27.tgz", - "integrity": "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==", + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", "dev": true, "dependencies": { - "@volar/language-core": "2.4.27", + "@volar/language-core": "2.4.28", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } @@ -2633,9 +2626,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "bin": { "acorn": "bin/acorn" }, @@ -2652,9 +2645,9 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.3", @@ -2803,26 +2796,26 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", - "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz", + "integrity": "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", - "js-tokens": "^9.0.1" + "js-tokens": "^10.0.0" } }, "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", "dev": true }, "node_modules/astro": { - "version": "5.16.9", - "resolved": "https://registry.npmjs.org/astro/-/astro-5.16.9.tgz", - "integrity": "sha512-gJvoZv0v8xCcKBcsxz1ZfXqoJ7sJJcyoKP8bUTjkuD4vDShLe0N26em4LQxitVv/2HLOpldQg67bEHB/qGoxJA==", + "version": "5.17.3", + "resolved": "https://registry.npmjs.org/astro/-/astro-5.17.3.tgz", + "integrity": "sha512-69dcfPe8LsHzklwj+hl+vunWUbpMB6pmg35mACjetxbJeUNNys90JaBM8ZiwsPK689SAj/4Zqb1ayaANls9/MA==", "dependencies": { "@astrojs/compiler": "^2.13.0", "@astrojs/internal-helpers": "0.7.5", @@ -2842,12 +2835,12 @@ "cssesc": "^3.0.0", "debug": "^4.4.3", "deterministic-object-hash": "^2.0.2", - "devalue": "^5.6.1", - "diff": "^5.2.0", + "devalue": "^5.6.2", + "diff": "^8.0.3", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.7.0", - "esbuild": "^0.25.0", + "esbuild": "^0.27.3", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.4.0", @@ -2868,16 +2861,16 @@ "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.3", - "shiki": "^3.20.0", + "shiki": "^3.21.0", "smol-toml": "^1.6.0", "svgo": "^4.0.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", - "unifont": "~0.7.1", + "unifont": "~0.7.3", "unist-util-visit": "^5.0.0", - "unstorage": "^1.17.3", + "unstorage": "^1.17.4", "vfile": "^6.0.3", "vite": "^6.4.1", "vitefu": "^1.1.1", @@ -2905,9 +2898,9 @@ } }, "node_modules/astro/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "bin": { "semver": "bin/semver.js" }, @@ -2971,11 +2964,14 @@ ] }, "node_modules/baseline-browser-mapping": { - "version": "2.9.14", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", - "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/bignumber.js": { @@ -3020,18 +3016,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", @@ -3081,9 +3065,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001764", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", - "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", + "version": "1.0.30001770", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", + "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", "funding": [ { "type": "opencollective", @@ -3171,9 +3155,9 @@ } }, "node_modules/ci-info": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", - "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", "funding": [ { "type": "github", @@ -3481,9 +3465,9 @@ } }, "node_modules/decode-named-character-reference": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", - "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", "dependencies": { "character-entities": "^2.0.0" }, @@ -3533,9 +3517,9 @@ "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==" }, "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", "engines": { "node": ">=8" } @@ -3552,9 +3536,9 @@ } }, "node_modules/devalue": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.1.tgz", - "integrity": "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==" + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.3.tgz", + "integrity": "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==" }, "node_modules/devlop": { "version": "1.1.0", @@ -3569,9 +3553,9 @@ } }, "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", "engines": { "node": ">=0.3.1" } @@ -3594,17 +3578,6 @@ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", @@ -3790,9 +3763,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.5.267", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", - "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==" + "version": "1.5.302", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", + "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==" }, "node_modules/emmet": { "version": "2.4.11", @@ -3824,9 +3797,9 @@ } }, "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "engines": { "node": ">=0.12" }, @@ -3840,9 +3813,9 @@ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==" }, "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -3851,32 +3824,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" } }, "node_modules/escalade": { @@ -3932,9 +3905,9 @@ } }, "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==" }, "node_modules/expect-type": { "version": "1.3.0", @@ -3967,22 +3940,6 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, "node_modules/fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -3999,15 +3956,6 @@ } ] }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -4046,18 +3994,6 @@ "node": "^12.20 || >= 14.13" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/flattie": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", @@ -4067,22 +4003,22 @@ } }, "node_modules/fontace": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.0.tgz", - "integrity": "sha512-moThBCItUe2bjZip5PF/iZClpKHGLwMvR79Kp8XpGRBrvoRSnySN4VcILdv3/MJzbhvUA5WeiUXF5o538m5fvg==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", + "integrity": "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==", "dependencies": { - "fontkitten": "^1.0.0" + "fontkitten": "^1.0.2" } }, "node_modules/fontkitten": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.1.tgz", - "integrity": "sha512-m+/cO+/kAU9farlejecXLgQH20+UXyH0K6oosGtogAz7BWco+KTYE60epKwMt8eVxqlOE2Fs+GoHVlGDUbKOoA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.2.tgz", + "integrity": "sha512-piJxbLnkD9Xcyi7dWJRnqszEURixe7CrF/efBfbffe2DPyabmuIuqraruY8cXTs19QoM8VJzx47BDRVNXETM7Q==", "dependencies": { "tiny-inflate": "^1.0.3" }, "engines": { - "node": ">=24.12.0" + "node": ">=20" } }, "node_modules/foreground-child": { @@ -4120,9 +4056,9 @@ } }, "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "hasInstallScript": true, "optional": true, "os": [ @@ -4177,9 +4113,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", "engines": { "node": ">=18" }, @@ -4196,6 +4132,7 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -4211,18 +4148,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/google-auth-library": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", @@ -4518,17 +4443,6 @@ "entities": "^4.4.0" } }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", @@ -4565,6 +4479,21 @@ "node": ">= 14" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/import-meta-resolve": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", @@ -4609,15 +4538,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -4626,18 +4546,6 @@ "node": ">=8" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -4655,15 +4563,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -4684,9 +4583,9 @@ } }, "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "dependencies": { "is-inside-container": "^1.0.0" }, @@ -4900,14 +4799,6 @@ "@libsql/win32-x64-msvc": "0.5.22" } }, - "node_modules/libsql/node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", - "engines": { - "node": ">=8" - } - }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -4945,12 +4836,12 @@ } }, "node_modules/magicast": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", - "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, @@ -4970,9 +4861,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -4991,9 +4882,9 @@ } }, "node_modules/marked": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", - "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.3.tgz", + "integrity": "sha512-jt1v2ObpyOKR8p4XaUJVk3YWRJ5n+i4+rjQopxvV32rSndTJXvIzuUdWWIy/1pFQMkQmvTXawzDNqOH/CUmx6A==", "bin": { "marked": "bin/marked.js" }, @@ -5031,9 +4922,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", @@ -5218,15 +5109,6 @@ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", @@ -5762,31 +5644,6 @@ } ] }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -5825,9 +5682,9 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "engines": { "node": ">=16 || 14 >=14.17" } @@ -5852,9 +5709,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", "funding": [ { "type": "github", @@ -5862,12 +5719,16 @@ } ], "bin": { - "nanoid": "bin/nanoid.cjs" + "nanoid": "bin/nanoid.js" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": "^18 || >=20" } }, + "node_modules/nca-ai-cms-astro-plugin": { + "resolved": "../nca-ai-cms-astro-plugin", + "link": true + }, "node_modules/neotraverse": { "version": "0.6.18", "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", @@ -6038,6 +5899,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-timeout": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", @@ -6092,6 +5965,17 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -6183,20 +6067,6 @@ "node": ">=18" } }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -6224,10 +6094,27 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/prettier": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.0.tgz", - "integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -6318,26 +6205,6 @@ "node": ">=12.0.0" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/radix3": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", @@ -6352,22 +6219,22 @@ } }, "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.3" + "react": "^19.2.4" } }, "node_modules/react-refresh": { @@ -6626,14 +6493,12 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": ">= 4" } }, "node_modules/rimraf": { @@ -6651,9 +6516,9 @@ } }, "node_modules/rollup": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", - "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.58.0.tgz", + "integrity": "sha512-wbT0mBmWbIvvq8NeEYWWvevvxnOyhKChir47S66WCxw1SXqhw7ssIYejnQEVt7XYQpsj2y8F9PM+Cr3SNEa0gw==", "dependencies": { "@types/estree": "1.0.8" }, @@ -6665,57 +6530,34 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.55.1", - "@rollup/rollup-android-arm64": "4.55.1", - "@rollup/rollup-darwin-arm64": "4.55.1", - "@rollup/rollup-darwin-x64": "4.55.1", - "@rollup/rollup-freebsd-arm64": "4.55.1", - "@rollup/rollup-freebsd-x64": "4.55.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", - "@rollup/rollup-linux-arm-musleabihf": "4.55.1", - "@rollup/rollup-linux-arm64-gnu": "4.55.1", - "@rollup/rollup-linux-arm64-musl": "4.55.1", - "@rollup/rollup-linux-loong64-gnu": "4.55.1", - "@rollup/rollup-linux-loong64-musl": "4.55.1", - "@rollup/rollup-linux-ppc64-gnu": "4.55.1", - "@rollup/rollup-linux-ppc64-musl": "4.55.1", - "@rollup/rollup-linux-riscv64-gnu": "4.55.1", - "@rollup/rollup-linux-riscv64-musl": "4.55.1", - "@rollup/rollup-linux-s390x-gnu": "4.55.1", - "@rollup/rollup-linux-x64-gnu": "4.55.1", - "@rollup/rollup-linux-x64-musl": "4.55.1", - "@rollup/rollup-openbsd-x64": "4.55.1", - "@rollup/rollup-openharmony-arm64": "4.55.1", - "@rollup/rollup-win32-arm64-msvc": "4.55.1", - "@rollup/rollup-win32-ia32-msvc": "4.55.1", - "@rollup/rollup-win32-x64-gnu": "4.55.1", - "@rollup/rollup-win32-x64-msvc": "4.55.1", + "@rollup/rollup-android-arm-eabi": "4.58.0", + "@rollup/rollup-android-arm64": "4.58.0", + "@rollup/rollup-darwin-arm64": "4.58.0", + "@rollup/rollup-darwin-x64": "4.58.0", + "@rollup/rollup-freebsd-arm64": "4.58.0", + "@rollup/rollup-freebsd-x64": "4.58.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.58.0", + "@rollup/rollup-linux-arm-musleabihf": "4.58.0", + "@rollup/rollup-linux-arm64-gnu": "4.58.0", + "@rollup/rollup-linux-arm64-musl": "4.58.0", + "@rollup/rollup-linux-loong64-gnu": "4.58.0", + "@rollup/rollup-linux-loong64-musl": "4.58.0", + "@rollup/rollup-linux-ppc64-gnu": "4.58.0", + "@rollup/rollup-linux-ppc64-musl": "4.58.0", + "@rollup/rollup-linux-riscv64-gnu": "4.58.0", + "@rollup/rollup-linux-riscv64-musl": "4.58.0", + "@rollup/rollup-linux-s390x-gnu": "4.58.0", + "@rollup/rollup-linux-x64-gnu": "4.58.0", + "@rollup/rollup-linux-x64-musl": "4.58.0", + "@rollup/rollup-openbsd-x64": "4.58.0", + "@rollup/rollup-openharmony-arm64": "4.58.0", + "@rollup/rollup-win32-arm64-msvc": "4.58.0", + "@rollup/rollup-win32-ia32-msvc": "4.58.0", + "@rollup/rollup-win32-x64-gnu": "4.58.0", + "@rollup/rollup-win32-x64-msvc": "4.58.0", "fsevents": "~2.3.2" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/s.color": { "version": "0.0.15", "resolved": "https://registry.npmjs.org/s.color/-/s.color-0.0.15.tgz", @@ -6742,9 +6584,9 @@ ] }, "node_modules/sanitize-html": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.0.tgz", - "integrity": "sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==", + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.1.tgz", + "integrity": "sha512-ehFCW+q1a4CSOWRAdX97BX/6/PDEkCqw7/0JXZAGQV57FQB3YOkTa/rrzHPeJ+Aghy4vZAFfWMYyfxIiB7F/gw==", "dependencies": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", @@ -6885,10 +6727,18 @@ "@img/sharp-win32-x64": "0.34.5" } }, + "node_modules/sharp/node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/sharp/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "bin": { "semver": "bin/semver.js" }, @@ -6916,16 +6766,16 @@ } }, "node_modules/shiki": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.21.0.tgz", - "integrity": "sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w==", - "dependencies": { - "@shikijs/core": "3.21.0", - "@shikijs/engine-javascript": "3.21.0", - "@shikijs/engine-oniguruma": "3.21.0", - "@shikijs/langs": "3.21.0", - "@shikijs/themes": "3.21.0", - "@shikijs/types": "3.21.0", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.22.0.tgz", + "integrity": "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==", + "dependencies": { + "@shikijs/core": "3.22.0", + "@shikijs/engine-javascript": "3.22.0", + "@shikijs/engine-oniguruma": "3.22.0", + "@shikijs/langs": "3.22.0", + "@shikijs/themes": "3.22.0", + "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } @@ -7202,18 +7052,6 @@ "node": ">=14.0.0" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -7317,9 +7155,9 @@ } }, "node_modules/typescript-auto-import-cache/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -7344,9 +7182,9 @@ "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==" }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==" + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==" }, "node_modules/unified": { "version": "11.0.5", @@ -7367,9 +7205,9 @@ } }, "node_modules/unifont": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.3.tgz", - "integrity": "sha512-b0GtQzKCyuSHGsfj5vyN8st7muZ6VCI4XD4vFlr7Uy1rlWVYxC3npnfk8MyreHxJYrz1ooLDqDzFe9XqQTlAhA==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.4.tgz", + "integrity": "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==", "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", @@ -7452,9 +7290,9 @@ } }, "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", @@ -7600,9 +7438,9 @@ } }, "node_modules/unstorage/node_modules/lru-cache": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", - "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "engines": { "node": "20 || >=22" } @@ -7760,6 +7598,449 @@ } } }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/vitefu": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", @@ -7779,18 +8060,18 @@ } }, "node_modules/vitest": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.17.tgz", - "integrity": "sha512-FQMeF0DJdWY0iOnbv466n/0BudNdKj1l5jYgl5JVTwjSsZSlqyXFt/9+1sEyhR6CLowbZpV7O1sCHrzBhucKKg==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "dependencies": { - "@vitest/expect": "4.0.17", - "@vitest/mocker": "4.0.17", - "@vitest/pretty-format": "4.0.17", - "@vitest/runner": "4.0.17", - "@vitest/snapshot": "4.0.17", - "@vitest/spy": "4.0.17", - "@vitest/utils": "4.0.17", + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", @@ -7818,10 +8099,10 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.17", - "@vitest/browser-preview": "4.0.17", - "@vitest/browser-webdriverio": "4.0.17", - "@vitest/ui": "4.0.17", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", "happy-dom": "*", "jsdom": "*" }, @@ -7856,9 +8137,9 @@ } }, "node_modules/volar-service-css": { - "version": "0.0.67", - "resolved": "https://registry.npmjs.org/volar-service-css/-/volar-service-css-0.0.67.tgz", - "integrity": "sha512-zV7C6enn9T9tuvQ6iSUyYEs34iPXR69Pf9YYWpbFYPWzVs22w96BtE8p04XYXbmjU6unt5oFt+iLL77bMB5fhA==", + "version": "0.0.68", + "resolved": "https://registry.npmjs.org/volar-service-css/-/volar-service-css-0.0.68.tgz", + "integrity": "sha512-lJSMh6f3QzZ1tdLOZOzovLX0xzAadPhx8EKwraDLPxBndLCYfoTvnNuiFFV8FARrpAlW5C0WkH+TstPaCxr00Q==", "dev": true, "dependencies": { "vscode-css-languageservice": "^6.3.0", @@ -7875,9 +8156,9 @@ } }, "node_modules/volar-service-emmet": { - "version": "0.0.67", - "resolved": "https://registry.npmjs.org/volar-service-emmet/-/volar-service-emmet-0.0.67.tgz", - "integrity": "sha512-UDBL5x7KptmuJZNCCXMlCndMhFult/tj+9jXq3FH1ZGS1E4M/1U5hC06pg1c6e4kn+vnR6bqmvX0vIhL4f98+A==", + "version": "0.0.68", + "resolved": "https://registry.npmjs.org/volar-service-emmet/-/volar-service-emmet-0.0.68.tgz", + "integrity": "sha512-nHvixrRQ83EzkQ4G/jFxu9Y4eSsXS/X2cltEPDM+K9qZmIv+Ey1w0tg1+6caSe8TU5Hgw4oSTwNMf/6cQb3LzQ==", "dev": true, "dependencies": { "@emmetio/css-parser": "^0.4.1", @@ -7895,9 +8176,9 @@ } }, "node_modules/volar-service-html": { - "version": "0.0.67", - "resolved": "https://registry.npmjs.org/volar-service-html/-/volar-service-html-0.0.67.tgz", - "integrity": "sha512-ljREMF79JbcjNvObiv69HK2HCl5UT7WTD10zi6CRFUHMbPfiF2UZ42HGLsEGSzaHGZz6H4IFjSS/qfENRLUviQ==", + "version": "0.0.68", + "resolved": "https://registry.npmjs.org/volar-service-html/-/volar-service-html-0.0.68.tgz", + "integrity": "sha512-fru9gsLJxy33xAltXOh4TEdi312HP80hpuKhpYQD4O5hDnkNPEBdcQkpB+gcX0oK0VxRv1UOzcGQEUzWCVHLfA==", "dev": true, "dependencies": { "vscode-html-languageservice": "^5.3.0", @@ -7914,9 +8195,9 @@ } }, "node_modules/volar-service-prettier": { - "version": "0.0.67", - "resolved": "https://registry.npmjs.org/volar-service-prettier/-/volar-service-prettier-0.0.67.tgz", - "integrity": "sha512-B4KnPJPNWFTkEDa6Fn08i5PpO6T1CecmLLTFZoXz2eI4Fxwba/3nDaaVSsEP7e/vEe+U5YqV9fBzayJT71G5xg==", + "version": "0.0.68", + "resolved": "https://registry.npmjs.org/volar-service-prettier/-/volar-service-prettier-0.0.68.tgz", + "integrity": "sha512-grUmWHkHlebMOd6V8vXs2eNQUw/bJGJMjekh/EPf/p2ZNTK0Uyz7hoBRngcvGfJHMsSXZH8w/dZTForIW/4ihw==", "dev": true, "dependencies": { "vscode-uri": "^3.0.8" @@ -7935,9 +8216,9 @@ } }, "node_modules/volar-service-typescript": { - "version": "0.0.67", - "resolved": "https://registry.npmjs.org/volar-service-typescript/-/volar-service-typescript-0.0.67.tgz", - "integrity": "sha512-rfQBy36Rm1PU9vLWHk8BYJ4r2j/CI024vocJcH4Nb6K2RTc2Irmw6UOVY5DdGiPRV5r+e10wLMK5njj/EcL8sA==", + "version": "0.0.68", + "resolved": "https://registry.npmjs.org/volar-service-typescript/-/volar-service-typescript-0.0.68.tgz", + "integrity": "sha512-z7B/7CnJ0+TWWFp/gh2r5/QwMObHNDiQiv4C9pTBNI2Wxuwymd4bjEORzrJ/hJ5Yd5+OzeYK+nFCKevoGEEeKw==", "dev": true, "dependencies": { "path-browserify": "^1.0.1", @@ -7957,9 +8238,9 @@ } }, "node_modules/volar-service-typescript-twoslash-queries": { - "version": "0.0.67", - "resolved": "https://registry.npmjs.org/volar-service-typescript-twoslash-queries/-/volar-service-typescript-twoslash-queries-0.0.67.tgz", - "integrity": "sha512-LD2R7WivDYp1SPgZrxx/0222xVTitDjm36oKo5+bfYG5kEgnw+BOPVHdwmvpJKg/RfssfxDI1ouwD4XkEDEfbA==", + "version": "0.0.68", + "resolved": "https://registry.npmjs.org/volar-service-typescript-twoslash-queries/-/volar-service-typescript-twoslash-queries-0.0.68.tgz", + "integrity": "sha512-NugzXcM0iwuZFLCJg47vI93su5YhTIweQuLmZxvz5ZPTaman16JCvmDZexx2rd5T/75SNuvvZmrTOTNYUsfe5w==", "dev": true, "dependencies": { "vscode-uri": "^3.0.8" @@ -7974,9 +8255,9 @@ } }, "node_modules/volar-service-typescript/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -7986,9 +8267,9 @@ } }, "node_modules/volar-service-yaml": { - "version": "0.0.67", - "resolved": "https://registry.npmjs.org/volar-service-yaml/-/volar-service-yaml-0.0.67.tgz", - "integrity": "sha512-jkdP/RF6wPIXEE3Ktnd81oJPn7aAvnVSiaqQHThC2Hrvo6xd9pEcqtbBUI+YfqVgvcMtXAkbtNO61K2GPhAiuA==", + "version": "0.0.68", + "resolved": "https://registry.npmjs.org/volar-service-yaml/-/volar-service-yaml-0.0.68.tgz", + "integrity": "sha512-84XgE02LV0OvTcwfqhcSwVg4of3MLNUWPMArO6Aj8YXqyEVnPu8xTEMY2btKSq37mVAPuaEVASI4e3ptObmqcA==", "dev": true, "dependencies": { "vscode-uri": "^3.0.8", diff --git a/package.json b/package.json index 1fdcc26..142b935 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,11 @@ "format:check": "prettier --check .", "typecheck": "astro check && tsc --noEmit", "test:a11y": "playwright test --project=chromium", - "test:a11y:report": "playwright test --project=chromium --reporter=html" + "test:a11y:report": "playwright test --project=chromium --reporter=html", + "plugin:link": "npm install --save nca-ai-cms-astro-plugin@file:../nca-ai-cms-astro-plugin", + "plugin:unlink": "npm install --save nca-ai-cms-astro-plugin@^0.1.0", + "plugin:lint": "node -e \"const p=require('./package.json'); const v=p.dependencies['nca-ai-cms-astro-plugin']||''; if(v.includes('file:')){console.error('ERROR: package.json contains file: reference for nca-ai-cms-astro-plugin. Run npm run plugin:unlink first.');process.exit(1)}else{console.log('OK: plugin dependency is registry version:',v)}\"", + "prepare": "husky" }, "dependencies": { "@anthropic-ai/sdk": "^0.71.2", @@ -27,6 +31,7 @@ "astro": "^5.16.4", "gray-matter": "^4.0.3", "marked": "^17.0.1", + "nca-ai-cms-astro-plugin": "^0.1.0", "react": "^19.2.1", "react-dom": "^19.2.1", "sanitize-html": "^2.17.0", @@ -41,6 +46,7 @@ "@types/sanitize-html": "^2.16.0", "@types/turndown": "^5.0.6", "@vitest/coverage-v8": "^4.0.15", + "husky": "^9.1.7", "prettier": "^3.7.4", "prettier-plugin-astro": "^0.14.1", "typescript": "^5.9.3", From a117830e86e48bc951f86fd6682c4320b3b51d35 Mon Sep 17 00:00:00 2001 From: Roland Golla Date: Sat, 21 Feb 2026 22:49:20 +0100 Subject: [PATCH 04/10] add pre-commit hook to auto-fix local plugin reference --- .husky/pre-commit | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100755 .husky/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..34c0df8 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,12 @@ +#!/bin/sh + +# Auto-fix local plugin reference before commit +# Replaces file:../nca-ai-cms-astro-plugin with ^0.1.0 in package.json +if grep -q '"file:../nca-ai-cms-astro-plugin"' package.json; then + echo "Fixing local plugin reference in package.json..." + sed -i 's|"file:../nca-ai-cms-astro-plugin"|"^0.1.0"|g' package.json + git add package.json +fi + +npm run plugin:lint +npm test From 9bdc4b7f8fc7e08d78eb9d0b6a912944123163c6 Mon Sep 17 00:00:00 2001 From: Roland Golla Date: Sat, 21 Feb 2026 23:36:36 +0100 Subject: [PATCH 05/10] integrate nca-ai-cms-astro-plugin and remove duplicate routes/services Register plugin in astro.config.mjs, remove 15 API routes and editor page now provided via injectRoute, remove 11 service files now in plugin, add CI fallback for old article path migration. Co-Authored-By: Claude Opus 4.6 --- .gitlab-ci.yml | 4 +- astro.config.mjs | 12 +- db/config.ts | 44 +-- package-lock.json | 2 +- src/pages/api/articles/[id].ts | 52 --- src/pages/api/articles/[id]/apply.ts | 88 ----- .../api/articles/[id]/regenerate-image.ts | 48 --- .../api/articles/[id]/regenerate-text.ts | 56 ---- .../api/articles/articles.integration.test.ts | 278 ---------------- src/pages/api/generate-content.ts | 34 -- src/pages/api/generate-image.ts | 27 -- src/pages/api/prompts.ts | 45 --- src/pages/api/save-image.ts | 36 -- src/pages/api/save.ts | 32 -- src/pages/api/scheduler/[id].ts | 31 -- src/pages/api/scheduler/generate.ts | 94 ------ src/pages/api/scheduler/publish.ts | 93 ------ src/pages/editor.astro | 11 - src/services/AutoPublisher.ts | 91 ------ src/services/ContentFetcher.ts | 89 ----- src/services/ContentGenerator.ts | 307 ------------------ src/services/FileWriter.test.ts | 80 ----- src/services/FileWriter.ts | 59 ---- src/services/ImageConverter.ts | 15 - .../ImageGenerator.integration.test.ts | 117 ------- src/services/ImageGenerator.test.ts | 119 ------- src/services/ImageGenerator.ts | 108 ------ src/services/PromptService.ts | 84 ----- src/services/index.ts | 7 - 29 files changed, 8 insertions(+), 2055 deletions(-) delete mode 100644 src/pages/api/articles/[id].ts delete mode 100644 src/pages/api/articles/[id]/apply.ts delete mode 100644 src/pages/api/articles/[id]/regenerate-image.ts delete mode 100644 src/pages/api/articles/[id]/regenerate-text.ts delete mode 100644 src/pages/api/articles/articles.integration.test.ts delete mode 100644 src/pages/api/generate-content.ts delete mode 100644 src/pages/api/generate-image.ts delete mode 100644 src/pages/api/prompts.ts delete mode 100644 src/pages/api/save-image.ts delete mode 100644 src/pages/api/save.ts delete mode 100644 src/pages/api/scheduler/[id].ts delete mode 100644 src/pages/api/scheduler/generate.ts delete mode 100644 src/pages/api/scheduler/publish.ts delete mode 100644 src/pages/editor.astro delete mode 100644 src/services/AutoPublisher.ts delete mode 100644 src/services/ContentFetcher.ts delete mode 100644 src/services/ContentGenerator.ts delete mode 100644 src/services/FileWriter.test.ts delete mode 100644 src/services/FileWriter.ts delete mode 100644 src/services/ImageConverter.ts delete mode 100644 src/services/ImageGenerator.integration.test.ts delete mode 100644 src/services/ImageGenerator.test.ts delete mode 100644 src/services/ImageGenerator.ts delete mode 100644 src/services/PromptService.ts delete mode 100644 src/services/index.ts diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7f909a9..cc6c7b1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,8 +21,8 @@ build: before_script: - docker login -u $CI_REGISTRY_USER -p $CI_JOB_TOKEN $CI_REGISTRY script: - # Backup content from old container - - docker cp $SHORTCODE1-web:/app/nca-ai-cms-content /tmp/articles 2>/dev/null || true + # Backup content from old container (fallback to old path for migration) + - docker cp $SHORTCODE1-web:/app/nca-ai-cms-content /tmp/articles 2>/dev/null || docker cp $SHORTCODE1-web:/app/src/content/articles /tmp/articles 2>/dev/null || true - docker cp $SHORTCODE1-web:/app/.astro /tmp/astro-db 2>/dev/null || true # Remove old container and start new one - docker compose -f docker-compose.conversis.yml -p $SHORTCODE1 rm -sfv || true diff --git a/astro.config.mjs b/astro.config.mjs index 4a724c3..4bf4d5e 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -4,6 +4,7 @@ import { loadEnv } from 'vite'; import react from '@astrojs/react'; import node from '@astrojs/node'; import db from '@astrojs/db'; +import ncaAiCms from 'nca-ai-cms-astro-plugin'; // Load .env files into process.env for SSR const { @@ -19,19 +20,10 @@ Object.assign(process.env, { GOOGLE_GEMINI_MODELS, }); -// Start auto-publisher in production (standalone Node server) -if (process.env.NODE_ENV === 'production') { - import('./src/services/AutoPublisher.js').then(({ startAutoPublisher }) => { - startAutoPublisher(); - }).catch((err) => { - console.error('[AutoPublisher] Failed to start:', err); - }); -} - // https://astro.build/config export default defineConfig({ site: 'https://semantik-html-barrierefrei.de', - integrations: [react(), db()], + integrations: [react(), ncaAiCms(), db()], output: 'server', adapter: node({ mode: 'standalone', diff --git a/db/config.ts b/db/config.ts index 3b5bcd4..545a9b3 100644 --- a/db/config.ts +++ b/db/config.ts @@ -1,44 +1,6 @@ -import { defineDb, defineTable, column } from 'astro:db'; - -// Site settings (hero, imprint, CTA config) -const SiteSettings = defineTable({ - columns: { - key: column.text({ primaryKey: true }), - value: column.text(), - updatedAt: column.date({ default: new Date() }), - }, -}); - -// Scheduled posts for content planner -const ScheduledPosts = defineTable({ - columns: { - id: column.text({ primaryKey: true }), - input: column.text(), - inputType: column.text(), // 'url' | 'keywords' - scheduledDate: column.date(), - status: column.text({ default: 'pending' }), // 'pending' | 'generated' | 'published' - generatedTitle: column.text({ optional: true }), - generatedDescription: column.text({ optional: true }), - generatedContent: column.text({ optional: true }), - generatedTags: column.text({ optional: true }), // JSON string - generatedImageData: column.text({ optional: true }), // base64 webp - generatedImageAlt: column.text({ optional: true }), - publishedPath: column.text({ optional: true }), - createdAt: column.date({ default: new Date() }), - }, -}); - -// AI Prompts (editable) -const Prompts = defineTable({ - columns: { - id: column.text({ primaryKey: true }), - name: column.text(), - category: column.text(), // 'content' | 'image' | 'analysis' - promptText: column.text(), - updatedAt: column.date({ default: new Date() }), - }, -}); +import { defineDb } from 'astro:db'; +// Tables are provided by nca-ai-cms-astro-plugin via extendDb export default defineDb({ - tables: { SiteSettings, Prompts, ScheduledPosts }, + tables: {}, }); diff --git a/package-lock.json b/package-lock.json index 8621a4f..def3566 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "astro": "^5.16.4", "gray-matter": "^4.0.3", "marked": "^17.0.1", - "nca-ai-cms-astro-plugin": "^0.1.0", + "nca-ai-cms-astro-plugin": "file:../nca-ai-cms-astro-plugin", "react": "^19.2.1", "react-dom": "^19.2.1", "sanitize-html": "^2.17.0", diff --git a/src/pages/api/articles/[id].ts b/src/pages/api/articles/[id].ts deleted file mode 100644 index ea5cd3c..0000000 --- a/src/pages/api/articles/[id].ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { APIRoute } from 'astro'; -import { - ArticleService, - ArticleNotFoundError, -} from '../../../services/ArticleService'; -import { jsonResponse, jsonError } from '../_utils'; - -// DELETE /api/articles/[id] - Delete an article by slug -export const DELETE: APIRoute = async ({ params }) => { - try { - const slug = params.id; - - if (!slug) { - return jsonError('Article ID required', 400); - } - - const service = new ArticleService(); - await service.delete(slug); - - return jsonResponse({ success: true }); - } catch (error) { - if (error instanceof ArticleNotFoundError) { - return jsonError(error, 404); - } - - console.error('Delete error:', error); - return jsonError(error); - } -}; - -// GET /api/articles/[id] - Get article details -export const GET: APIRoute = async ({ params }) => { - try { - const slug = params.id; - - if (!slug) { - return jsonError('Article ID required', 400); - } - - const service = new ArticleService(); - const article = await service.read(slug); - - if (!article) { - return jsonError('Article not found', 404); - } - - return jsonResponse(article); - } catch (error) { - console.error('Read error:', error); - return jsonError(error); - } -}; diff --git a/src/pages/api/articles/[id]/apply.ts b/src/pages/api/articles/[id]/apply.ts deleted file mode 100644 index d54f38b..0000000 --- a/src/pages/api/articles/[id]/apply.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { APIRoute } from 'astro'; -import * as fs from 'fs/promises'; -import * as path from 'path'; -import matter from 'gray-matter'; -import { - ArticleService, - ArticleNotFoundError, -} from '../../../../services/ArticleService'; -import { convertToWebP } from '../../../../services/ImageConverter'; -import { jsonResponse, jsonError } from '../../_utils'; - -interface ApplyRequest { - // For text updates - title?: string; - description?: string; - content?: string; - tags?: string[]; - // For image updates - imageUrl?: string; - imageAlt?: string; -} - -// POST /api/articles/[id]/apply - Save regenerated content or image -export const POST: APIRoute = async ({ params, request }) => { - try { - const slug = params.id; - - if (!slug) { - return jsonError('Article ID required', 400); - } - - const data: ApplyRequest = await request.json(); - const service = new ArticleService(); - - // Read existing article - const existingArticle = await service.read(slug); - if (!existingArticle) { - throw new ArticleNotFoundError(slug); - } - - // Handle image update if imageUrl is provided - if (data.imageUrl) { - const heroPath = path.join(existingArticle.folderPath, 'hero.webp'); - - console.log('Saving image to:', heroPath); - console.log('Image URL length:', data.imageUrl.length); - - // Decode base64 image data and convert to WebP - const base64Data = data.imageUrl.replace(/^data:image\/\w+;base64,/, ''); - - await convertToWebP(base64Data, heroPath); - console.log('Image saved successfully'); - - // Update imageAlt if provided - if (data.imageAlt) { - const indexPath = path.join(existingArticle.folderPath, 'index.md'); - const fileContent = await fs.readFile(indexPath, 'utf-8'); - const { data: frontmatter, content } = matter(fileContent); - - frontmatter.imageAlt = data.imageAlt; - - const updatedContent = matter.stringify(content, frontmatter); - await fs.writeFile(indexPath, updatedContent); - } - } - - // Handle text update if content is provided - if (data.content || data.title || data.description) { - await service.updateContent(slug, { - ...(data.title && { title: data.title }), - ...(data.description && { description: data.description }), - ...(data.content && { content: data.content }), - }); - } - - return jsonResponse({ - success: true, - articleId: existingArticle.articleId, - }); - } catch (error) { - if (error instanceof ArticleNotFoundError) { - return jsonError(error, 404); - } - - console.error('Apply changes error:', error); - return jsonError(error); - } -}; diff --git a/src/pages/api/articles/[id]/regenerate-image.ts b/src/pages/api/articles/[id]/regenerate-image.ts deleted file mode 100644 index f251117..0000000 --- a/src/pages/api/articles/[id]/regenerate-image.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { APIRoute } from 'astro'; -import { - ArticleService, - ArticleNotFoundError, -} from '../../../../services/ArticleService'; -import { ImageGenerator } from '../../../../services/ImageGenerator'; -import { getEnvVariable } from '../../../../utils/envUtils'; -import { jsonResponse, jsonError } from '../../_utils'; - -// POST /api/articles/[id]/regenerate-image - Generate new image for article -// Returns preview of new image URL WITHOUT saving -export const POST: APIRoute = async ({ params }) => { - try { - const slug = params.id; - - if (!slug) { - return jsonError('Article ID required', 400); - } - - // Read existing article to get title for image generation - const service = new ArticleService(); - const existingArticle = await service.read(slug); - - if (!existingArticle) { - throw new ArticleNotFoundError(slug); - } - - // Generate new image using article title - const apiKey = getEnvVariable('GOOGLE_GEMINI_API_KEY'); - const generator = new ImageGenerator({ apiKey }); - const image = await generator.generate(existingArticle.title); - - return jsonResponse({ - url: image.url, - alt: image.alt, - // Include article info for reference - articleId: existingArticle.articleId, - articleTitle: existingArticle.title, - }); - } catch (error) { - if (error instanceof ArticleNotFoundError) { - return jsonError(error, 404); - } - - console.error('Regenerate image error:', error); - return jsonError(error); - } -}; diff --git a/src/pages/api/articles/[id]/regenerate-text.ts b/src/pages/api/articles/[id]/regenerate-text.ts deleted file mode 100644 index c87a9d8..0000000 --- a/src/pages/api/articles/[id]/regenerate-text.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { APIRoute } from 'astro'; -import { - ArticleService, - ArticleNotFoundError, -} from '../../../../services/ArticleService'; -import { ContentGenerator } from '../../../../services/ContentGenerator'; -import { PromptService } from '../../../../services/PromptService'; -import { getEnvVariable } from '../../../../utils/envUtils'; -import { jsonResponse, jsonError } from '../../_utils'; - -// POST /api/articles/[id]/regenerate-text - Generate new content for article -// Returns preview of new content WITHOUT saving -export const POST: APIRoute = async ({ params }) => { - try { - const slug = params.id; - - if (!slug) { - return jsonError('Article ID required', 400); - } - - // Read existing article to get title for regeneration - const service = new ArticleService(); - const existingArticle = await service.read(slug); - - if (!existingArticle) { - throw new ArticleNotFoundError(slug); - } - - // Generate new content using existing title as keywords - const apiKey = getEnvVariable('GOOGLE_GEMINI_API_KEY'); - const promptService = new PromptService(); - const generator = new ContentGenerator({ apiKey, promptService }); - - // Use the existing title as keywords for regeneration - const newArticle = await generator.generateFromKeywords( - existingArticle.title - ); - - return jsonResponse({ - title: newArticle.title, - description: newArticle.description, - content: newArticle.content, - tags: newArticle.tags, - // Include original article info for reference - originalTitle: existingArticle.title, - articleId: existingArticle.articleId, - }); - } catch (error) { - if (error instanceof ArticleNotFoundError) { - return jsonError(error, 404); - } - - console.error('Regenerate text error:', error); - return jsonError(error); - } -}; diff --git a/src/pages/api/articles/articles.integration.test.ts b/src/pages/api/articles/articles.integration.test.ts deleted file mode 100644 index 012804f..0000000 --- a/src/pages/api/articles/articles.integration.test.ts +++ /dev/null @@ -1,278 +0,0 @@ -import { describe, it, expect, beforeAll, afterAll } from 'vitest'; -import * as fs from 'fs/promises'; -import * as path from 'path'; -import { ArticleService } from '../../../services/ArticleService'; -import { ImageGenerator } from '../../../services/ImageGenerator'; -import { ContentGenerator } from '../../../services/ContentGenerator'; -import sharp from 'sharp'; - -/** - * Integration tests for Article API endpoints - * These tests make real API calls to Google AI services. - * - * Run with: npm test -- --run articles.integration - * - * Requires GOOGLE_GEMINI_API_KEY environment variable - */ - -const apiKey = process.env.GOOGLE_GEMINI_API_KEY; -const TEST_ARTICLE_SLUG = - 'mehrsprachigkeit-barrierefreiheit-7-wege-zu-einem-inklusiven-web'; - -describe.skipIf(!apiKey)('Article API Integration Tests', () => { - let articleService: ArticleService; - let imageGenerator: ImageGenerator; - let contentGenerator: ContentGenerator; - let originalArticle: Awaited>; - let originalHeroBuffer: Buffer | null = null; - - beforeAll(async () => { - articleService = new ArticleService(); - imageGenerator = new ImageGenerator({ apiKey: apiKey! }); - contentGenerator = new ContentGenerator({ apiKey: apiKey! }); - - // Read original article for comparison - originalArticle = await articleService.read(TEST_ARTICLE_SLUG); - - // Backup original hero image if exists - if (originalArticle?.folderPath) { - const heroPath = path.join(originalArticle.folderPath, 'hero.webp'); - try { - originalHeroBuffer = await fs.readFile(heroPath); - } catch { - originalHeroBuffer = null; - } - } - }); - - afterAll(async () => { - // Restore original hero image if backed up - if (originalHeroBuffer && originalArticle?.folderPath) { - const heroPath = path.join(originalArticle.folderPath, 'hero.webp'); - await fs.writeFile(heroPath, originalHeroBuffer); - } - }); - - describe('Article Service', () => { - it('reads existing article', async () => { - const article = await articleService.read(TEST_ARTICLE_SLUG); - - expect(article).not.toBeNull(); - expect(article?.title).toBeDefined(); - expect(article?.description).toBeDefined(); - expect(article?.folderPath).toBeDefined(); - - console.log('Article found:', { - title: article?.title, - folderPath: article?.folderPath, - }); - }); - - it('returns null for non-existent article', async () => { - const article = await articleService.read('non-existent-article-slug'); - expect(article).toBeNull(); - }); - }); - - describe('Image Regeneration Flow', () => { - let generatedImageData: { url: string; alt: string } | null = null; - - it('generates new image for article (real AI call)', async () => { - const article = await articleService.read(TEST_ARTICLE_SLUG); - expect(article).not.toBeNull(); - - console.log('Generating image for:', article?.title); - - const result = await imageGenerator.generate(article!.title); - - expect(result.url).toMatch(/^data:image\/png;base64,.+/); - expect(result.alt).toBeDefined(); - expect(result.alt.length).toBeGreaterThan(10); - - generatedImageData = { url: result.url, alt: result.alt }; - - console.log('Generated image:', { - alt: result.alt, - urlLength: result.url.length, - }); - }, 60000); - - it('saves generated image to article folder', async () => { - expect(generatedImageData).not.toBeNull(); - expect(originalArticle).not.toBeNull(); - - const heroPath = path.join(originalArticle!.folderPath, 'hero.webp'); - - // Decode base64 and convert to WebP (same as apply.ts) - const base64Data = generatedImageData!.url.replace( - /^data:image\/\w+;base64,/, - '' - ); - const imageBuffer = Buffer.from(base64Data, 'base64'); - - // Verify the buffer is valid image data - expect(imageBuffer.length).toBeGreaterThan(1000); - - // Convert to WebP using sharp - const webpBuffer = await sharp(imageBuffer) - .webp({ quality: 85 }) - .toBuffer(); - - expect(webpBuffer.length).toBeGreaterThan(1000); - - // Write to file - await fs.writeFile(heroPath, webpBuffer); - - // Verify file was written - const savedFile = await fs.readFile(heroPath); - expect(savedFile.length).toBe(webpBuffer.length); - - console.log('Image saved:', { - path: heroPath, - originalSize: imageBuffer.length, - webpSize: webpBuffer.length, - }); - }, 10000); - - it('updates imageAlt in frontmatter using gray-matter', async () => { - expect(generatedImageData).not.toBeNull(); - expect(originalArticle).not.toBeNull(); - - const indexPath = path.join(originalArticle!.folderPath, 'index.md'); - const matter = await import('gray-matter'); - - // Read and parse current file - const fileContent = await fs.readFile(indexPath, 'utf-8'); - const { data: frontmatter, content } = matter.default(fileContent); - - // Update imageAlt - frontmatter.imageAlt = generatedImageData!.alt; - - // Write updated content - const updatedContent = matter.default.stringify(content, frontmatter); - await fs.writeFile(indexPath, updatedContent); - - // Verify the update - const verifyContent = await fs.readFile(indexPath, 'utf-8'); - const { data: verifyFrontmatter } = matter.default(verifyContent); - expect(verifyFrontmatter.imageAlt).toBe(generatedImageData!.alt); - - console.log('ImageAlt updated to:', generatedImageData!.alt); - }); - }); - - describe('Text Regeneration Flow', () => { - let generatedContent: { - title: string; - description: string; - content: string; - } | null = null; - - it('generates new content for article (real AI call)', async () => { - const article = await articleService.read(TEST_ARTICLE_SLUG); - expect(article).not.toBeNull(); - - console.log('Generating content for:', article?.title); - - const result = await contentGenerator.generateFromKeywords( - article!.title - ); - - expect(result.title).toBeDefined(); - expect(result.description).toBeDefined(); - expect(result.content).toBeDefined(); - expect(result.title.length).toBeGreaterThan(10); - expect(result.description.length).toBeGreaterThan(20); - expect(result.content.length).toBeGreaterThan(100); - - generatedContent = { - title: result.title, - description: result.description, - content: result.content, - }; - - console.log('Generated content:', { - title: result.title, - descriptionLength: result.description.length, - contentLength: result.content.length, - }); - }, 90000); - - it('saves generated content using ArticleService', async () => { - expect(generatedContent).not.toBeNull(); - - // Note: We're not actually saving to avoid overwriting the article - // This test verifies the updateContent method works - console.log('Content ready to save:', { - title: generatedContent!.title, - description: generatedContent!.description.substring(0, 50) + '...', - }); - - // Verify the service method exists and is callable - expect(typeof articleService.updateContent).toBe('function'); - }); - }); - - describe('Error Handling', () => { - it('handles invalid article slug', async () => { - const article = await articleService.read('definitely-not-a-real-slug'); - expect(article).toBeNull(); - }); - - it('handles invalid API key for image generation', async () => { - const badGenerator = new ImageGenerator({ apiKey: 'invalid-key' }); - - await expect(badGenerator.generate('Test Topic')).rejects.toThrow(); - }, 30000); - - it('handles invalid API key for content generation', async () => { - const badGenerator = new ContentGenerator({ apiKey: 'invalid-key' }); - - await expect( - badGenerator.generateFromKeywords('Test Keywords') - ).rejects.toThrow(); - }, 30000); - }); -}); - -describe.skipIf(!apiKey)('Apply Endpoint Logic Tests', () => { - it('correctly strips base64 prefix from PNG', () => { - const pngDataUrl = - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='; - const base64Data = pngDataUrl.replace(/^data:image\/\w+;base64,/, ''); - - expect(base64Data).toBe( - 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==' - ); - expect(base64Data).not.toContain('data:'); - }); - - it('correctly strips base64 prefix from JPEG', () => { - const jpegDataUrl = 'data:image/jpeg;base64,/9j/4AAQSkZJRg=='; - const base64Data = jpegDataUrl.replace(/^data:image\/\w+;base64,/, ''); - - expect(base64Data).toBe('/9j/4AAQSkZJRg=='); - }); - - it('converts base64 to buffer correctly', () => { - const base64 = 'SGVsbG8gV29ybGQ='; // "Hello World" - const buffer = Buffer.from(base64, 'base64'); - - expect(buffer.toString('utf-8')).toBe('Hello World'); - }); - - it('sharp converts PNG buffer to WebP', async () => { - // Minimal valid 1x1 PNG - const pngBase64 = - 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='; - const pngBuffer = Buffer.from(pngBase64, 'base64'); - - const webpBuffer = await sharp(pngBuffer).webp({ quality: 85 }).toBuffer(); - - expect(webpBuffer.length).toBeGreaterThan(0); - - // Verify it's valid WebP by checking magic bytes (RIFF....WEBP) - expect(webpBuffer.subarray(0, 4).toString('ascii')).toBe('RIFF'); - expect(webpBuffer.subarray(8, 12).toString('ascii')).toBe('WEBP'); - }); -}); diff --git a/src/pages/api/generate-content.ts b/src/pages/api/generate-content.ts deleted file mode 100644 index 17eaf58..0000000 --- a/src/pages/api/generate-content.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { APIRoute } from 'astro'; -import { ContentGenerator } from '../../services/ContentGenerator'; -import { PromptService } from '../../services/PromptService'; -import { getEnvVariable } from '../../utils/envUtils'; -import { jsonResponse, jsonError } from './_utils'; - -export const POST: APIRoute = async ({ request }) => { - try { - const { url, keywords } = await request.json(); - - if (!url && !keywords) { - return jsonError('URL or keywords required', 400); - } - - const apiKey = getEnvVariable('GOOGLE_GEMINI_API_KEY'); - const promptService = new PromptService(); - const generator = new ContentGenerator({ apiKey, promptService }); - const article = url - ? await generator.generateFromUrl(url) - : await generator.generateFromKeywords(keywords); - - return jsonResponse({ - title: article.title, - description: article.description, - content: article.content, - filepath: article.filepath, - tags: article.tags, - date: article.date.toISOString(), - }); - } catch (error) { - console.error('Generation error:', error); - return jsonError(error); - } -}; diff --git a/src/pages/api/generate-image.ts b/src/pages/api/generate-image.ts deleted file mode 100644 index bf66819..0000000 --- a/src/pages/api/generate-image.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { APIRoute } from 'astro'; -import { ImageGenerator } from '../../services/ImageGenerator'; -import { getEnvVariable } from '../../utils/envUtils'; -import { jsonResponse, jsonError } from './_utils'; - -export const POST: APIRoute = async ({ request }) => { - try { - const { title } = await request.json(); - - if (!title) { - return jsonError('Title is required', 400); - } - - const apiKey = getEnvVariable('GOOGLE_GEMINI_API_KEY'); - const generator = new ImageGenerator({ apiKey }); - const image = await generator.generate(title); - - return jsonResponse({ - url: image.url, - alt: image.alt, - filepath: image.filepath, - }); - } catch (error) { - console.error('Image generation error:', error); - return jsonError(error); - } -}; diff --git a/src/pages/api/prompts.ts b/src/pages/api/prompts.ts deleted file mode 100644 index 9453c5e..0000000 --- a/src/pages/api/prompts.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { APIRoute } from 'astro'; -import { PromptService } from '../../services/PromptService'; -import { jsonResponse, jsonError } from './_utils'; - -const service = new PromptService(); - -// GET /api/prompts - Get all prompts and settings -export const GET: APIRoute = async () => { - try { - const [prompts, settings] = await Promise.all([ - service.getAllPrompts(), - service.getAllSettings(), - ]); - - return jsonResponse({ - prompts, - settings, - }); - } catch (error) { - console.error('Get prompts error:', error); - return jsonError(error); - } -}; - -// POST /api/prompts - Update a prompt or setting -export const POST: APIRoute = async ({ request }) => { - try { - const data = await request.json(); - - if (data.type === 'prompt' && data.id && data.promptText !== undefined) { - await service.updatePrompt(data.id, data.promptText); - return jsonResponse({ success: true, type: 'prompt', id: data.id }); - } - - if (data.type === 'setting' && data.key && data.value !== undefined) { - await service.updateSetting(data.key, data.value); - return jsonResponse({ success: true, type: 'setting', key: data.key }); - } - - return jsonError('Invalid request: missing type, id/key, or value', 400); - } catch (error) { - console.error('Update prompt error:', error); - return jsonError(error); - } -}; diff --git a/src/pages/api/save-image.ts b/src/pages/api/save-image.ts deleted file mode 100644 index dd1313c..0000000 --- a/src/pages/api/save-image.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { APIRoute } from 'astro'; -import path from 'node:path'; -import { convertToWebP } from '../../services/ImageConverter'; -import { jsonResponse, jsonError } from './_utils'; - -export const POST: APIRoute = async ({ request }) => { - try { - const { url, folderPath } = await request.json(); - - if (!url || !folderPath) { - return jsonError('URL and folderPath are required', 400); - } - - // Extract base64 data from data URL - const base64Match = url.match(/^data:image\/\w+;base64,(.+)$/); - if (!base64Match) { - return jsonError('Invalid image data URL', 400); - } - - const base64Data = base64Match[1]; - - // Save hero.webp in the article folder - const filepath = path.join(folderPath, 'hero.webp'); - const fullPath = path.resolve(process.cwd(), filepath); - - await convertToWebP(base64Data, fullPath); - - return jsonResponse({ - success: true, - filepath: filepath, - }); - } catch (error) { - console.error('Image save error:', error); - return jsonError(error); - } -}; diff --git a/src/pages/api/save.ts b/src/pages/api/save.ts deleted file mode 100644 index b2d9b88..0000000 --- a/src/pages/api/save.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { APIRoute } from 'astro'; -import { Article } from '../../domain/entities/Article'; -import { FileWriter } from '../../services/FileWriter'; -import { jsonResponse, jsonError } from './_utils'; - -export const POST: APIRoute = async ({ request }) => { - try { - const data = await request.json(); - - const article = new Article({ - title: data.title, - description: data.description, - content: data.content, - date: new Date(data.date || Date.now()), - tags: data.tags || [], - image: './hero.webp', - imageAlt: data.imageAlt, - }); - - const writer = new FileWriter(); - const result = await writer.write(article); - - return jsonResponse({ - success: true, - filepath: result.filepath, - folderPath: article.folderPath, - }); - } catch (error) { - console.error('Save error:', error); - return jsonError(error); - } -}; diff --git a/src/pages/api/scheduler/[id].ts b/src/pages/api/scheduler/[id].ts deleted file mode 100644 index 054a031..0000000 --- a/src/pages/api/scheduler/[id].ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { APIRoute } from 'astro'; -import { SchedulerService } from '../../../services/SchedulerService'; -import { AstroSchedulerDBAdapter } from '../../../services/SchedulerDBAdapter'; -import { jsonResponse, jsonError } from '../_utils'; - -function getService(): SchedulerService { - return new SchedulerService(new AstroSchedulerDBAdapter()); -} - -export const DELETE: APIRoute = async ({ params }) => { - try { - const id = params.id; - if (!id) { - return jsonError('id is required', 400); - } - - const service = getService(); - await service.delete(id); - - return jsonResponse({ success: true }); - } catch (error) { - console.error('Scheduler delete error:', error); - const status = - error instanceof Error && error.message.includes('not found') - ? 404 - : error instanceof Error && error.message.includes('Cannot delete') - ? 403 - : 500; - return jsonError(error, status); - } -}; diff --git a/src/pages/api/scheduler/generate.ts b/src/pages/api/scheduler/generate.ts deleted file mode 100644 index a7ced87..0000000 --- a/src/pages/api/scheduler/generate.ts +++ /dev/null @@ -1,94 +0,0 @@ -import type { APIRoute } from 'astro'; -import { SchedulerService } from '../../../services/SchedulerService'; -import { AstroSchedulerDBAdapter } from '../../../services/SchedulerDBAdapter'; -import { ContentGenerator } from '../../../services/ContentGenerator'; -import { ImageGenerator } from '../../../services/ImageGenerator'; -import { PromptService } from '../../../services/PromptService'; -import { getEnvVariable } from '../../../utils/envUtils'; -import { jsonResponse, jsonError } from '../_utils'; - -function getService(): SchedulerService { - return new SchedulerService(new AstroSchedulerDBAdapter()); -} - -export const POST: APIRoute = async ({ request }) => { - try { - const { id, mode = 'all' } = await request.json(); - - if (!id) { - return jsonError('id is required', 400); - } - - const service = getService(); - const post = await service.getById(id); - - if (!post.canGenerate()) { - return jsonError( - `Cannot generate: post is in status "${post.status}"`, - 400 - ); - } - - const apiKey = getEnvVariable('GOOGLE_GEMINI_API_KEY'); - - if (mode === 'text' || mode === 'all') { - const promptService = new PromptService(); - const contentGenerator = new ContentGenerator({ apiKey, promptService }); - - const article = - post.inputType === 'url' - ? await contentGenerator.generateFromUrl(post.input) - : await contentGenerator.generateFromKeywords(post.input); - - if (mode === 'text') { - await service.updateText(id, { - title: article.title, - description: article.description, - content: article.content, - tags: article.tags, - }); - } else { - // mode === 'all': generate image too - let imageData: string | undefined; - let imageAlt: string | undefined; - try { - const imageGenerator = new ImageGenerator({ apiKey }); - const image = await imageGenerator.generate(article.title); - imageData = image.base64; - imageAlt = image.alt; - } catch (error) { - console.warn( - 'Image generation failed, continuing without image:', - error - ); - } - - await service.markGenerated(id, { - title: article.title, - description: article.description, - content: article.content, - tags: article.tags, - imageData, - imageAlt, - }); - } - } else if (mode === 'image') { - // Use existing title or fallback to input - const title = post.generatedTitle || post.input; - const imageGenerator = new ImageGenerator({ apiKey }); - const image = await imageGenerator.generate(title); - - await service.updateImage(id, { - imageData: image.base64 || '', - imageAlt: image.alt, - }); - } - - const updated = await service.getById(id); - - return jsonResponse({ post: updated }); - } catch (error) { - console.error('Scheduler generate error:', error); - return jsonError(error); - } -}; diff --git a/src/pages/api/scheduler/publish.ts b/src/pages/api/scheduler/publish.ts deleted file mode 100644 index 3fbf0a2..0000000 --- a/src/pages/api/scheduler/publish.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { APIRoute } from 'astro'; -import { SchedulerService } from '../../../services/SchedulerService'; -import { AstroSchedulerDBAdapter } from '../../../services/SchedulerDBAdapter'; -import { Article } from '../../../domain/entities/Article'; -import { FileWriter } from '../../../services/FileWriter'; -import * as path from 'path'; -import { convertToWebP } from '../../../services/ImageConverter'; -import { jsonResponse, jsonError } from '../_utils'; - -function getService(): SchedulerService { - return new SchedulerService(new AstroSchedulerDBAdapter()); -} - -async function publishPost( - service: SchedulerService, - postId: string -): Promise<{ id: string; publishedPath: string }> { - const post = await service.getById(postId); - - if (!post.canPublish()) { - throw new Error(`Cannot publish: post is in status "${post.status}"`); - } - - if (!post.generatedTitle || !post.generatedContent) { - throw new Error('Cannot publish: missing generated content'); - } - - // Create article with the scheduled date (not today) - const articleProps = { - title: post.generatedTitle, - description: post.generatedDescription || '', - content: post.generatedContent, - date: post.scheduledDate, - tags: post.parsedTags, - image: './hero.webp', - ...(post.generatedImageAlt ? { imageAlt: post.generatedImageAlt } : {}), - }; - const article = new Article(articleProps); - - // Write article to filesystem - const writer = new FileWriter(); - await writer.write(article); - - // Write image if available - if (post.generatedImageData) { - const imagePath = path.join(process.cwd(), article.folderPath, 'hero.webp'); - await convertToWebP(post.generatedImageData, imagePath); - } - - // Mark as published in DB - await service.markPublished(post.id, article.folderPath); - - return { id: post.id, publishedPath: article.folderPath }; -} - -export const POST: APIRoute = async ({ request }) => { - try { - const data = await request.json(); - const service = getService(); - - // Auto-publish all due posts - if (data.mode === 'auto') { - const duePosts = await service.getDuePosts(); - const results: { id: string; publishedPath: string }[] = []; - const failed: { id: string; error: string }[] = []; - - for (const post of duePosts) { - try { - const result = await publishPost(service, post.id); - results.push(result); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - console.error(`Failed to auto-publish ${post.id}:`, error); - failed.push({ id: post.id, error: message }); - } - } - - return jsonResponse({ published: results, failed }); - } - - // Publish single post - if (!data.id) { - return jsonError('id or mode:"auto" is required', 400); - } - - const result = await publishPost(service, data.id); - - return jsonResponse({ success: true, ...result }); - } catch (error) { - console.error('Scheduler publish error:', error); - return jsonError(error); - } -}; diff --git a/src/pages/editor.astro b/src/pages/editor.astro deleted file mode 100644 index 3d81eb5..0000000 --- a/src/pages/editor.astro +++ /dev/null @@ -1,11 +0,0 @@ ---- -import Layout from '../layouts/Layout.astro'; -import EditorComponent from '../components/Editor'; ---- - - -
-

Content Editor

- -
-
diff --git a/src/services/AutoPublisher.ts b/src/services/AutoPublisher.ts deleted file mode 100644 index 05128f6..0000000 --- a/src/services/AutoPublisher.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { SchedulerService } from './SchedulerService'; -import { AstroSchedulerDBAdapter } from './SchedulerDBAdapter'; -import { Article } from '../domain/entities/Article'; -import { FileWriter } from './FileWriter'; -import { convertToWebP } from './ImageConverter'; -import * as path from 'path'; - -const INTERVAL_MS = 60 * 60 * 1000; // 60 minutes - -let intervalId: ReturnType | null = null; -let isRunning = false; - -async function publishDuePosts(): Promise { - if (isRunning) return; - isRunning = true; - - try { - const service = new SchedulerService(new AstroSchedulerDBAdapter()); - const duePosts = await service.getDuePosts(); - - if (duePosts.length === 0) return; - - console.log(`[AutoPublisher] Found ${duePosts.length} due post(s)`); - - let published = 0; - let failed = 0; - - for (const post of duePosts) { - try { - if (!post.generatedTitle || !post.generatedContent) { - console.warn(`[AutoPublisher] Skipping ${post.id}: missing generated content`); - failed++; - continue; - } - - const articleProps = { - title: post.generatedTitle, - description: post.generatedDescription || '', - content: post.generatedContent, - date: post.scheduledDate, - tags: post.parsedTags, - image: './hero.webp', - ...(post.generatedImageAlt ? { imageAlt: post.generatedImageAlt } : {}), - }; - const article = new Article(articleProps); - - const writer = new FileWriter(); - await writer.write(article); - - if (post.generatedImageData) { - const imagePath = path.join(process.cwd(), article.folderPath, 'hero.webp'); - await convertToWebP(post.generatedImageData, imagePath); - } - - await service.markPublished(post.id, article.folderPath); - published++; - console.log(`[AutoPublisher] Published ${post.id} -> ${article.folderPath}`); - } catch (error) { - failed++; - console.error(`[AutoPublisher] Failed to publish ${post.id}:`, error); - } - } - - console.log(`[AutoPublisher] Done: ${published} published, ${failed} failed`); - } catch (error) { - console.error('[AutoPublisher] Error checking due posts:', error); - } finally { - isRunning = false; - } -} - -export function startAutoPublisher(): void { - if (intervalId) return; - - console.log(`[AutoPublisher] Starting (interval: ${INTERVAL_MS / 1000 / 60} minutes)`); - - // Run once on startup after a short delay to let the DB initialize - setTimeout(() => { - publishDuePosts(); - }, 10_000); - - intervalId = setInterval(publishDuePosts, INTERVAL_MS); -} - -export function stopAutoPublisher(): void { - if (intervalId) { - clearInterval(intervalId); - intervalId = null; - console.log('[AutoPublisher] Stopped'); - } -} diff --git a/src/services/ContentFetcher.ts b/src/services/ContentFetcher.ts deleted file mode 100644 index 7f965cf..0000000 --- a/src/services/ContentFetcher.ts +++ /dev/null @@ -1,89 +0,0 @@ -import TurndownService from 'turndown'; -import { Source } from '../domain/entities/Source'; - -export type FetchedContent = { - title: string; - content: string; - url: string; -}; - -export class ContentFetcher { - private turndown: TurndownService; - - constructor() { - this.turndown = new TurndownService({ - headingStyle: 'atx', - codeBlockStyle: 'fenced', - bulletListMarker: '-', - }); - - // Remove unwanted elements - this.turndown.remove([ - 'script', - 'style', - 'nav', - 'footer', - 'aside', - 'noscript', - ]); - } - - async fetch(source: Source): Promise { - const response = await fetch(source.url, { - headers: { - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', - Accept: - 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'Accept-Language': 'de-DE,de;q=0.9,en;q=0.8', - }, - }); - - if (!response.ok) { - throw new Error(`Failed to fetch ${source.url}: ${response.status}`); - } - - const html = await response.text(); - const title = this.extractTitle(html); - const content = this.htmlToMarkdown(html); - - return { - title, - content, - url: source.url, - }; - } - - private extractTitle(html: string): string { - // Try og:title first, then title tag - const ogMatch = html.match( - /]*property="og:title"[^>]*content="([^"]+)"/i - ); - if (ogMatch?.[1]) return ogMatch[1].trim(); - - const titleMatch = html.match(/]*>([^<]+)<\/title>/i); - return titleMatch?.[1]?.trim() ?? 'Untitled'; - } - - private htmlToMarkdown(html: string): string { - // Extract main content area first - let content = html; - - const mainMatch = - content.match(/]*>([\s\S]*?)<\/main>/i) || - content.match(/]*>([\s\S]*?)<\/article>/i) || - content.match( - /]*class="[^"]*(?:content|article|post|entry)[^"]*"[^>]*>([\s\S]*?)<\/div>/i - ); - - if (mainMatch?.[1]) { - content = mainMatch[1]; - } - - // Convert to markdown using turndown - const markdown = this.turndown.turndown(content); - - // Normalize whitespace - return markdown.replace(/\n{3,}/g, '\n\n').trim(); - } -} diff --git a/src/services/ContentGenerator.ts b/src/services/ContentGenerator.ts deleted file mode 100644 index 4907f45..0000000 --- a/src/services/ContentGenerator.ts +++ /dev/null @@ -1,307 +0,0 @@ -import { GoogleGenerativeAI, SchemaType } from '@google/generative-ai'; -import { Source } from '../domain/entities/Source'; -import { Article, type ArticleProps } from '../domain/entities/Article'; -import { ContentFetcher, type FetchedContent } from './ContentFetcher'; - -// PromptService interface for dependency injection (avoids astro:db import in tests) -export interface IPromptService { - getPrompt(id: string): Promise; - getCTAConfig(): Promise<{ url: string; style: string; prompt: string }>; - getCoreTags(): Promise; -} - -export interface GeneratedContent { - title: string; - description: string; - content: string; - tags: string[]; -} - -export interface ContentGeneratorConfig { - apiKey: string; - model?: string; - promptService?: IPromptService; -} - -interface SourceAnalysis { - topic: string; - keyPoints: string[]; - uniqueInsights: string[]; - codeExamples: string[]; -} - -// Fallback values when database is not available -const DEFAULT_CONTACT_URL = - 'https://nevercodealone.de/de/landingpages/barrierefreies-webdesign'; - -const DEFAULT_CORE_TAGS = ['Semantik', 'HTML', 'Barrierefrei']; - -const DEFAULT_SYSTEM_PROMPT = `Du bist ein erfahrener technischer Content-Writer für Web-Entwicklung. -Deine Aufgabe ist es, hochwertige deutsche Fachartikel zu erstellen. - -Zielgruppe: Content-Marketing-Professionals und Frontend-Entwickler -Tonalität: Professionell, aber zugänglich. Technisch korrekt, nicht übermäßig akademisch. - -KRITISCH - 100% Originalität: -- Schreibe einen KOMPLETT EIGENSTÄNDIGEN Artikel -- KEINE Sätze, Formulierungen oder Strukturen aus externen Quellen übernehmen -- KEINE Hinweise auf Quellen, Referenzen oder Inspiration im Text -- Nutze ausschließlich DEIN Expertenwissen zur Barrierefreiheit -- Jeder Satz muss NEU formuliert sein - wie von einem Experten geschrieben -- Der Artikel muss wirken als käme er aus eigener Fachkenntnis - -Regeln: -- Schreibe auf Deutsch -- Mindestens 800 Wörter -- Verwende praktische Codebeispiele (eigene Beispiele, nicht kopiert) -- WICHTIG: Content MUSS mit einer H1-Überschrift (# Titel) beginnen -- Danach H2 (##) und H3 (###) Hierarchie ohne Sprünge -- WICHTIG: Nur Markdown, KEINE HTML-Tags wie

,

, etc. -- WICHTIG: Integriere die Keywords "Semantik", "HTML" und "Barrierefrei" natürlich in den Text - -Titel-Regeln: -- Das Hauptthema/Keyword MUSS im Titel vorkommen -- Nutze Zahlen wenn möglich (z.B. "5 Tipps", "3 Fehler") -- Zeige den Nutzen/Benefit (z.B. "So vermeidest du...", "Warum X wichtig ist") -- Wecke Neugier oder löse ein Problem`; - -function buildSourceAnalysisSchema() { - return { - type: SchemaType.OBJECT as const, - properties: { - topic: { - type: SchemaType.STRING, - description: 'Das Hauptthema in 2-5 Wörtern', - }, - keyPoints: { - type: SchemaType.ARRAY, - items: { type: SchemaType.STRING }, - description: 'Die wichtigsten Kernaussagen/Fakten', - }, - uniqueInsights: { - type: SchemaType.ARRAY, - items: { type: SchemaType.STRING }, - description: 'Besondere/einzigartige Erkenntnisse oder Tipps', - }, - codeExamples: { - type: SchemaType.ARRAY, - items: { type: SchemaType.STRING }, - description: 'Wichtige Code-Beispiele oder Patterns', - }, - }, - required: ['topic', 'keyPoints', 'uniqueInsights', 'codeExamples'], - } satisfies import('@google/generative-ai').Schema; -} - -export class ContentGenerator { - private client: GoogleGenerativeAI; - private model: string; - private fetcher: ContentFetcher; - private promptService: IPromptService | undefined; - - constructor(config: ContentGeneratorConfig) { - this.client = new GoogleGenerativeAI(config.apiKey); - this.model = config.model || 'gemini-2.5-flash'; - this.fetcher = new ContentFetcher(); - this.promptService = config.promptService; - } - - async generateFromUrl(sourceUrl: string): Promise
{ - const source = new Source(sourceUrl); - const fetchedContent = await this.fetcher.fetch(source); - - // Step 1: Analyze source to detect topic and extract insights - const analysis = await this.analyzeSource(fetchedContent); - - // Step 2: Generate article based on analysis - const generated = await this.generateContent(analysis); - - const props: ArticleProps = { - title: generated.title, - description: generated.description, - content: generated.content, - date: new Date(), - tags: generated.tags, - }; - - return new Article(props); - } - - async generateFromKeywords(keywords: string): Promise
{ - // Research the keywords using AI - const analysis = await this.researchKeywords(keywords); - - // Generate article based on research - const generated = await this.generateContent(analysis); - - const props: ArticleProps = { - title: generated.title, - description: generated.description, - content: generated.content, - date: new Date(), - tags: generated.tags, - }; - - return new Article(props); - } - - private async analyzeSource( - fetched: FetchedContent - ): Promise { - const model = this.client.getGenerativeModel({ - model: this.model, - generationConfig: { - responseMimeType: 'application/json', - responseSchema: buildSourceAnalysisSchema(), - }, - }); - - const prompt = `Analysiere diesen Web-Artikel und extrahiere die wichtigsten Informationen. - -Titel: ${fetched.title} -URL: ${fetched.url} - -Inhalt: -${fetched.content.slice(0, 12000)} - -Identifiziere: -1. Das Hauptthema (fokussiert auf Web-Entwicklung/Barrierefreiheit) -2. Die wichtigsten Kernaussagen -3. Besondere Erkenntnisse oder einzigartige Tipps -4. Relevante Code-Beispiele oder Patterns`; - - const result = await model.generateContent(prompt); - const text = result.response.text(); - return JSON.parse(text); - } - - private async researchKeywords(keywords: string): Promise { - const model = this.client.getGenerativeModel({ - model: this.model, - generationConfig: { - responseMimeType: 'application/json', - responseSchema: buildSourceAnalysisSchema(), - }, - }); - - const prompt = `Du bist ein Experte für Web-Accessibility und barrierefreie Webentwicklung. - -Recherchiere zum Thema: "${keywords}" - -Nutze dein Fachwissen um: -1. Das Hauptthema klar zu definieren (Bezug zu Barrierefreiheit/Web-Accessibility) -2. Die wichtigsten Fakten, Best Practices und WCAG-Richtlinien zusammenzufassen -3. Weniger bekannte aber wichtige Tipps und Erkenntnisse zu identifizieren -4. Praktische Code-Beispiele oder Patterns vorzuschlagen - -Fokussiere auf aktuelle Standards und praktische Anwendbarkeit.`; - - const result = await model.generateContent(prompt); - const text = result.response.text(); - return JSON.parse(text); - } - - private async generateContent( - analysis: SourceAnalysis - ): Promise { - const systemPrompt = await this.buildSystemPrompt(); - const userPrompt = this.buildUserPrompt(analysis); - const coreTags = await this.getCoreTags(); - - const model = this.client.getGenerativeModel({ - model: this.model, - systemInstruction: systemPrompt, - generationConfig: { - responseMimeType: 'application/json', - responseSchema: { - type: SchemaType.OBJECT, - properties: { - title: { - type: SchemaType.STRING, - description: 'SEO-optimierter Titel, max 60 Zeichen', - }, - description: { - type: SchemaType.STRING, - description: 'Meta-Description, max 155 Zeichen', - }, - tags: { - type: SchemaType.ARRAY, - items: { type: SchemaType.STRING }, - description: 'Relevante Tags für den Artikel', - }, - content: { - type: SchemaType.STRING, - description: - 'Vollständiger Markdown-Inhalt. MUSS mit H1 (# Titel) beginnen, dann H2/H3 Hierarchie. Keine HTML-Tags.', - }, - }, - required: ['title', 'description', 'tags', 'content'], - }, - }, - }); - - const result = await model.generateContent(userPrompt); - const text = result.response.text(); - const data = JSON.parse(text); - - return { - title: data.title, - description: data.description, - content: data.content, - tags: [...new Set([...coreTags, ...data.tags])], - }; - } - - private async buildSystemPrompt(): Promise { - // Try to load from database - if (this.promptService) { - try { - const [basePrompt, ctaConfig] = await Promise.all([ - this.promptService.getPrompt('system_prompt'), - this.promptService.getCTAConfig(), - ]); - - if (basePrompt) { - // Add CTA instructions to the system prompt - return `${basePrompt} - -- WICHTIG: Beende den Artikel mit einem einzigartigen Call-to-Action: - - Link: ${ctaConfig.url} - - Stil: ${ctaConfig.style} - ${ctaConfig.prompt}`; - } - } catch (error) { - console.warn('Failed to load prompts from database, using defaults'); - } - } - - // Fallback to default with static CTA - return `${DEFAULT_SYSTEM_PROMPT} - -- WICHTIG: Beende den Artikel mit einem Call-to-Action, der zum Thema passt. - Verwende diesen Link: [Kontakt aufnehmen](${DEFAULT_CONTACT_URL})`; - } - - private buildUserPrompt(analysis: SourceAnalysis): string { - return `Schreibe als Accessibility-Experte einen deutschen Fachartikel zum Thema: ${analysis.topic} - -Behandle diese Aspekte aus deinem Fachwissen: -${analysis.keyPoints.map((p) => `- ${p}`).join('\n')} -${analysis.uniqueInsights.map((p) => `- ${p}`).join('\n')} - -${analysis.codeExamples.length > 0 ? `Zeige praktische Code-Beispiele für:\n${analysis.codeExamples.map((c) => `- ${c}`).join('\n')}` : ''} - -Wichtig: Schreibe komplett eigenständig aus deiner Expertise heraus.`; - } - - private async getCoreTags(): Promise { - if (this.promptService) { - try { - return await this.promptService.getCoreTags(); - } catch { - // Fall through to default - } - } - return DEFAULT_CORE_TAGS; - } -} diff --git a/src/services/FileWriter.test.ts b/src/services/FileWriter.test.ts deleted file mode 100644 index c0bb20b..0000000 --- a/src/services/FileWriter.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { FileWriter } from './FileWriter'; -import { Article } from '../domain/entities/Article'; -import * as fs from 'fs/promises'; -import * as path from 'path'; -import * as os from 'os'; - -describe('FileWriter', () => { - let tempDir: string; - let fileWriter: FileWriter; - - beforeEach(async () => { - tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'filewriter-test-')); - fileWriter = new FileWriter(tempDir); - }); - - afterEach(async () => { - await fs.rm(tempDir, { recursive: true, force: true }); - }); - - const createTestArticle = (overrides = {}) => - new Article({ - title: 'Test Article', - description: 'Test description', - content: '# Test\n\nContent here', - date: new Date('2025-12-07'), - tags: ['test'], - ...overrides, - }); - - it('creates article folder with index.md', async () => { - const article = createTestArticle(); - const result = await fileWriter.write(article); - - expect(result.created).toBe(true); - expect(result.filepath).toContain('/index.md'); - - const content = await fs.readFile(result.filepath, 'utf-8'); - expect(content).toContain('title: "Test Article"'); - }); - - it('creates nested year/month/slug folder structure', async () => { - const article = createTestArticle(); - await fileWriter.write(article); - - const expectedFolder = path.join( - tempDir, - 'nca-ai-cms-content/2025/12/test-article' - ); - const stats = await fs.stat(expectedFolder); - expect(stats.isDirectory()).toBe(true); - - const indexPath = path.join(expectedFolder, 'index.md'); - const indexStats = await fs.stat(indexPath); - expect(indexStats.isFile()).toBe(true); - }); - - it('includes image path in frontmatter when provided', async () => { - const article = createTestArticle({ - image: './hero.webp', - imageAlt: 'Test image alt', - }); - - const result = await fileWriter.write(article); - const content = await fs.readFile(result.filepath, 'utf-8'); - - expect(content).toContain('image: "./hero.webp"'); - expect(content).toContain('imageAlt: "Test image alt"'); - }); - - it('handles duplicate articles by appending counter', async () => { - const article = createTestArticle(); - - const result1 = await fileWriter.write(article); - const result2 = await fileWriter.write(article); - - expect(result1.filepath).toContain('/index.md'); - expect(result2.filepath).toContain('/index-2.md'); - }); -}); diff --git a/src/services/FileWriter.ts b/src/services/FileWriter.ts deleted file mode 100644 index e825338..0000000 --- a/src/services/FileWriter.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Article } from '../domain/entities/Article'; -import * as fs from 'fs/promises'; -import * as path from 'path'; - -export interface WriteResult { - filepath: string; - created: boolean; -} - -export class FileWriter { - private basePath: string; - - constructor(basePath: string = process.cwd()) { - this.basePath = basePath; - } - - async write(article: Article): Promise { - const filepath = path.join(this.basePath, article.filepath); - const dir = path.dirname(filepath); - - // Create directory if it doesn't exist - await fs.mkdir(dir, { recursive: true }); - - // Check if file already exists - const exists = await this.fileExists(filepath); - if (exists) { - const newPath = await this.getUniqueFilepath(filepath); - await fs.writeFile(newPath, article.toMarkdown(), 'utf-8'); - return { filepath: newPath, created: true }; - } - - await fs.writeFile(filepath, article.toMarkdown(), 'utf-8'); - return { filepath, created: true }; - } - - private async fileExists(filepath: string): Promise { - try { - await fs.access(filepath); - return true; - } catch { - return false; - } - } - - private async getUniqueFilepath(filepath: string): Promise { - const ext = path.extname(filepath); - const base = filepath.slice(0, -ext.length); - - let counter = 2; - let newPath = `${base}-${counter}${ext}`; - - while (await this.fileExists(newPath)) { - counter++; - newPath = `${base}-${counter}${ext}`; - } - - return newPath; - } -} diff --git a/src/services/ImageConverter.ts b/src/services/ImageConverter.ts deleted file mode 100644 index 019072e..0000000 --- a/src/services/ImageConverter.ts +++ /dev/null @@ -1,15 +0,0 @@ -import sharp from 'sharp'; -import * as fs from 'fs/promises'; -import * as path from 'path'; - -export async function convertToWebP( - base64: string, - outputPath: string -): Promise { - const buffer = Buffer.from(base64, 'base64'); - const webpBuffer = await sharp(buffer) - .webp({ quality: 85, effort: 6 }) - .toBuffer(); - await fs.mkdir(path.dirname(outputPath), { recursive: true }); - await fs.writeFile(outputPath, webpBuffer); -} diff --git a/src/services/ImageGenerator.integration.test.ts b/src/services/ImageGenerator.integration.test.ts deleted file mode 100644 index b6aa26e..0000000 --- a/src/services/ImageGenerator.integration.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { describe, it, expect, beforeAll } from 'vitest'; -import { ImageGenerator } from './ImageGenerator'; - -/** - * Integration tests for ImageGenerator - * These tests make real API calls to Google AI services. - * - * Run with: npm test -- --run ImageGenerator.integration - * - * Requires GOOGLE_GEMINI_API_KEY environment variable - */ - -const apiKey = process.env.GOOGLE_GEMINI_API_KEY; - -describe.skipIf(!apiKey)('ImageGenerator Integration', () => { - let generator: ImageGenerator; - - beforeAll(() => { - generator = new ImageGenerator({ apiKey: apiKey! }); - }); - - describe('SEO filename generation (real API)', () => { - it('generates SEO filename for Forms topic', async () => { - const result = await generator.generate('Forms'); - - expect(result.filepath).toMatch(/^public\/images\/.+\.png$/); - expect(result.filepath.toLowerCase()).toMatch( - /barrierefreiheit|form|accessibility/ - ); - console.log('Generated filepath:', result.filepath); - }, 30000); - - it('generates SEO filename for Images topic', async () => { - const result = await generator.generate('Images'); - - expect(result.filepath).toMatch(/^public\/images\/.+\.png$/); - console.log('Generated filepath:', result.filepath); - }, 30000); - - it('generates SEO filename for Keyboard and Focus topic', async () => { - const result = await generator.generate('Keyboard and Focus'); - - expect(result.filepath).toMatch(/^public\/images\/.+\.png$/); - console.log('Generated filepath:', result.filepath); - }, 30000); - }); - - describe('Image generation (real API)', () => { - it('generates image for topic', async () => { - const result = await generator.generate('Contrast'); - - expect(result.url).toMatch(/^data:image\/png;base64,.+/); - expect(result.alt).toContain('Barrierefreiheit'); - expect(result.filepath).toMatch(/\.png$/); - - console.log('Generated image:'); - console.log(' - Alt:', result.alt); - console.log(' - Filepath:', result.filepath); - console.log(' - URL length:', result.url.length); - }, 60000); - - it('generates image with title context', async () => { - const result = await generator.generate( - 'Barrierefreie Webformulare gestalten' - ); - - expect(result.url).toMatch(/^data:image\/png;base64,.+/); - expect(result.alt).toContain('Barrierefreie Webformulare gestalten'); - - console.log('Generated with title:'); - console.log(' - Alt:', result.alt); - console.log(' - Filepath:', result.filepath); - }, 60000); - }); - - describe('Alt text generation', () => { - it('generates German alt text', async () => { - const result = await generator.generate('Tables'); - - expect(result.alt).toMatch(/Illustration|Thema|Barrierefreiheit/); - console.log('Alt text:', result.alt); - }, 30000); - }); - - describe('Error handling', () => { - it('handles invalid API key gracefully', async () => { - const badGenerator = new ImageGenerator({ apiKey: 'invalid-key' }); - - await expect(badGenerator.generate('Test')).rejects.toThrow(); - }, 30000); - }); -}); - -describe.skipIf(!apiKey)('SEO Filename Quality', () => { - let generator: ImageGenerator; - - beforeAll(() => { - generator = new ImageGenerator({ apiKey: apiKey! }); - }); - - it('generates unique filenames for different topics', async () => { - const topics = ['Forms', 'Images', 'Tables']; - const filenames: string[] = []; - - for (const topic of topics) { - const result = await generator.generate(topic); - filenames.push(result.filepath); - } - - // All filenames should be unique - const uniqueFilenames = new Set(filenames); - expect(uniqueFilenames.size).toBe(topics.length); - - console.log('Generated unique filenames:'); - filenames.forEach((f, i) => console.log(` ${topics[i]}: ${f}`)); - }, 90000); -}); diff --git a/src/services/ImageGenerator.test.ts b/src/services/ImageGenerator.test.ts deleted file mode 100644 index c47fea9..0000000 --- a/src/services/ImageGenerator.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { ImageGenerator } from './ImageGenerator'; - -// Mock the Google AI clients with proper class implementations -vi.mock('@google/genai', () => { - return { - GoogleGenAI: class MockGoogleGenAI { - models = { - generateImages: vi.fn().mockResolvedValue({ - generatedImages: [ - { - image: { - imageBytes: 'dGVzdC1pbWFnZS1kYXRh', - }, - }, - ], - }), - }; - }, - PersonGeneration: { - DONT_ALLOW: 'DONT_ALLOW', - ALLOW_ADULT: 'ALLOW_ADULT', - ALLOW_ALL: 'ALLOW_ALL', - }, - }; -}); - -vi.mock('@google/generative-ai', () => { - return { - GoogleGenerativeAI: class MockGoogleGenerativeAI { - getGenerativeModel() { - return { - generateContent: vi.fn().mockResolvedValue({ - response: { - text: () => 'barrierefreiheit-formulare-accessible-web-forms', - }, - }), - }; - } - }, - }; -}); - -describe('ImageGenerator', () => { - let generator: ImageGenerator; - - beforeEach(() => { - vi.clearAllMocks(); - generator = new ImageGenerator({ apiKey: 'test-api-key' }); - }); - - describe('constructor', () => { - it('creates instance with default model', () => { - expect(generator).toBeInstanceOf(ImageGenerator); - }); - - it('accepts custom model', () => { - const customGenerator = new ImageGenerator({ - apiKey: 'test-key', - model: 'imagen-4.0-ultra-generate-001', - }); - expect(customGenerator).toBeInstanceOf(ImageGenerator); - }); - }); - - describe('generate', () => { - it('generates image with topic only', async () => { - const result = await generator.generate('Forms'); - - expect(result).toHaveProperty('url'); - expect(result).toHaveProperty('alt'); - expect(result).toHaveProperty('filepath'); - expect(result.url).toContain('data:image/png;base64,'); - expect(result.alt).toContain('Barrierefreiheit'); - }); - - it('generates image with title', async () => { - const result = await generator.generate('Barrierefreie Formulare'); - - expect(result.alt).toContain('Barrierefreie Formulare'); - }); - - it('returns filepath with .webp extension', async () => { - const result = await generator.generate('Images'); - - expect(result.filepath).toMatch(/\.webp$/); - expect(result.filepath).toMatch(/^dist\/client\/images\//); - }); - - it('generates SEO-friendly filename via AI', async () => { - const result = await generator.generate('Forms'); - - expect(result.filepath).toContain('barrierefreiheit'); - }); - }); - - describe('alt text generation', () => { - it('includes topic in alt text', async () => { - const result = await generator.generate('Keyboard and Focus'); - - expect(result.alt).toContain('Keyboard and Focus'); - }); - - it('uses title in alt text', async () => { - const result = await generator.generate('Formulare richtig gestalten'); - - expect(result.alt).toContain('Formulare richtig gestalten'); - }); - }); -}); - -describe('ImageGenerator prompt building', () => { - it('creates prompt with accessibility context', async () => { - const generator = new ImageGenerator({ apiKey: 'test-key' }); - const result = await generator.generate('CAPTCHA'); - - expect(result.url).toBeDefined(); - }); -}); diff --git a/src/services/ImageGenerator.ts b/src/services/ImageGenerator.ts deleted file mode 100644 index 970baeb..0000000 --- a/src/services/ImageGenerator.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { GoogleGenAI, PersonGeneration } from '@google/genai'; -import { GoogleGenerativeAI } from '@google/generative-ai'; -import { Slug } from '../domain/value-objects/Slug'; - -export interface GeneratedImage { - url: string; - alt: string; - filepath: string; - base64?: string; -} - -export interface ImageGeneratorConfig { - apiKey: string; - model?: string; -} - -export class ImageGenerator { - private client: GoogleGenAI; - private textClient: GoogleGenerativeAI; - private model: string; - - constructor(config: ImageGeneratorConfig) { - this.client = new GoogleGenAI({ apiKey: config.apiKey }); - this.textClient = new GoogleGenerativeAI(config.apiKey); - this.model = config.model || 'imagen-4.0-generate-001'; - } - - async generate(title: string): Promise { - const prompt = this.buildPrompt(title); - const filename = await this.generateSeoFilename(title); - const filepath = `dist/client/images/${filename}.webp`; - - try { - const response = await this.client.models.generateImages({ - model: this.model, - prompt: prompt, - config: { - numberOfImages: 1, - aspectRatio: '16:9', - personGeneration: PersonGeneration.DONT_ALLOW, - }, - }); - - if (!response.generatedImages || response.generatedImages.length === 0) { - throw new Error('No image generated'); - } - - const imageData = response.generatedImages[0]; - if (!imageData) { - throw new Error('No image data in response'); - } - const base64 = imageData.image?.imageBytes; - - if (!base64) { - throw new Error('No image data received'); - } - - return { - url: `data:image/png;base64,${base64}`, - alt: this.generateAlt(title), - filepath, - base64, - }; - } catch (error) { - console.error('Image generation error:', error); - throw new Error( - `Image generation failed: ${error instanceof Error ? error.message : 'Unknown error'}` - ); - } - } - - private buildPrompt(title: string): string { - return `Blog header image about "${title}" for a web accessibility article. Minimal Precisionism style inspired by Charles Sheeler: clean geometric shapes, sharp focus, smooth surfaces, no people. IMPORTANT: absolutely no text, no letters, no words, no typography, no labels, no captions anywhere in the image.`; - } - - private generateAlt(title: string): string { - return `Illustration zum Thema ${title} - Barrierefreiheit im Web`; - } - - private async generateSeoFilename(title: string): Promise { - const model = this.textClient.getGenerativeModel({ - model: 'gemini-2.0-flash', - }); - - const prompt = `Generate a single SEO-optimized filename for an image about web accessibility article titled "${title}". -Requirements: -- German and English keywords mixed -- Lowercase, words separated by hyphens -- Max 5-6 words -- No file extension -- Focus on: barrierefreiheit, accessibility, web, and the topic -- Return ONLY the filename, nothing else - -Example for topic "Forms": barrierefreiheit-formulare-accessible-forms`; - - try { - const result = await model.generateContent(prompt); - const filename = result.response - .text() - .trim() - .toLowerCase() - .replace(/[^a-z0-9-]/g, ''); - return filename || Slug.generate(`barrierefreiheit-${title}`); - } catch { - return Slug.generate(`barrierefreiheit-${title}-accessibility`); - } - } -} diff --git a/src/services/PromptService.ts b/src/services/PromptService.ts deleted file mode 100644 index 90390e9..0000000 --- a/src/services/PromptService.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { db, Prompts, SiteSettings, eq } from 'astro:db'; - -export interface CTAConfig { - url: string; - style: string; - prompt: string; -} - -export class PromptService { - async getPrompt(id: string): Promise { - const result = await db - .select() - .from(Prompts) - .where(eq(Prompts.id, id)) - .get(); - return result?.promptText ?? null; - } - - async updatePrompt(id: string, text: string): Promise { - await db - .update(Prompts) - .set({ promptText: text, updatedAt: new Date() }) - .where(eq(Prompts.id, id)); - } - - async getAllPrompts(): Promise< - Array<{ - id: string; - name: string; - category: string; - promptText: string; - }> - > { - return await db.select().from(Prompts); - } - - async getSetting(key: string): Promise { - const result = await db - .select() - .from(SiteSettings) - .where(eq(SiteSettings.key, key)) - .get(); - return result?.value ?? null; - } - - async updateSetting(key: string, value: string): Promise { - await db - .update(SiteSettings) - .set({ value, updatedAt: new Date() }) - .where(eq(SiteSettings.key, key)); - } - - async getAllSettings(): Promise> { - return await db.select().from(SiteSettings); - } - - async getCTAConfig(): Promise { - const [url, style, prompt] = await Promise.all([ - this.getSetting('cta_url'), - this.getSetting('cta_style'), - this.getPrompt('cta_prompt'), - ]); - - return { - url: - url ?? - 'https://nevercodealone.de/de/landingpages/barrierefreies-webdesign', - style: - style ?? - 'Professionell, einladend, mit klarem Nutzenversprechen. Deutsche Sprache.', - prompt: prompt ?? 'Generiere einen einzigartigen Call-to-Action.', - }; - } - - async getCoreTags(): Promise { - const tags = await this.getSetting('core_tags'); - if (!tags) return ['Semantik', 'HTML', 'Barrierefrei']; - try { - return JSON.parse(tags); - } catch { - return ['Semantik', 'HTML', 'Barrierefrei']; - } - } -} diff --git a/src/services/index.ts b/src/services/index.ts deleted file mode 100644 index 8b9535c..0000000 --- a/src/services/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { ContentFetcher, type FetchedContent } from './ContentFetcher'; -export { - ContentGenerator, - type GeneratedContent, - type ContentGeneratorConfig, -} from './ContentGenerator'; -export { FileWriter, type WriteResult } from './FileWriter'; From 6ddb28c19f667bf8a6f3dcb2ebb41abba0cc8083 Mon Sep 17 00:00:00 2001 From: Roland Golla Date: Sat, 21 Feb 2026 23:52:46 +0100 Subject: [PATCH 06/10] bump nca-ai-cms-astro-plugin to ^1.0.0 and update local link path --- .husky/pre-commit | 6 ++--- package-lock.json | 59 +++++++++++++++-------------------------------- package.json | 6 ++--- 3 files changed, 25 insertions(+), 46 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 34c0df8..cf5b959 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,10 +1,10 @@ #!/bin/sh # Auto-fix local plugin reference before commit -# Replaces file:../nca-ai-cms-astro-plugin with ^0.1.0 in package.json -if grep -q '"file:../nca-ai-cms-astro-plugin"' package.json; then +# Replaces file: references with ^1.0.0 in package.json +if grep -q '"file:.*nca-ai-cms-astro-plugin"' package.json; then echo "Fixing local plugin reference in package.json..." - sed -i 's|"file:../nca-ai-cms-astro-plugin"|"^0.1.0"|g' package.json + sed -i 's|"file:[^"]*nca-ai-cms-astro-plugin"|"^1.0.0"|g' package.json git add package.json fi diff --git a/package-lock.json b/package-lock.json index def3566..6057b73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "astro": "^5.16.4", "gray-matter": "^4.0.3", "marked": "^17.0.1", - "nca-ai-cms-astro-plugin": "file:../nca-ai-cms-astro-plugin", + "nca-ai-cms-astro-plugin": "^1.0.0", "react": "^19.2.1", "react-dom": "^19.2.1", "sanitize-html": "^2.17.0", @@ -39,43 +39,6 @@ "vitest": "^4.0.15" } }, - "../nca-ai-cms-astro-plugin": { - "version": "0.1.0", - "devDependencies": { - "@astrojs/db": "^0.18.3", - "@astrojs/react": "^4.4.2", - "@google/genai": "^1.31.0", - "@google/generative-ai": "^0.24.1", - "@types/sanitize-html": "^2.16.0", - "@types/turndown": "^5.0.6", - "astro": "^5.16.4", - "gray-matter": "^4.0.3", - "marked": "^17.0.1", - "react": "^19.2.1", - "react-dom": "^19.2.1", - "sanitize-html": "^2.17.0", - "sharp": "^0.34.5", - "turndown": "^7.2.2", - "typescript": "^5.9.3", - "vitest": "^4.0.15", - "zod": "^3.25.76" - }, - "peerDependencies": { - "@astrojs/db": "^0.18.0", - "@astrojs/react": "^4.0.0", - "@google/genai": "^1.0.0", - "@google/generative-ai": "^0.24.0", - "astro": "^5.0.0", - "gray-matter": "^4.0.0", - "marked": "^17.0.0", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "sanitize-html": "^2.0.0", - "sharp": "^0.34.0", - "turndown": "^7.0.0", - "zod": "^3.0.0" - } - }, "node_modules/@anthropic-ai/sdk": { "version": "0.71.2", "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.71.2.tgz", @@ -5726,8 +5689,24 @@ } }, "node_modules/nca-ai-cms-astro-plugin": { - "resolved": "../nca-ai-cms-astro-plugin", - "link": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nca-ai-cms-astro-plugin/-/nca-ai-cms-astro-plugin-1.0.0.tgz", + "integrity": "sha512-LNV+4B1CB8IRRpmtcrZVTwcGpWfkw/q3GR82bV8koySWCG6IqSx9PqY+EU17wJy7GM0pzpfpbjzUtQ8Xa/acTA==", + "peerDependencies": { + "@astrojs/db": "^0.18.0", + "@astrojs/react": "^4.0.0", + "@google/genai": "^1.0.0", + "@google/generative-ai": "^0.24.0", + "astro": "^5.0.0", + "gray-matter": "^4.0.0", + "marked": "^17.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "sanitize-html": "^2.0.0", + "sharp": "^0.34.0", + "turndown": "^7.0.0", + "zod": "^3.0.0" + } }, "node_modules/neotraverse": { "version": "0.6.18", diff --git a/package.json b/package.json index 142b935..40f9785 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ "typecheck": "astro check && tsc --noEmit", "test:a11y": "playwright test --project=chromium", "test:a11y:report": "playwright test --project=chromium --reporter=html", - "plugin:link": "npm install --save nca-ai-cms-astro-plugin@file:../nca-ai-cms-astro-plugin", - "plugin:unlink": "npm install --save nca-ai-cms-astro-plugin@^0.1.0", + "plugin:link": "npm install --save nca-ai-cms-astro-plugin@file:../../contribute/nca-ai-cms-astro-plugin", + "plugin:unlink": "npm install --save nca-ai-cms-astro-plugin@^1.0.0", "plugin:lint": "node -e \"const p=require('./package.json'); const v=p.dependencies['nca-ai-cms-astro-plugin']||''; if(v.includes('file:')){console.error('ERROR: package.json contains file: reference for nca-ai-cms-astro-plugin. Run npm run plugin:unlink first.');process.exit(1)}else{console.log('OK: plugin dependency is registry version:',v)}\"", "prepare": "husky" }, @@ -31,7 +31,7 @@ "astro": "^5.16.4", "gray-matter": "^4.0.3", "marked": "^17.0.1", - "nca-ai-cms-astro-plugin": "^0.1.0", + "nca-ai-cms-astro-plugin": "^1.0.0", "react": "^19.2.1", "react-dom": "^19.2.1", "sanitize-html": "^2.17.0", From c57a7a48d14e670278d13473567a71b17d1d5fd7 Mon Sep 17 00:00:00 2001 From: Roland Golla Date: Sat, 21 Feb 2026 23:52:52 +0100 Subject: [PATCH 07/10] remove scheduler API route (moved to plugin) --- src/pages/api/scheduler.ts | 44 -------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 src/pages/api/scheduler.ts diff --git a/src/pages/api/scheduler.ts b/src/pages/api/scheduler.ts deleted file mode 100644 index 85c9717..0000000 --- a/src/pages/api/scheduler.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { APIRoute } from 'astro'; -import { SchedulerService } from '../../services/SchedulerService'; -import { AstroSchedulerDBAdapter } from '../../services/SchedulerDBAdapter'; -import { jsonResponse, jsonError } from './_utils'; - -function getService(): SchedulerService { - return new SchedulerService(new AstroSchedulerDBAdapter()); -} - -export const GET: APIRoute = async () => { - try { - const service = getService(); - const posts = await service.list(); - return jsonResponse({ posts }); - } catch (error) { - console.error('Scheduler list error:', error); - return jsonError(error); - } -}; - -export const POST: APIRoute = async ({ request }) => { - try { - const data = await request.json(); - - if (!data.input || !data.scheduledDate) { - return jsonError('input and scheduledDate are required', 400); - } - - const service = getService(); - const post = await service.create({ - input: data.input, - scheduledDate: new Date(data.scheduledDate), - }); - - return jsonResponse({ post }, 201); - } catch (error) { - console.error('Scheduler create error:', error); - const status = - error instanceof Error && error.message.includes('already scheduled') - ? 409 - : 500; - return jsonError(error, status); - } -}; From f23074447bdb294be59bd32c30b14bb564aa1994 Mon Sep 17 00:00:00 2001 From: Roland Golla Date: Sun, 22 Feb 2026 00:08:21 +0100 Subject: [PATCH 08/10] pin nca-ai-cms-astro-plugin to exact version 1.0.1 --- .husky/pre-commit | 4 ++-- package-lock.json | 8 ++++---- package.json | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index cf5b959..7159007 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,10 +1,10 @@ #!/bin/sh # Auto-fix local plugin reference before commit -# Replaces file: references with ^1.0.0 in package.json +# Replaces file: references with pinned version in package.json if grep -q '"file:.*nca-ai-cms-astro-plugin"' package.json; then echo "Fixing local plugin reference in package.json..." - sed -i 's|"file:[^"]*nca-ai-cms-astro-plugin"|"^1.0.0"|g' package.json + sed -i 's|"file:[^"]*nca-ai-cms-astro-plugin"|"1.0.1"|g' package.json git add package.json fi diff --git a/package-lock.json b/package-lock.json index 6057b73..11a0918 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "astro": "^5.16.4", "gray-matter": "^4.0.3", "marked": "^17.0.1", - "nca-ai-cms-astro-plugin": "^1.0.0", + "nca-ai-cms-astro-plugin": "1.0.1", "react": "^19.2.1", "react-dom": "^19.2.1", "sanitize-html": "^2.17.0", @@ -5689,9 +5689,9 @@ } }, "node_modules/nca-ai-cms-astro-plugin": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/nca-ai-cms-astro-plugin/-/nca-ai-cms-astro-plugin-1.0.0.tgz", - "integrity": "sha512-LNV+4B1CB8IRRpmtcrZVTwcGpWfkw/q3GR82bV8koySWCG6IqSx9PqY+EU17wJy7GM0pzpfpbjzUtQ8Xa/acTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nca-ai-cms-astro-plugin/-/nca-ai-cms-astro-plugin-1.0.1.tgz", + "integrity": "sha512-5T32Sba2/kh/B3h/A3DwHk1DX9CQvbiCeM5c4MBJbbND3sTMP3wXPQTMkDPkEb1LJaCoiSFRpSKa/t3dIzZstg==", "peerDependencies": { "@astrojs/db": "^0.18.0", "@astrojs/react": "^4.0.0", diff --git a/package.json b/package.json index 40f9785..e4619e4 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "test:a11y": "playwright test --project=chromium", "test:a11y:report": "playwright test --project=chromium --reporter=html", "plugin:link": "npm install --save nca-ai-cms-astro-plugin@file:../../contribute/nca-ai-cms-astro-plugin", - "plugin:unlink": "npm install --save nca-ai-cms-astro-plugin@^1.0.0", + "plugin:unlink": "npm install --save nca-ai-cms-astro-plugin@1.0.1", "plugin:lint": "node -e \"const p=require('./package.json'); const v=p.dependencies['nca-ai-cms-astro-plugin']||''; if(v.includes('file:')){console.error('ERROR: package.json contains file: reference for nca-ai-cms-astro-plugin. Run npm run plugin:unlink first.');process.exit(1)}else{console.log('OK: plugin dependency is registry version:',v)}\"", "prepare": "husky" }, @@ -31,7 +31,7 @@ "astro": "^5.16.4", "gray-matter": "^4.0.3", "marked": "^17.0.1", - "nca-ai-cms-astro-plugin": "^1.0.0", + "nca-ai-cms-astro-plugin": "1.0.1", "react": "^19.2.1", "react-dom": "^19.2.1", "sanitize-html": "^2.17.0", From 299af566a55898d4bcf0f7ae2b4eb5b81bb65746 Mon Sep 17 00:00:00 2001 From: Roland Golla Date: Sun, 22 Feb 2026 00:41:05 +0100 Subject: [PATCH 09/10] fix CI content path from src/content/articles to nca-ai-cms-content --- .gitlab-ci.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cc6c7b1..69bddae 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,21 +21,15 @@ build: before_script: - docker login -u $CI_REGISTRY_USER -p $CI_JOB_TOKEN $CI_REGISTRY script: - # Backup content from old container (fallback to old path for migration) - - docker cp $SHORTCODE1-web:/app/nca-ai-cms-content /tmp/articles 2>/dev/null || docker cp $SHORTCODE1-web:/app/src/content/articles /tmp/articles 2>/dev/null || true + - docker cp $SHORTCODE1-web:/app/nca-ai-cms-content /tmp/articles 2>/dev/null || true - docker cp $SHORTCODE1-web:/app/.astro /tmp/astro-db 2>/dev/null || true - # Remove old container and start new one - docker compose -f docker-compose.conversis.yml -p $SHORTCODE1 rm -sfv || true - docker compose -f docker-compose.conversis.yml -p $SHORTCODE1 up -d --remove-orphans - # Restore content to new container - docker cp /tmp/articles/. $SHORTCODE1-web:/app/nca-ai-cms-content/ 2>/dev/null || true - docker cp /tmp/astro-db/. $SHORTCODE1-web:/app/.astro/ 2>/dev/null || true - # Fix ownership for non-root user - docker exec -u root $SHORTCODE1-web chown -R astro:nodejs /app/nca-ai-cms-content 2>/dev/null || true - docker exec -u root $SHORTCODE1-web chown -R astro:nodejs /app/.astro 2>/dev/null || true - # Rebuild to pick up restored content (creates DB on first deploy) - docker exec $SHORTCODE1-web npm run build - # Cleanup - rm -rf /tmp/articles /tmp/astro-db 2>/dev/null || true deploy_stage: From e21d03458ddd87ce43570b08e7b234f635df0495 Mon Sep 17 00:00:00 2001 From: Roland Golla Date: Sun, 22 Feb 2026 01:04:45 +0100 Subject: [PATCH 10/10] remove duplicate auth, editor and middleware files now provided by plugin --- src/components/Editor.tsx | 150 ------ src/components/editor/GenerateTab.tsx | 523 ------------------- src/components/editor/PlannerTab.tsx | 486 ------------------ src/components/editor/SettingsTab.tsx | 393 --------------- src/components/editor/styles.ts | 579 ---------------------- src/components/editor/types.ts | 48 -- src/components/editor/useTabNavigation.ts | 36 -- src/layouts/Layout.astro | 2 +- src/middleware.ts | 60 --- src/pages/api/_utils.ts | 20 - src/pages/api/auth-check.ts | 8 - src/pages/api/login.ts | 33 -- src/pages/api/logout.ts | 6 - src/pages/login.astro | 355 ------------- 14 files changed, 1 insertion(+), 2698 deletions(-) delete mode 100644 src/components/Editor.tsx delete mode 100644 src/components/editor/GenerateTab.tsx delete mode 100644 src/components/editor/PlannerTab.tsx delete mode 100644 src/components/editor/SettingsTab.tsx delete mode 100644 src/components/editor/styles.ts delete mode 100644 src/components/editor/types.ts delete mode 100644 src/components/editor/useTabNavigation.ts delete mode 100644 src/middleware.ts delete mode 100644 src/pages/api/_utils.ts delete mode 100644 src/pages/api/auth-check.ts delete mode 100644 src/pages/api/login.ts delete mode 100644 src/pages/api/logout.ts delete mode 100644 src/pages/login.astro diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx deleted file mode 100644 index f1ed86c..0000000 --- a/src/components/Editor.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { useState } from 'react'; -import { styles } from './editor/styles'; -import { useTabNavigation } from './editor/useTabNavigation'; -import PlannerTab from './editor/PlannerTab'; -import SettingsTab from './editor/SettingsTab'; -import { - GenerateTabProvider, - GenerateTabControls, - GenerateTabPreview, -} from './editor/GenerateTab'; - -type TabType = 'generate' | 'planner' | 'settings'; - -const mainTabs = ['generate', 'planner', 'settings'] as const; - -export default function Editor() { - const [activeTab, setActiveTab] = useState('generate'); - const { handleTabKeyDown } = useTabNavigation(); - - const handleLogout = async () => { - await fetch('/api/logout', { method: 'POST' }); - window.location.href = '/login'; - }; - - const isFullWidth = activeTab === 'settings' || activeTab === 'planner'; - - return ( - -
-
-
-

- {activeTab === 'generate' - ? 'Content Generator' - : activeTab === 'planner' - ? 'Content Planer' - : 'Einstellungen'} -

- -
- - {/* Tab Navigation */} -
- - - -
- - {/* Planner Tab */} - {activeTab === 'planner' && ( -
- -
- )} - - {/* Settings Tab */} - {activeTab === 'settings' && ( -
- -
- )} - - {/* Generate Tab - Controls */} - {activeTab === 'generate' && } -
- - {/* Preview Area - only show in generate tab */} - {activeTab === 'generate' && } -
-
- ); -} diff --git a/src/components/editor/GenerateTab.tsx b/src/components/editor/GenerateTab.tsx deleted file mode 100644 index bbc5563..0000000 --- a/src/components/editor/GenerateTab.tsx +++ /dev/null @@ -1,523 +0,0 @@ -import { useState, createContext, useContext } from 'react'; -import type { ReactNode } from 'react'; -import { styles } from './styles'; -import type { GeneratedArticle, GeneratedImage } from './types'; - -interface GenerateTabState { - input: string; - error: string | null; - article: GeneratedArticle | null; - image: GeneratedImage | null; - generating: boolean; - regeneratingText: boolean; - regeneratingImage: boolean; - publishing: boolean; - published: boolean; - handleInputChange: (value: string) => void; - getInputType: () => 'url' | 'keywords' | 'empty'; - canGenerate: () => boolean; - handleGenerateAll: () => Promise; - handleRegenerateText: () => Promise; - handleRegenerateImage: () => Promise; - handlePublish: () => Promise; - handleCreateNew: () => void; - hasContent: boolean; - isLoading: boolean; - getStatusText: () => string; -} - -const GenerateTabContext = createContext(null); - -function useGenerateTabContext(): GenerateTabState { - const ctx = useContext(GenerateTabContext); - if (!ctx) - throw new Error( - 'GenerateTab compound components must be used within GenerateTabProvider' - ); - return ctx; -} - -export function GenerateTabProvider({ children }: { children: ReactNode }) { - const [input, setInput] = useState(''); - const [error, setError] = useState(null); - const [article, setArticle] = useState(null); - const [image, setImage] = useState(null); - const [generating, setGenerating] = useState(false); - const [regeneratingText, setRegeneratingText] = useState(false); - const [regeneratingImage, setRegeneratingImage] = useState(false); - const [publishing, setPublishing] = useState(false); - const [published, setPublished] = useState(false); - - const handleInputChange = (value: string) => { - setInput(value); - setArticle(null); - setImage(null); - setPublished(false); - setError(null); - }; - - const isUrl = (value: string): boolean => { - try { - new URL(value); - return true; - } catch { - return false; - } - }; - - const getInputType = (): 'url' | 'keywords' | 'empty' => { - if (!input.trim()) return 'empty'; - return isUrl(input.trim()) ? 'url' : 'keywords'; - }; - - const canGenerate = () => { - return input.trim().length > 0; - }; - - const generateText = async (): Promise => { - const type = getInputType(); - const response = await fetch('/api/generate-content', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify( - type === 'url' ? { url: input.trim() } : { keywords: input.trim() } - ), - }); - - if (!response.ok) { - const data = await response.json(); - throw new Error(data.error || 'Text generation failed'); - } - - return response.json(); - }; - - const generateImage = async ( - title: string - ): Promise => { - const response = await fetch('/api/generate-image', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ title }), - }); - - if (!response.ok) { - const data = await response.json(); - throw new Error(data.error || 'Image generation failed'); - } - - return response.json(); - }; - - const handleGenerateAll = async () => { - if (!canGenerate()) return; - - setGenerating(true); - setError(null); - setArticle(null); - setImage(null); - - try { - const textData = await generateText(); - setArticle(textData); - - if (textData?.title) { - const imageData = await generateImage(textData.title); - setImage(imageData); - } - } catch (err) { - setError(err instanceof Error ? err.message : 'Generation failed'); - } finally { - setGenerating(false); - } - }; - - const handleRegenerateText = async () => { - if (!canGenerate()) return; - - setRegeneratingText(true); - setError(null); - - try { - const data = await generateText(); - setArticle(data); - } catch (err) { - setError(err instanceof Error ? err.message : 'Text generation failed'); - } finally { - setRegeneratingText(false); - } - }; - - const handleRegenerateImage = async () => { - if (!article?.title) return; - - setRegeneratingImage(true); - setError(null); - - try { - const data = await generateImage(article.title); - setImage(data); - } catch (err) { - setError(err instanceof Error ? err.message : 'Image generation failed'); - } finally { - setRegeneratingImage(false); - } - }; - - const handlePublish = async () => { - if (!article || !image) return; - - setPublishing(true); - setError(null); - - try { - const articleData = { - ...article, - imageAlt: image.alt, - }; - - const articleResponse = await fetch('/api/save', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(articleData), - }); - - if (!articleResponse.ok) { - const data = await articleResponse.json(); - throw new Error(data.error || 'Article save failed'); - } - - const { folderPath } = await articleResponse.json(); - - const imageResponse = await fetch('/api/save-image', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ url: image.url, folderPath }), - }); - - if (!imageResponse.ok) { - const data = await imageResponse.json(); - throw new Error(data.error || 'Image save failed'); - } - - setPublished(true); - } catch (err) { - setError(err instanceof Error ? err.message : 'Publish failed'); - } finally { - setPublishing(false); - } - }; - - const handleCreateNew = () => { - setInput(''); - setArticle(null); - setImage(null); - setPublished(false); - setError(null); - }; - - const hasContent = article !== null; - const isLoading = - generating || regeneratingText || regeneratingImage || publishing; - - const getStatusText = (): string => { - if (generating) return 'Generiere Bild und Text...'; - if (regeneratingText) return 'Generiere Text neu...'; - if (regeneratingImage) return 'Generiere Bild neu...'; - if (publishing) return 'Veröffentliche Artikel...'; - if (published) return 'Artikel erfolgreich veröffentlicht.'; - if (error) return `Fehler: ${error}`; - if (hasContent) return 'Inhalt wurde generiert.'; - return ''; - }; - - const value: GenerateTabState = { - input, - error, - article, - image, - generating, - regeneratingText, - regeneratingImage, - publishing, - published, - handleInputChange, - getInputType, - canGenerate, - handleGenerateAll, - handleRegenerateText, - handleRegenerateImage, - handlePublish, - handleCreateNew, - hasContent, - isLoading, - getStatusText, - }; - - return ( - - {children} - - ); -} - -export function GenerateTabControls() { - const { - input, - error, - article, - published, - generating, - regeneratingText, - regeneratingImage, - publishing, - image, - handleInputChange, - getInputType, - canGenerate, - handleGenerateAll, - handleRegenerateText, - handleRegenerateImage, - handlePublish, - handleCreateNew, - hasContent, - isLoading, - getStatusText, - } = useGenerateTabContext(); - - return ( - <> -
- {getStatusText()} -
-
- {/* Published State */} - {published ? ( -
-
- - - - -
-

Artikel veröffentlicht!

-

{article?.filepath}

- -
- ) : ( - <> - {/* Input Field */} -
- - handleInputChange(e.target.value)} - placeholder="https://... oder Keywords eingeben" - style={styles.input} - disabled={isLoading} - /> - - {getInputType() === 'url' - ? 'URL erkannt – Inhalt wird analysiert' - : getInputType() === 'keywords' - ? 'Keywords erkannt – Recherche wird durchgeführt' - : 'URL für Analyse oder Keywords für Recherche'} - -
- - {/* Generate Button - before content exists */} - {canGenerate() && !hasContent && ( - - )} - - {/* After content generated: Regenerate + Publish */} - {hasContent && ( -
-
- - -
- -
- )} - - )} - - {error && ( -
- {error} -
- )} -
- - ); -} - -export function GenerateTabPreview() { - const { image, article, published } = useGenerateTabContext(); - - return ( -
- {image && ( -
-
-

- - - - - - Bild - {published && ( - Veröffentlicht - )} -

- {image.filepath} -
-
- {image.alt} -

{image.alt}

-
-
- )} - - {article && ( -
-
-

- - - - - - - Text - {published && ( - Veröffentlicht - )} -

- {article.filepath} -
-
-
- Titel - {article.title} -
-
- Beschreibung - {article.description} -
-
-
-
{article.content}
-
-
- )} -
- ); -} diff --git a/src/components/editor/PlannerTab.tsx b/src/components/editor/PlannerTab.tsx deleted file mode 100644 index 17996b6..0000000 --- a/src/components/editor/PlannerTab.tsx +++ /dev/null @@ -1,486 +0,0 @@ -import { useState, useEffect } from 'react'; -import { styles } from './styles'; -import type { ScheduledPostData } from './types'; - -export default function PlannerTab() { - const [scheduledPosts, setScheduledPosts] = useState([]); - const [plannerLoading, setPlannerLoading] = useState(false); - const [plannerError, setPlannerError] = useState(null); - const [plannerInput, setPlannerInput] = useState(''); - const [plannerDate, setPlannerDate] = useState(''); - const [generatingId, setGeneratingId] = useState(null); - const [generatingMode, setGeneratingMode] = useState(null); - const [publishingId, setPublishingId] = useState(null); - const [autoPublishing, setAutoPublishing] = useState(false); - - useEffect(() => { - loadPlanner(); - }, []); - - const loadPlanner = async () => { - setPlannerLoading(true); - setPlannerError(null); - try { - // Auto-publish due posts on load - setAutoPublishing(true); - const autoResponse = await fetch('/api/scheduler/publish', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ mode: 'auto' }), - }); - setAutoPublishing(false); - - if (!autoResponse.ok) { - setPlannerError('Auto-Veröffentlichung fehlgeschlagen'); - } else { - const autoData = await autoResponse.json(); - if (autoData.failed && autoData.failed.length > 0) { - const failedIds = autoData.failed.map((f: { id: string }) => f.id).join(', '); - setPlannerError(`Auto-Veröffentlichung fehlgeschlagen für: ${failedIds}`); - } - } - - const response = await fetch('/api/scheduler'); - if (!response.ok) throw new Error('Failed to load planner'); - const data = await response.json(); - setScheduledPosts(data.posts || []); - } catch (err) { - setPlannerError( - err instanceof Error ? err.message : 'Failed to load planner' - ); - } finally { - setPlannerLoading(false); - setAutoPublishing(false); - } - }; - - const handleAddScheduledPost = async () => { - if (!plannerInput.trim() || !plannerDate) return; - setPlannerError(null); - try { - const response = await fetch('/api/scheduler', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - input: plannerInput.trim(), - scheduledDate: plannerDate, - }), - }); - if (!response.ok) { - const data = await response.json(); - throw new Error(data.error || 'Failed to add entry'); - } - setPlannerInput(''); - setPlannerDate(''); - await loadPlanner(); - } catch (err) { - setPlannerError( - err instanceof Error ? err.message : 'Failed to add entry' - ); - } - }; - - const handleDeleteScheduledPost = async (id: string) => { - setPlannerError(null); - try { - const response = await fetch(`/api/scheduler/${id}`, { - method: 'DELETE', - }); - if (!response.ok) { - const data = await response.json(); - throw new Error(data.error || 'Failed to delete entry'); - } - await loadPlanner(); - } catch (err) { - setPlannerError( - err instanceof Error ? err.message : 'Failed to delete entry' - ); - } - }; - - const handleGenerateScheduledPost = async ( - id: string, - mode: 'all' | 'text' | 'image' = 'all' - ) => { - setGeneratingId(id); - setGeneratingMode(mode); - setPlannerError(null); - try { - const response = await fetch('/api/scheduler/generate', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ id, mode }), - }); - if (!response.ok) { - const data = await response.json(); - throw new Error(data.error || 'Generation failed'); - } - await loadPlanner(); - } catch (err) { - setPlannerError(err instanceof Error ? err.message : 'Generation failed'); - } finally { - setGeneratingId(null); - setGeneratingMode(null); - } - }; - - const handlePublishScheduledPost = async (id: string) => { - setPublishingId(id); - setPlannerError(null); - try { - const response = await fetch('/api/scheduler/publish', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ id }), - }); - if (!response.ok) { - const data = await response.json(); - throw new Error(data.error || 'Publish failed'); - } - await loadPlanner(); - } catch (err) { - setPlannerError(err instanceof Error ? err.message : 'Publish failed'); - } finally { - setPublishingId(null); - } - }; - - const getStatusBadge = ( - status: string - ): { label: string; color: string; bg: string } => { - switch (status) { - case 'pending': - return { - label: 'Geplant', - color: '#f59e0b', - bg: 'rgba(245, 158, 11, 0.12)', - }; - case 'generated': - return { - label: 'Generiert', - color: '#3b82f6', - bg: 'rgba(59, 130, 246, 0.12)', - }; - case 'published': - return { - label: 'Veröffentlicht', - color: '#4ade80', - bg: 'rgba(74, 222, 128, 0.12)', - }; - default: - return { - label: status, - color: '#9a9590', - bg: 'rgba(154, 149, 144, 0.12)', - }; - } - }; - - const formatDate = (dateStr: string): string => { - const d = new Date(dateStr); - return d.toLocaleDateString('de-DE', { - day: '2-digit', - month: '2-digit', - year: 'numeric', - }); - }; - - if (plannerLoading) { - return ( -
- - - {autoPublishing - ? 'Veröffentliche fällige Beiträge...' - : 'Lade Planer...'} - -
- ); - } - - return ( -
- {plannerError && ( -
- {plannerError} -
- )} - - {/* Add new entry form */} -
-
-
- - setPlannerInput(e.target.value)} - placeholder="https://... oder Keywords eingeben" - style={styles.input} - /> -
-
- - setPlannerDate(e.target.value)} - style={styles.input} - /> -
-
- -
-
-
- - {/* Post list */} - {scheduledPosts.length === 0 ? ( -
- Noch keine Beiträge geplant. Füge oben einen neuen Eintrag hinzu. -
- ) : ( -
- {scheduledPosts.map((post) => { - const badge = getStatusBadge(post.status); - const isGenerating = generatingId === post.id; - const isPublishing = publishingId === post.id; - const isBusy = isGenerating || isPublishing; - - return ( -
-
-
- - {formatDate(post.scheduledDate)} - - - {badge.label} - - - {post.inputType === 'url' ? 'URL' : 'Keywords'} - -
-
- {post.status === 'pending' && ( - - )} - {post.status === 'generated' && ( - <> - - - - Vorschau - - - - )} - {post.status !== 'published' && ( - - )} -
-
- -
-
{post.input}
- - {/* Preview generated image */} - {post.generatedImageData && ( -
- {post.generatedImageAlt - {post.generatedImageAlt && ( -

{post.generatedImageAlt}

- )} -
- )} - - {/* Preview generated content */} - {post.generatedTitle && ( -
-
- Titel - - {post.generatedTitle} - -
- {post.generatedDescription && ( -
- - Beschreibung - - - {post.generatedDescription} - -
- )} -
- )} - - {post.publishedPath && ( -
- {post.publishedPath} -
- )} -
-
- ); - })} -
- )} -
- ); -} diff --git a/src/components/editor/SettingsTab.tsx b/src/components/editor/SettingsTab.tsx deleted file mode 100644 index 28352d7..0000000 --- a/src/components/editor/SettingsTab.tsx +++ /dev/null @@ -1,393 +0,0 @@ -import { useState, useEffect } from 'react'; -import { styles } from './styles'; -import { useTabNavigation } from './useTabNavigation'; -import type { Prompt, Setting, SettingsSubTab } from './types'; - -const SETTINGS_GROUPS: { - key: SettingsSubTab; - label: string; - items: { type: 'setting' | 'prompt'; id: string }[]; -}[] = [ - { - key: 'homepage', - label: 'Homepage', - items: [ - { type: 'setting', id: 'hero_kicker' }, - { type: 'setting', id: 'hero_title' }, - { type: 'setting', id: 'hero_title_accent' }, - { type: 'setting', id: 'hero_description' }, - ], - }, - { - key: 'content-ai', - label: 'Content-KI', - items: [ - { type: 'prompt', id: 'system_prompt' }, - { type: 'prompt', id: 'user_prompt_template' }, - { type: 'prompt', id: 'cta_prompt' }, - ], - }, - { - key: 'analysis-ai', - label: 'Analyse-KI', - items: [ - { type: 'prompt', id: 'source_analysis' }, - { type: 'prompt', id: 'keyword_research' }, - ], - }, - { - key: 'image-ai', - label: 'Bild-KI', - items: [ - { type: 'prompt', id: 'image_prompt' }, - { type: 'prompt', id: 'filename_prompt' }, - { type: 'prompt', id: 'alt_text_template' }, - ], - }, - { - key: 'website', - label: 'Website', - items: [ - { type: 'setting', id: 'cta_url' }, - { type: 'setting', id: 'cta_style' }, - { type: 'setting', id: 'core_tags' }, - { type: 'setting', id: 'imprint_content' }, - ], - }, -]; - -export default function SettingsTab() { - const [prompts, setPrompts] = useState([]); - const [settings, setSettings] = useState([]); - const [settingsLoading, setSettingsLoading] = useState(false); - const [settingsError, setSettingsError] = useState(null); - const [savingId, setSavingId] = useState(null); - const [editingPrompt, setEditingPrompt] = useState(null); - const [editingSetting, setEditingSetting] = useState(null); - const [editValues, setEditValues] = useState>({}); - const [activeSettingsTab, setActiveSettingsTab] = - useState('homepage'); - - const { handleTabKeyDown } = useTabNavigation(); - - useEffect(() => { - loadSettings(); - }, []); - - const loadSettings = async () => { - setSettingsLoading(true); - setSettingsError(null); - try { - const response = await fetch('/api/prompts'); - if (!response.ok) { - throw new Error('Failed to load settings'); - } - const data = await response.json(); - setPrompts(data.prompts || []); - setSettings(data.settings || []); - } catch (err) { - setSettingsError( - err instanceof Error ? err.message : 'Failed to load settings' - ); - } finally { - setSettingsLoading(false); - } - }; - - const handleEditPrompt = (id: string, currentValue: string) => { - setEditingPrompt(id); - setEditValues({ ...editValues, [id]: currentValue }); - }; - - const handleEditSetting = (key: string, currentValue: string) => { - setEditingSetting(key); - setEditValues({ ...editValues, [key]: currentValue }); - }; - - const handleCancelEdit = () => { - setEditingPrompt(null); - setEditingSetting(null); - }; - - const handleSavePrompt = async (id: string) => { - setSavingId(id); - try { - const response = await fetch('/api/prompts', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - type: 'prompt', - id, - promptText: editValues[id], - }), - }); - if (!response.ok) { - throw new Error('Failed to save prompt'); - } - setPrompts( - prompts.map((p) => - p.id === id ? { ...p, promptText: editValues[id] ?? p.promptText } : p - ) - ); - setEditingPrompt(null); - } catch (err) { - setSettingsError( - err instanceof Error ? err.message : 'Failed to save prompt' - ); - } finally { - setSavingId(null); - } - }; - - const handleSaveSetting = async (key: string) => { - setSavingId(key); - try { - const response = await fetch('/api/prompts', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - type: 'setting', - key, - value: editValues[key], - }), - }); - if (!response.ok) { - throw new Error('Failed to save setting'); - } - setSettings( - settings.map((s) => - s.key === key ? { ...s, value: editValues[key] ?? s.value } : s - ) - ); - setEditingSetting(null); - } catch (err) { - setSettingsError( - err instanceof Error ? err.message : 'Failed to save setting' - ); - } finally { - setSavingId(null); - } - }; - - const getSettingLabel = (key: string): string => { - const labels: Record = { - hero_kicker: 'Hero Kicker', - hero_title: 'Hero Titel', - hero_title_accent: 'Hero Titel Akzent', - hero_description: 'Hero Beschreibung', - cta_url: 'CTA URL', - cta_style: 'CTA Stil', - core_tags: 'Core Tags (JSON)', - imprint_content: 'Impressum (Markdown)', - }; - return labels[key] || key; - }; - - const getPromptLabel = (id: string): string => { - const labels: Record = { - system_prompt: 'System Prompt', - user_prompt_template: 'User Prompt Vorlage', - source_analysis: 'Quellen-Analyse', - keyword_research: 'Keyword-Recherche', - cta_prompt: 'CTA Generierung', - image_prompt: 'Bild-Prompt', - filename_prompt: 'Dateiname-Prompt', - alt_text_template: 'Alt-Text Vorlage', - }; - return labels[id] || id; - }; - - const settingsTabKeys = SETTINGS_GROUPS.map( - (g) => g.key - ) as readonly string[]; - - const renderSettingsCard = (item: { - type: 'setting' | 'prompt'; - id: string; - }) => { - if (item.type === 'prompt') { - const prompt = prompts.find((p) => p.id === item.id); - if (!prompt) return null; - const isEditing = editingPrompt === prompt.id; - - return ( -
-
- - {getPromptLabel(prompt.id)} - - {prompt.category} -
- {isEditing ? ( -
-