From ee86fa5df7919c1d61da3a22474c33d8890bd630 Mon Sep 17 00:00:00 2001 From: "Sebastian BURGIN-FIX (ext)" Date: Thu, 22 Oct 2026 21:40:47 +0200 Subject: [PATCH 1/2] feat: the API serves the widget, npm package dropped - GET /widget/v1/feedback.js and its lazy chunks/ are served by Laravel with Access-Control-Allow-Origin: *, Cross-Origin-Resource-Policy and ETag. The deploy build used to copy them to public/, where the web server answered without CORS and screenshots failed on other websites. - npm run build compiles the widget straight into resources/widget/v1; publish-api.mjs, the release workflow, type definitions, sri.json, publint and attw are gone. The widget package is private. - README: one script tag from the API instead of npm, loading options, Laravel example, CSP, deployment build step. - Tests: Pest for headers, caching, 304 and path restrictions; full stack E2E loads the widget by script tag from the API and sends a screenshot. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 72 -- .github/workflows/widget.yml | 2 - .gitignore | 1 + CHANGELOG.md | 2 + README.md | 73 +- SECURITY.md | 2 +- .../Controllers/WidgetAssetController.php | 42 + config/feedback.php | 12 + e2e/feedback.e2e.ts | 26 + e2e/fixtures/script.html | 26 + package.json | 4 +- routes/api.php | 6 + tests/Feature/HttpTest.php | 2 +- tests/Feature/WidgetAssetTest.php | 70 ++ widget/LICENSE | 21 - widget/README.md | 37 - widget/package-lock.json | 729 +----------------- widget/package.json | 52 +- widget/scripts/build.mjs | 37 +- widget/scripts/publish-api.mjs | 14 - widget/tsconfig.build.json | 12 - 21 files changed, 253 insertions(+), 989 deletions(-) delete mode 100644 .github/workflows/release.yml create mode 100644 app/Http/Controllers/WidgetAssetController.php create mode 100644 e2e/fixtures/script.html create mode 100644 tests/Feature/WidgetAssetTest.php delete mode 100644 widget/LICENSE delete mode 100644 widget/README.md delete mode 100644 widget/scripts/publish-api.mjs delete mode 100644 widget/tsconfig.build.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 62d637c..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Release - -on: - push: - tags: ['v*'] - -permissions: - contents: read - -concurrency: - group: release-${{ github.ref }} - cancel-in-progress: false - -jobs: - release: - name: Publish widget and GitHub release - runs-on: ubuntu-24.04 - timeout-minutes: 15 - permissions: - contents: write - id-token: write - defaults: - run: - working-directory: widget - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 - with: - node-version: 26 - cache: npm - cache-dependency-path: widget/package-lock.json - registry-url: https://registry.npmjs.org - - - name: Check tag matches package version - run: test "v$(node -p "require('./package.json').version")" = "${GITHUB_REF_NAME}" - - - run: npm ci - - run: npm run check - - # Trusted publishing (OIDC, no token) once the package is linked to this - # workflow on npmjs.com. Only the very first publish needs the NPM_TOKEN - # secret, because a trusted publisher can only be added to an existing - # package. Remove the secret afterwards. Published versions are skipped. - - name: Publish to npm - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: | - name="$(node -p "require('./package.json').name")" - version="$(node -p "require('./package.json').version")" - if npm view "${name}@${version}" version >/dev/null 2>&1; then - echo "${name}@${version} is already published, skipping." - else - npm publish --access public --provenance - fi - - - name: GitHub release with changelog and SRI hash - env: - GH_TOKEN: ${{ github.token }} - TAG: ${{ github.ref_name }} - run: | - version="${TAG#v}" - notes="$(awk -v v="$version" '$0 ~ "^## \\[" v "\\]" {f=1; next} /^## \[/ {f=0} f' ../CHANGELOG.md)" - sri="$(node -p "require('./dist/sri.json')['feedback.iife.js']")" - fence="$(printf '\140\140\140')" - { - printf '%s\n\n### Script-Tag\n\n%shtml\n' "$notes" "$fence" - printf '\n%s\n' "$sri" "$fence" - } > notes.md - gh release create "$TAG" dist/feedback.iife.js dist/feedback.js dist/sri.json --title "$TAG" --notes-file notes.md diff --git a/.github/workflows/widget.yml b/.github/workflows/widget.yml index 8ea3e9c..176ae94 100644 --- a/.github/workflows/widget.yml +++ b/.github/workflows/widget.yml @@ -41,5 +41,3 @@ jobs: - run: npm run test:coverage - run: npm run build - run: npm run size - - run: npm run publint - - run: npm run attw diff --git a/.gitignore b/.gitignore index ab6dca1..e7338bb 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ Thumbs.db /e2e/test-results /e2e/playwright-report /public/widget +/resources/widget diff --git a/CHANGELOG.md b/CHANGELOG.md index 265b78d..8d053d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,13 @@ All notable changes to the API and the widget are documented here. The format fo ### Changed +- The widget is no longer published to npm: the API serves it at `/widget/v1/feedback.js` (one script tag, no npm, no CDN, updated with every deploy). `npm run build` compiles it into `resources/widget/v1`. The npm package `@codebar-ag/feedback-widget` 1.0.0 stays available but gets no updates. Removed: release workflow, type definitions, SRI file, publint and attw. - The widget only appears on pages the API accepts: it checks the project config first and stays hidden on a `403` (origin or IP not allowed). Refused `GET` requests now carry `Access-Control-Allow-Origin` so the widget can read the `403`; the preflight for sending stays refused. - Dev dependencies: vite 8.3.5, typescript-eslint 8.72.0. ### Fixed +- Screenshots with the script tag from the API: the lazy screenshot chunk was served without `Access-Control-Allow-Origin`, so other websites could not load it. - Screenshots of pages using Alpine, Vue or other framework attributes (`:class`, `x-on:click`) were blank. ## [1.0.0] - 2026-10-16 diff --git a/README.md b/README.md index 1940d21..a856a12 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@

Tests E2E - npm PHP 8.5 Laravel 13 MIT License @@ -42,14 +41,14 @@ **For your stack** -- **Any website or app**: npm package or a single script tag, React, Vue, Alpine, Livewire, plain HTML. Isolated in a Shadow DOM, 18 KB gzip, no runtime dependencies +- **Any website or app**: one script tag, served by your own API. React, Vue, Alpine, Livewire, plain HTML, no npm and no build step on the website. Isolated in a Shadow DOM, 18 KB gzip, no runtime dependencies. Updates arrive with every API deploy - **Many projects, many environments**: one API serves any number of projects, each with several origins (wildcards and `localhost:*` included), optional IP ranges, its own color and daily limit - **Privacy first**: mask elements with `data-feedback-mask`, password fields are always hidden, console logs are redacted in the browser - **Your data, your server**: a small Laravel API with origin and IP allowlists, rate limits and a honeypot. Screenshots are stored in your database (SQLite, MySQL or PostgreSQL) ## How it works -1. The widget runs on your website, in a Shadow DOM that leaves your styles untouched. +1. Your website loads the widget from your API with one script tag. It runs in a Shadow DOM that leaves your styles untouched. 2. A visitor writes feedback and can attach a screenshot of the whole page. In marking mode they draw rectangles or highlight text:

@@ -60,7 +59,7 @@ 4. The API checks origin, IP and rate limits, stores everything in your database and answers with a signed link to the feedback, which the thank you screen offers to copy. 5. Your team works through the feedback with Artisan commands or shares a [feedback page link](#feedback-pages-and-share-links) with whoever fixes it, who can mark it as completed right there. -The repository contains both parts: the **API**, a Laravel 13 app in the root folder, and the **widget** in [`widget/`](widget), published as [`@codebar-ag/feedback-widget`](https://www.npmjs.com/package/@codebar-ag/feedback-widget). +The repository contains both parts: the **API**, a Laravel 13 app in the root folder, and the **widget** in [`widget/`](widget), which the deploy build compiles and the API serves at `/widget/v1/feedback.js`. ## Quick start @@ -92,21 +91,23 @@ php artisan feedback:project:create "Example Website" --origin=https://www.examp Daily limit ........................................................... (no limit) ``` -**3. Add the widget to your website** +**3. Build the widget** ```bash -npm install @codebar-ag/feedback-widget +npm ci && npm run build # Node 26, compiles the widget into resources/widget/v1 ``` -```ts -import { initFeedback } from '@codebar-ag/feedback-widget'; +**4. Add it to your website** -await initFeedback({ - projectKey: 'fbk_live_92T7QQB6fj6Zb37jBGzrDI', - endpoint: 'https://feedback.example.com', -}); +```html + + ``` +That is all: no npm package, no bundler, no third-party CDN. The script always matches your API version. + ## Widget ### Options @@ -114,7 +115,7 @@ await initFeedback({ Only `projectKey` and `endpoint` are required. ```ts -const feedback = await initFeedback({ +const feedback = await CodebarFeedback.init({ projectKey: 'fbk_live_92T7QQB6fj6Zb37jBGzrDI', endpoint: 'https://feedback.example.com', @@ -144,7 +145,7 @@ const feedback = await initFeedback({ }); ``` -Call `initFeedback()` as early as possible: console errors are captured from that moment on. Calling it twice returns the same instance. +Call `CodebarFeedback.init()` as early as possible: console errors are captured from that moment on. Calling it twice returns the same instance. ### Methods and events @@ -177,26 +178,39 @@ Events: `ready`, `open`, `close`, `before_send`, `after_send`, `error`. Password fields and `autocomplete="cc-*"` fields are always masked. Images from other hosts without CORS headers and cross-origin iframes render empty. If rendering fails, the feedback is sent without a screenshot. -### Script tag +### Loading the script + +The API serves the widget at `/widget/v1/feedback.js` (cached 5 minutes) and loads the screenshot code lazily from `/widget/v1/chunks/…` (cached for a year, the file name changes with every build). Both are sent with `Access-Control-Allow-Origin: *`, so they work on any website. A new widget version reaches every website with the next API deploy. -No build step needed: +**Without blocking the page**, start it when the script has loaded: ```html - - + ``` -Each [release](https://github.com/codebar-ag/feedback.codebar.ch/releases) lists the SRI hash for `integrity="…"`. +**From your own JavaScript** (Vite, webpack, a strict CSP without inline handlers), load it on demand, for example after the first interaction: + +```js +function loadFeedback(endpoint) { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.src = `${endpoint}/widget/v1/feedback.js`; + script.onload = () => resolve(window.CodebarFeedback); + script.onerror = reject; + document.head.append(script); + }); +} + +const CodebarFeedback = await loadFeedback('https://feedback.example.com'); +const feedback = await CodebarFeedback.init({ projectKey: 'fbk_live_…', endpoint: 'https://feedback.example.com' }); +``` ### Laravel example ```blade - + + + diff --git a/package.json b/package.json index 63c8e1d..eab0cdd 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "codebar-feedback", "private": true, - "description": "Build entry for deployments: installs and builds the widget and copies it to public/widget/v1.", + "description": "Build entry for deployments: installs and builds the widget, which the API serves at /widget/v1/feedback.js.", "license": "MIT", "engines": { "node": ">=26" }, "scripts": { "postinstall": "npm --prefix widget ci --audit false --fund false", - "build": "npm --prefix widget run build && npm --prefix widget run publish:api" + "build": "npm --prefix widget run build" } } diff --git a/routes/api.php b/routes/api.php index 5552642..dc33833 100644 --- a/routes/api.php +++ b/routes/api.php @@ -8,6 +8,7 @@ use App\Http\Controllers\HealthController; use App\Http\Controllers\ProjectConfigController; use App\Http\Controllers\StoreFeedbackController; +use App\Http\Controllers\WidgetAssetController; use App\Http\Middleware\ApplyProjectCors; use App\Http\Middleware\EnforceIpPolicy; use App\Http\Middleware\EnforceOriginPolicy; @@ -18,6 +19,11 @@ Route::get('/up', HealthController::class)->name('health'); +// Script tag build of the widget, for websites without npm. +Route::get('widget/v1/{path}', WidgetAssetController::class) + ->where('path', 'feedback\.js|chunks/capture-[A-Za-z0-9_-]+\.js') + ->name('widget.asset'); + Route::prefix('v1/projects/{publicKey}') ->middleware([ ResolveProject::class, diff --git a/tests/Feature/HttpTest.php b/tests/Feature/HttpTest.php index 849813b..ff479c9 100644 --- a/tests/Feature/HttpTest.php +++ b/tests/Feature/HttpTest.php @@ -52,7 +52,7 @@ it('exposes no web or auth routes', function (): void { $uris = collect(app('router')->getRoutes()->getRoutes())->map->uri()->sort()->values()->all(); - expect($uris)->toBe(['oh-dear-health-check-results', 'up', 'v1/feedback/{entry}', 'v1/feedback/{entry}/screenshot', 'v1/feedback/{entry}/status', 'v1/projects/{publicKey}/config', 'v1/projects/{publicKey}/feedback', 'v1/projects/{publicKey}/feedback']); + expect($uris)->toBe(['oh-dear-health-check-results', 'up', 'v1/feedback/{entry}', 'v1/feedback/{entry}/screenshot', 'v1/feedback/{entry}/status', 'v1/projects/{publicKey}/config', 'v1/projects/{publicKey}/feedback', 'v1/projects/{publicKey}/feedback', 'widget/v1/{path}']); }); it('seeds without errors (deploys run migrate --seed)', function (): void { diff --git a/tests/Feature/WidgetAssetTest.php b/tests/Feature/WidgetAssetTest.php new file mode 100644 index 0000000..627a934 --- /dev/null +++ b/tests/Feature/WidgetAssetTest.php @@ -0,0 +1,70 @@ +dir = sys_get_temp_dir().'/widget-'.bin2hex(random_bytes(4)); + File::ensureDirectoryExists($this->dir.'/chunks'); + File::put($this->dir.'/feedback.js', 'var CodebarFeedback={};'); + File::put($this->dir.'/chunks/capture-Ab12_-x.js', 'export const capturePage=1;'); + File::put($this->dir.'/secret.php', ' $this->dir]); +}); + +afterEach(function (): void { + File::deleteDirectory($this->dir); +}); + +it('serves the widget script to any website', function (): void { + test()->get('/widget/v1/feedback.js', ['Origin' => 'https://web.atrion.test', 'Sec-Fetch-Dest' => 'script', 'Sec-Fetch-Mode' => 'no-cors']) + ->assertOk() + ->assertContent('var CodebarFeedback={};') + ->assertHeader('Content-Type', 'text/javascript; charset=utf-8') + ->assertHeader('Cache-Control', 'max-age=300, public') + ->assertHeader('Access-Control-Allow-Origin', '*') + ->assertHeader('Cross-Origin-Resource-Policy', 'cross-origin') + ->assertHeader('X-Content-Type-Options', 'nosniff') + ->assertHeader('ETag'); +}); + +it('serves the hashed screenshot chunk with CORS and a long cache', function (): void { + // Loaded with import(): a CORS request, it fails without Access-Control-Allow-Origin. + test()->get('/widget/v1/chunks/capture-Ab12_-x.js', ['Origin' => 'https://web.atrion.test', 'Sec-Fetch-Mode' => 'cors', 'Sec-Fetch-Dest' => 'script']) + ->assertOk() + ->assertContent('export const capturePage=1;') + ->assertHeader('Access-Control-Allow-Origin', '*') + ->assertHeader('Cache-Control', 'immutable, max-age=31536000, public'); + +}); + +it('answers 304 for an unchanged script', function (): void { + $etag = (string) test()->get('/widget/v1/feedback.js')->headers->get('ETag'); + + test()->get('/widget/v1/feedback.js', ['If-None-Match' => $etag])->assertStatus(304)->assertContent(''); + File::put($this->dir.'/feedback.js', 'var CodebarFeedback={v:2};'); + test()->get('/widget/v1/feedback.js', ['If-None-Match' => $etag])->assertOk()->assertContent('var CodebarFeedback={v:2};'); +}); + +it('serves nothing else and nothing before the build', function (string $path): void { + test()->get($path)->assertForbidden()->assertContent(''); +})->with([ + '/widget/v1/secret.php', + '/widget/v1/sri.json', + '/widget/v1/../../.env', + '/widget/v1/chunks/../secret.php', + '/widget/v1/chunks/other.js', + '/widget/v2/feedback.js', + '/widget/v1/', +]); + +it('reveals nothing when the widget was not built', function (): void { + config(['feedback.widget_path' => $this->dir.'/missing']); + + test()->get('/widget/v1/feedback.js')->assertForbidden()->assertContent(''); +}); + +it('is a script, not a page: typing the URL into a browser gets the usual 403', function (): void { + test()->get('/widget/v1/feedback.js', ['Sec-Fetch-Mode' => 'navigate', 'Sec-Fetch-Dest' => 'document'])->assertForbidden()->assertContent(''); +}); diff --git a/widget/LICENSE b/widget/LICENSE deleted file mode 100644 index 2de099e..0000000 --- a/widget/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 Codebar Solutions AG - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/widget/README.md b/widget/README.md deleted file mode 100644 index 2ded658..0000000 --- a/widget/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# @codebar-ag/feedback-widget - -A feedback widget for websites and web apps: visitors describe a problem, mark it on a full-page screenshot and send it, together with browser data and console errors. Vanilla TypeScript in a Shadow DOM, 18 KB gzip, no runtime dependencies. - -It sends to the self-hosted [codebar Feedback API](https://github.com/codebar-ag/feedback). - -```bash -npm install @codebar-ag/feedback-widget -``` - -```ts -import { initFeedback } from '@codebar-ag/feedback-widget'; - -const feedback = await initFeedback({ - projectKey: 'fbk_live_…', - endpoint: 'https://feedback.example.com', - locale: 'en_CH', - identity: { id: '4711', name: 'Anna Muster', email: 'anna@example.com' }, // optional -}); - -feedback.on('after_send', ({ id }) => console.log('Feedback stored', id)); -``` - -Without a build step: - -```html - - -``` - -All options, methods and events are documented in the [repository README](https://github.com/codebar-ag/feedback#widget). - -## License - -MIT diff --git a/widget/package-lock.json b/widget/package-lock.json index eed6026..5c14db6 100644 --- a/widget/package-lock.json +++ b/widget/package-lock.json @@ -1,18 +1,17 @@ { - "name": "@codebar-ag/feedback-widget", + "name": "codebar-feedback-widget", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@codebar-ag/feedback-widget", + "name": "codebar-feedback-widget", "version": "1.0.0", "license": "MIT", "dependencies": { "modern-screenshot": "4.7.0" }, "devDependencies": { - "@arethetypeswrong/cli": "0.18.5", "@axe-core/playwright": "4.14.0", "@eslint/js": "10.0.1", "@playwright/test": "1.64.1", @@ -24,7 +23,6 @@ "globals": "17.13.0", "jsdom": "30.1.3", "prettier": "3.9.10", - "publint": "0.3.25", "size-limit": "14.2.0", "typescript": "6.0.3", "typescript-eslint": "8.72.0", @@ -35,69 +33,6 @@ "node": ">=26" } }, - "node_modules/@andrewbranch/untar.js": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@andrewbranch/untar.js/-/untar.js-1.0.4.tgz", - "integrity": "sha512-pVXSwPsLuw8IGLo2Di0EaOfsk+ntVvpkk942J/sHYIkwvtKUakEcPh7HBgZ6tuimgzKSEHgCvO4XgQ05DEbwDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@arethetypeswrong/cli": { - "version": "0.18.5", - "resolved": "https://registry.npmjs.org/@arethetypeswrong/cli/-/cli-0.18.5.tgz", - "integrity": "sha512-gM+8vRsQOD/Uc7EnBedUhkG5OCsDWE4uoak5QvomGpMpaky0Eh41p04nIMgrWb8EOmqZUJGc6zz9hsP6E56R7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@arethetypeswrong/core": "0.18.5", - "chalk": "^4.1.2", - "cli-table3": "^0.6.3", - "commander": "^10.0.1", - "marked": "^9.1.2", - "marked-terminal": "^7.1.0", - "semver": "^7.5.4" - }, - "bin": { - "attw": "dist/index.js" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@arethetypeswrong/core": { - "version": "0.18.5", - "resolved": "https://registry.npmjs.org/@arethetypeswrong/core/-/core-0.18.5.tgz", - "integrity": "sha512-9ytjzGwxjm9Uz7I9avfbt5vlQt6uk9uRRESzJjqrznl6WKvI6dwYTo+vJ3U02Wrq/mR3iql/PzhvHhKdJIAjDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@andrewbranch/untar.js": "^1.0.3", - "@loaderkit/resolve": "^1.0.2", - "cjs-module-lexer": "^1.2.3", - "fflate": "^0.8.3", - "lru-cache": "^11.0.1", - "semver": "^7.5.4", - "typescript": "5.6.1-rc", - "validate-npm-package-name": "^5.0.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@arethetypeswrong/core/node_modules/typescript": { - "version": "5.6.1-rc", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.1-rc.tgz", - "integrity": "sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/@asamuzakjp/css-color": { "version": "7.1.5", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-7.1.5.tgz", @@ -207,13 +142,6 @@ "node": ">=18" } }, - "node_modules/@braidai/lang": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@braidai/lang/-/lang-1.1.2.tgz", - "integrity": "sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==", - "dev": true, - "license": "ISC" - }, "node_modules/@bramus/specificity": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.3.tgz", @@ -251,17 +179,6 @@ "keyv": "^5.6.0" } }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/@csstools/color-helpers": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.2.tgz", @@ -666,16 +583,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@loaderkit/resolve": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@loaderkit/resolve/-/resolve-1.0.6.tgz", - "integrity": "sha512-G8FdIoF5CypfwmD9rl8BXod5HDn8JqB0CCNBXDTaRZ+yRYhARrrSToX1zg1zy9jX3zLqigsELwhT4gNtkdQAUg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@braidai/lang": "^1.0.0" - } - }, "node_modules/@oxc-project/types": { "version": "0.154.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.154.0.tgz", @@ -702,22 +609,6 @@ "node": ">=20" } }, - "node_modules/@publint/pack": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@publint/pack/-/pack-0.1.7.tgz", - "integrity": "sha512-4EDEmvxWtgsCnnVeBvtFIFZtUhPPt1+bA9JrSwU4Sa//6oKtzCSlGGXYJr44OD9aGISymbieJ4mCKHUygUDU+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyexec": "^1.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://bjornlu.com/sponsor" - } - }, "node_modules/@rolldown/binding-android-arm-eabi": { "version": "1.2.14", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.14.tgz", @@ -998,19 +889,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, "node_modules/@size-limit/file": { "version": "14.2.0", "resolved": "https://registry.npmjs.org/@size-limit/file/-/file-14.2.0.tgz", @@ -1433,58 +1311,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-escapes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", - "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1574,127 +1400,6 @@ "node": ">=18" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/cli-highlight": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", - "dev": true, - "license": "ISC", - "dependencies": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" - }, - "bin": { - "highlight": "bin/highlight" - }, - "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" - } - }, - "node_modules/cli-highlight/node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "dev": true, - "license": "MIT" - }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1780,20 +1485,6 @@ "node": ">=8" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/emojilib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", - "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", - "dev": true, - "license": "MIT" - }, "node_modules/entities": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/entities/-/entities-8.1.0.tgz", @@ -1807,19 +1498,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/es-module-lexer": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", @@ -1827,16 +1505,6 @@ "dev": true, "license": "MIT" }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2074,13 +1742,6 @@ } } }, - "node_modules/fflate": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", - "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", - "dev": true, - "license": "MIT" - }, "node_modules/file-entry-cache": { "version": "11.1.5", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", @@ -2142,16 +1803,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2178,16 +1829,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/hashery": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", @@ -2201,16 +1842,6 @@ "node": ">=20" } }, - "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, "node_modules/hookified": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", @@ -2261,16 +1892,6 @@ "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", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2717,54 +2338,6 @@ "source-map-js": "^1.2.1" } }, - "node_modules/marked": { - "version": "9.1.6", - "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", - "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", - "dev": true, - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 16" - } - }, - "node_modules/marked-terminal": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", - "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "ansi-regex": "^6.1.0", - "chalk": "^5.4.1", - "cli-highlight": "^2.1.11", - "cli-table3": "^0.6.5", - "node-emoji": "^2.2.0", - "supports-hyperlinks": "^3.1.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "marked": ">=1 <16" - } - }, - "node_modules/marked-terminal/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/mdn-data": { "version": "2.27.1", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", @@ -2794,16 +2367,6 @@ "integrity": "sha512-9YxN+ddPSMMlhylOv25VHzXrl9u67QRxoh7+SEewGtgUw7t6hHTrjptSDJUSne9oG4Xk/h2cwG15nIt4Hc9ujg==", "license": "MIT" }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2811,18 +2374,6 @@ "dev": true, "license": "MIT" }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, "node_modules/nanoid": { "version": "3.3.20", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", @@ -2859,32 +2410,6 @@ "dev": true, "license": "MIT" }, - "node_modules/node-emoji": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", - "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^4.6.0", - "char-regex": "^1.0.2", - "emojilib": "^2.4.0", - "skin-tone": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/obug": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz", @@ -2949,13 +2474,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/package-manager-detector": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.9.0.tgz", - "integrity": "sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA==", - "dev": true, - "license": "MIT" - }, "node_modules/parse5": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", @@ -2969,23 +2487,6 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse5": "^6.0.1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true, - "license": "MIT" - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3110,28 +2611,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/publint": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/publint/-/publint-0.3.25.tgz", - "integrity": "sha512-WMMbn/d7JqRlfXQL/JUI4n5skC8cJ5WVr8/wT5mmiYWkUdENkZr1I5rgs2+dQPVRgoJd+JRYtB46MTsuDylw9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@publint/pack": "^0.1.7", - "package-manager-detector": "^1.8.0", - "picocolors": "^1.1.1", - "sade": "^1.8.1" - }, - "bin": { - "publint": "src/cli.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://bjornlu.com/sponsor" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3162,16 +2641,6 @@ "dev": true, "license": "MIT" }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -3216,19 +2685,6 @@ "@rolldown/binding-win32-x64-msvc": "1.2.14" } }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -3295,19 +2751,6 @@ "node": "^22.19.0 || ^24.5.0 || >=26.0.0" } }, - "node_modules/skin-tone": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", - "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "unicode-emoji-modifier-base": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/source-map-js": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", @@ -3325,97 +2768,6 @@ "dev": true, "license": "MIT" }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", - "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=14.18" - }, - "funding": { - "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/tinybench": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.2.tgz", @@ -3590,16 +2942,6 @@ "dev": true, "license": "MIT" }, - "node_modules/unicode-emoji-modifier-base": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", - "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3610,16 +2952,6 @@ "punycode": "^2.1.0" } }, - "node_modules/validate-npm-package-name": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", - "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/vite": { "version": "8.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz", @@ -3868,24 +3200,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -3903,45 +3217,6 @@ "dev": true, "license": "MIT" }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "16.2.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", - "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/widget/package.json b/widget/package.json index c0b1248..abdb0dd 100644 --- a/widget/package.json +++ b/widget/package.json @@ -1,46 +1,10 @@ { - "name": "@codebar-ag/feedback-widget", + "name": "codebar-feedback-widget", "version": "1.0.0", - "description": "Self-hosted feedback widget: full-page screenshots with annotations, diagnostics and drafts. Vanilla TypeScript, Shadow DOM, no runtime dependencies in the core.", + "private": true, + "description": "Feedback widget, served by the API at /widget/v1/feedback.js. Vanilla TypeScript, Shadow DOM.", "license": "MIT", "type": "module", - "repository": { - "type": "git", - "url": "git+https://github.com/codebar-ag/feedback.git", - "directory": "widget" - }, - "homepage": "https://github.com/codebar-ag/feedback#widget", - "bugs": { - "url": "https://github.com/codebar-ag/feedback/issues" - }, - "author": "codebar Solutions AG ", - "keywords": [ - "feedback", - "feedback-widget", - "bug-report", - "screenshot", - "annotation", - "user-feedback", - "laravel", - "typescript", - "shadow-dom" - ], - "sideEffects": false, - "files": [ - "dist" - ], - "main": "./dist/feedback.js", - "module": "./dist/feedback.js", - "types": "./dist/index.d.ts", - "unpkg": "./dist/feedback.iife.js", - "jsdelivr": "./dist/feedback.iife.js", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/feedback.js" - }, - "./package.json": "./package.json" - }, "engines": { "node": ">=26" }, @@ -53,16 +17,12 @@ "test": "vitest run", "test:coverage": "vitest run --coverage", "size": "size-limit", - "publint": "publint --strict", - "attw": "attw --pack . --profile esm-only", - "check": "npm run lint && npm run typecheck && npm run test && npm run build && npm run size && npm run publint && npm run attw", + "check": "npm run lint && npm run typecheck && npm run test && npm run build && npm run size", "e2e": "playwright test -c playwright.config.ts", "e2e:full": "NODE_PATH=$PWD/node_modules playwright test -c ../e2e/playwright.config.ts", - "publish:api": "node scripts/publish-api.mjs", "screenshots": "npm run build && node scripts/screenshots.mjs" }, "devDependencies": { - "@arethetypeswrong/cli": "0.18.5", "@axe-core/playwright": "4.14.0", "@eslint/js": "10.0.1", "@playwright/test": "1.64.1", @@ -74,7 +34,6 @@ "globals": "17.13.0", "jsdom": "30.1.3", "prettier": "3.9.10", - "publint": "0.3.25", "size-limit": "14.2.0", "typescript": "6.0.3", "typescript-eslint": "8.72.0", @@ -83,8 +42,5 @@ }, "dependencies": { "modern-screenshot": "4.7.0" - }, - "publishConfig": { - "access": "public" } } diff --git a/widget/scripts/build.mjs b/widget/scripts/build.mjs index 3ece611..b027dbd 100644 --- a/widget/scripts/build.mjs +++ b/widget/scripts/build.mjs @@ -1,8 +1,8 @@ -// Builds dist/feedback.js (ESM + lazy chunk), dist/feedback.iife.js (global -// CodebarFeedback), the type definitions and dist/sri.json. -import { execFileSync } from 'node:child_process'; -import { createHash } from 'node:crypto'; -import { readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'; +// Builds dist/feedback.iife.js (global CodebarFeedback) with its lazy screenshot +// chunk and copies both to the API (resources/widget/v1), which serves them at +// /widget/v1/feedback.js. dist/feedback.js (ESM) is used by the tests and the +// playground only. +import { cpSync, mkdirSync, readFileSync, readdirSync, rmSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { build } from 'vite'; @@ -46,22 +46,11 @@ await build({ }, }); -execFileSync(process.execPath, [resolve(root, 'node_modules/typescript/bin/tsc'), '-p', resolve(root, 'tsconfig.build.json')], { - stdio: 'inherit', -}); -rmSync(resolve(dist, 'iife.d.ts'), { force: true }); - -const sri = (file) => - 'sha384-' + - createHash('sha384') - .update(readFileSync(resolve(dist, file))) - .digest('base64'); -writeFileSync( - resolve(dist, 'sri.json'), - JSON.stringify( - { version: pkg.version, 'feedback.iife.js': sri('feedback.iife.js'), [`chunks/${chunk}`]: sri(`chunks/${chunk}`) }, - null, - 2, - ) + '\n', -); -console.log(`built ${pkg.name}@${pkg.version} (chunk ${chunk})`); +const api = resolve(root, '../resources/widget/v1'); +rmSync(api, { recursive: true, force: true }); +mkdirSync(api, { recursive: true }); +cpSync(resolve(dist, 'feedback.iife.js'), resolve(api, 'feedback.js')); +cpSync(resolve(dist, 'chunks'), resolve(api, 'chunks'), { recursive: true }); +// Older deploys copied to public/, where the web server answered without CORS. +rmSync(resolve(root, '../public/widget'), { recursive: true, force: true }); +console.log(`built widget ${pkg.version} (chunk ${chunk}) and copied it to resources/widget/v1`); diff --git a/widget/scripts/publish-api.mjs b/widget/scripts/publish-api.mjs deleted file mode 100644 index 78aefad..0000000 --- a/widget/scripts/publish-api.mjs +++ /dev/null @@ -1,14 +0,0 @@ -// Copies the IIFE build to the Laravel app (public/widget/v1/) so the API can -// serve the script tag variant itself. -import { cpSync, mkdirSync, rmSync } from 'node:fs'; -import { dirname, resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const root = resolve(dirname(fileURLToPath(import.meta.url)), '..'); -const target = resolve(root, '../public/widget/v1'); -rmSync(target, { recursive: true, force: true }); -mkdirSync(target, { recursive: true }); -cpSync(resolve(root, 'dist/feedback.iife.js'), resolve(target, 'feedback.js')); -cpSync(resolve(root, 'dist/chunks'), resolve(target, 'chunks'), { recursive: true }); -cpSync(resolve(root, 'dist/sri.json'), resolve(target, 'sri.json')); -console.log(`copied widget to ${target}`); diff --git a/widget/tsconfig.build.json b/widget/tsconfig.build.json deleted file mode 100644 index f4597fb..0000000 --- a/widget/tsconfig.build.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "noEmit": false, - "declaration": true, - "emitDeclarationOnly": true, - "outDir": "dist", - "rootDir": "src", - "types": [] - }, - "include": ["src"] -} From 6df143da20331de1f54ed2d176b433d96b447a24 Mon Sep 17 00:00:00 2001 From: "Sebastian BURGIN-FIX (ext)" Date: Thu, 22 Oct 2026 21:45:16 +0200 Subject: [PATCH 2/2] fix(widget): leave noscript and template out of screenshots The clone is rendered without scripts, so