@slashgear/gdpr-cookie-scanner 3.7.0 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +3 -0
- package/.gitattributes +1 -0
- package/.github/workflows/website.yml +80 -0
- package/CHANGELOG.md +8 -0
- package/CONTRIBUTING.md +32 -4
- package/package.json +7 -3
- package/pnpm-workspace.yaml +3 -0
- package/scripts/build-showcase.mjs +113 -0
- package/website/Dockerfile +55 -0
- package/website/node_modules/.bin/oxfmt +21 -0
- package/website/node_modules/.bin/oxlint +21 -0
- package/website/node_modules/.bin/tsc +21 -0
- package/website/node_modules/.bin/tsserver +21 -0
- package/website/node_modules/.bin/tsx +21 -0
- package/website/package.json +29 -0
- package/{docs → website/public}/index.html +70 -20
- package/website/public/reports/www.20minutes.fr/after-accept.png +3 -0
- package/website/public/reports/www.20minutes.fr/after-reject.png +3 -0
- package/{docs/reports/www.leboncoin.fr/gdpr-report-leboncoin.fr-2026-02-22.html → website/public/reports/www.20minutes.fr/gdpr-report-20minutes.fr-2026-02-22.html} +194 -51
- package/website/public/reports/www.20minutes.fr/modal-initial.png +3 -0
- package/website/public/reports/www.arte.tv/after-accept.png +3 -0
- package/website/public/reports/www.arte.tv/after-reject.png +3 -0
- package/{docs → website/public}/reports/www.arte.tv/gdpr-report-arte.tv-2026-02-24.html +1 -0
- package/website/public/reports/www.arte.tv/modal-initial.png +3 -0
- package/website/public/reports/www.backmarket.fr/after-accept.png +3 -0
- package/website/public/reports/www.backmarket.fr/after-reject.png +3 -0
- package/website/public/reports/www.backmarket.fr/gdpr-report-backmarket.fr-2026-02-24.html +1530 -0
- package/website/public/reports/www.backmarket.fr/modal-initial.png +3 -0
- package/website/public/reports/www.deezer.com/after-accept.png +3 -0
- package/website/public/reports/www.deezer.com/after-reject.png +3 -0
- package/{docs → website/public}/reports/www.deezer.com/gdpr-report-deezer.com-2026-02-22.html +1 -0
- package/website/public/reports/www.deezer.com/modal-initial.png +3 -0
- package/website/public/reports/www.france.tv/after-accept.png +3 -0
- package/website/public/reports/www.france.tv/after-reject.png +3 -0
- package/website/public/reports/www.france.tv/gdpr-report-france.tv-2026-02-23.html +977 -0
- package/website/public/reports/www.france.tv/modal-initial.png +3 -0
- package/website/public/reports/www.m6.fr/after-accept.png +3 -0
- package/website/public/reports/www.m6.fr/after-reject.png +3 -0
- package/website/public/reports/www.m6.fr/gdpr-report-m6.fr-2026-02-28.html +1862 -0
- package/website/public/reports/www.m6.fr/modal-initial.png +3 -0
- package/website/public/reports/www.netflix.com/after-accept.png +3 -0
- package/website/public/reports/www.netflix.com/after-reject.png +3 -0
- package/{docs → website/public}/reports/www.netflix.com/gdpr-report-netflix.com-2026-02-23.html +1 -0
- package/website/public/reports/www.netflix.com/modal-initial.png +3 -0
- package/website/public/reports/www.radiofrance.fr/after-accept.png +3 -0
- package/website/public/reports/www.radiofrance.fr/after-reject.png +3 -0
- package/{docs → website/public}/reports/www.radiofrance.fr/gdpr-report-radiofrance.fr-2026-02-24.html +1 -0
- package/website/public/reports/www.radiofrance.fr/modal-initial.png +3 -0
- package/website/public/reports/www.tf1.fr/after-accept.png +3 -0
- package/website/public/reports/www.tf1.fr/after-reject.png +3 -0
- package/website/public/reports/www.tf1.fr/gdpr-report-tf1.fr-2026-02-23.html +1512 -0
- package/website/public/reports/www.tf1.fr/modal-initial.png +3 -0
- package/website/src/index.ts +15 -0
- package/website/src/security.ts +26 -0
- package/website/tsconfig.json +14 -0
- package/.github/workflows/pages.yml +0 -40
- package/docs/reports/www.arte.tv/after-accept.png +0 -0
- package/docs/reports/www.arte.tv/after-reject.png +0 -0
- package/docs/reports/www.deezer.com/after-accept.png +0 -0
- package/docs/reports/www.deezer.com/after-reject.png +0 -0
- package/docs/reports/www.impots.gouv.fr/after-accept.png +0 -0
- package/docs/reports/www.impots.gouv.fr/after-reject.png +0 -0
- package/docs/reports/www.impots.gouv.fr/gdpr-report-impots.gouv.fr-2026-02-22.html +0 -751
- package/docs/reports/www.leboncoin.fr/after-accept.png +0 -0
- package/docs/reports/www.leboncoin.fr/after-reject.png +0 -0
- package/docs/reports/www.netflix.com/after-accept.png +0 -0
- package/docs/reports/www.netflix.com/after-reject.png +0 -0
- package/docs/reports/www.radiofrance.fr/after-accept.png +0 -0
- package/docs/reports/www.radiofrance.fr/after-reject.png +0 -0
- /package/{docs → website/public}/style.css +0 -0
package/.dockerignore
CHANGED
package/.gitattributes
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
website/public/reports/**/*.png filter=lfs diff=lfs merge=lfs -text
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
name: Website
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- "website/**"
|
|
8
|
+
- "pnpm-workspace.yaml"
|
|
9
|
+
- ".github/workflows/website.yml"
|
|
10
|
+
|
|
11
|
+
env:
|
|
12
|
+
SCW_DEFAULT_REGION: fr-par
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build-and-push:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
packages: write
|
|
20
|
+
|
|
21
|
+
outputs:
|
|
22
|
+
image: ${{ steps.meta.outputs.image }}
|
|
23
|
+
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v6
|
|
26
|
+
with:
|
|
27
|
+
lfs: true
|
|
28
|
+
|
|
29
|
+
- name: Log in to GitHub Container Registry
|
|
30
|
+
uses: docker/login-action@v3
|
|
31
|
+
with:
|
|
32
|
+
registry: ghcr.io
|
|
33
|
+
username: ${{ github.actor }}
|
|
34
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
35
|
+
|
|
36
|
+
- name: Set up QEMU
|
|
37
|
+
uses: docker/setup-qemu-action@v3
|
|
38
|
+
|
|
39
|
+
- name: Set up Docker Buildx
|
|
40
|
+
uses: docker/setup-buildx-action@v3
|
|
41
|
+
|
|
42
|
+
- name: Compute image name
|
|
43
|
+
id: meta
|
|
44
|
+
run: |
|
|
45
|
+
echo "image=ghcr.io/$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')-website" >> "$GITHUB_OUTPUT"
|
|
46
|
+
|
|
47
|
+
- name: Build and push
|
|
48
|
+
uses: docker/build-push-action@v6
|
|
49
|
+
with:
|
|
50
|
+
context: .
|
|
51
|
+
file: website/Dockerfile
|
|
52
|
+
push: true
|
|
53
|
+
platforms: linux/amd64,linux/arm64
|
|
54
|
+
tags: |
|
|
55
|
+
${{ steps.meta.outputs.image }}:latest
|
|
56
|
+
${{ steps.meta.outputs.image }}:${{ github.sha }}
|
|
57
|
+
cache-from: type=gha
|
|
58
|
+
cache-to: type=gha,mode=max
|
|
59
|
+
|
|
60
|
+
deploy:
|
|
61
|
+
needs: build-and-push
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
environment: production
|
|
64
|
+
|
|
65
|
+
steps:
|
|
66
|
+
- name: Install Scaleway CLI
|
|
67
|
+
run: |
|
|
68
|
+
curl -fsSL https://raw.githubusercontent.com/scaleway/scaleway-cli/master/scripts/get.sh | sh
|
|
69
|
+
|
|
70
|
+
- name: Deploy to Scaleway Serverless Container
|
|
71
|
+
run: |
|
|
72
|
+
scw container container update ${{ secrets.SCW_CONTAINER_ID }} \
|
|
73
|
+
registry-image=${{ needs.build-and-push.outputs.image }}:${{ github.sha }} \
|
|
74
|
+
region=${{ env.SCW_DEFAULT_REGION }} \
|
|
75
|
+
redeploy=true
|
|
76
|
+
env:
|
|
77
|
+
SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY }}
|
|
78
|
+
SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY }}
|
|
79
|
+
SCW_DEFAULT_ORGANIZATION_ID: ${{ secrets.SCW_ORGANIZATION_ID }}
|
|
80
|
+
SCW_DEFAULT_PROJECT_ID: ${{ secrets.SCW_PROJECT_ID }}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @slashgear/gdpr-cookie-scanner
|
|
2
2
|
|
|
3
|
+
## 3.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 88d944f: Introduce pnpm workspace monorepo and `website` package.
|
|
8
|
+
|
|
9
|
+
Converts the repo to a pnpm workspace and adds a new private `website` package (`@slashgear/gdpr-website`) built with Hono on Node.js. The package exposes a Docker image (`ghcr.io/slashgear/gdpr-website`) intended for deployment as a Scaleway Serverless Container at `gdpr.slashgear.dev`. A dedicated GitHub Actions workflow (`website.yml`) builds and pushes the image on every push to `main` that touches `website/**`, then redeploys the container via the Scaleway CLI.
|
|
10
|
+
|
|
3
11
|
## 3.7.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
package/CONTRIBUTING.md
CHANGED
|
@@ -2,20 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
## Prerequisites
|
|
4
4
|
|
|
5
|
-
- Node.js ≥
|
|
5
|
+
- Node.js ≥ 24
|
|
6
6
|
- pnpm
|
|
7
|
-
- Playwright: `
|
|
7
|
+
- Playwright: `pnpm exec playwright install chromium`
|
|
8
|
+
|
|
9
|
+
## Repository structure
|
|
10
|
+
|
|
11
|
+
This is a **pnpm workspace monorepo** with two packages:
|
|
12
|
+
|
|
13
|
+
| Package | Path | Description |
|
|
14
|
+
| -------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------ |
|
|
15
|
+
| `@slashgear/gdpr-cookie-scanner` | `/` (root) | Published CLI tool |
|
|
16
|
+
| `@slashgear/gdpr-website` | `website/` | Hono web app — not published, deployed as a Docker image to [gdpr.slashgear.dev](https://gdpr.slashgear.dev) |
|
|
8
17
|
|
|
9
18
|
## Getting started
|
|
10
19
|
|
|
11
20
|
```bash
|
|
12
21
|
git clone https://github.com/Slashgear/gdpr-report.git
|
|
13
22
|
cd gdpr-report
|
|
14
|
-
pnpm install
|
|
15
|
-
pnpm build
|
|
23
|
+
pnpm install # installs all workspace packages
|
|
24
|
+
pnpm build # builds the CLI
|
|
16
25
|
|
|
17
26
|
# Run the CLI locally
|
|
18
27
|
node dist/cli.js scan https://example.com
|
|
28
|
+
|
|
29
|
+
# Run the website locally (http://localhost:8080)
|
|
30
|
+
pnpm website:dev
|
|
19
31
|
```
|
|
20
32
|
|
|
21
33
|
## Workflow
|
|
@@ -28,6 +40,7 @@ node dist/cli.js scan https://example.com
|
|
|
28
40
|
pnpm lint
|
|
29
41
|
pnpm typecheck
|
|
30
42
|
pnpm build
|
|
43
|
+
pnpm website:typecheck
|
|
31
44
|
```
|
|
32
45
|
4. Open a Pull Request targeting `main`
|
|
33
46
|
|
|
@@ -37,6 +50,21 @@ node dist/cli.js scan https://example.com
|
|
|
37
50
|
- **Consent modal detection** — improve CMP detection in `src/scanner/consent-modal.ts`
|
|
38
51
|
- **Compliance rules** — refine scoring rules in `src/analyzers/compliance.ts`
|
|
39
52
|
- **Report** — improve report rendering in `src/report/generator.ts`
|
|
53
|
+
- **Website** — Hono server and landing page in `website/src/` and `website/public/`
|
|
54
|
+
|
|
55
|
+
## Website — live reports showcase
|
|
56
|
+
|
|
57
|
+
The `website/public/reports/` directory contains pre-generated HTML reports (grades A–D only).
|
|
58
|
+
After adding new reports, run:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pnpm build:showcase # regenerates the report cards in website/public/index.html
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The script reads `website/public/reports/` and injects sorted report cards between the
|
|
65
|
+
`<!-- ── REPORTS_START ── -->` / `<!-- ── REPORTS_END ── -->` markers.
|
|
66
|
+
|
|
67
|
+
Report screenshots (`.png`) are tracked via **Git LFS** — make sure `git lfs` is installed before cloning.
|
|
40
68
|
|
|
41
69
|
## Releasing
|
|
42
70
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slashgear/gdpr-cookie-scanner",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "CLI tool to scan websites for GDPR cookie consent compliance",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"compliance",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"rgpd",
|
|
13
13
|
"scanner"
|
|
14
14
|
],
|
|
15
|
-
"homepage": "https://
|
|
15
|
+
"homepage": "https://gdpr.slashgear.dev",
|
|
16
16
|
"bugs": {
|
|
17
17
|
"url": "https://github.com/Slashgear/gdpr-cookie-scanner/issues"
|
|
18
18
|
},
|
|
@@ -68,6 +68,10 @@
|
|
|
68
68
|
"changeset": "changeset",
|
|
69
69
|
"update-trackers": "node --experimental-strip-types scripts/update-trackers.ts",
|
|
70
70
|
"update-trackers:dry": "node --experimental-strip-types scripts/update-trackers.ts --dry-run",
|
|
71
|
-
"update:ocd": "node scripts/update-cookie-db.mjs"
|
|
71
|
+
"update:ocd": "node scripts/update-cookie-db.mjs",
|
|
72
|
+
"build:showcase": "node scripts/build-showcase.mjs && pnpm format",
|
|
73
|
+
"website:dev": "pnpm --filter @slashgear/gdpr-website dev",
|
|
74
|
+
"website:build": "pnpm --filter @slashgear/gdpr-website build",
|
|
75
|
+
"website:typecheck": "pnpm --filter @slashgear/gdpr-website typecheck"
|
|
72
76
|
}
|
|
73
77
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads docs/reports/<host>/ directories, extracts grade/score/date from
|
|
3
|
+
* each HTML report, and regenerates the "Live Reports" cards in docs/index.html.
|
|
4
|
+
*
|
|
5
|
+
* Usage: node scripts/build-showcase.mjs
|
|
6
|
+
* Or: pnpm build:showcase
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readdir, readFile, writeFile } from "fs/promises";
|
|
10
|
+
import { join, resolve } from "path";
|
|
11
|
+
import { existsSync } from "fs";
|
|
12
|
+
|
|
13
|
+
const WEBSITE_PUBLIC_DIR = resolve("website/public");
|
|
14
|
+
const REPORTS_DIR = join(WEBSITE_PUBLIC_DIR, "reports");
|
|
15
|
+
const INDEX_HTML = join(WEBSITE_PUBLIC_DIR, "index.html");
|
|
16
|
+
|
|
17
|
+
const START_MARKER = "<!-- ── REPORTS_START ── -->";
|
|
18
|
+
const END_MARKER = "<!-- ── REPORTS_END ── -->";
|
|
19
|
+
|
|
20
|
+
async function extractMeta(hostDir) {
|
|
21
|
+
const files = await readdir(join(REPORTS_DIR, hostDir));
|
|
22
|
+
const htmlFile = files.find((f) => f.endsWith(".html"));
|
|
23
|
+
if (!htmlFile) return null;
|
|
24
|
+
|
|
25
|
+
const content = await readFile(join(REPORTS_DIR, hostDir, htmlFile), "utf8");
|
|
26
|
+
|
|
27
|
+
// oxfmt may split closing tags across lines — match opening tag content only
|
|
28
|
+
const grade = content.match(/"grade-badge"[^>]*>([A-F])<\/div>/)?.[1] ?? "?";
|
|
29
|
+
const score = content.match(/"score-num"[^>]*>(\d+)/)?.[1] ?? "?";
|
|
30
|
+
|
|
31
|
+
// Date from filename: gdpr-report-<host>-YYYY-MM-DD.html
|
|
32
|
+
const dateRaw = htmlFile.match(/(\d{4}-\d{2}-\d{2})\.html$/)?.[1];
|
|
33
|
+
const dateStr = dateRaw
|
|
34
|
+
? new Date(dateRaw).toLocaleDateString("en-GB", {
|
|
35
|
+
day: "numeric",
|
|
36
|
+
month: "short",
|
|
37
|
+
year: "numeric",
|
|
38
|
+
})
|
|
39
|
+
: "";
|
|
40
|
+
|
|
41
|
+
const displayHost = hostDir.replace(/^www\./, "");
|
|
42
|
+
|
|
43
|
+
// Inject modal screenshot if PNG exists alongside the HTML but isn't referenced yet
|
|
44
|
+
const pngPath = join(REPORTS_DIR, hostDir, "modal-initial.png");
|
|
45
|
+
const htmlPath = join(REPORTS_DIR, hostDir, htmlFile);
|
|
46
|
+
if (existsSync(pngPath) && !content.includes("modal-initial.png")) {
|
|
47
|
+
const IMG = `<img src="modal-initial.png" alt="Consent modal screenshot" class="modal-screenshot" />`;
|
|
48
|
+
// Insert right after the section-body div that follows <h2>Consent modal</h2>
|
|
49
|
+
const patched = content.replace(
|
|
50
|
+
/(<h2>Consent modal<\/h2>[\s\S]*?<div class="section-body">)/,
|
|
51
|
+
`$1\n ${IMG}`,
|
|
52
|
+
);
|
|
53
|
+
if (patched !== content) {
|
|
54
|
+
await writeFile(htmlPath, patched, "utf8");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { grade, score: parseInt(score, 10) || 0, dateStr, displayHost, hostDir, htmlFile };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function buildCard({ grade, score, dateStr, displayHost, hostDir, htmlFile }) {
|
|
62
|
+
return ` <!-- ${displayHost} — ${score}/100 ${grade} -->
|
|
63
|
+
<div class="report-card">
|
|
64
|
+
<div class="report-header">
|
|
65
|
+
<div class="grade-badge grade-${grade}">${grade}</div>
|
|
66
|
+
<div class="report-meta">
|
|
67
|
+
<h3>${displayHost}</h3>
|
|
68
|
+
<span class="score">${score} / 100</span>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
<p class="report-date">Scanned ${dateStr}</p>
|
|
72
|
+
<a class="btn btn-outline" href="reports/${hostDir}/${htmlFile}">
|
|
73
|
+
View report →
|
|
74
|
+
</a>
|
|
75
|
+
</div>`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function main() {
|
|
79
|
+
const hosts = (await readdir(REPORTS_DIR, { withFileTypes: true }))
|
|
80
|
+
.filter((d) => d.isDirectory())
|
|
81
|
+
.map((d) => d.name);
|
|
82
|
+
|
|
83
|
+
const reports = (await Promise.all(hosts.map(extractMeta))).filter(Boolean);
|
|
84
|
+
|
|
85
|
+
// Sort by score descending (best first)
|
|
86
|
+
reports.sort((a, b) => b.score - a.score);
|
|
87
|
+
|
|
88
|
+
const cards = reports.map(buildCard).join("\n\n");
|
|
89
|
+
|
|
90
|
+
let index = await readFile(INDEX_HTML, "utf8");
|
|
91
|
+
const startIdx = index.indexOf(START_MARKER);
|
|
92
|
+
const endIdx = index.indexOf(END_MARKER);
|
|
93
|
+
|
|
94
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
95
|
+
throw new Error(`Markers not found in ${INDEX_HTML}. Add:\n ${START_MARKER}\n ${END_MARKER}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const newIndex =
|
|
99
|
+
index.slice(0, startIdx + START_MARKER.length) +
|
|
100
|
+
"\n" +
|
|
101
|
+
cards +
|
|
102
|
+
"\n " +
|
|
103
|
+
index.slice(endIdx);
|
|
104
|
+
|
|
105
|
+
await writeFile(INDEX_HTML, newIndex, "utf8");
|
|
106
|
+
console.log(`✓ ${INDEX_HTML} updated with ${reports.length} report cards`);
|
|
107
|
+
reports.forEach((r) => console.log(` ${r.grade} ${r.score}/100 ${r.displayHost}`));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
main().catch((e) => {
|
|
111
|
+
console.error(e);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Build context: repo root (docker build -f website/Dockerfile .)
|
|
2
|
+
|
|
3
|
+
# ── Stage 1: build ────────────────────────────────────────────────────────────
|
|
4
|
+
FROM node:24-slim AS builder
|
|
5
|
+
WORKDIR /app
|
|
6
|
+
|
|
7
|
+
# Install compression tools (brotli, zstd, zopfli)
|
|
8
|
+
RUN apt-get update && apt-get install -y --no-install-recommends brotli zstd zopfli \
|
|
9
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
10
|
+
|
|
11
|
+
RUN npm install -g pnpm@latest
|
|
12
|
+
|
|
13
|
+
# Copy workspace manifests for layer caching
|
|
14
|
+
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
15
|
+
COPY website/package.json ./website/
|
|
16
|
+
|
|
17
|
+
# Install website deps only (no playwright, no CLI deps)
|
|
18
|
+
RUN pnpm install --frozen-lockfile --filter @slashgear/gdpr-website
|
|
19
|
+
|
|
20
|
+
# Copy source and compile
|
|
21
|
+
COPY website/tsconfig.json ./website/
|
|
22
|
+
COPY website/src ./website/src
|
|
23
|
+
RUN pnpm --filter @slashgear/gdpr-website build
|
|
24
|
+
|
|
25
|
+
# Copy static assets and precompress them
|
|
26
|
+
COPY website/public ./website/public
|
|
27
|
+
RUN find /app/website/public -type f \( -name "*.html" -o -name "*.css" -o -name "*.js" -o -name "*.json" -o -name "*.svg" \) | \
|
|
28
|
+
while read f; do \
|
|
29
|
+
brotli --best -k "$f"; \
|
|
30
|
+
zstd --ultra -22 -k "$f"; \
|
|
31
|
+
zopfli --gzip "$f"; \
|
|
32
|
+
done
|
|
33
|
+
|
|
34
|
+
# ── Stage 2: runtime ──────────────────────────────────────────────────────────
|
|
35
|
+
FROM node:24-slim
|
|
36
|
+
WORKDIR /app
|
|
37
|
+
ENV NODE_ENV=production
|
|
38
|
+
|
|
39
|
+
RUN npm install -g pnpm@latest
|
|
40
|
+
|
|
41
|
+
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
42
|
+
COPY website/package.json ./website/
|
|
43
|
+
RUN pnpm install --prod --frozen-lockfile --filter @slashgear/gdpr-website
|
|
44
|
+
|
|
45
|
+
# Static assets with precompressed variants (.br, .zst, .gz)
|
|
46
|
+
COPY --from=builder /app/website/public ./website/public
|
|
47
|
+
|
|
48
|
+
# Compiled server
|
|
49
|
+
COPY --from=builder /app/website/dist ./website/dist
|
|
50
|
+
|
|
51
|
+
WORKDIR /app/website
|
|
52
|
+
ENV PORT=8080
|
|
53
|
+
EXPOSE 8080
|
|
54
|
+
|
|
55
|
+
CMD ["node", "dist/index.js"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/oxfmt@0.34.0/node_modules/oxfmt/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/oxfmt@0.34.0/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/oxfmt@0.34.0/node_modules/oxfmt/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/oxfmt@0.34.0/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../oxfmt/bin/oxfmt" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../oxfmt/bin/oxfmt" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/oxlint@1.49.0/node_modules/oxlint/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/oxlint@1.49.0/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/oxlint@1.49.0/node_modules/oxlint/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/oxlint@1.49.0/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../oxlint/bin/oxlint" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../oxlint/bin/oxlint" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../typescript/bin/tsc" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../typescript/bin/tsserver" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/tsx@4.21.0/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/tsx@4.21.0/node_modules:/home/runner/work/gdpr-cookie-scanner/gdpr-cookie-scanner/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../tsx/dist/cli.mjs" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../tsx/dist/cli.mjs" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@slashgear/gdpr-website",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Web interface for gdpr-cookie-scanner",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"dev": "tsx src/index.ts",
|
|
10
|
+
"start": "node dist/index.js",
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"lint": "oxlint .",
|
|
13
|
+
"format": "oxfmt ."
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@hono/node-server": "^1.14.0",
|
|
17
|
+
"hono": "^4.7.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^22.0.0",
|
|
21
|
+
"oxfmt": "^0.34.0",
|
|
22
|
+
"oxlint": "^1.48.0",
|
|
23
|
+
"tsx": "^4.19.0",
|
|
24
|
+
"typescript": "^5.5.0"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=24.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|