Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfaee1e375 | ||
|
|
080fb2440d |
+3
-3
@@ -1,4 +1,4 @@
|
||||
# Vendoo 1.35.0 – Beispielkonfiguration für Windows, Docker und NAS
|
||||
# Vendoo 1.34.3 – Beispielkonfiguration für Windows, Docker und NAS
|
||||
# Diese Datei enthält ausschließlich Platzhalter. Für den Betrieb als .env kopieren.
|
||||
# Niemals eine produktive .env in Release-ZIPs, Git oder Support-Logs aufnehmen.
|
||||
|
||||
@@ -44,7 +44,7 @@ VENDOO_DOMAIN=
|
||||
|
||||
# Optional: öffentliche JSON-Manifest-URL für die Updateprüfung
|
||||
VENDOO_UPDATE_MANIFEST_URL=
|
||||
# Server-, Mehrbenutzer- und Deployment-Sicherheit (1.35.0)
|
||||
# Server-, Mehrbenutzer- und Deployment-Sicherheit (1.34.3)
|
||||
# Standardmäßig nur auf diesem PC erreichbar. Für LAN bewusst auf 0.0.0.0 setzen.
|
||||
VENDOO_BIND_HOST=127.0.0.1
|
||||
# Kommagetrennte zusätzliche Hostnamen/IPs, z. B. vendoo.intern,192.168.178.50
|
||||
@@ -59,7 +59,7 @@ VENDOO_READ_RATE_LIMIT=360
|
||||
VENDOO_MUTATION_RATE_LIMIT=120
|
||||
|
||||
|
||||
# Produktionsbereitstellung 1.35.0
|
||||
# Produktionsbereitstellung 1.34.3
|
||||
# Mindestens 32 zufällige Zeichen. Wird beim ersten Admin-Setup abgefragt.
|
||||
VENDOO_SETUP_TOKEN=
|
||||
VENDOO_REQUIRE_SETUP_TOKEN=true
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
- [ ] `npm ci`
|
||||
- [ ] `npm run verify:git`
|
||||
- [ ] `npm run check`
|
||||
- [ ] `npm run verify:architecture`
|
||||
- [ ] `npm run verify:deployment`
|
||||
- [ ] `npm run verify:db`
|
||||
- [ ] Windows-/Docker-/Browser-Abnahme dokumentiert oder ausdrücklich als offen markiert
|
||||
|
||||
@@ -45,9 +45,6 @@ jobs:
|
||||
- name: Syntaxprüfung
|
||||
run: npm run check
|
||||
|
||||
- name: Plattformarchitektur und Design Tokens prüfen
|
||||
run: npm run verify:architecture
|
||||
|
||||
- name: Deployment-Gate
|
||||
run: npm run verify:deployment
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ jobs:
|
||||
npm run verify:git-regression
|
||||
npm run verify:git-staging
|
||||
npm run check
|
||||
npm run verify:architecture
|
||||
npm run verify:deployment
|
||||
npm run verify:config
|
||||
npm run verify:installer-shell
|
||||
|
||||
BIN
Binary file not shown.
@@ -1,28 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 1.35.0 – Modular Platform Kernel, Design-System Contracts & Security Architecture Foundation
|
||||
|
||||
### Neu
|
||||
|
||||
- Modularer Plattformkernel mit Lifecycle, Event Bus, Feature Flags, Modul- und Policy-Registry.
|
||||
- Zwölf bestehende Fachbereiche als validierte `legacy-bridge`-Module mit expliziten Abhängigkeiten, Capabilities und Ownership.
|
||||
- Deny-by-default Route Registry: neue Kernel-Routen benötigen Besitzer und explizite Policy.
|
||||
- Administrativ geschützte Plattform-, Modul- und Theme-Vertragsendpunkte.
|
||||
- Kernel-Healthcheck in `/readyz` und geordneter Kernel-Shutdown.
|
||||
- Versionierte Design-Token-Quelle mit 33 Tokens für Light, Dark und High Contrast.
|
||||
- Deterministischer Token-Generator und browserseitige Theme Runtime.
|
||||
- Sichere Theme-Definitionen: nur freigegebene Farb- und Dimensionswerte, kein freies CSS/JS.
|
||||
- Modul-JSON-Schema, Architekturgrenzen, Migrationsroadmap und Security-Architekturdokumentation.
|
||||
- Neues `npm run verify:architecture` mit Zyklus-, Policy-, Event-, Theme- und Token-Regressionstests.
|
||||
- GitHub Draft Pull Request #7 für `release/1.35.0-platform-kernel`.
|
||||
|
||||
### Kompatibilität
|
||||
|
||||
- Kein Reset, keine Framework-Migration, keine Datenbankablösung.
|
||||
- Windows-Lokalbetrieb, Docker, NAS, VPS, Zero-Edit-Installer und Git-Sicherheitsgate bleiben erhalten.
|
||||
- Bestehende CSS-Variablen bleiben über eine Token-Kompatibilitätsbrücke funktionsfähig.
|
||||
- Bestehende Routen werden in 1.35.0 noch nicht massenhaft verschoben; die Migration erfolgt fachmodulweise.
|
||||
|
||||
## 1.34.3 – Git-Staging Update-Archiv-Ausschluss Hotfix
|
||||
|
||||
### Behoben
|
||||
|
||||
-11
@@ -979,14 +979,3 @@ Vendoo bündelt direkte API-Veröffentlichungen und Extension-Workflows in einer
|
||||
- neutraler FLUX-Studio-Start und Arbeitsbereich-Reset
|
||||
- stabilisierte Benutzerbearbeitung und sichere Update-Antwort
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Plattformarchitektur ab 1.35.0
|
||||
|
||||
Vendoo wird als modularer Monolith weiterentwickelt. Version 1.35.0 ergänzt einen Plattformkernel, validierte Modulmanifeste, Lifecycle, Event Bus, Feature Flags, Policy Engine und Routenverträge. Die bestehenden Fachbereiche sind als `legacy-bridge` registriert und werden in späteren Releases einzeln migriert.
|
||||
|
||||
Das Design-System verwendet eine versionierte Tokenquelle unter `public/design-system/tokens/source.json`. Light, Dark und High Contrast werden deterministisch in CSS erzeugt. Freies CSS ist für den geplanten Theme Manager ausgeschlossen.
|
||||
|
||||
Neue Kernel-Routen benötigen zwingend einen Modulbesitzer und eine explizite Policy. Unbekannte Policies verweigern den Zugriff. Externe Erweiterungen dürfen nicht im Hauptprozess ausgeführt werden; der isolierte Extension-Host bleibt als deaktivierter Feature-Flag vorbereitet.
|
||||
|
||||
+23
-66
@@ -1,16 +1,16 @@
|
||||
f344f75df655bc133e6c4abb2d215b2234fb22a4bb155c217317c36ee4f0f8fb .dockerignore
|
||||
1b43579196c0b46adf1cd291612878bc6d4ea83d999cc019d421cd94332a4497 .env.example
|
||||
bd750f6b33c65babda1e9e87416b8db206a8a82109373d31238286794a6fd486 .env.example
|
||||
58e3f4b76cc9398f32dcd5481b2080e7e5ba322aca04b8b8374446299c5d7dd6 .github/CODEOWNERS
|
||||
5bd335eb750d084ca12b6a3b3f35d0cd7bc454ba24ed280187c66acf75eaf396 .github/dependabot.yml
|
||||
f65684e98b01d0e0b6bfe1284b5de3354559e4cd51e634fe9e9e2e5473b890e5 .github/pull_request_template.md
|
||||
caf8166f14dbd7c354c70c482548ea4e7ceca473c64c9c13a1297b30e0b3855f .github/workflows/ci.yml
|
||||
5d7c1365f5319df6a756b8cc48d42d91ae954e463062a1c5e9df0b7f9d2da7f4 .github/workflows/release.yml
|
||||
3653210a322b7b4a04d31a69bfab75190c9d4ee51fc9d6b55de45e6a1145b99b .github/pull_request_template.md
|
||||
55732bab53a88c24d9755bd3ec7e0b5a0f96f9589876d721dd45aef0d47fadf0 .github/workflows/ci.yml
|
||||
b65a764fe196c55ad041570b67b68abaa8d6eefe2f43be841273c5fd3f0a662a .github/workflows/release.yml
|
||||
9e7decb6c419fc032644ad361043e69770104ea5904bfcce4942f9416787d975 .gitignore
|
||||
9bfa732952ad571d06527b32d96d79133fc23fa607df00de440b2802f543608c .npmrc
|
||||
1e4d8f485bc932ec1bb1cbf5100fb7897788e165d46af9247afb11ecbc69158c CHANGELOG.md
|
||||
5ffff7948e72e933f5e4b18af3e33b65fc20dce11bbfdf76efbd58c5966ec4e3 CHANGELOG.md
|
||||
61823fc9fdc036170cce6edeecbb612b442d1c65fc9f7a7eaba47e5016316ee5 CLAUDE.md
|
||||
2e0077cd6dd83d3a07fcef25b7e37ab5fc056dbe1ca034b33d55135234045882 Dockerfile
|
||||
712347f03bb08feed4538a9c4f397504a6801f50d03acfe962b5ba7eb534fe08 FEATURES.md
|
||||
b730d810c7c9c4a6fb201494c43978c2e1ba8c092e3543a9b3e424694c9f577f FEATURES.md
|
||||
7f78a0d512de717ba76c9a5e3e1bd2e52db9edf146e8eeb7297458fde5d92afa GitHub-Deploy-Assistent.bat
|
||||
302ab1609d08b3059ba6563b676c64f0709651f2e3c531857bfca3840c535a79 MockupDesign/AdminDashboard.png
|
||||
49f7625a1299a16576feabfd7824ec3511391bd9b7c53a1fba9232da7e8bd6c6 MockupDesign/ChatGPT Image 7. Juli 2026, 10_05_19 (1).png
|
||||
@@ -24,38 +24,13 @@ b1c62e118d9be316274e966c558cdcccdd41afb3a9f05a60ec1e4a3433d2ca0c MockupDesign/L
|
||||
a52983b9ea2bf920d115485d10bc30226da88bc5cba8b00bebf801b21c096346 MockupDesign/Publish.png
|
||||
8c8bf278d0c6725e1dbb97b3c3d0c36a9bfc0753c3c86433fdc294bec70dbc0e MockupDesign/Startseite.png
|
||||
62f14221b1d71c9e631efdadd221397ff657adae4b737df52c8bff1610b90999 MockupDesign/Vorlagen.png
|
||||
d0ef5e20267debda98199c158974ccca0bdc3b614bfa29263c0863297a3c6fd3 README.md
|
||||
fafda88c1e46cbe9f2085ec140220b9f04182ac0ce32df854954e39bb025f51e SECURITY.md
|
||||
83834fdbc2a44866b20d6cb9fa7adfd6ba345fccd519299ba658e61d28e48819 README.md
|
||||
c25d3afa3adbde80250c698a984af41c9db0702b2584aada9b6064d7843da70e SECURITY.md
|
||||
2851c914a1b50a036cb265ddd32e5545cc7e8e34ba9d682445926acc23eb8f28 THIRD_PARTY_NOTICES.md
|
||||
4c424ef64a09639953388bd2e749d45e9a0d3d533c8b6fce547292237a0a48f9 app/architecture-boundaries.json
|
||||
a6bdb9b6fdc97a0b2bbc052bd242a9885a75bcc46ee4f0414d8b7e215b6940de app/contracts/module.schema.json
|
||||
8e213cf43eec8f7965adc9529e44346c1689005c00bbd37dcd48c6774a391fbe app/core/themes/theme-contract.mjs
|
||||
d84a573699e413e0a0803459e0d05c5c4e6e35d3499eb634f95302d151e75c4b app/kernel/errors.mjs
|
||||
a34333cbc784d39b39a8071a437972c6175c551104dc2d82fc4c0e0506b18259 app/kernel/event-bus.mjs
|
||||
0e8f691f96e1ef0a7a86d32cae7b7e1db04325fe94a32b8c1f484a5b6950f3b4 app/kernel/feature-flags.mjs
|
||||
a0223a00691908862aaf1fd9383b6d86d67f26325c509280b8c2daa5cfcee28e app/kernel/lifecycle.mjs
|
||||
ba1f4f799a508ff113738cce7ac9f5af27739cb60d7f04d7a6c5878de3c5b7f4 app/kernel/module-contract.mjs
|
||||
1baa99ffd80fffcfed69f21a5db8911500f35c0bd9df23cd02a7fb3b563266df app/kernel/module-registry.mjs
|
||||
e89075bc42b6352edbf35c343f0fcb1250b23fa8850b5ccef3d20710ecf3bec5 app/kernel/platform-kernel.mjs
|
||||
7944f68d995a5d70273f8a985973f668fc62a414d85636d92f3774e34d39a37f app/kernel/policy-engine.mjs
|
||||
8fd120558114a0357d55b070c3a150464f57d6d87a84594c10562774ed994213 app/kernel/route-registry.mjs
|
||||
0bb099111f7cf49780389d0b078083756a448b663661ee22475d84d38a5f9719 app/modules/ai/module.json
|
||||
83ad66e0eba88246d292c96fb1ec02b9c59ed15614bbbf4a4a989787fe51e2d3 app/modules/auth/module.json
|
||||
7646d3b546cb34ed9c973f73bbc27d43d5c35478e5eea92f468fa6924cbfdc16 app/modules/catalog.mjs
|
||||
083db2c78089db389e51e743078c847f1067f3d1a39081080c601d31556f15fd app/modules/flux-studio/module.json
|
||||
82b31b548c603cdc4e21b50707fd3d21114b85ef1f61db607c881f9ac1809132 app/modules/listings/module.json
|
||||
6453b41f4074b37c7f7a2c30aa8a0eba8c4233f8d474251919094ed0c15fe38d app/modules/media/module.json
|
||||
9723a9b12a9b472b2ed8c4635cdce663b2f0f65643d8ae0b6219715d853f740d app/modules/operations/module.json
|
||||
b71c0f607bbba70b4485fcce883a7d2ab4a533a6dc203eb7de616aa4d8bae435 app/modules/publishing/module.json
|
||||
61aa66129d18e2c5e6c2e7319de836a4713fde1ddcc31c6a5ca56c22d22d9b48 app/modules/quality/module.json
|
||||
1f02d199bac34cdfe2e41c32dfd450f2160493d9589b11eb1ca595fba74d5daf app/modules/security/module.json
|
||||
452a5262c418b393bdd8754fe90f9ea14edf61338f8dcfd6438f0852920f608c app/modules/system/module.json
|
||||
1fb7c2eaffa03cca8f91f14d20d10bad06841e34a5927376fe2ccd502670fa1b app/modules/themes/module.json
|
||||
11b4a1bc94b68998a3a2a8bec766bc56c7ff829939d914b0ce896f963feb10ed app/modules/users/module.json
|
||||
69ce3be6a8a4a28725c5c7f9dc8803415ab0d1e503047b66dfcc9a44c9e90109 bootstrap.mjs
|
||||
4a3273ec603224a70ceb81c0d5ed6617c3ba2d3111342b4e646a79752f4b3f87 compose.bind-mounts.example.yaml
|
||||
afe1021b726e5bf3a93d5ed64aee836975c7063a4ffd623d815e4749768fc2a7 compose.vps.yaml
|
||||
4be1b7e10a5923ccd314dbaa83d6c6fbf201cb100e878226feb36689d5b3ae2a compose.yaml
|
||||
2259c85b731bb029cf5fd82e78a2a4b7f4bc70a5c2c085e76daa2025ae72e138 compose.vps.yaml
|
||||
06565b1235d84d435740a8dcc54e08b23194922f16724200d535567bc5d08267 compose.yaml
|
||||
c62149ccb007896c2ac2c238b593bb5233aca97719eaaeed067348eb43a078bd db/schema.sql
|
||||
49690c4e4c8e84de8eb40ffe79823493b7be20fe46fa2ad9e6544f7f0afbfd2c deploy/Caddyfile
|
||||
cc794bca282d79a27d860d0bfef4641769f4e9f31172778a1d9d8205558d948e docs/ABNAHMEBERICHT_1_32_0.md
|
||||
@@ -64,13 +39,12 @@ de0d86f806a70195050613f5b56f6c4da1ee2eff02a9a991b6aacee229c46ca5 docs/ABNAHMEBE
|
||||
26c87fa38f804749a9363812127dc33ef69e201e4ef9b59206f6fe6e15c845b0 docs/ABNAHMEBERICHT_1_34_1.md
|
||||
f792d366c13c5a4a9b4e8d4054e26190d9c327a7a770005d74bbe2b31bb84ab3 docs/ABNAHMEBERICHT_1_34_2.md
|
||||
bc638fd21222ada7b15e40adff80a318aebec58139978109cbfd30040815a8d4 docs/ABNAHMEBERICHT_1_34_3.md
|
||||
c0ae5c4176ba6f7510be047d4feaf383cd646152131003ebccc6bfbf8c8056f5 docs/ABNAHMEBERICHT_1_35_0.md
|
||||
67a1be91b0c2d2797716f785f2f770c881cf88d7fd617722468df8d949d0638a docs/AUDIT_1_31_0_IST_UND_LUECKENANALYSE.md
|
||||
2acba8fcc5da42e5a808925a86628bc0c6fc817ddb082b0ead3857c6a43460f1 docs/BRANDING.md
|
||||
f23c643236c84b0e3715620598f358065748b6d090229003e23df8ad9320b54e docs/FULL_LISTING_EDITOR_2026.md
|
||||
ba9a9b93622fadc399cd9067d80b4ea339193eb19825106264a3b86b7128d107 docs/GENERATOR_STUDIO_2026.md
|
||||
54f45ae5c2f12b6314b3f1bc3e59e753c64ca7cfe39644725d765e9f02f637ef docs/GITHUB_DEPLOYMENT_1_34_0.md
|
||||
2f91666756e40d38cbf6bce35be6664e7b7ab81494e26900d7c6c43ab017d313 docs/GITHUB_IMPORT_STATUS.md
|
||||
83448fb3e3fae37ca4d172867c57ce1c4a2a2d1581d72bfb8d3003aad8b7e723 docs/GITHUB_IMPORT_STATUS.md
|
||||
65d19c1576f4a6011008a7e447a3530222517752fb59ebc5bbb506b791e32605 docs/HOTFIX_1_23_1_EDITOR_CACHE_VINTED_FIELDS.md
|
||||
c9c8f934a0c4192dae29df913588188aab1321364d1c46b9c66da5c754e9df41 docs/HOTFIX_1_23_2_FLUX_EDITOR_JOB_COLLAPSE.md
|
||||
2306ff399000c07a9d7e260a58d92c748fa3219922a4dda25f6b2121a1ad2dbc docs/HOTFIX_STUDIO_FLOW_DETAIL.md
|
||||
@@ -104,8 +78,6 @@ ac834ee57ab6bd8fe3dd0238837193e66e01d7e78590a289347b002591da3fb4 docs/RELEASE_1
|
||||
4c2c141ffc85b1d7517672abf2b28bb19fe4769505c0eb4d37e22bae06826cb3 docs/RELEASE_1_34_2_VERIFICATION.json
|
||||
d55eaec79e10800a930fa030367ffa34037755e5757bc532e72d25711c5ca09a docs/RELEASE_1_34_3_UPDATE_ARCHIVE_EXCLUSION.md
|
||||
4bb731af51e786a4827ec48bc7c4d4843c287456aedc09d8b74aa0eed3c0ec16 docs/RELEASE_1_34_3_VERIFICATION.json
|
||||
e7bb3bb950e3c5303746f43139ca8f7c985d2d73896b44f5b707dd46a9152529 docs/RELEASE_1_35_0_PLATFORM_KERNEL.md
|
||||
33eb9f0059a2ddc4b4debe5bf733720f2a5bb22f2710bab0a0cc9ac1b898ff11 docs/RELEASE_1_35_0_VERIFICATION.json
|
||||
fe5756f788b2ab131f0832504d90d46d1ed744bfe470fd040803985234afa3cf docs/SLICE_02_ACCEPTANCE.md
|
||||
05614ecf41becc399acfa6a3b2f681f37a8c80f5ce0130b98c578b2c8c60713c docs/SLICE_05_ACCEPTANCE.md
|
||||
0838bcc6464a6beff39f0b4b056e513c1a390193521d9a8fc5d440d38034e9d0 docs/SLICE_06_ACCEPTANCE.md
|
||||
@@ -138,10 +110,6 @@ d425fc1724ac55853bf3433348486de8f8aab111f53fefee3c532beb1deb82af docs/SLICE_32_
|
||||
aa7ebc90470071566749c61b0dece4169f87666dcca4b88922f7fbafcfdd5a84 docs/SLICE_34_VERIFICATION.json
|
||||
4b66a14b4515e38477f10325ab9d03d9782ad0d426845aefe2d508986ed2cd81 docs/UI_FOUNDATION_2026.md
|
||||
5e655fcf3bb1182f8850b4104b56697ea952b01d2698af6d3d40b05e338b3506 docs/UI_UX_FIXES_SLICE_03.md
|
||||
e5f065ba8d516a5ebd47e7e6859d0aab40ef69797413e358d969ef27c454fcdd docs/architecture/DESIGN_SYSTEM_1_35_0.md
|
||||
cc0edf92756a51414563b4407dedb21d1e3b36682f4bfbbc334aeb33a22ae1b4 docs/architecture/MIGRATION_ROADMAP_1_35_0.md
|
||||
eb47b055dd336e8360ec7c4712dee4ae37f109f3820e3f185969e2327d790cf8 docs/architecture/MODULE_SYSTEM_1_35_0.md
|
||||
d7773faac0e93510a5a51c91de997df03547b51f3124267dcf137d4e99299562 docs/architecture/SECURITY_ARCHITECTURE_1_35_0.md
|
||||
cc77a92ca268d3c444c6113a9cfaa38d3c805a579f6b1b21b7dd43a6ada58551 docs/superpowers/specs/2026-07-06-autolister-features-design.md
|
||||
c7db96df5fed3c8336062204322d62b4870f5c8ab1d30ea48cc6e7e38ed3513b extension/README.txt
|
||||
3873602c1529daad6647a7585c858d817821de0afbec93a26d1b3f5a7b9cb01e extension/background.js
|
||||
@@ -261,15 +229,15 @@ ddb9c7677600795a4adf8a19a7525b7551455459d16eed4cdfbe751ef9ddf0eb lib/zip.mjs
|
||||
2924f4e8abe5ebbd099fe1ceb2bc6ceb4bdda55fa3e57dd299cfc05d310a9246 local-ai/start-local-flux.ps1
|
||||
7c6176d8311b1ba9d511beb40edd41b8248e61c298528bbec3f9ef1171856c05 local-ai/stop-local-flux.ps1
|
||||
5f220a2dca02cc0764f6d0157677611b26c88860b48367eb661b6e47a1b92265 local-ai/workflows/vendoo_flux_schnell_api.json
|
||||
b192a542334cf3ba96bcdb7f6f1d3139d03ab50ea39e7d3746ccf40774491bb2 package-lock.json
|
||||
51a24a9f7f970b75ccab6b19e847a2f4ec7ed8eb8df4c7b9a54fa87d20d6ebb3 package.json
|
||||
d076846dd47faf06b9783dcff3407da3ead1a02a0583c2dd900566dd2a73486b package-lock.json
|
||||
bbcef72048cac0ee13d58fd8b4b24cfb4e8a42fe4efeb8da0f386d996a36f564 package.json
|
||||
3da71dd28cb25c7c7317b9061855d40c6a3ab8bc39391963b49ee1e707f838e5 platforms/ebay-de.mjs
|
||||
cc1ca2b4384c8c548bf755dc33101d248541bf893c3d5f35369961f904afcc00 platforms/ebay-ka.mjs
|
||||
fa52b247dd758aeee06120499bd1ae6c53e9f460f8bd7762b8f06141b201e32b platforms/etsy.mjs
|
||||
5c925e3934a4c9e2f26dc15b3449a249f5ce20ecebbd459b15365a34804824d6 platforms/vinted.mjs
|
||||
a2ea3f5f56dba8bd4d23fa54a26fd941567b4febdd1ed1b4e9f5bcc1c9786e53 public/android-chrome-192x192.png
|
||||
46ea6ba4ca6ca2352f37353b8bc4ca258935d29ecb8d3c9b133a48ea75c0c58f public/android-chrome-512x512.png
|
||||
adc20f43652cd20cc8951e76da8e2739e55a141398385ff48dd3c5a666e94a2d public/app.js
|
||||
706c4e4349f0ac9ac1af89b87ae81c4ee3f08f4191248098ab4dd28835a12932 public/app.js
|
||||
e0e7b8ef01c36f0625b6157aa46bd69de05c17be0b054208fb635d6507b52154 public/apple-touch-icon.png
|
||||
a2ea3f5f56dba8bd4d23fa54a26fd941567b4febdd1ed1b4e9f5bcc1c9786e53 public/brand/favicons/android-chrome-192x192.png
|
||||
46ea6ba4ca6ca2352f37353b8bc4ca258935d29ecb8d3c9b133a48ea75c0c58f public/brand/favicons/android-chrome-512x512.png
|
||||
@@ -303,40 +271,31 @@ ed3dfa59e989b154fac88a3c72ac840290ee3e1565d44aa75596bf0055c74020 public/brand/i
|
||||
3f39371abd1efa6cc8366aac9a6d067f3339b8f2a2da77ab8a26c6bdcf8c4fce public/brand/logo/vendoo-logo-original.png
|
||||
b56d07515f6220de7fd0cb149fed598a7361c985ae6ca7cb14329a99e8947aee public/brand/logo/vendoo-logo-transparent.png
|
||||
e83921d2af9930ab27df1abe64374cb9311189bfaaa951068109177443571a07 public/brand/logo/vendoo-wordmark-transparent.png
|
||||
1711a9387f2d6e75797b77c8daa92b94d7a15e128476e4b8aa9d54ccb0692a0e public/core/api-client.js
|
||||
f30395399a5c6d7e0a884b22f28f7648e6343695cee9960d6ff13b34de7b48f1 public/core/frontend-module-registry.js
|
||||
fb3dbcd03f7ed15b773b582c2a2b785c3040d3319aba5b7c682a7e74e1cf3b4e public/core/safe-dom.js
|
||||
9c73eb996e6f1aa4dee07aed367ca6ba2dffbeac0f3d8dcd8deab8cd123da4e3 public/design-system/theme-contract.json
|
||||
d4715f78f47be6c9be9b03c67de051d824bc2abfd239d691438d14ba09dea0eb public/design-system/theme-runtime.js
|
||||
5163f6ef56fea0afb1254dac370f65e7c18463902a7970ed2cbeca84571fd60b public/design-system/tokens/source.json
|
||||
3f9062017494212a7eadc1daaf2e0396e79e861cb464e76d3a36ca08d8729955 public/design-system/tokens.css
|
||||
15bf8dd655b34a8015447e834d387af12685238ede598f5f0e7927eea2933483 public/favicon.ico
|
||||
c5c46fdbafd46a0a26c88126ddc1bc8a82e751f2edf49b414b6e6a72d7923afd public/index.html
|
||||
83dca0cd4307c0c4e7ab784346ca651c2469e30d6c6254b6fc669eeca89e1a1f public/index.html
|
||||
141c261b2d0ea713af140d2e89f98b9c9046584bef60e97d8bf3d6b6f504efa2 public/login.html
|
||||
59291f75eca405e3856bafea76cc601d681060eb6b36bf82a4be3c3ab7eac290 public/mobile-upload-expired.html
|
||||
f046f166d89fc32774b87deb02e6d144fd2f40c0925aac67487e5b7c489a0ec1 public/mobile-upload.css
|
||||
6152e4fe715efbf8fd6a7c4286f23172bc3cf15dde085e5e4f7389ad5e65ad73 public/mobile-upload.html
|
||||
f13846801a8493aa43df6ec3d78b826b21b59e8f21d47735f68fd0b278e440c7 public/mobile-upload.js
|
||||
3352513208e08e9ba2c56509734de788c83bdf93bc83ded6a1bb237822f84ec2 public/modules/README.md
|
||||
d2c88e1199eb060211be40b9c83b1382b829cc5b32af22a161ad83c6c0784765 public/style.css
|
||||
a6f90e7e6ba15519619000e628c024feb3667ef013f44d62a3d6645cceb2ddf2 public/style.css
|
||||
ff525e0af963e6f54636cfdc1bd2d045aaf82e7b0199755eb5cdf4c52110be2e public/vendoo-icons.js
|
||||
deb1a45f6414a2357046ff1d66fc58114549ba5ad695ed40fcceef2aef9f64a8 scripts/docker-backup.sh
|
||||
20e7590a7633b6c61086a5596d0bec1d343f0ff0eecc444ac932130ff363e258 scripts/docker-entrypoint.sh
|
||||
be72c39f00415cd57b69835346f00043841ce9f853b10436eb1799ca42980cb5 scripts/docker-restore.sh
|
||||
e0d184804eec84d84859b544c8173f38dfd9e9363e8d68238e409bb0175887e4 scripts/github-deploy.ps1
|
||||
cca73fc41c82d9689c0e109352a62e904d45f8883d77f89974bbd20679a48615 scripts/github-deploy.sh
|
||||
6563b5f92e0bfba32b6659bb3c9232ea1b59a3746f33cae0bf942a16799c946e scripts/github-deploy.ps1
|
||||
2e9c33fb6d955d41d4a717056aab639bd9cf924454e1ffca6a7380d097c533b8 scripts/github-deploy.sh
|
||||
0a9bafc79c1c5d59fb9b51ebc34c00eec4a4e9a5e873ce1818f50f3ab983da70 scripts/install-local-flux.ps1
|
||||
2924f4e8abe5ebbd099fe1ceb2bc6ceb4bdda55fa3e57dd299cfc05d310a9246 scripts/runtime-start-local-flux.ps1
|
||||
7c6176d8311b1ba9d511beb40edd41b8248e61c298528bbec3f9ef1171856c05 scripts/runtime-stop-local-flux.ps1
|
||||
1a5736cf5fccc991df122163977c2eac80281bd99a54a5680b3d7eae99e4b79d scripts/start-local-flux.bat
|
||||
3bf3aa74f5cc1f2946bb2da637eb30fe8daec783a32f9721ebb0468e20df90bd scripts/uninstall-local-flux.ps1
|
||||
a2b4868505cd169ee3c5a692eedb90241675401ddbe5cb9e026fb4091384af1d server.mjs
|
||||
ac7c937b32b74a898c5eeb85aeaece05fe4207d977ed388129271f8e991a30a9 setup-gui.ps1
|
||||
0951af11647d4b408248b76daa455916a6e89a05c0a5b9693ae728c697d01ede server.mjs
|
||||
ffee63d39a4aa0c57631a66522c1f2ec422b40a804a0e081962efdc37eeeaeb5 setup-gui.ps1
|
||||
cf4519ce5adc01f05458e3286e86f3a419cceaff5fcdd869a1d07f1c1d1a0d8c setup.bat
|
||||
285403934cdf25226f085c9bb2bf174ea7fb12a59e60afd326fa379b38340608 setup.ps1
|
||||
f7f2eccf8e1f1af608e8076774f0fbb35616ae65962bf68119b732cd79e2c9bc setup.sh
|
||||
0af0ccd1ce1ae914c5e120b5ca06d3d65e83a47035ed74f5a1804d802288a099 setup.ps1
|
||||
3256acb4e2fa0877e34edf2404b53484316332fa29b159ee6dbbf94f0c4dfc61 setup.sh
|
||||
90485a16e3468cb9a6750646700fbb036cb9ac410e9b8bd5c582f7b923bf3c31 tools/build-release.mjs
|
||||
68ab3cada7062bb2a226e5d55d0063788674e9724ea504630261f604c2841e71 tools/generate-design-tokens.mjs
|
||||
294659f2beb607540abbc3d6b4a1658525448a42f8880f2b282081a259c4e35b tools/prepare-git-staging.mjs
|
||||
8f9ede5f112ee03323db3a6c73aee00b7037f5139dad241b11baf5f0e9cee4e0 tools/verify-config-store.mjs
|
||||
1bf7158230c79b64149f3632ef0e1474f284f2e6b98069aeb6fc7360645e971d tools/verify-db-migrations.py
|
||||
@@ -345,13 +304,11 @@ f7f2eccf8e1f1af608e8076774f0fbb35616ae65962bf68119b732cd79e2c9bc setup.sh
|
||||
cbd91b45d97e11bd4f5ca2f9d2edf2b5931cb2aa336cb71afc30aad9cffbe1e0 tools/verify-deployment-1.34.0.mjs
|
||||
01a43040a95cdac40703f49031acd9267cdba99d9027a51718f35ffb643237fc tools/verify-deployment-1.34.2.mjs
|
||||
906767e574c06aedebb2d7797150bda2f1e4c4536e8f82f4c7de2e3cc8398c8e tools/verify-deployment-1.34.3.mjs
|
||||
71d00227a59592cc0ad8ba321c63350ec25748ef965d8cd0eff1317738c7fba8 tools/verify-deployment-1.35.0.mjs
|
||||
1f052446f6c9627420afeb48aa09771b61f9e08ba573f6a0651b2843ef0b79f8 tools/verify-git-safety-regression.mjs
|
||||
5bba3d7ae042b0bcf4eb56bacb3f6e41e1b0e00cadd7666505792ba6748cc987 tools/verify-git-safety.mjs
|
||||
2f8d687319619240efe5849c276f7723e69755c66aa3b9609e41fdf0cda01e55 tools/verify-git-staging.mjs
|
||||
1d4351e4a827e718766a1677ddfbc3771a91942d0d5a93753e39f1b99c68a4dd tools/verify-hotfix-1.23.2.mjs
|
||||
44c95844422be3deaad6054ee3f0cced9c25986815d976e0c64b042d0c2c5e1e tools/verify-installer-shell.sh
|
||||
cab03e98687af87f359f6424d440fc0b644da97b3c614509b8c7d008d4b18a8d tools/verify-platform-architecture-1.35.0.mjs
|
||||
36e35fedb08923c22fd8551389d33432b66dbd4215d95c594ef29bfc790c98e0 tools/verify-release-1.25.0.mjs
|
||||
89621694b7bad18b6f3f1ac1956032f64112c40d3edc069ca581dc813d57d628 tools/verify-release-1.25.1-static.mjs
|
||||
c908f5186cf2d71ef83d5e649327ae19965568933acfb32057ca00d29cf890bc tools/verify-release-1.25.1.mjs
|
||||
@@ -368,4 +325,4 @@ a372258b97aa68d59a1fe0fd3f380d019c59412d5c408a6064f437d5cfc6e709 tools/verify-s
|
||||
21061ed5dc88feef48e80efffd9a1054d38598dce5a5d3f9f6bde39b539b83f0 tools/verify-slice32-batch.mjs
|
||||
0f05b1ac3b686cf535bcfb3e6f6df24a29c3429ade1b3c666c1b99abb1a7311b tools/verify-slice32-image-render.mjs
|
||||
0f3f684019763a14f5fbaec42e42c680cf748d930d46cd607fc61eb413ca4553 tools/verify-ui-contracts-1.26.1.mjs
|
||||
c98b9e6e1c09022b2d4416b5bdc74354cdb9151012c00b5622f65f3620018c47 update-manifest.json
|
||||
8b9180ea0587d13f75ff63bc56e0c5c1c7b1c55ca4cca7d8814394ee9ce0785d update-manifest.json
|
||||
|
||||
@@ -1,25 +1,11 @@
|
||||
# Vendoo 1.35.0
|
||||
# Vendoo 1.34.3
|
||||
|
||||
Vendoo 1.35.0 führt die produktionsorientierte **Modular Platform Foundation** ein. Ein neuer Plattformkernel registriert bestehende Bereiche als klar versionierte `legacy-bridge`-Module, prüft Abhängigkeiten, startet und stoppt Module geordnet und verlangt für neue Kernel-Routen explizite Deny-by-default-Policies. Der bestehende Funktionsumfang bleibt unverändert; es gibt keinen Big-Bang-Umbau, keinen Reset und keine Framework-Migration.
|
||||
Vendoo 1.34.3 führt eine **Zero-Edit-Installation** ein: Auf Windows startet `setup.bat` eine grafische Installationsmaske; auf Linux, NAS und VPS führt `setup.sh` dialogbasiert durch alle Pflichtangaben. Niemand muss `.env`, Compose- oder Proxy-Dateien manuell bearbeiten. Zugangsdaten für AI und Marktplätze werden nach dem ersten Login in den Vendoo-Einstellungen gepflegt.
|
||||
|
||||
Das Design-System besitzt nun eine versionierte Tokenquelle mit Light-, Dark- und High-Contrast-Vertrag. Die bestehende Oberfläche nutzt weiterhin ihre bisherigen Variablennamen über eine Kompatibilitätsbrücke. Ein späterer Theme Manager darf nur typisierte, freigegebene Tokens verändern – freies CSS oder JavaScript ist ausgeschlossen.
|
||||
|
||||
Die **Zero-Edit-Installation** bleibt erhalten: Auf Windows startet `setup.bat` eine grafische Installationsmaske; auf Linux, NAS und VPS führt `setup.sh` durch alle Pflichtangaben. Der GitHub-Deploy-Assistent filtert lokale Laufzeit-, Update-, Backup- und Secret-Dateien weiterhin vor dem Push.
|
||||
Der GitHub-Deploy-Assistent verwendet ab 1.34.3 auf allen Plattformen denselben geprüften Staging-Builder. Lokale `.env.*`, Operations-Daten, Uploads, Backups, Logs, Datenbanken und Modelle werden automatisch ausgeschlossen. Bei einem echten Sicherheitsfund zeigt Windows jede Detailzeile an und öffnet zusätzlich den Bericht `logs/github-deploy-safety-report.txt`.
|
||||
|
||||
Der lokale Ordner `updates/` mit Update-, Rollback- und Slice-Sicherungen wird ebenfalls vollständig ausgeschlossen. Aktive Installationsdateien im Projektroot und unter `scripts/` bleiben Teil des GitHub-Imports.
|
||||
|
||||
## Plattformarchitektur 1.35.0
|
||||
|
||||
- Kernel: `app/kernel/`
|
||||
- Modulmanifeste: `app/modules/*/module.json`
|
||||
- Architekturgrenzen: `app/architecture-boundaries.json`
|
||||
- Design-Token-Quelle: `public/design-system/tokens/source.json`
|
||||
- Theme-Vertrag: `public/design-system/theme-contract.json`
|
||||
- Architektur-Gate: `npm run verify:architecture`
|
||||
- vollständige technische Dokumentation: `docs/architecture/`
|
||||
|
||||
Die zwölf registrierten Bestandsmodule stehen bewusst auf `legacy-bridge`. Das zeigt transparent, welche Bereiche bereits einen Vertrag besitzen, aber noch schrittweise aus `server.mjs`, `public/app.js` und `public/style.css` herausgelöst werden müssen.
|
||||
|
||||
## Einfachster Start
|
||||
|
||||
- **Windows lokal oder Docker Desktop:** `setup.bat` doppelklicken, Ziel und Netzwerkzugriff auswählen, **Installieren** anklicken.
|
||||
@@ -159,7 +145,7 @@ Lokaler Multi-Plattform Listing-Generator mit AI, Lagerverwaltung und Publishing
|
||||
|
||||
## Aktueller Entwicklungsstand
|
||||
|
||||
- Version: `1.35.0`
|
||||
- Version: `1.34.3`
|
||||
- UI-Slice: `UI 2026 / Slice 07 – Cross-Browser Extensions, Responsive UI & Mobile Upload`
|
||||
- Startseite: `Studio Flow`
|
||||
- Tech Stack: Node.js, Express (ESM), Vanilla JS, SQLite (`better-sqlite3`)
|
||||
@@ -296,7 +282,7 @@ Die Fortschrittsanzeige verwendet im FLUX-Studio-Pro-Pfad ausschließlich reale
|
||||
|
||||
Vendoo 1.28.0 prüft Artikel vor dem Publishing auf Blocker, Vollständigkeit, Bildqualität, mögliche Duplikate und Plattformtauglichkeit. AI-Verbesserungen werden nur als Vorschlag angezeigt und erst nach ausdrücklicher Bestätigung gespeichert.
|
||||
|
||||
## Installationsziel wählen (1.35.0)
|
||||
## Installationsziel wählen (1.34.3)
|
||||
|
||||
`setup.bat` führt auf Windows durch lokale Node-Installation oder Docker Desktop. Für Linux, NAS, Portainer und VPS steht `setup.sh` bereit. Beide Wege verwenden dieselbe `.env`, dieselben Compose-Dateien und dieselben persistenten Datenbereiche.
|
||||
|
||||
|
||||
-11
@@ -56,14 +56,3 @@ Sicherheitsprobleme nicht mit Zugangsdaten oder personenbezogenen Daten in öffe
|
||||
- Bearbeitungssperren verhindern parallele Änderungen, ersetzen aber keine Backups.
|
||||
- Audit-Logs können personenbezogene Betriebsdaten wie Benutzer, IP und Aktionen enthalten; Aufbewahrung und Zugriff müssen organisatorisch geregelt werden.
|
||||
|
||||
|
||||
|
||||
## Architektur-Sicherheitsregeln ab 1.35.0
|
||||
|
||||
- Neue Kernel-Routen sind Deny-by-default und benötigen eine registrierte Policy.
|
||||
- Modulmanifeste, Abhängigkeiten, Capabilities und Ownership werden vor dem Start validiert.
|
||||
- Zyklische Abhängigkeiten, fehlende Capabilities und doppelte Ressourcen-Ownership blockieren den Kernelstart.
|
||||
- Drittanbieter-Erweiterungen dürfen nicht im Vendoo-Hauptprozess ausgeführt werden.
|
||||
- Themes dürfen nur freigegebene, typisierte Design Tokens verändern; freies CSS, JavaScript und externe URLs sind verboten.
|
||||
- Der Event Bus ordnet Listener einem Owner zu, damit Module beim Stoppen ihre Listener vollständig entfernen können.
|
||||
- Das Architektur-Gate `npm run verify:architecture` ist verpflichtender Bestandteil von CI und Releases.
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"architecture": "modular-monolith",
|
||||
"rules": {
|
||||
"modulePrefix": "vendoo.",
|
||||
"denyImplicitRoutes": true,
|
||||
"denyCrossModuleDatabaseAccess": true,
|
||||
"denyCrossModuleInternalImports": true,
|
||||
"thirdPartyExtensionsInMainProcess": false,
|
||||
"themeCustomCss": false,
|
||||
"themeTokensOnly": true
|
||||
},
|
||||
"migration": {
|
||||
"mode": "strangler",
|
||||
"legacyBridgeStatus": "legacy-bridge",
|
||||
"frameworkMigration": false,
|
||||
"databaseReplacement": false
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://vendoo.local/contracts/module.schema.json",
|
||||
"title": "Vendoo Module Manifest",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["schemaVersion", "id", "name", "version", "type", "status", "requires", "permissions", "provides", "consumes", "owns"],
|
||||
"properties": {
|
||||
"schemaVersion": { "const": 1 },
|
||||
"id": { "type": "string", "pattern": "^vendoo\\.[a-z][a-z0-9-]*$" },
|
||||
"name": { "type": "string", "minLength": 1, "maxLength": 80 },
|
||||
"version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$" },
|
||||
"type": { "enum": ["core", "feature", "adapter", "extension-host"] },
|
||||
"status": { "enum": ["native", "legacy-bridge", "disabled"] },
|
||||
"description": { "type": "string", "maxLength": 500 },
|
||||
"requires": { "$ref": "#/$defs/moduleIds" },
|
||||
"permissions": { "$ref": "#/$defs/capabilities" },
|
||||
"provides": { "$ref": "#/$defs/capabilities" },
|
||||
"consumes": { "$ref": "#/$defs/capabilities" },
|
||||
"owns": { "$ref": "#/$defs/capabilities" }
|
||||
},
|
||||
"$defs": {
|
||||
"moduleIds": {
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": { "type": "string", "pattern": "^vendoo\\.[a-z][a-z0-9-]*$" }
|
||||
},
|
||||
"capabilities": {
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*(?:\\.[a-z][a-z0-9-]*)+$" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { APP_ROOT } from '../../../lib/runtime-paths.mjs';
|
||||
import { PlatformError } from '../../kernel/errors.mjs';
|
||||
|
||||
const source = JSON.parse(readFileSync(join(APP_ROOT, 'public', 'design-system', 'tokens', 'source.json'), 'utf8'));
|
||||
const HEX_COLOR = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
||||
const RGB_COLOR = /^rgba?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)(?:\s*,\s*(0|1|0?\.\d+))?\s*\)$/i;
|
||||
|
||||
function validColor(value) {
|
||||
if (HEX_COLOR.test(value)) return true;
|
||||
const match = value.match(RGB_COLOR);
|
||||
if (!match) return false;
|
||||
const channels = match.slice(1, 4).map(Number);
|
||||
const alpha = match[4] === undefined ? 1 : Number(match[4]);
|
||||
return channels.every(channel => Number.isFinite(channel) && channel >= 0 && channel <= 255) && Number.isFinite(alpha) && alpha >= 0 && alpha <= 1;
|
||||
}
|
||||
const DIMENSION = /^-?\d+(?:\.\d+)?px$/i;
|
||||
|
||||
function validateValue(token, value) {
|
||||
const raw = String(value || '').trim();
|
||||
if (token.type === 'color' && !validColor(raw)) return false;
|
||||
if (token.type === 'dimension') {
|
||||
if (!DIMENSION.test(raw)) return false;
|
||||
const numeric = Number.parseFloat(raw);
|
||||
if (token.min !== undefined && numeric < Number(token.min)) return false;
|
||||
if (token.max !== undefined && numeric > Number(token.max)) return false;
|
||||
}
|
||||
return ['color', 'dimension'].includes(token.type);
|
||||
}
|
||||
|
||||
export function getThemeContract() {
|
||||
return {
|
||||
schemaVersion: source.schemaVersion,
|
||||
contractVersion: source.contractVersion,
|
||||
themes: [...source.themes],
|
||||
tokens: Object.fromEntries(Object.entries(source.tokens).map(([name, token]) => [name, {
|
||||
css: token.css,
|
||||
type: token.type,
|
||||
customizable: Boolean(token.customizable),
|
||||
...(token.min !== undefined ? { min: token.min } : {}),
|
||||
...(token.max !== undefined ? { max: token.max } : {}),
|
||||
}])),
|
||||
};
|
||||
}
|
||||
|
||||
export function validateThemeDefinition(theme) {
|
||||
if (!theme || typeof theme !== 'object' || Array.isArray(theme)) {
|
||||
throw new PlatformError('Theme-Definition ist ungültig.', { code: 'THEME_INVALID', status: 400, expose: true });
|
||||
}
|
||||
const id = String(theme.id || '').trim();
|
||||
const name = String(theme.name || '').trim();
|
||||
if (!/^[a-z][a-z0-9-]{2,39}$/.test(id) || ['light', 'dark', 'contrast', 'system'].includes(id)) throw new PlatformError('Theme-ID ist ungültig.', { code: 'THEME_ID_INVALID', status: 400, expose: true });
|
||||
if (!name || name.length > 80) throw new PlatformError('Theme-Name ist ungültig.', { code: 'THEME_NAME_INVALID', status: 400, expose: true });
|
||||
const values = {};
|
||||
for (const [tokenName, value] of Object.entries(theme.values || {})) {
|
||||
const token = source.tokens[tokenName];
|
||||
if (!token || !token.customizable) throw new PlatformError(`Token ist nicht anpassbar: ${tokenName}`, { code: 'THEME_TOKEN_FORBIDDEN', status: 400, expose: true });
|
||||
if (!validateValue(token, value)) throw new PlatformError(`Ungültiger Wert für ${tokenName}.`, { code: 'THEME_VALUE_INVALID', status: 400, expose: true });
|
||||
values[tokenName] = String(value).trim();
|
||||
}
|
||||
return Object.freeze({ schemaVersion: 1, id, name, values });
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
export class PlatformError extends Error {
|
||||
constructor(message, { code = 'PLATFORM_ERROR', status = 500, details = null, expose = false, cause = undefined } = {}) {
|
||||
super(String(message || 'Unbekannter Plattformfehler'), { cause });
|
||||
this.name = 'PlatformError';
|
||||
this.code = String(code || 'PLATFORM_ERROR');
|
||||
this.status = Number.isInteger(status) ? status : 500;
|
||||
this.details = details;
|
||||
this.expose = Boolean(expose || this.status < 500);
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizePlatformError(error) {
|
||||
if (error instanceof PlatformError) return error;
|
||||
return new PlatformError(error?.message || 'Interner Serverfehler', {
|
||||
code: error?.code || 'INTERNAL_ERROR',
|
||||
status: Number.isInteger(error?.status) ? error.status : 500,
|
||||
details: error?.details || null,
|
||||
expose: Boolean(error?.expose),
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
|
||||
export function platformErrorPayload(error, requestId = null) {
|
||||
const normalized = normalizePlatformError(error);
|
||||
return {
|
||||
error: normalized.expose ? normalized.message : 'Interner Serverfehler',
|
||||
code: normalized.code,
|
||||
request_id: requestId || null,
|
||||
...(normalized.expose && normalized.details ? { details: normalized.details } : {}),
|
||||
};
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import { PlatformError } from './errors.mjs';
|
||||
|
||||
const EVENT_PATTERN = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/;
|
||||
|
||||
export class EventBus {
|
||||
#listeners = new Map();
|
||||
#maxListeners;
|
||||
|
||||
constructor({ maxListenersPerEvent = 50 } = {}) {
|
||||
this.#maxListeners = Math.max(1, Number(maxListenersPerEvent) || 50);
|
||||
}
|
||||
|
||||
on(eventName, handler, { owner = 'core', once = false } = {}) {
|
||||
if (!EVENT_PATTERN.test(String(eventName))) {
|
||||
throw new PlatformError(`Ungültiger Eventname: ${eventName}`, { code: 'EVENT_NAME_INVALID', status: 500 });
|
||||
}
|
||||
if (typeof handler !== 'function') {
|
||||
throw new PlatformError('Event-Handler muss eine Funktion sein.', { code: 'EVENT_HANDLER_INVALID', status: 500 });
|
||||
}
|
||||
const listeners = this.#listeners.get(eventName) || [];
|
||||
if (listeners.length >= this.#maxListeners) {
|
||||
throw new PlatformError(`Zu viele Listener für ${eventName}.`, { code: 'EVENT_LISTENER_LIMIT', status: 500 });
|
||||
}
|
||||
const entry = Object.freeze({ handler, owner: String(owner || 'core'), once: Boolean(once) });
|
||||
listeners.push(entry);
|
||||
this.#listeners.set(eventName, listeners);
|
||||
return () => this.off(eventName, handler);
|
||||
}
|
||||
|
||||
once(eventName, handler, options = {}) {
|
||||
return this.on(eventName, handler, { ...options, once: true });
|
||||
}
|
||||
|
||||
off(eventName, handler) {
|
||||
const listeners = this.#listeners.get(eventName) || [];
|
||||
const remaining = listeners.filter(entry => entry.handler !== handler);
|
||||
if (remaining.length) this.#listeners.set(eventName, remaining);
|
||||
else this.#listeners.delete(eventName);
|
||||
return remaining.length !== listeners.length;
|
||||
}
|
||||
|
||||
removeOwner(owner) {
|
||||
let removed = 0;
|
||||
for (const [eventName, listeners] of this.#listeners) {
|
||||
const remaining = listeners.filter(entry => {
|
||||
const match = entry.owner === owner;
|
||||
if (match) removed += 1;
|
||||
return !match;
|
||||
});
|
||||
if (remaining.length) this.#listeners.set(eventName, remaining);
|
||||
else this.#listeners.delete(eventName);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
async emit(eventName, payload = null, context = {}) {
|
||||
const listeners = [...(this.#listeners.get(eventName) || [])];
|
||||
const results = [];
|
||||
for (const entry of listeners) {
|
||||
try {
|
||||
results.push({ owner: entry.owner, ok: true, value: await entry.handler(payload, Object.freeze({ ...context, eventName })) });
|
||||
} catch (error) {
|
||||
results.push({ owner: entry.owner, ok: false, error });
|
||||
} finally {
|
||||
if (entry.once) this.off(eventName, entry.handler);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
snapshot() {
|
||||
return [...this.#listeners.entries()].map(([event, listeners]) => ({
|
||||
event,
|
||||
listeners: listeners.map(entry => ({ owner: entry.owner, once: entry.once })),
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import { PlatformError } from './errors.mjs';
|
||||
|
||||
const FLAG_PATTERN = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/;
|
||||
|
||||
function envKey(flag) {
|
||||
return `VENDOO_FEATURE_${flag.replace(/[^a-z0-9]/gi, '_').toUpperCase()}`;
|
||||
}
|
||||
|
||||
function parseBoolean(value, fallback) {
|
||||
if (value === undefined || value === null || value === '') return fallback;
|
||||
if (/^(1|true|yes|on)$/i.test(String(value))) return true;
|
||||
if (/^(0|false|no|off)$/i.test(String(value))) return false;
|
||||
throw new PlatformError(`Ungültiger Feature-Flag-Wert: ${value}`, { code: 'FEATURE_FLAG_VALUE_INVALID', status: 500 });
|
||||
}
|
||||
|
||||
export class FeatureFlagRegistry {
|
||||
#flags = new Map();
|
||||
|
||||
define(name, { defaultValue = false, owner = 'vendoo.system', description = '', mutable = false } = {}) {
|
||||
const normalized = String(name || '');
|
||||
if (!FLAG_PATTERN.test(normalized)) {
|
||||
throw new PlatformError(`Ungültiger Feature-Flag: ${normalized}`, { code: 'FEATURE_FLAG_NAME_INVALID', status: 500 });
|
||||
}
|
||||
if (this.#flags.has(normalized)) {
|
||||
throw new PlatformError(`Feature-Flag bereits definiert: ${normalized}`, { code: 'FEATURE_FLAG_DUPLICATE', status: 500 });
|
||||
}
|
||||
const configured = parseBoolean(process.env[envKey(normalized)], Boolean(defaultValue));
|
||||
this.#flags.set(normalized, {
|
||||
name: normalized,
|
||||
value: configured,
|
||||
defaultValue: Boolean(defaultValue),
|
||||
owner: String(owner),
|
||||
description: String(description),
|
||||
mutable: Boolean(mutable),
|
||||
source: process.env[envKey(normalized)] === undefined ? 'default' : 'environment',
|
||||
});
|
||||
return configured;
|
||||
}
|
||||
|
||||
isEnabled(name) {
|
||||
if (!this.#flags.has(name)) {
|
||||
throw new PlatformError(`Unbekannter Feature-Flag: ${name}`, { code: 'FEATURE_FLAG_UNKNOWN', status: 500 });
|
||||
}
|
||||
return this.#flags.get(name).value;
|
||||
}
|
||||
|
||||
set(name, value) {
|
||||
const flag = this.#flags.get(name);
|
||||
if (!flag) throw new PlatformError(`Unbekannter Feature-Flag: ${name}`, { code: 'FEATURE_FLAG_UNKNOWN', status: 404, expose: true });
|
||||
if (!flag.mutable) throw new PlatformError(`Feature-Flag ist nicht zur Laufzeit änderbar: ${name}`, { code: 'FEATURE_FLAG_IMMUTABLE', status: 409, expose: true });
|
||||
flag.value = Boolean(value);
|
||||
flag.source = 'runtime';
|
||||
return flag.value;
|
||||
}
|
||||
|
||||
list() {
|
||||
return [...this.#flags.values()].map(flag => ({ ...flag }));
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { PlatformError } from './errors.mjs';
|
||||
|
||||
export class LifecycleManager {
|
||||
#state = 'created';
|
||||
#hooks = [];
|
||||
|
||||
register({ id, order = 100, start = async () => {}, stop = async () => {} }) {
|
||||
if (!id || this.#hooks.some(hook => hook.id === id)) {
|
||||
throw new PlatformError(`Ungültiger oder doppelter Lifecycle-Hook: ${id}`, { code: 'LIFECYCLE_HOOK_INVALID' });
|
||||
}
|
||||
this.#hooks.push({ id: String(id), order: Number(order) || 100, start, stop, state: 'registered' });
|
||||
}
|
||||
|
||||
async start(context) {
|
||||
if (!['created', 'stopped'].includes(this.#state)) throw new PlatformError(`Kernel kann aus Zustand ${this.#state} nicht starten.`, { code: 'LIFECYCLE_STATE_INVALID' });
|
||||
this.#state = 'starting';
|
||||
const started = [];
|
||||
try {
|
||||
for (const hook of [...this.#hooks].sort((a, b) => a.order - b.order)) {
|
||||
hook.state = 'starting';
|
||||
await hook.start(context);
|
||||
hook.state = 'running';
|
||||
started.push(hook);
|
||||
}
|
||||
this.#state = 'running';
|
||||
} catch (error) {
|
||||
for (const hook of started.reverse()) {
|
||||
try { await hook.stop({ ...context, rollback: true }); } catch {}
|
||||
hook.state = 'stopped';
|
||||
}
|
||||
this.#state = 'failed';
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async stop(context) {
|
||||
if (!['running', 'starting'].includes(this.#state)) return;
|
||||
this.#state = 'stopping';
|
||||
for (const hook of [...this.#hooks].sort((a, b) => b.order - a.order)) {
|
||||
if (hook.state !== 'running') continue;
|
||||
try { await hook.stop(context); } finally { hook.state = 'stopped'; }
|
||||
}
|
||||
this.#state = 'stopped';
|
||||
}
|
||||
|
||||
snapshot() {
|
||||
return { state: this.#state, hooks: this.#hooks.map(({ start: _s, stop: _t, ...hook }) => ({ ...hook })) };
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { PlatformError } from './errors.mjs';
|
||||
|
||||
const MODULE_ID = /^vendoo\.[a-z][a-z0-9-]*$/;
|
||||
const SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
||||
const CAPABILITY = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/;
|
||||
const TYPES = new Set(['core', 'feature', 'adapter', 'extension-host']);
|
||||
const STATUSES = new Set(['native', 'legacy-bridge', 'disabled']);
|
||||
|
||||
function uniqueStrings(values, label, pattern = CAPABILITY) {
|
||||
if (!Array.isArray(values)) throw new PlatformError(`${label} muss ein Array sein.`, { code: 'MODULE_MANIFEST_INVALID' });
|
||||
const normalized = values.map(value => String(value || '').trim());
|
||||
if (normalized.some(value => !pattern.test(value))) {
|
||||
throw new PlatformError(`${label} enthält einen ungültigen Eintrag.`, { code: 'MODULE_MANIFEST_INVALID' });
|
||||
}
|
||||
if (new Set(normalized).size !== normalized.length) {
|
||||
throw new PlatformError(`${label} enthält Duplikate.`, { code: 'MODULE_MANIFEST_INVALID' });
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function validateModuleManifest(manifest) {
|
||||
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
|
||||
throw new PlatformError('Modulmanifest fehlt oder ist ungültig.', { code: 'MODULE_MANIFEST_INVALID' });
|
||||
}
|
||||
const normalized = {
|
||||
schemaVersion: Number(manifest.schemaVersion || 1),
|
||||
id: String(manifest.id || '').trim(),
|
||||
name: String(manifest.name || '').trim(),
|
||||
version: String(manifest.version || '').trim(),
|
||||
type: String(manifest.type || '').trim(),
|
||||
status: String(manifest.status || 'native').trim(),
|
||||
description: String(manifest.description || '').trim(),
|
||||
requires: Object.freeze(uniqueStrings(manifest.requires || [], 'requires', MODULE_ID)),
|
||||
permissions: Object.freeze(uniqueStrings(manifest.permissions || [], 'permissions')),
|
||||
provides: Object.freeze(uniqueStrings(manifest.provides || [], 'provides')),
|
||||
consumes: Object.freeze(uniqueStrings(manifest.consumes || [], 'consumes')),
|
||||
owns: Object.freeze(uniqueStrings(manifest.owns || [], 'owns')),
|
||||
};
|
||||
if (normalized.schemaVersion !== 1) throw new PlatformError('Nicht unterstützte Manifestversion.', { code: 'MODULE_MANIFEST_VERSION' });
|
||||
if (!MODULE_ID.test(normalized.id)) throw new PlatformError(`Ungültige Modul-ID: ${normalized.id}`, { code: 'MODULE_ID_INVALID' });
|
||||
if (!normalized.name) throw new PlatformError(`Modulname fehlt: ${normalized.id}`, { code: 'MODULE_NAME_REQUIRED' });
|
||||
if (!SEMVER.test(normalized.version)) throw new PlatformError(`Ungültige Modulversion: ${normalized.id}`, { code: 'MODULE_VERSION_INVALID' });
|
||||
if (!TYPES.has(normalized.type)) throw new PlatformError(`Ungültiger Modultyp: ${normalized.id}`, { code: 'MODULE_TYPE_INVALID' });
|
||||
if (!STATUSES.has(normalized.status)) throw new PlatformError(`Ungültiger Modulstatus: ${normalized.id}`, { code: 'MODULE_STATUS_INVALID' });
|
||||
if (normalized.requires.includes(normalized.id)) throw new PlatformError(`Modul darf sich nicht selbst benötigen: ${normalized.id}`, { code: 'MODULE_SELF_DEPENDENCY' });
|
||||
return Object.freeze(normalized);
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
import { PlatformError } from './errors.mjs';
|
||||
import { validateModuleManifest } from './module-contract.mjs';
|
||||
|
||||
export class ModuleRegistry {
|
||||
#modules = new Map();
|
||||
#startOrder = [];
|
||||
#ownership = new Map();
|
||||
|
||||
register({ manifest, lifecycle = {} }) {
|
||||
const validated = validateModuleManifest(manifest);
|
||||
if (this.#modules.has(validated.id)) {
|
||||
throw new PlatformError(`Modul bereits registriert: ${validated.id}`, { code: 'MODULE_DUPLICATE' });
|
||||
}
|
||||
for (const resource of validated.owns) {
|
||||
const owner = this.#ownership.get(resource);
|
||||
if (owner) throw new PlatformError(`Ressource ${resource} wird bereits von ${owner} besessen.`, { code: 'MODULE_OWNERSHIP_CONFLICT' });
|
||||
}
|
||||
const record = {
|
||||
manifest: validated,
|
||||
lifecycle: {
|
||||
start: typeof lifecycle.start === 'function' ? lifecycle.start : async () => {},
|
||||
stop: typeof lifecycle.stop === 'function' ? lifecycle.stop : async () => {},
|
||||
health: typeof lifecycle.health === 'function' ? lifecycle.health : async () => ({ ok: true }),
|
||||
},
|
||||
state: validated.status === 'disabled' ? 'disabled' : 'registered',
|
||||
startedAt: null,
|
||||
error: null,
|
||||
};
|
||||
this.#modules.set(validated.id, record);
|
||||
for (const resource of validated.owns) this.#ownership.set(resource, validated.id);
|
||||
return validated;
|
||||
}
|
||||
|
||||
get(id) {
|
||||
return this.#modules.get(id) || null;
|
||||
}
|
||||
|
||||
#resolveStartOrder() {
|
||||
const visiting = new Set();
|
||||
const visited = new Set();
|
||||
const order = [];
|
||||
const visit = id => {
|
||||
if (visited.has(id)) return;
|
||||
if (visiting.has(id)) throw new PlatformError(`Zyklische Modulabhängigkeit bei ${id}.`, { code: 'MODULE_DEPENDENCY_CYCLE' });
|
||||
const record = this.#modules.get(id);
|
||||
if (!record) throw new PlatformError(`Fehlende Modulabhängigkeit: ${id}`, { code: 'MODULE_DEPENDENCY_MISSING' });
|
||||
visiting.add(id);
|
||||
for (const dependency of record.manifest.requires) visit(dependency);
|
||||
visiting.delete(id);
|
||||
visited.add(id);
|
||||
order.push(id);
|
||||
};
|
||||
for (const id of this.#modules.keys()) visit(id);
|
||||
const capabilities = new Set([...this.#modules.values()].flatMap(record => record.manifest.provides));
|
||||
for (const record of this.#modules.values()) {
|
||||
for (const capability of record.manifest.consumes) {
|
||||
if (!capabilities.has(capability)) throw new PlatformError(`Fehlende Capability ${capability} für ${record.manifest.id}.`, { code: 'MODULE_CAPABILITY_MISSING' });
|
||||
}
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
async startAll(context) {
|
||||
this.#startOrder = this.#resolveStartOrder();
|
||||
const started = [];
|
||||
for (const id of this.#startOrder) {
|
||||
const record = this.#modules.get(id);
|
||||
if (record.state === 'disabled') continue;
|
||||
try {
|
||||
record.state = 'starting';
|
||||
await record.lifecycle.start(Object.freeze({ ...context, module: record.manifest }));
|
||||
record.state = 'running';
|
||||
record.startedAt = new Date().toISOString();
|
||||
started.push(id);
|
||||
} catch (error) {
|
||||
record.state = 'failed';
|
||||
record.error = error?.message || String(error);
|
||||
for (const startedId of started.reverse()) {
|
||||
const startedRecord = this.#modules.get(startedId);
|
||||
try { await startedRecord.lifecycle.stop(Object.freeze({ ...context, module: startedRecord.manifest, rollback: true })); } catch {}
|
||||
startedRecord.state = 'stopped';
|
||||
}
|
||||
throw new PlatformError(`Modulstart fehlgeschlagen: ${id}`, { code: 'MODULE_START_FAILED', cause: error, details: { module: id } });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async stopAll(context) {
|
||||
for (const id of [...this.#startOrder].reverse()) {
|
||||
const record = this.#modules.get(id);
|
||||
if (!record || record.state !== 'running') continue;
|
||||
try {
|
||||
record.state = 'stopping';
|
||||
await record.lifecycle.stop(Object.freeze({ ...context, module: record.manifest }));
|
||||
record.state = 'stopped';
|
||||
} catch (error) {
|
||||
record.state = 'failed';
|
||||
record.error = error?.message || String(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async health() {
|
||||
const checks = [];
|
||||
for (const [id, record] of this.#modules) {
|
||||
if (record.state === 'disabled') {
|
||||
checks.push({ id, ok: true, state: 'disabled' });
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const result = await record.lifecycle.health({ module: record.manifest });
|
||||
checks.push({ id, state: record.state, ok: record.state === 'running' && result?.ok !== false, ...(result || {}) });
|
||||
} catch (error) {
|
||||
checks.push({ id, state: record.state, ok: false, error: error?.message || String(error) });
|
||||
}
|
||||
}
|
||||
return checks;
|
||||
}
|
||||
|
||||
snapshot() {
|
||||
return [...this.#modules.values()].map(record => ({
|
||||
...record.manifest,
|
||||
state: record.state,
|
||||
startedAt: record.startedAt,
|
||||
error: record.error,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
import { EventBus } from './event-bus.mjs';
|
||||
import { FeatureFlagRegistry } from './feature-flags.mjs';
|
||||
import { LifecycleManager } from './lifecycle.mjs';
|
||||
import { ModuleRegistry } from './module-registry.mjs';
|
||||
import { PolicyEngine } from './policy-engine.mjs';
|
||||
import { RouteRegistry } from './route-registry.mjs';
|
||||
import { loadBuiltInModules } from '../modules/catalog.mjs';
|
||||
|
||||
export function createPlatformKernel({ version, hasPermission }) {
|
||||
const events = new EventBus();
|
||||
const features = new FeatureFlagRegistry();
|
||||
const modules = new ModuleRegistry();
|
||||
const policies = new PolicyEngine();
|
||||
const routes = new RouteRegistry({ policyEngine: policies });
|
||||
const lifecycle = new LifecycleManager();
|
||||
|
||||
policies.register('platform.authenticated', ({ user }) => Boolean(user), {
|
||||
description: 'Erfordert eine gültige Vendoo-Sitzung.',
|
||||
});
|
||||
policies.register('platform.security-admin', ({ user }) => Boolean(user && hasPermission(user.role, 'security.manage')), {
|
||||
description: 'Erfordert die Berechtigung security.manage.',
|
||||
});
|
||||
|
||||
features.define('platform.module-kernel', { defaultValue: true, description: 'Aktiviert den modularen Plattformkernel.' });
|
||||
features.define('platform.route-contracts', { defaultValue: true, description: 'Aktiviert registrierte Routenverträge.' });
|
||||
features.define('themes.token-runtime', { defaultValue: true, owner: 'vendoo.themes', description: 'Aktiviert den versionierten Design-Token-Vertrag.' });
|
||||
features.define('extensions.isolated-host', { defaultValue: false, owner: 'vendoo.security', description: 'Reserviert für einen späteren isolierten Extension-Host.' });
|
||||
|
||||
for (const module of loadBuiltInModules()) modules.register(module);
|
||||
lifecycle.register({
|
||||
id: 'modules',
|
||||
order: 20,
|
||||
start: context => modules.startAll(context),
|
||||
stop: context => modules.stopAll(context),
|
||||
});
|
||||
|
||||
const context = Object.freeze({ version, events, features, policies });
|
||||
return Object.freeze({
|
||||
version,
|
||||
events,
|
||||
features,
|
||||
lifecycle,
|
||||
modules,
|
||||
policies,
|
||||
routes,
|
||||
async start() {
|
||||
await lifecycle.start(context);
|
||||
await events.emit('platform.started', { version }, { owner: 'vendoo.system' });
|
||||
},
|
||||
async stop() {
|
||||
await events.emit('platform.stopping', { version }, { owner: 'vendoo.system' });
|
||||
await lifecycle.stop(context);
|
||||
},
|
||||
async health() {
|
||||
const moduleChecks = await modules.health();
|
||||
return {
|
||||
ok: lifecycle.snapshot().state === 'running' && moduleChecks.every(check => check.ok),
|
||||
lifecycle: lifecycle.snapshot(),
|
||||
modules: moduleChecks,
|
||||
};
|
||||
},
|
||||
snapshot() {
|
||||
return {
|
||||
version,
|
||||
architecture: 'modular-monolith',
|
||||
lifecycle: lifecycle.snapshot(),
|
||||
modules: modules.snapshot(),
|
||||
features: features.list(),
|
||||
policies: policies.list(),
|
||||
routes: routes.list(),
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { PlatformError } from './errors.mjs';
|
||||
|
||||
const POLICY_ID = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/;
|
||||
|
||||
export class PolicyEngine {
|
||||
#policies = new Map();
|
||||
|
||||
register(id, evaluator, { owner = 'vendoo.security', description = '' } = {}) {
|
||||
const normalized = String(id || '');
|
||||
if (!POLICY_ID.test(normalized)) throw new PlatformError(`Ungültige Policy-ID: ${normalized}`, { code: 'POLICY_ID_INVALID' });
|
||||
if (this.#policies.has(normalized)) throw new PlatformError(`Policy bereits registriert: ${normalized}`, { code: 'POLICY_DUPLICATE' });
|
||||
if (typeof evaluator !== 'function') throw new PlatformError(`Policy benötigt eine Prüffunktion: ${normalized}`, { code: 'POLICY_EVALUATOR_INVALID' });
|
||||
this.#policies.set(normalized, { id: normalized, evaluator, owner, description });
|
||||
}
|
||||
|
||||
async evaluate(id, context) {
|
||||
const policy = this.#policies.get(id);
|
||||
if (!policy) {
|
||||
return { allowed: false, reason: 'policy_not_registered', policy: id };
|
||||
}
|
||||
const result = await policy.evaluator(Object.freeze({ ...context }));
|
||||
if (result === true) return { allowed: true, policy: id };
|
||||
if (result === false || result == null) return { allowed: false, reason: 'denied', policy: id };
|
||||
return { allowed: Boolean(result.allowed), reason: result.reason || null, policy: id, details: result.details || null };
|
||||
}
|
||||
|
||||
list() {
|
||||
return [...this.#policies.values()].map(({ evaluator: _evaluator, ...policy }) => ({ ...policy }));
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import { PlatformError, platformErrorPayload } from './errors.mjs';
|
||||
|
||||
const METHODS = new Set(['get', 'post', 'put', 'patch', 'delete']);
|
||||
const ROUTE_ID = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/;
|
||||
|
||||
export class RouteRegistry {
|
||||
#routes = new Map();
|
||||
#policyEngine;
|
||||
|
||||
constructor({ policyEngine }) {
|
||||
this.#policyEngine = policyEngine;
|
||||
}
|
||||
|
||||
register(app, definition) {
|
||||
const method = String(definition.method || '').toLowerCase();
|
||||
const id = String(definition.id || '');
|
||||
const path = String(definition.path || '');
|
||||
if (!ROUTE_ID.test(id)) throw new PlatformError(`Ungültige Route-ID: ${id}`, { code: 'ROUTE_ID_INVALID' });
|
||||
if (!METHODS.has(method)) throw new PlatformError(`Ungültige HTTP-Methode für ${id}.`, { code: 'ROUTE_METHOD_INVALID' });
|
||||
if (!path.startsWith('/')) throw new PlatformError(`Ungültiger Routenpfad für ${id}.`, { code: 'ROUTE_PATH_INVALID' });
|
||||
if (!definition.policy) throw new PlatformError(`Route ${id} benötigt eine explizite Policy.`, { code: 'ROUTE_POLICY_REQUIRED' });
|
||||
if (!definition.owner) throw new PlatformError(`Route ${id} benötigt einen Modulbesitzer.`, { code: 'ROUTE_OWNER_REQUIRED' });
|
||||
if (typeof definition.handler !== 'function') throw new PlatformError(`Route ${id} benötigt einen Handler.`, { code: 'ROUTE_HANDLER_REQUIRED' });
|
||||
const key = `${method.toUpperCase()} ${path}`;
|
||||
if (this.#routes.has(key)) throw new PlatformError(`Route bereits registriert: ${key}`, { code: 'ROUTE_DUPLICATE' });
|
||||
|
||||
const contract = Object.freeze({
|
||||
id,
|
||||
method: method.toUpperCase(),
|
||||
path,
|
||||
owner: String(definition.owner),
|
||||
policy: String(definition.policy),
|
||||
audit: String(definition.audit || ''),
|
||||
rateLimit: String(definition.rateLimit || 'default'),
|
||||
csrf: definition.csrf !== false,
|
||||
stability: String(definition.stability || 'internal'),
|
||||
});
|
||||
this.#routes.set(key, contract);
|
||||
|
||||
app[method](path, ...(definition.middleware || []), async (req, res, next) => {
|
||||
try {
|
||||
const decision = await this.#policyEngine.evaluate(contract.policy, { req, user: req.user, route: contract });
|
||||
if (!decision.allowed) {
|
||||
throw new PlatformError('Keine Berechtigung für diese Aktion.', {
|
||||
code: 'POLICY_DENIED', status: 403, expose: true,
|
||||
details: { policy: contract.policy, reason: decision.reason },
|
||||
});
|
||||
}
|
||||
await definition.handler(req, res, { route: contract, decision });
|
||||
} catch (error) {
|
||||
if (res.headersSent) return next(error);
|
||||
const status = Number.isInteger(error?.status) ? error.status : 500;
|
||||
res.status(status).json(platformErrorPayload(error, req.requestId));
|
||||
}
|
||||
});
|
||||
return contract;
|
||||
}
|
||||
|
||||
list() {
|
||||
return [...this.#routes.values()].map(route => ({ ...route }));
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "vendoo.ai",
|
||||
"name": "AI Services",
|
||||
"version": "1.0.0",
|
||||
"type": "feature",
|
||||
"status": "legacy-bridge",
|
||||
"description": "Vertrag für den bestehenden Vendoo-Bereich AI Services; schrittweise Migration ohne Funktionsbruch.",
|
||||
"requires": [
|
||||
"vendoo.security",
|
||||
"vendoo.media"
|
||||
],
|
||||
"permissions": [
|
||||
"generation.execute"
|
||||
],
|
||||
"provides": [
|
||||
"ai.text",
|
||||
"ai.images"
|
||||
],
|
||||
"consumes": [
|
||||
"media.assets",
|
||||
"security.policies"
|
||||
],
|
||||
"owns": [
|
||||
"ai.providers",
|
||||
"ai.jobs"
|
||||
]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "vendoo.auth",
|
||||
"name": "Authentication",
|
||||
"version": "1.0.0",
|
||||
"type": "core",
|
||||
"status": "legacy-bridge",
|
||||
"description": "Vertrag für den bestehenden Vendoo-Bereich Authentication; schrittweise Migration ohne Funktionsbruch.",
|
||||
"requires": [
|
||||
"vendoo.security"
|
||||
],
|
||||
"permissions": [
|
||||
"sessions.manage"
|
||||
],
|
||||
"provides": [
|
||||
"auth.sessions",
|
||||
"auth.identity"
|
||||
],
|
||||
"consumes": [
|
||||
"security.policies"
|
||||
],
|
||||
"owns": [
|
||||
"auth.sessions",
|
||||
"auth.tokens"
|
||||
]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const moduleNames = ['system', 'security', 'auth', 'users', 'themes', 'media', 'listings', 'ai', 'flux-studio', 'quality', 'publishing', 'operations'];
|
||||
|
||||
export function loadBuiltInModules() {
|
||||
return moduleNames.map(name => ({
|
||||
manifest: JSON.parse(readFileSync(join(here, name, 'module.json'), 'utf8')),
|
||||
lifecycle: {
|
||||
async start() {},
|
||||
async stop() {},
|
||||
async health() { return { ok: true, bridge: 'legacy' }; },
|
||||
},
|
||||
}));
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "vendoo.flux-studio",
|
||||
"name": "FLUX Studio",
|
||||
"version": "1.0.0",
|
||||
"type": "feature",
|
||||
"status": "legacy-bridge",
|
||||
"description": "Vertrag für den bestehenden Vendoo-Bereich FLUX Studio; schrittweise Migration ohne Funktionsbruch.",
|
||||
"requires": [
|
||||
"vendoo.ai"
|
||||
],
|
||||
"permissions": [
|
||||
"generation.execute"
|
||||
],
|
||||
"provides": [
|
||||
"flux.prompt-jobs"
|
||||
],
|
||||
"consumes": [
|
||||
"ai.images"
|
||||
],
|
||||
"owns": [
|
||||
"flux.generations",
|
||||
"flux.history"
|
||||
]
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "vendoo.listings",
|
||||
"name": "Listings & Inventory",
|
||||
"version": "1.0.0",
|
||||
"type": "feature",
|
||||
"status": "legacy-bridge",
|
||||
"description": "Vertrag für den bestehenden Vendoo-Bereich Listings & Inventory; schrittweise Migration ohne Funktionsbruch.",
|
||||
"requires": [
|
||||
"vendoo.auth",
|
||||
"vendoo.media"
|
||||
],
|
||||
"permissions": [
|
||||
"listings.view",
|
||||
"listings.edit",
|
||||
"listings.delete",
|
||||
"inventory.edit"
|
||||
],
|
||||
"provides": [
|
||||
"listings.catalog",
|
||||
"inventory.stock"
|
||||
],
|
||||
"consumes": [
|
||||
"media.assets",
|
||||
"auth.identity"
|
||||
],
|
||||
"owns": [
|
||||
"listings.records",
|
||||
"inventory.locations"
|
||||
]
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "vendoo.media",
|
||||
"name": "Media Library",
|
||||
"version": "1.0.0",
|
||||
"type": "feature",
|
||||
"status": "legacy-bridge",
|
||||
"description": "Vertrag für den bestehenden Vendoo-Bereich Media Library; schrittweise Migration ohne Funktionsbruch.",
|
||||
"requires": [
|
||||
"vendoo.auth"
|
||||
],
|
||||
"permissions": [
|
||||
"media.view",
|
||||
"media.edit",
|
||||
"media.delete"
|
||||
],
|
||||
"provides": [
|
||||
"media.assets",
|
||||
"media.derivatives"
|
||||
],
|
||||
"consumes": [
|
||||
"auth.identity"
|
||||
],
|
||||
"owns": [
|
||||
"media.library",
|
||||
"media.uploads"
|
||||
]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "vendoo.operations",
|
||||
"name": "Operations Center",
|
||||
"version": "1.0.0",
|
||||
"type": "feature",
|
||||
"status": "legacy-bridge",
|
||||
"description": "Vertrag für den bestehenden Vendoo-Bereich Operations Center; schrittweise Migration ohne Funktionsbruch.",
|
||||
"requires": [
|
||||
"vendoo.security"
|
||||
],
|
||||
"permissions": [
|
||||
"operations.manage"
|
||||
],
|
||||
"provides": [
|
||||
"operations.backup",
|
||||
"operations.update"
|
||||
],
|
||||
"consumes": [
|
||||
"security.audit"
|
||||
],
|
||||
"owns": [
|
||||
"operations.backups",
|
||||
"operations.updates"
|
||||
]
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "vendoo.publishing",
|
||||
"name": "Publishing",
|
||||
"version": "1.0.0",
|
||||
"type": "feature",
|
||||
"status": "legacy-bridge",
|
||||
"description": "Vertrag für den bestehenden Vendoo-Bereich Publishing; schrittweise Migration ohne Funktionsbruch.",
|
||||
"requires": [
|
||||
"vendoo.listings",
|
||||
"vendoo.security"
|
||||
],
|
||||
"permissions": [
|
||||
"publishing.view",
|
||||
"publishing.execute",
|
||||
"publishing.manage"
|
||||
],
|
||||
"provides": [
|
||||
"publishing.jobs",
|
||||
"publishing.adapters"
|
||||
],
|
||||
"consumes": [
|
||||
"listings.catalog",
|
||||
"security.policies"
|
||||
],
|
||||
"owns": [
|
||||
"publishing.queue",
|
||||
"publishing.events"
|
||||
]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "vendoo.quality",
|
||||
"name": "Quality Center",
|
||||
"version": "1.0.0",
|
||||
"type": "feature",
|
||||
"status": "legacy-bridge",
|
||||
"description": "Vertrag für den bestehenden Vendoo-Bereich Quality Center; schrittweise Migration ohne Funktionsbruch.",
|
||||
"requires": [
|
||||
"vendoo.listings",
|
||||
"vendoo.ai"
|
||||
],
|
||||
"permissions": [
|
||||
"quality.execute"
|
||||
],
|
||||
"provides": [
|
||||
"quality.reports"
|
||||
],
|
||||
"consumes": [
|
||||
"listings.catalog",
|
||||
"ai.text"
|
||||
],
|
||||
"owns": [
|
||||
"quality.history"
|
||||
]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "vendoo.security",
|
||||
"name": "Security Core",
|
||||
"version": "1.0.0",
|
||||
"type": "core",
|
||||
"status": "legacy-bridge",
|
||||
"description": "Vertrag für den bestehenden Vendoo-Bereich Security Core; schrittweise Migration ohne Funktionsbruch.",
|
||||
"requires": [
|
||||
"vendoo.system"
|
||||
],
|
||||
"permissions": [
|
||||
"security.manage"
|
||||
],
|
||||
"provides": [
|
||||
"security.policies",
|
||||
"security.audit"
|
||||
],
|
||||
"consumes": [
|
||||
"platform.events"
|
||||
],
|
||||
"owns": [
|
||||
"security.permissions",
|
||||
"security.rate-limits"
|
||||
]
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "vendoo.system",
|
||||
"name": "System Kernel",
|
||||
"version": "1.0.0",
|
||||
"type": "core",
|
||||
"status": "legacy-bridge",
|
||||
"description": "Vertrag für den bestehenden Vendoo-Bereich System Kernel; schrittweise Migration ohne Funktionsbruch.",
|
||||
"requires": [],
|
||||
"permissions": [
|
||||
"dashboard.view"
|
||||
],
|
||||
"provides": [
|
||||
"platform.lifecycle",
|
||||
"platform.events",
|
||||
"platform.features"
|
||||
],
|
||||
"consumes": [],
|
||||
"owns": [
|
||||
"system.runtime",
|
||||
"system.health"
|
||||
]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "vendoo.themes",
|
||||
"name": "Themes & Design System",
|
||||
"version": "1.0.0",
|
||||
"type": "feature",
|
||||
"status": "legacy-bridge",
|
||||
"description": "Vertrag für den bestehenden Vendoo-Bereich Themes & Design System; schrittweise Migration ohne Funktionsbruch.",
|
||||
"requires": [
|
||||
"vendoo.system"
|
||||
],
|
||||
"permissions": [
|
||||
"settings.manage"
|
||||
],
|
||||
"provides": [
|
||||
"themes.tokens",
|
||||
"themes.runtime"
|
||||
],
|
||||
"consumes": [
|
||||
"platform.events"
|
||||
],
|
||||
"owns": [
|
||||
"themes.definitions",
|
||||
"themes.user-preferences"
|
||||
]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "vendoo.users",
|
||||
"name": "Users & Roles",
|
||||
"version": "1.0.0",
|
||||
"type": "feature",
|
||||
"status": "legacy-bridge",
|
||||
"description": "Vertrag für den bestehenden Vendoo-Bereich Users & Roles; schrittweise Migration ohne Funktionsbruch.",
|
||||
"requires": [
|
||||
"vendoo.auth"
|
||||
],
|
||||
"permissions": [
|
||||
"users.manage"
|
||||
],
|
||||
"provides": [
|
||||
"users.directory",
|
||||
"users.roles"
|
||||
],
|
||||
"consumes": [
|
||||
"auth.identity"
|
||||
],
|
||||
"owns": [
|
||||
"users.accounts",
|
||||
"users.preferences"
|
||||
]
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: vendoo:1.35.0
|
||||
image: vendoo:1.34.3
|
||||
container_name: vendoo
|
||||
restart: unless-stopped
|
||||
init: true
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: vendoo:1.35.0
|
||||
image: vendoo:1.34.3
|
||||
container_name: vendoo
|
||||
restart: unless-stopped
|
||||
init: true
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
# Vendoo 1.35.0 – Abnahmebericht
|
||||
|
||||
## Bestanden
|
||||
|
||||
- JavaScript-/MJS-Syntax aller bestehenden und neuen Kernbestandteile
|
||||
- Plattform-Architecture-Gate
|
||||
- zwölf Modulmanifeste und Abhängigkeiten
|
||||
- Zykluserkennung
|
||||
- fehlende Capability wird blockiert
|
||||
- doppelte Ressourcen-Ownership wird blockiert
|
||||
- Deny-by-default-Route ohne Policy wird blockiert
|
||||
- nicht berechtigter Zugriff auf registrierte Route ergibt HTTP 403
|
||||
- Event-Auslieferung und Owner-Cleanup
|
||||
- Lifecycle- und Modulshutdown
|
||||
- sichere Theme-Werte und verbotene Token
|
||||
- RGB-Bereichsprüfung
|
||||
- deterministische Token-Generierung
|
||||
- Light, Dark und High Contrast vorhanden
|
||||
- Safe-DOM-Vertrag ohne `innerHTML`
|
||||
- API-Client-Vertrag mit CSRF und Same-Origin
|
||||
- Deployment-Gate 1.35.0
|
||||
- Git-Sicherheitsgate: 365 Dateien ohne Secrets/Laufzeitdaten
|
||||
- Git-Sicherheits-Regressionstest
|
||||
- Git-Staging-Regressionstest
|
||||
- persistenter Konfigurationsspeicher
|
||||
- Shell-Installer-Simulation für Docker, NAS und VPS
|
||||
- 68 Migrationseinheiten gegen frische Datenbank
|
||||
- 68 Migrationseinheiten gegen bestehende Datenbank
|
||||
- vorhandener Testdatensatz blieb erhalten
|
||||
|
||||
## Dependency-Test
|
||||
|
||||
`npm ci` wurde versucht. Die Lockdatei wurde aufgelöst, aber `better-sqlite3` konnte in der Sandbox nicht fertig gebaut werden:
|
||||
|
||||
1. Prebuild-Download scheiterte wegen fehlender DNS-/Netzverbindung zu GitHub.
|
||||
2. Der Fallback mit lokal vorhandenen Node-Headern begann erfolgreich zu kompilieren, wurde aber beim SQLite-C-Build durch das Ausführungslimit beendet.
|
||||
|
||||
Dieser Punkt wird nicht als bestanden ausgegeben.
|
||||
|
||||
## Nicht praktisch getestet
|
||||
|
||||
- Windows PowerShell 5.1 und WinForms
|
||||
- echter Windows-Lokalstart
|
||||
- echter Docker-/NAS-/Portainer-Start
|
||||
- VPS/Caddy/TLS
|
||||
- Browser-End-to-End-Test
|
||||
- visuelle Regression aller Seiten
|
||||
- vollständiger Node-Serverstart mit nativ kompiliertem `better-sqlite3`
|
||||
- GitHub Actions und GHCR-Liveausführung
|
||||
|
||||
## Daten- und Kompatibilitätsaussage
|
||||
|
||||
Keine Datenbank, Uploads, Backups, Operations-Daten, Benutzer, Artikel, API-Schlüssel, FLUX-Modelle oder ComfyUI-Daten wurden in das Paket aufgenommen oder gelöscht. Es gibt keine Framework- oder Datenbankmigration.
|
||||
@@ -1,10 +1,10 @@
|
||||
# Vendoo 1.35.0 – GitHub-Importstatus
|
||||
# Vendoo 1.34.3 – GitHub-Importstatus
|
||||
|
||||
- Importbranch: `release/1.35.0-platform-kernel`
|
||||
- Repository: `Masterluke77/vendoo` (privat)
|
||||
- Importbranch: `release/1.34.3-initial-import`
|
||||
- Zielbranch: `main`
|
||||
- Draft Pull Request: `#7`
|
||||
- Repository: privat
|
||||
- Draft Pull Request: `#6`
|
||||
|
||||
Der vollständige Quellstand wird über den Vendoo GitHub-Deploy-Assistenten übertragen. Vor dem Merge müssen Git-Sicherheitsgate, Architektur-Gate, Datenbankmigrationen, vollständiger Diff und GitHub Actions erfolgreich sein.
|
||||
Version 1.34.3 schließt den lokalen Ordner `updates/` einschließlich `updates/backups/`, Slice-Sicherungen und Rollback-Artefakten vollständig aus dem temporären Git-Staging aus. Aktive Dateien aus dem Projektroot und aus `scripts/` werden weiterhin importiert.
|
||||
|
||||
Lokale `.env.*`, Datenbanken, Uploads, Backups, Logs, Operations-, Update- und Modellordner bleiben ausgeschlossen.
|
||||
Der Pull Request darf erst nach vollständigem Quellimport, geprüftem Diff, grünem CI-Lauf und bestätigter Secret-/Laufzeitdatenprüfung zusammengeführt werden.
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
# Vendoo 1.35.0 – Modular Platform Kernel, Design-System Contracts & Security Architecture Foundation
|
||||
|
||||
## Ziel
|
||||
|
||||
Version 1.35.0 stoppt den unkontrollierten weiteren Ausbau der großen Bestandsdateien und schafft eine verbindliche Plattformarchitektur. Die bestehende Anwendung bleibt funktionsfähig und wird nicht per Big-Bang ersetzt.
|
||||
|
||||
## Implementiert
|
||||
|
||||
### Plattformkernel
|
||||
|
||||
- `app/kernel/platform-kernel.mjs`
|
||||
- geordneter Start und Stop
|
||||
- Lifecycle-Rollback bei fehlgeschlagenem Start
|
||||
- Kernelzustand im Readiness-Check
|
||||
- geordneter Kernelshutdown vor dem SQLite-Checkpoint
|
||||
|
||||
### Modulsystem
|
||||
|
||||
- zwölf validierte Bestandsmodule
|
||||
- Manifestvertrag und JSON-Schema
|
||||
- Abhängigkeitsauflösung mit Zykluserkennung
|
||||
- Capability-Prüfung
|
||||
- eindeutige Ressourcen-Ownership
|
||||
- Start-Rollback bereits gestarteter Module
|
||||
- Status `legacy-bridge` für noch nicht vollständig migrierte Bereiche
|
||||
|
||||
### Sicherheit
|
||||
|
||||
- Policy Engine mit Deny-by-default
|
||||
- Routenregistrierung nur mit Modulbesitzer und Policy
|
||||
- standardisierte Plattformfehler mit Request-ID
|
||||
- geschützte Admin-Endpunkte für Kernel-, Modul- und Theme-Vertragsstatus
|
||||
- Owner-basierter Event-Bus
|
||||
- feste Feature-Flag-Registrierung
|
||||
- isolierter Extension-Host bleibt standardmäßig deaktiviert
|
||||
|
||||
### Design-System
|
||||
|
||||
- 33 versionierte Design Tokens
|
||||
- Light, Dark und High Contrast
|
||||
- deterministische CSS- und Vertragsgenerierung
|
||||
- Kompatibilitätsbrücke zu bestehenden CSS-Variablen
|
||||
- Browser-Theme-Runtime mit Systempräferenz
|
||||
- sichere Backend-Validierung für künftige Theme-Definitionen
|
||||
- kein freies CSS, JavaScript oder externe URL-Werte
|
||||
|
||||
### Frontend-Verträge
|
||||
|
||||
- Frontend Module Registry mit `mount`/`unmount`
|
||||
- Listener-Cleanup über `AbortController`
|
||||
- Safe-DOM-Helfer auf Basis von `textContent`
|
||||
- zentraler zukünftiger API-Client mit Same-Origin-Credentials, CSRF und standardisierten Fehlern
|
||||
|
||||
## Neue interne Endpunkte
|
||||
|
||||
Nur Benutzer mit `security.manage`:
|
||||
|
||||
- `GET /api/security/platform`
|
||||
- `GET /api/security/platform/modules`
|
||||
- `GET /api/security/platform/theme-contract`
|
||||
|
||||
## Nicht behauptet
|
||||
|
||||
Version 1.35.0 ist die Foundation, nicht die vollständige Migration. `server.mjs`, `public/app.js`, `public/style.css` und `lib/db.mjs` bleiben weiterhin groß. Die Fachmodule werden ab 1.38.0 einzeln migriert. Der sichtbare Theme Manager folgt in einem eigenen Release.
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"release": "1.35.0",
|
||||
"architecture": "modular-monolith",
|
||||
"modules": 12,
|
||||
"design_tokens": 33,
|
||||
"themes": ["light", "dark", "contrast"],
|
||||
"feature_flags": 4,
|
||||
"policies": 2,
|
||||
"new_registered_routes": 3,
|
||||
"git_branch": "release/1.35.0-platform-kernel",
|
||||
"draft_pull_request": 7,
|
||||
"checks": {
|
||||
"syntax": true,
|
||||
"architecture_gate": true,
|
||||
"deployment_gate": true,
|
||||
"git_safety": true,
|
||||
"git_safety_regression": true,
|
||||
"git_staging_regression": true,
|
||||
"config_store": true,
|
||||
"installer_shell_simulation": true,
|
||||
"fresh_db_migration_blocks": 68,
|
||||
"existing_db_migration_blocks": 68,
|
||||
"existing_row_preserved": true,
|
||||
"npm_ci": false,
|
||||
"npm_ci_reason": "Native better-sqlite3 build could not finish in the network-restricted, time-limited sandbox."
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
# Vendoo 1.35.0 – Design-System- und Theme-Vertrag
|
||||
|
||||
## Quelle der Wahrheit
|
||||
|
||||
Die editierbare Tokenquelle liegt unter:
|
||||
|
||||
`public/design-system/tokens/source.json`
|
||||
|
||||
Aus ihr erzeugt `tools/generate-design-tokens.mjs` deterministisch:
|
||||
|
||||
- `public/design-system/tokens.css`
|
||||
- `public/design-system/theme-contract.json`
|
||||
|
||||
Direkte Änderungen an `tokens.css` sind nicht zulässig.
|
||||
|
||||
## Ebenen
|
||||
|
||||
- Primitive/semantische Farb-, Radius-, Schatten-, Dichte-, Layout- und Bewegungswerte
|
||||
- CSS-Variablen mit Präfix `--vd-*`
|
||||
- Kompatibilitätsbrücke für bestehende Variablen wie `--bg`, `--accent` und `--radius`
|
||||
|
||||
## Themes
|
||||
|
||||
1. `light`
|
||||
2. `dark`
|
||||
3. `contrast`
|
||||
4. Browserpräferenz `system` über die Theme Runtime
|
||||
|
||||
## Sicherheit
|
||||
|
||||
Der zukünftige Theme Manager darf ausschließlich Tokens mit `customizable: true` verändern. Freies CSS, HTML, URLs, JavaScript, Schattenausdrücke und beliebige Variablennamen sind nicht erlaubt. Farben und Dimensionen werden typ- und bereichsgeprüft. Ein Theme wird nie ungeprüft als Stylesheet gespeichert.
|
||||
|
||||
## Nächster Ausbau
|
||||
|
||||
- persistente Themes pro Benutzer und systemweit
|
||||
- Live-Vorschau in isoliertem Vorschaucontainer
|
||||
- automatische Kontrastprüfung
|
||||
- Import/Export eines versionierten Theme-JSON
|
||||
- Versionsverlauf und Zurücksetzen
|
||||
@@ -1,25 +0,0 @@
|
||||
# Vendoo Modularisierungs-Roadmap ab 1.35.0
|
||||
|
||||
## 1.35.0 – Foundation
|
||||
|
||||
Kernel, Manifeste, Lifecycle, Event Bus, Feature Flags, Policy-/Routenverträge, Design Tokens und Architekturtests.
|
||||
|
||||
## 1.36.0 – Theme Manager
|
||||
|
||||
Persistente sichere Themeverwaltung, Vorschau, Import/Export, Kontrast- und Accessibility-Gates.
|
||||
|
||||
## 1.37.0 – Security & Secrets
|
||||
|
||||
Secret Provider, Schema Registry, strukturierte Logs, sichere Fehlergrenzen und CSP-Migrationsgrundlage.
|
||||
|
||||
## 1.38.0 – Auth, Users & Settings
|
||||
|
||||
Erster vollständig migrierter Backend-/Frontend-Modulverbund einschließlich Vertragstests.
|
||||
|
||||
## 1.39.0 – Listings, Inventory & Media
|
||||
|
||||
Fachmodule, Repository-Schicht, UI-Komponenten und Datenzugriffsgrenzen.
|
||||
|
||||
## 1.40.0 – Publishing, AI & FLUX
|
||||
|
||||
Adapterverträge, Job-Schnittstellen, FLUX-Modul und Vorbereitung des isolierten Extension-Hosts.
|
||||
@@ -1,31 +0,0 @@
|
||||
# Vendoo 1.35.0 – Modulsystem
|
||||
|
||||
## Ziel
|
||||
|
||||
Vendoo wird schrittweise als modularer Monolith organisiert. Der bestehende Code bleibt in 1.35.0 funktional unverändert und wird über `legacy-bridge`-Manifeste erfasst. Neue Funktionen müssen die Kernelverträge verwenden; bestehende Bereiche werden in späteren Releases einzeln migriert.
|
||||
|
||||
## Verbindliche Regeln
|
||||
|
||||
1. Jedes Modul besitzt eine eindeutige ID `vendoo.<name>` und ein validiertes `module.json`.
|
||||
2. Abhängigkeiten müssen explizit und frei von Zyklen sein.
|
||||
3. Neue Routen benötigen Besitzer, Policy, Auditklasse, Rate-Limit-Klasse und Stabilitätsstatus.
|
||||
4. Unbekannte Policies verweigern den Zugriff. Es gibt keinen Allow-by-default-Fallback.
|
||||
5. Module kommunizieren künftig über öffentliche Services, Capabilities und Events, nicht über interne Querimporte.
|
||||
6. Ein Modul darf langfristig nur seine eigenen Tabellen beziehungsweise Repository-Schnittstellen verändern.
|
||||
7. Externe Erweiterungen werden nicht im Vendoo-Hauptprozess ausgeführt. Ein isolierter Extension-Host bleibt bis zur sicheren Umsetzung deaktiviert.
|
||||
8. Modulaktivierung durch bloßes Kopieren einer Datei ist unzulässig.
|
||||
|
||||
## Status 1.35.0
|
||||
|
||||
Die Bereiche System, Security, Auth, Benutzer, Themes, Medien, Artikel, AI, FLUX Studio, Quality Center, Publishing und Operations sind registriert. Ihr Status `legacy-bridge` bedeutet: Der Vertrag existiert, die bestehende Implementierung liegt aber noch überwiegend in `server.mjs`, `lib/`, `public/app.js` und `public/style.css`.
|
||||
|
||||
## Migrationsmethode
|
||||
|
||||
Vendoo verwendet das Strangler-Verfahren:
|
||||
|
||||
- neue Architektur neben dem Bestand aufbauen,
|
||||
- einen fachlichen Bereich vollständig migrieren,
|
||||
- Verhalten und Datenbestand prüfen,
|
||||
- erst danach den alten Pfad entfernen.
|
||||
|
||||
Kein Big-Bang-Refactoring, keine Framework-Migration und kein Datenbankreset.
|
||||
@@ -1,29 +0,0 @@
|
||||
# Vendoo 1.35.0 – Sicherheitsarchitektur
|
||||
|
||||
## Neue Grundlage
|
||||
|
||||
- Deny-by-default Policy Engine für neue Kernel-Routen
|
||||
- explizite Routenverträge
|
||||
- standardisierte interne Fehler mit Request-ID
|
||||
- validierte Modulmanifeste und Abhängigkeiten
|
||||
- Owner-basierter Event-Bus mit sauberem Listener-Abbau
|
||||
- Feature Flags mit fester Registrierung und sicherem Environment-Override
|
||||
- sichere Theme-Werte statt frei eingebbarem CSS
|
||||
- geordneter Kernelstart und Kernelshutdown
|
||||
|
||||
## Bestehender Schutz bleibt aktiv
|
||||
|
||||
Sitzungen, CSRF, Rollen, Berechtigungen, Rate Limits, Origin-/Hostprüfung, Uploadschutz, Audit und Ressourcenlocks werden nicht ersetzt oder umgangen.
|
||||
|
||||
## Offene Produktiv-Gates
|
||||
|
||||
Diese Punkte werden bewusst nicht als bereits gelöst ausgegeben:
|
||||
|
||||
- vollständige CSP ohne `unsafe-inline`
|
||||
- zentral validierte Request-/Response-Schemas für alle Bestandsrouten
|
||||
- verschlüsselter Secret Provider je Plattform
|
||||
- isolierter Extension-Host
|
||||
- vollständige Repository-Trennung aller Datenbankzugriffe
|
||||
- automatisierte Browser-, Accessibility- und Penetrationstests
|
||||
|
||||
Bis zu ihrer Umsetzung bleiben die entsprechenden Feature Flags deaktiviert oder die Altpfade unter bestehenden Schutzmechanismen aktiv.
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "vendoo",
|
||||
"version": "1.35.0",
|
||||
"version": "1.34.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "vendoo",
|
||||
"version": "1.35.0",
|
||||
"version": "1.34.3",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"better-sqlite3": "^12.11.1",
|
||||
|
||||
+5
-8
@@ -1,28 +1,25 @@
|
||||
{
|
||||
"name": "vendoo",
|
||||
"version": "1.35.0",
|
||||
"version": "1.34.3",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node bootstrap.mjs",
|
||||
"dev": "node --watch bootstrap.mjs",
|
||||
"check": "node --check bootstrap.mjs && node --check server.mjs && node --check lib/config-store.mjs && node --check lib/runtime-paths.mjs && node --check lib/db.mjs && node --check lib/auth.mjs && node --check lib/security.mjs && node --check lib/operations-center.mjs && node --check lib/images.mjs && node --check lib/ai-local-images.mjs && node --check lib/ai-model-photos.mjs && node --check lib/ai-flux-prompt-jobs.mjs && node --check lib/image-editor.mjs && node --check lib/image-batch-jobs.mjs && node --check lib/quality-center.mjs && node --check public/app.js && node --check public/design-system/theme-runtime.js && node --check public/core/safe-dom.js && node --check public/core/frontend-module-registry.js && node --check public/core/api-client.js && node --check app/kernel/errors.mjs && node --check app/kernel/event-bus.mjs && node --check app/kernel/feature-flags.mjs && node --check app/kernel/module-contract.mjs && node --check app/kernel/module-registry.mjs && node --check app/kernel/policy-engine.mjs && node --check app/kernel/route-registry.mjs && node --check app/kernel/lifecycle.mjs && node --check app/kernel/platform-kernel.mjs && node --check app/modules/catalog.mjs && node --check app/core/themes/theme-contract.mjs && node --check tools/generate-design-tokens.mjs && node --check tools/verify-platform-architecture-1.35.0.mjs && node --check tools/verify-config-store.mjs && node --check tools/verify-git-safety.mjs && node --check tools/verify-deployment-1.35.0.mjs && node --check tools/verify-git-safety-regression.mjs && node --check tools/prepare-git-staging.mjs && node --check tools/verify-git-staging.mjs",
|
||||
"check": "node --check bootstrap.mjs && node --check lib/config-store.mjs && node --check server.mjs && node --check lib/runtime-paths.mjs && node --check lib/db.mjs && node --check lib/auth.mjs && node --check lib/security.mjs && node --check lib/operations-center.mjs && node --check lib/images.mjs && node --check lib/ai-local-images.mjs && node --check lib/ai-model-photos.mjs && node --check lib/ai-flux-prompt-jobs.mjs && node --check lib/image-editor.mjs && node --check lib/image-batch-jobs.mjs && node --check lib/quality-center.mjs && node --check public/app.js && node --check tools/verify-config-store.mjs && node --check tools/verify-git-safety.mjs && node --check tools/verify-deployment-1.34.3.mjs && node --check tools/verify-git-safety-regression.mjs && node --check tools/prepare-git-staging.mjs && node --check tools/verify-git-staging.mjs",
|
||||
"verify:slice30": "node tools/verify-slice30-mock.mjs",
|
||||
"verify:db": "python tools/verify-db-migrations.py",
|
||||
"verify:slice31": "node tools/verify-slice31-image-editor.mjs",
|
||||
"verify:hotfix": "node tools/verify-release-1.25.1.mjs",
|
||||
"verify:gallery": "node tools/verify-release-1.25.1-static.mjs",
|
||||
"verify:release": "node tools/verify-deployment-1.35.0.mjs",
|
||||
"verify:release": "node tools/verify-deployment-1.34.3.mjs",
|
||||
"verify:ui-contracts": "node tools/verify-ui-contracts-1.26.1.mjs",
|
||||
"verify:deployment": "node tools/verify-deployment-1.35.0.mjs",
|
||||
"verify:deployment": "node tools/verify-deployment-1.34.3.mjs",
|
||||
"verify:git": "node tools/verify-git-safety.mjs",
|
||||
"verify:config": "node tools/verify-config-store.mjs",
|
||||
"verify:installer-shell": "sh tools/verify-installer-shell.sh",
|
||||
"verify:git-regression": "node tools/verify-git-safety-regression.mjs",
|
||||
"verify:git-staging": "node tools/verify-git-staging.mjs",
|
||||
"generate:tokens": "node tools/generate-design-tokens.mjs",
|
||||
"verify:architecture": "node tools/verify-platform-architecture-1.35.0.mjs",
|
||||
"verify:all": "npm run verify:git && npm run verify:git-regression && npm run verify:git-staging && npm run check && npm run verify:architecture && npm run verify:deployment && npm run verify:config && npm run verify:installer-shell && npm run verify:db"
|
||||
"verify:git-staging": "node tools/verify-git-staging.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
|
||||
+4
-10
@@ -1,4 +1,4 @@
|
||||
const VENDOO_UI_BUILD = '1.35.0';
|
||||
const VENDOO_UI_BUILD = '1.34.3';
|
||||
|
||||
const state = {
|
||||
photos: [],
|
||||
@@ -6479,21 +6479,15 @@ function escapeHtml(str) {
|
||||
// --- Dark Mode ---
|
||||
function setupDarkMode() {
|
||||
const saved = localStorage.getItem('vendoo-theme');
|
||||
if (window.VendooTheme) {
|
||||
window.VendooTheme.setTheme(saved === 'light' || saved === 'dark' || saved === 'contrast' ? saved : 'system', { persist: false });
|
||||
} else if (saved === 'dark' || (!saved && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
if (saved === 'dark' || (!saved && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
}
|
||||
updateThemeIcon();
|
||||
|
||||
document.getElementById('theme-toggle').addEventListener('click', () => {
|
||||
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
||||
const nextTheme = isDark ? 'light' : 'dark';
|
||||
if (window.VendooTheme) window.VendooTheme.setTheme(nextTheme);
|
||||
else {
|
||||
document.documentElement.setAttribute('data-theme', nextTheme);
|
||||
localStorage.setItem('vendoo-theme', nextTheme);
|
||||
}
|
||||
document.documentElement.setAttribute('data-theme', isDark ? 'light' : 'dark');
|
||||
localStorage.setItem('vendoo-theme', isDark ? 'light' : 'dark');
|
||||
updateThemeIcon();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
export class VendooApiError extends Error {
|
||||
constructor(message, { status = 0, code = 'API_ERROR', requestId = null, details = null } = {}) {
|
||||
super(message);
|
||||
this.name = 'VendooApiError';
|
||||
this.status = status;
|
||||
this.code = code;
|
||||
this.requestId = requestId;
|
||||
this.details = details;
|
||||
}
|
||||
}
|
||||
|
||||
function readCookie(name) {
|
||||
const prefix = `${encodeURIComponent(name)}=`;
|
||||
return document.cookie.split(';').map(value => value.trim()).find(value => value.startsWith(prefix))?.slice(prefix.length) || '';
|
||||
}
|
||||
|
||||
export async function apiRequest(path, { method = 'GET', body, headers = {}, signal } = {}) {
|
||||
const verb = String(method).toUpperCase();
|
||||
const requestHeaders = new Headers(headers);
|
||||
requestHeaders.set('Accept', 'application/json');
|
||||
if (body !== undefined) requestHeaders.set('Content-Type', 'application/json');
|
||||
if (!['GET', 'HEAD', 'OPTIONS'].includes(verb)) requestHeaders.set('X-CSRF-Token', decodeURIComponent(readCookie('csrf_token')));
|
||||
const response = await fetch(path, {
|
||||
method: verb,
|
||||
credentials: 'same-origin',
|
||||
headers: requestHeaders,
|
||||
body: body === undefined ? undefined : JSON.stringify(body),
|
||||
signal,
|
||||
redirect: 'error',
|
||||
});
|
||||
const requestId = response.headers.get('x-vendoo-request-id');
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok) throw new VendooApiError(payload.error || `HTTP ${response.status}`, {
|
||||
status: response.status,
|
||||
code: payload.code || 'API_ERROR',
|
||||
requestId: payload.request_id || requestId,
|
||||
details: payload.details || null,
|
||||
});
|
||||
return payload;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
const MODULE_ID = /^vendoo\.[a-z][a-z0-9-]*$/;
|
||||
|
||||
export class FrontendModuleRegistry {
|
||||
#modules = new Map();
|
||||
#active = null;
|
||||
|
||||
register(definition) {
|
||||
const id = String(definition?.id || '');
|
||||
if (!MODULE_ID.test(id)) throw new Error(`Ungültige Frontend-Modul-ID: ${id}`);
|
||||
if (this.#modules.has(id)) throw new Error(`Frontend-Modul bereits registriert: ${id}`);
|
||||
if (typeof definition.mount !== 'function') throw new Error(`Frontend-Modul benötigt mount(): ${id}`);
|
||||
this.#modules.set(id, Object.freeze({
|
||||
id,
|
||||
version: String(definition.version || '1.0.0'),
|
||||
status: String(definition.status || 'native'),
|
||||
mount: definition.mount,
|
||||
unmount: typeof definition.unmount === 'function' ? definition.unmount : async () => {},
|
||||
}));
|
||||
}
|
||||
|
||||
async mount(id, root, services = {}) {
|
||||
if (!(root instanceof Element)) throw new Error('Frontend-Modul benötigt ein gültiges Root-Element.');
|
||||
const module = this.#modules.get(id);
|
||||
if (!module) throw new Error(`Frontend-Modul ist nicht registriert: ${id}`);
|
||||
await this.unmount();
|
||||
const controller = new AbortController();
|
||||
const context = Object.freeze({
|
||||
root,
|
||||
services: Object.freeze({ ...services }),
|
||||
signal: controller.signal,
|
||||
listen(target, event, handler, options = {}) {
|
||||
target.addEventListener(event, handler, { ...options, signal: controller.signal });
|
||||
},
|
||||
});
|
||||
await module.mount(context);
|
||||
this.#active = { module, context, controller };
|
||||
return module;
|
||||
}
|
||||
|
||||
async unmount() {
|
||||
if (!this.#active) return;
|
||||
const active = this.#active;
|
||||
this.#active = null;
|
||||
active.controller.abort();
|
||||
await active.module.unmount(active.context);
|
||||
}
|
||||
|
||||
list() {
|
||||
return [...this.#modules.values()].map(({ mount: _mount, unmount: _unmount, ...module }) => ({ ...module }));
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
export function setText(node, value) {
|
||||
if (!(node instanceof Node)) throw new TypeError('DOM-Ziel fehlt.');
|
||||
node.textContent = value == null ? '' : String(value);
|
||||
return node;
|
||||
}
|
||||
|
||||
export function createElement(tagName, { className = '', text = '', attributes = {}, children = [] } = {}) {
|
||||
if (!/^[a-z][a-z0-9-]*$/i.test(String(tagName))) throw new TypeError('Ungültiger Elementname.');
|
||||
const element = document.createElement(tagName);
|
||||
if (className) element.className = String(className);
|
||||
if (text !== '') setText(element, text);
|
||||
for (const [name, value] of Object.entries(attributes)) {
|
||||
if (!/^(aria-[a-z-]+|data-[a-z-]+|id|role|title|type|name|value|href|src|alt|tabindex)$/i.test(name)) {
|
||||
throw new TypeError(`Attribut ist nicht freigegeben: ${name}`);
|
||||
}
|
||||
if (['href', 'src'].includes(name.toLowerCase())) {
|
||||
const url = new URL(String(value), window.location.origin);
|
||||
if (!['http:', 'https:', 'blob:'].includes(url.protocol) || (['http:', 'https:'].includes(url.protocol) && url.origin !== window.location.origin)) {
|
||||
throw new TypeError(`Externe oder unsichere URL ist nicht erlaubt: ${name}`);
|
||||
}
|
||||
}
|
||||
element.setAttribute(name, String(value));
|
||||
}
|
||||
for (const child of children) element.append(child);
|
||||
return element;
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"contractVersion": "1.0.0",
|
||||
"themes": [
|
||||
"light",
|
||||
"dark",
|
||||
"contrast"
|
||||
],
|
||||
"tokens": {
|
||||
"color.canvas": {
|
||||
"css": "--vd-canvas",
|
||||
"type": "color",
|
||||
"customizable": true
|
||||
},
|
||||
"color.surface": {
|
||||
"css": "--vd-surface",
|
||||
"type": "color",
|
||||
"customizable": true
|
||||
},
|
||||
"color.surfaceRaised": {
|
||||
"css": "--vd-surface-raised",
|
||||
"type": "color",
|
||||
"customizable": true
|
||||
},
|
||||
"color.surfaceMuted": {
|
||||
"css": "--vd-surface-muted",
|
||||
"type": "color",
|
||||
"customizable": true
|
||||
},
|
||||
"color.surfaceQuiet": {
|
||||
"css": "--vd-surface-quiet",
|
||||
"type": "color",
|
||||
"customizable": true
|
||||
},
|
||||
"color.line": {
|
||||
"css": "--vd-line",
|
||||
"type": "color",
|
||||
"customizable": true
|
||||
},
|
||||
"color.lineStrong": {
|
||||
"css": "--vd-line-strong",
|
||||
"type": "color",
|
||||
"customizable": true
|
||||
},
|
||||
"color.ink": {
|
||||
"css": "--vd-ink",
|
||||
"type": "color",
|
||||
"customizable": true
|
||||
},
|
||||
"color.inkSecondary": {
|
||||
"css": "--vd-ink-secondary",
|
||||
"type": "color",
|
||||
"customizable": true
|
||||
},
|
||||
"color.inkMuted": {
|
||||
"css": "--vd-ink-muted",
|
||||
"type": "color",
|
||||
"customizable": true
|
||||
},
|
||||
"color.brand": {
|
||||
"css": "--vd-brand",
|
||||
"type": "color",
|
||||
"customizable": true
|
||||
},
|
||||
"color.brandStrong": {
|
||||
"css": "--vd-brand-strong",
|
||||
"type": "color",
|
||||
"customizable": true
|
||||
},
|
||||
"color.brandSoft": {
|
||||
"css": "--vd-brand-soft",
|
||||
"type": "color",
|
||||
"customizable": false
|
||||
},
|
||||
"color.success": {
|
||||
"css": "--vd-success",
|
||||
"type": "color",
|
||||
"customizable": true
|
||||
},
|
||||
"color.successSoft": {
|
||||
"css": "--vd-success-soft",
|
||||
"type": "color",
|
||||
"customizable": false
|
||||
},
|
||||
"color.warning": {
|
||||
"css": "--vd-warning",
|
||||
"type": "color",
|
||||
"customizable": true
|
||||
},
|
||||
"color.warningSoft": {
|
||||
"css": "--vd-warning-soft",
|
||||
"type": "color",
|
||||
"customizable": false
|
||||
},
|
||||
"color.danger": {
|
||||
"css": "--vd-danger",
|
||||
"type": "color",
|
||||
"customizable": true
|
||||
},
|
||||
"color.dangerSoft": {
|
||||
"css": "--vd-danger-soft",
|
||||
"type": "color",
|
||||
"customizable": false
|
||||
},
|
||||
"color.info": {
|
||||
"css": "--vd-info",
|
||||
"type": "color",
|
||||
"customizable": true
|
||||
},
|
||||
"color.infoSoft": {
|
||||
"css": "--vd-info-soft",
|
||||
"type": "color",
|
||||
"customizable": false
|
||||
},
|
||||
"color.focus": {
|
||||
"css": "--vd-focus",
|
||||
"type": "color",
|
||||
"customizable": false
|
||||
},
|
||||
"shadow.float": {
|
||||
"css": "--vd-shadow-float",
|
||||
"type": "shadow",
|
||||
"customizable": false
|
||||
},
|
||||
"shadow.soft": {
|
||||
"css": "--vd-shadow-soft",
|
||||
"type": "shadow",
|
||||
"customizable": false
|
||||
},
|
||||
"radius.xs": {
|
||||
"css": "--vd-radius-xs",
|
||||
"type": "dimension",
|
||||
"customizable": true,
|
||||
"min": 0,
|
||||
"max": 16
|
||||
},
|
||||
"radius.sm": {
|
||||
"css": "--vd-radius-sm",
|
||||
"type": "dimension",
|
||||
"customizable": true,
|
||||
"min": 0,
|
||||
"max": 20
|
||||
},
|
||||
"radius.md": {
|
||||
"css": "--vd-radius-md",
|
||||
"type": "dimension",
|
||||
"customizable": true,
|
||||
"min": 0,
|
||||
"max": 28
|
||||
},
|
||||
"layout.sidebarWidth": {
|
||||
"css": "--vd-sidebar-width",
|
||||
"type": "dimension",
|
||||
"customizable": false
|
||||
},
|
||||
"layout.topbarHeight": {
|
||||
"css": "--vd-topbar-height",
|
||||
"type": "dimension",
|
||||
"customizable": false
|
||||
},
|
||||
"density.controlHeight": {
|
||||
"css": "--vd-control-height",
|
||||
"type": "dimension",
|
||||
"customizable": true,
|
||||
"min": 32,
|
||||
"max": 52
|
||||
},
|
||||
"density.contentGap": {
|
||||
"css": "--vd-content-gap",
|
||||
"type": "dimension",
|
||||
"customizable": true,
|
||||
"min": 8,
|
||||
"max": 32
|
||||
},
|
||||
"motion.fast": {
|
||||
"css": "--vd-motion-fast",
|
||||
"type": "duration",
|
||||
"customizable": false
|
||||
},
|
||||
"motion.normal": {
|
||||
"css": "--vd-motion-normal",
|
||||
"type": "duration",
|
||||
"customizable": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
const STORAGE_KEY = 'vendoo-theme';
|
||||
const SAFE_THEMES = new Set(['light', 'dark', 'contrast', 'system']);
|
||||
|
||||
function effectiveTheme(preference) {
|
||||
if (preference !== 'system') return preference;
|
||||
return window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
function applyTheme(preference) {
|
||||
const normalized = SAFE_THEMES.has(preference) ? preference : 'system';
|
||||
const resolved = effectiveTheme(normalized);
|
||||
document.documentElement.dataset.theme = resolved;
|
||||
document.documentElement.dataset.themePreference = normalized;
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function setTheme(preference, { persist = true } = {}) {
|
||||
if (!SAFE_THEMES.has(preference)) throw new Error('Unbekanntes Vendoo-Theme.');
|
||||
if (persist) localStorage.setItem(STORAGE_KEY, preference);
|
||||
const resolved = applyTheme(preference);
|
||||
window.dispatchEvent(new CustomEvent('vendoo:theme-changed', { detail: { preference, resolved } }));
|
||||
return resolved;
|
||||
}
|
||||
|
||||
const preference = SAFE_THEMES.has(localStorage.getItem(STORAGE_KEY)) ? localStorage.getItem(STORAGE_KEY) : 'system';
|
||||
applyTheme(preference);
|
||||
window.matchMedia?.('(prefers-color-scheme: dark)').addEventListener?.('change', () => {
|
||||
if (document.documentElement.dataset.themePreference === 'system') applyTheme('system');
|
||||
});
|
||||
|
||||
window.VendooTheme = Object.freeze({
|
||||
get preference() { return document.documentElement.dataset.themePreference || 'system'; },
|
||||
get resolved() { return document.documentElement.dataset.theme || 'light'; },
|
||||
setTheme,
|
||||
supported: Object.freeze([...SAFE_THEMES]),
|
||||
});
|
||||
@@ -1,113 +0,0 @@
|
||||
/* Automatisch generiert aus design-system/tokens/source.json. Nicht direkt bearbeiten. */
|
||||
/* Contract 1.0.0 */
|
||||
:root {
|
||||
--vd-canvas: #f7f5f1;
|
||||
--vd-surface: #fffdf9;
|
||||
--vd-surface-raised: #ffffff;
|
||||
--vd-surface-muted: #f1efea;
|
||||
--vd-surface-quiet: #ebe8e1;
|
||||
--vd-line: #e2ded6;
|
||||
--vd-line-strong: #cbc5ba;
|
||||
--vd-ink: #171714;
|
||||
--vd-ink-secondary: #5f5b54;
|
||||
--vd-ink-muted: #8c877e;
|
||||
--vd-brand: #e33a16;
|
||||
--vd-brand-strong: #c92f10;
|
||||
--vd-brand-soft: rgba(227, 58, 22, 0.08);
|
||||
--vd-success: #2f7d50;
|
||||
--vd-success-soft: #e7f2ea;
|
||||
--vd-warning: #b96a23;
|
||||
--vd-warning-soft: #fbefe3;
|
||||
--vd-danger: #c44234;
|
||||
--vd-danger-soft: #f9e8e5;
|
||||
--vd-info: #3975ba;
|
||||
--vd-info-soft: #e8f0f9;
|
||||
--vd-focus: rgba(227, 58, 22, 0.18);
|
||||
--vd-shadow-float: 0 18px 48px rgba(35, 30, 24, 0.12);
|
||||
--vd-shadow-soft: 0 8px 24px rgba(35, 30, 24, 0.06);
|
||||
--vd-radius-xs: 5px;
|
||||
--vd-radius-sm: 8px;
|
||||
--vd-radius-md: 12px;
|
||||
--vd-sidebar-width: 148px;
|
||||
--vd-topbar-height: 60px;
|
||||
--vd-control-height: 40px;
|
||||
--vd-content-gap: 16px;
|
||||
--vd-motion-fast: 120ms;
|
||||
--vd-motion-normal: 180ms;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--vd-canvas: #171714;
|
||||
--vd-surface: #1d1d19;
|
||||
--vd-surface-raised: #22221e;
|
||||
--vd-surface-muted: #292923;
|
||||
--vd-surface-quiet: #303029;
|
||||
--vd-line: #37372f;
|
||||
--vd-line-strong: #4a4940;
|
||||
--vd-ink: #f2efe8;
|
||||
--vd-ink-secondary: #c3beb4;
|
||||
--vd-ink-muted: #8f8a80;
|
||||
--vd-brand: #ff5a32;
|
||||
--vd-brand-strong: #ff744f;
|
||||
--vd-brand-soft: rgba(255, 90, 50, 0.12);
|
||||
--vd-success: #75bd8c;
|
||||
--vd-success-soft: #203629;
|
||||
--vd-warning: #e2a35f;
|
||||
--vd-warning-soft: #3b2e20;
|
||||
--vd-danger: #ed7568;
|
||||
--vd-danger-soft: #3b2523;
|
||||
--vd-info: #78a9df;
|
||||
--vd-info-soft: #202f40;
|
||||
--vd-focus: rgba(255, 90, 50, 0.26);
|
||||
--vd-shadow-float: 0 20px 56px rgba(0, 0, 0, 0.38);
|
||||
--vd-shadow-soft: 0 8px 26px rgba(0, 0, 0, 0.20);
|
||||
--vd-radius-xs: 5px;
|
||||
--vd-radius-sm: 8px;
|
||||
--vd-radius-md: 12px;
|
||||
--vd-sidebar-width: 148px;
|
||||
--vd-topbar-height: 60px;
|
||||
--vd-control-height: 40px;
|
||||
--vd-content-gap: 16px;
|
||||
--vd-motion-fast: 120ms;
|
||||
--vd-motion-normal: 180ms;
|
||||
}
|
||||
|
||||
[data-theme="contrast"] {
|
||||
--vd-canvas: #000000;
|
||||
--vd-surface: #090909;
|
||||
--vd-surface-raised: #111111;
|
||||
--vd-surface-muted: #1b1b1b;
|
||||
--vd-surface-quiet: #242424;
|
||||
--vd-line: #ffffff;
|
||||
--vd-line-strong: #ffffff;
|
||||
--vd-ink: #ffffff;
|
||||
--vd-ink-secondary: #ffffff;
|
||||
--vd-ink-muted: #f2f2f2;
|
||||
--vd-brand: #ffe600;
|
||||
--vd-brand-strong: #ffffff;
|
||||
--vd-brand-soft: rgba(255, 230, 0, 0.20);
|
||||
--vd-success: #66ff99;
|
||||
--vd-success-soft: #002d12;
|
||||
--vd-warning: #ffd000;
|
||||
--vd-warning-soft: #332a00;
|
||||
--vd-danger: #ff6b6b;
|
||||
--vd-danger-soft: #3a0000;
|
||||
--vd-info: #72c7ff;
|
||||
--vd-info-soft: #00253d;
|
||||
--vd-focus: rgba(255, 230, 0, 0.55);
|
||||
--vd-shadow-float: 0 0 0 2px #ffffff;
|
||||
--vd-shadow-soft: 0 0 0 1px #ffffff;
|
||||
--vd-radius-xs: 0px;
|
||||
--vd-radius-sm: 2px;
|
||||
--vd-radius-md: 4px;
|
||||
--vd-sidebar-width: 168px;
|
||||
--vd-topbar-height: 64px;
|
||||
--vd-control-height: 44px;
|
||||
--vd-content-gap: 18px;
|
||||
--vd-motion-fast: 0ms;
|
||||
--vd-motion-normal: 0ms;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
:root { --vd-motion-fast: 0ms; --vd-motion-normal: 0ms; }
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"contractVersion": "1.0.0",
|
||||
"themes": ["light", "dark", "contrast"],
|
||||
"tokens": {
|
||||
"color.canvas": { "css": "--vd-canvas", "type": "color", "customizable": true, "light": "#f7f5f1", "dark": "#171714", "contrast": "#000000" },
|
||||
"color.surface": { "css": "--vd-surface", "type": "color", "customizable": true, "light": "#fffdf9", "dark": "#1d1d19", "contrast": "#090909" },
|
||||
"color.surfaceRaised": { "css": "--vd-surface-raised", "type": "color", "customizable": true, "light": "#ffffff", "dark": "#22221e", "contrast": "#111111" },
|
||||
"color.surfaceMuted": { "css": "--vd-surface-muted", "type": "color", "customizable": true, "light": "#f1efea", "dark": "#292923", "contrast": "#1b1b1b" },
|
||||
"color.surfaceQuiet": { "css": "--vd-surface-quiet", "type": "color", "customizable": true, "light": "#ebe8e1", "dark": "#303029", "contrast": "#242424" },
|
||||
"color.line": { "css": "--vd-line", "type": "color", "customizable": true, "light": "#e2ded6", "dark": "#37372f", "contrast": "#ffffff" },
|
||||
"color.lineStrong": { "css": "--vd-line-strong", "type": "color", "customizable": true, "light": "#cbc5ba", "dark": "#4a4940", "contrast": "#ffffff" },
|
||||
"color.ink": { "css": "--vd-ink", "type": "color", "customizable": true, "light": "#171714", "dark": "#f2efe8", "contrast": "#ffffff" },
|
||||
"color.inkSecondary": { "css": "--vd-ink-secondary", "type": "color", "customizable": true, "light": "#5f5b54", "dark": "#c3beb4", "contrast": "#ffffff" },
|
||||
"color.inkMuted": { "css": "--vd-ink-muted", "type": "color", "customizable": true, "light": "#8c877e", "dark": "#8f8a80", "contrast": "#f2f2f2" },
|
||||
"color.brand": { "css": "--vd-brand", "type": "color", "customizable": true, "light": "#e33a16", "dark": "#ff5a32", "contrast": "#ffe600" },
|
||||
"color.brandStrong": { "css": "--vd-brand-strong", "type": "color", "customizable": true, "light": "#c92f10", "dark": "#ff744f", "contrast": "#ffffff" },
|
||||
"color.brandSoft": { "css": "--vd-brand-soft", "type": "color", "customizable": false, "light": "rgba(227, 58, 22, 0.08)", "dark": "rgba(255, 90, 50, 0.12)", "contrast": "rgba(255, 230, 0, 0.20)" },
|
||||
"color.success": { "css": "--vd-success", "type": "color", "customizable": true, "light": "#2f7d50", "dark": "#75bd8c", "contrast": "#66ff99" },
|
||||
"color.successSoft": { "css": "--vd-success-soft", "type": "color", "customizable": false, "light": "#e7f2ea", "dark": "#203629", "contrast": "#002d12" },
|
||||
"color.warning": { "css": "--vd-warning", "type": "color", "customizable": true, "light": "#b96a23", "dark": "#e2a35f", "contrast": "#ffd000" },
|
||||
"color.warningSoft": { "css": "--vd-warning-soft", "type": "color", "customizable": false, "light": "#fbefe3", "dark": "#3b2e20", "contrast": "#332a00" },
|
||||
"color.danger": { "css": "--vd-danger", "type": "color", "customizable": true, "light": "#c44234", "dark": "#ed7568", "contrast": "#ff6b6b" },
|
||||
"color.dangerSoft": { "css": "--vd-danger-soft", "type": "color", "customizable": false, "light": "#f9e8e5", "dark": "#3b2523", "contrast": "#3a0000" },
|
||||
"color.info": { "css": "--vd-info", "type": "color", "customizable": true, "light": "#3975ba", "dark": "#78a9df", "contrast": "#72c7ff" },
|
||||
"color.infoSoft": { "css": "--vd-info-soft", "type": "color", "customizable": false, "light": "#e8f0f9", "dark": "#202f40", "contrast": "#00253d" },
|
||||
"color.focus": { "css": "--vd-focus", "type": "color", "customizable": false, "light": "rgba(227, 58, 22, 0.18)", "dark": "rgba(255, 90, 50, 0.26)", "contrast": "rgba(255, 230, 0, 0.55)" },
|
||||
"shadow.float": { "css": "--vd-shadow-float", "type": "shadow", "customizable": false, "light": "0 18px 48px rgba(35, 30, 24, 0.12)", "dark": "0 20px 56px rgba(0, 0, 0, 0.38)", "contrast": "0 0 0 2px #ffffff" },
|
||||
"shadow.soft": { "css": "--vd-shadow-soft", "type": "shadow", "customizable": false, "light": "0 8px 24px rgba(35, 30, 24, 0.06)", "dark": "0 8px 26px rgba(0, 0, 0, 0.20)", "contrast": "0 0 0 1px #ffffff" },
|
||||
"radius.xs": { "css": "--vd-radius-xs", "type": "dimension", "customizable": true, "min": 0, "max": 16, "light": "5px", "dark": "5px", "contrast": "0px" },
|
||||
"radius.sm": { "css": "--vd-radius-sm", "type": "dimension", "customizable": true, "min": 0, "max": 20, "light": "8px", "dark": "8px", "contrast": "2px" },
|
||||
"radius.md": { "css": "--vd-radius-md", "type": "dimension", "customizable": true, "min": 0, "max": 28, "light": "12px", "dark": "12px", "contrast": "4px" },
|
||||
"layout.sidebarWidth": { "css": "--vd-sidebar-width", "type": "dimension", "customizable": false, "light": "148px", "dark": "148px", "contrast": "168px" },
|
||||
"layout.topbarHeight": { "css": "--vd-topbar-height", "type": "dimension", "customizable": false, "light": "60px", "dark": "60px", "contrast": "64px" },
|
||||
"density.controlHeight": { "css": "--vd-control-height", "type": "dimension", "customizable": true, "min": 32, "max": 52, "light": "40px", "dark": "40px", "contrast": "44px" },
|
||||
"density.contentGap": { "css": "--vd-content-gap", "type": "dimension", "customizable": true, "min": 8, "max": 32, "light": "16px", "dark": "16px", "contrast": "18px" },
|
||||
"motion.fast": { "css": "--vd-motion-fast", "type": "duration", "customizable": false, "light": "120ms", "dark": "120ms", "contrast": "0ms" },
|
||||
"motion.normal": { "css": "--vd-motion-normal", "type": "duration", "customizable": false, "light": "180ms", "dark": "180ms", "contrast": "0ms" }
|
||||
}
|
||||
}
|
||||
+4
-5
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="vendoo-build" content="1.35.0">
|
||||
<meta name="vendoo-build" content="1.34.3">
|
||||
<title>Vendoo</title>
|
||||
<link rel="icon" href="/brand/favicons/favicon.ico" sizes="any">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/brand/favicons/favicon-16x16.png">
|
||||
@@ -13,7 +13,7 @@
|
||||
<link rel="manifest" href="/brand/favicons/site.webmanifest">
|
||||
<meta name="msapplication-config" content="/brand/favicons/browserconfig.xml">
|
||||
<meta name="theme-color" content="#FAF6F2">
|
||||
<link rel="stylesheet" href="style.css?v=1.35.0">
|
||||
<link rel="stylesheet" href="style.css?v=1.34.3">
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
@@ -2262,9 +2262,8 @@ PUBLIC_BASE_URL=https://vendoo.example.de</pre>
|
||||
<div id="toast" class="toast"></div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/qrious@4.0.2/dist/qrious.min.js"></script>
|
||||
<script src="design-system/theme-runtime.js?v=1.35.0"></script>
|
||||
<script src="vendoo-icons.js?v=1.35.0"></script>
|
||||
<script src="app.js?v=1.35.0"></script>
|
||||
<script src="vendoo-icons.js?v=1.34.3"></script>
|
||||
<script src="app.js?v=1.34.3"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# Frontend-Module
|
||||
|
||||
Neue Frontend-Module werden künftig als ES-Module unter diesem Verzeichnis angelegt und über `FrontendModuleRegistry` mit einem klaren `mount`-/`unmount`-Lebenszyklus betrieben.
|
||||
|
||||
Die Bestandsoberfläche aus `public/app.js` bleibt in 1.35.0 unverändert aktiv. Sie wird in späteren Releases tabweise migriert. Direkte neue globale Event-Listener und ungeprüfte `innerHTML`-Zuweisungen sind für migrierte Module nicht zulässig.
|
||||
+109
-34
@@ -1,36 +1,29 @@
|
||||
@import url('./design-system/tokens.css?v=1.35.0');
|
||||
|
||||
:root {
|
||||
color-scheme: light;
|
||||
/* Kompatibilitätsbrücke: bestehende Oberfläche bleibt unverändert, neue Module verwenden --vd-* direkt. */
|
||||
--bg: var(--vd-canvas);
|
||||
--bg-card: var(--vd-surface-raised);
|
||||
--bg-input: var(--vd-surface-raised);
|
||||
--bg-hover: var(--vd-surface-muted);
|
||||
--bg-page: var(--vd-surface-quiet);
|
||||
--border: var(--vd-line);
|
||||
--border-focus: var(--vd-brand);
|
||||
--text: var(--vd-ink);
|
||||
--text-muted: var(--vd-ink-muted);
|
||||
--text-secondary: var(--vd-ink-secondary);
|
||||
--accent: var(--vd-brand);
|
||||
--accent-hover: var(--vd-brand-strong);
|
||||
--accent-light: var(--vd-brand-soft);
|
||||
--orange: var(--vd-warning);
|
||||
--orange-hover: var(--vd-warning);
|
||||
--orange-light: var(--vd-warning-soft);
|
||||
--success: var(--vd-success);
|
||||
--danger: var(--vd-danger);
|
||||
--warning: var(--vd-warning);
|
||||
--radius: var(--vd-radius-md);
|
||||
--radius-sm: var(--vd-radius-sm);
|
||||
--shadow-sm: none;
|
||||
--shadow: var(--vd-shadow-soft);
|
||||
--bg: #f6f5f0;
|
||||
--bg-card: #ffffff;
|
||||
--bg-input: #ffffff;
|
||||
--bg-hover: #eeedea;
|
||||
--bg-page: #eae8e1;
|
||||
--border: #e0ddd4;
|
||||
--border-focus: #2c6e49;
|
||||
--text: #2c2c2a;
|
||||
--text-muted: #888880;
|
||||
--text-secondary: #6b6b64;
|
||||
--accent: #2c6e49;
|
||||
--accent-hover: #245a3b;
|
||||
--accent-light: rgba(44,110,73,0.08);
|
||||
--orange: #c77b2a;
|
||||
--orange-hover: #b06a1e;
|
||||
--orange-light: rgba(199,123,42,0.1);
|
||||
--success: #2c6e49;
|
||||
--danger: #c0392b;
|
||||
--warning: #c77b2a;
|
||||
--radius: 12px;
|
||||
--radius-sm: 8px;
|
||||
--shadow-sm: 0 1px 3px rgba(0,0,0,0.06);
|
||||
--shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
[data-theme="dark"] { color-scheme: dark; }
|
||||
[data-theme="contrast"] { color-scheme: dark; }
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
@@ -1270,11 +1263,93 @@ main { max-width: 960px; margin: 0 auto; padding: 24px 28px; width: 100%; }
|
||||
.topbar-btn span { display: none; }
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Vendoo Design System Contract 1.0
|
||||
Tokenquelle: public/design-system/tokens/source.json
|
||||
Generiert: public/design-system/tokens.css
|
||||
/* ==========================================================================
|
||||
Vendoo UI Foundation 2026
|
||||
Freigegebene Designrichtung: Editorial Commerce + Studio Flow
|
||||
========================================================================== */
|
||||
:root {
|
||||
--vd-canvas: #f7f5f1;
|
||||
--vd-surface: #fffdf9;
|
||||
--vd-surface-raised: #ffffff;
|
||||
--vd-surface-muted: #f1efea;
|
||||
--vd-surface-quiet: #ebe8e1;
|
||||
--vd-line: #e2ded6;
|
||||
--vd-line-strong: #cbc5ba;
|
||||
--vd-ink: #171714;
|
||||
--vd-ink-secondary: #5f5b54;
|
||||
--vd-ink-muted: #8c877e;
|
||||
--vd-brand: #e33a16;
|
||||
--vd-brand-strong: #c92f10;
|
||||
--vd-brand-soft: rgba(227, 58, 22, 0.08);
|
||||
--vd-success: #2f7d50;
|
||||
--vd-success-soft: #e7f2ea;
|
||||
--vd-warning: #b96a23;
|
||||
--vd-warning-soft: #fbefe3;
|
||||
--vd-danger: #c44234;
|
||||
--vd-danger-soft: #f9e8e5;
|
||||
--vd-info: #3975ba;
|
||||
--vd-info-soft: #e8f0f9;
|
||||
--vd-focus: rgba(227, 58, 22, 0.18);
|
||||
--vd-shadow-float: 0 18px 48px rgba(35, 30, 24, 0.12);
|
||||
--vd-shadow-soft: 0 8px 24px rgba(35, 30, 24, 0.06);
|
||||
--vd-radius-xs: 5px;
|
||||
--vd-radius-sm: 8px;
|
||||
--vd-radius-md: 12px;
|
||||
--vd-sidebar-width: 148px;
|
||||
--vd-topbar-height: 60px;
|
||||
|
||||
/* Legacy token bridge */
|
||||
--bg: var(--vd-canvas);
|
||||
--bg-card: var(--vd-surface-raised);
|
||||
--bg-input: var(--vd-surface-raised);
|
||||
--bg-hover: var(--vd-surface-muted);
|
||||
--bg-page: var(--vd-surface-quiet);
|
||||
--border: var(--vd-line);
|
||||
--border-focus: var(--vd-brand);
|
||||
--text: var(--vd-ink);
|
||||
--text-muted: var(--vd-ink-muted);
|
||||
--text-secondary: var(--vd-ink-secondary);
|
||||
--accent: var(--vd-brand);
|
||||
--accent-hover: var(--vd-brand-strong);
|
||||
--accent-light: var(--vd-brand-soft);
|
||||
--orange: var(--vd-warning);
|
||||
--orange-hover: var(--vd-warning);
|
||||
--orange-light: var(--vd-warning-soft);
|
||||
--success: var(--vd-success);
|
||||
--danger: var(--vd-danger);
|
||||
--warning: var(--vd-warning);
|
||||
--radius: var(--vd-radius-md);
|
||||
--radius-sm: var(--vd-radius-sm);
|
||||
--shadow-sm: none;
|
||||
--shadow: var(--vd-shadow-soft);
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--vd-canvas: #171714;
|
||||
--vd-surface: #1d1d19;
|
||||
--vd-surface-raised: #22221e;
|
||||
--vd-surface-muted: #292923;
|
||||
--vd-surface-quiet: #303029;
|
||||
--vd-line: #37372f;
|
||||
--vd-line-strong: #4a4940;
|
||||
--vd-ink: #f2efe8;
|
||||
--vd-ink-secondary: #c3beb4;
|
||||
--vd-ink-muted: #8f8a80;
|
||||
--vd-brand: #ff5a32;
|
||||
--vd-brand-strong: #ff744f;
|
||||
--vd-brand-soft: rgba(255, 90, 50, 0.12);
|
||||
--vd-success: #75bd8c;
|
||||
--vd-success-soft: #203629;
|
||||
--vd-warning: #e2a35f;
|
||||
--vd-warning-soft: #3b2e20;
|
||||
--vd-danger: #ed7568;
|
||||
--vd-danger-soft: #3b2523;
|
||||
--vd-info: #78a9df;
|
||||
--vd-info-soft: #202f40;
|
||||
--vd-focus: rgba(255, 90, 50, 0.26);
|
||||
--vd-shadow-float: 0 20px 56px rgba(0, 0, 0, 0.38);
|
||||
--vd-shadow-soft: 0 8px 26px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
html { background: var(--vd-canvas); }
|
||||
body {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Vendoo 1.35.0 – kontrollierter GitHub-Erstimport
|
||||
# Vendoo 1.34.3 – kontrollierter GitHub-Erstimport
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
$RepoUrl = 'https://github.com/Masterluke77/vendoo.git'
|
||||
$PullRequestUrl = 'https://github.com/Masterluke77/vendoo/pull/7'
|
||||
$Branch = 'release/1.35.0-platform-kernel'
|
||||
$PullRequestUrl = 'https://github.com/Masterluke77/vendoo/pull/6'
|
||||
$Branch = 'release/1.34.3-initial-import'
|
||||
$WorkRoot = Join-Path $env:TEMP ('vendoo-github-deploy-' + [Guid]::NewGuid().ToString('N'))
|
||||
$CloneDir = Join-Path $WorkRoot 'repo'
|
||||
|
||||
@@ -123,7 +123,7 @@ function Run-SafetyGate {
|
||||
|
||||
try {
|
||||
Write-Host ''
|
||||
Write-Host 'Vendoo 1.35.0 – GitHub Deploy Assistent' -ForegroundColor Green
|
||||
Write-Host 'Vendoo 1.34.3 – GitHub Deploy Assistent' -ForegroundColor Green
|
||||
Write-Host 'Repository: Masterluke77/vendoo (privat)' -ForegroundColor DarkGray
|
||||
Write-Host ''
|
||||
|
||||
@@ -165,7 +165,7 @@ try {
|
||||
$confirm = Read-Host 'Geprüften Vendoo-Stand als Release-Branch zu GitHub übertragen? Zum Fortfahren PUSH eingeben'
|
||||
if ($confirm -cne 'PUSH') { Warn 'Deployment abgebrochen. Auf GitHub wurde nichts verändert.'; exit 0 }
|
||||
|
||||
Invoke-Git @('commit','-m','fix: import Vendoo 1.35.0 update archive exclusion hotfix')
|
||||
Invoke-Git @('commit','-m','fix: import Vendoo 1.34.3 update archive exclusion hotfix')
|
||||
Invoke-Git @('push','--set-upstream','origin',$Branch)
|
||||
Ok "Branch $Branch wurde erfolgreich übertragen."
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
set -eu
|
||||
ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||
REPO_URL="https://github.com/Masterluke77/vendoo.git"
|
||||
BRANCH="release/1.35.0-platform-kernel"
|
||||
PR_URL="https://github.com/Masterluke77/vendoo/pull/7"
|
||||
BRANCH="release/1.34.3-initial-import"
|
||||
PR_URL="https://github.com/Masterluke77/vendoo/pull/6"
|
||||
WORK_DIR=$(mktemp -d)
|
||||
cleanup() { rm -rf "$WORK_DIR"; }
|
||||
trap cleanup EXIT INT TERM
|
||||
@@ -45,6 +45,6 @@ git status --short
|
||||
printf 'Zum Pushen exakt PUSH eingeben: '
|
||||
read -r answer
|
||||
[ "$answer" = "PUSH" ] || { echo "Abgebrochen."; exit 0; }
|
||||
git commit -m "feat: import Vendoo 1.35.0 modular platform kernel"
|
||||
git commit -m "fix: import Vendoo 1.34.3 update archive exclusion hotfix"
|
||||
git push --set-upstream origin "$BRANCH"
|
||||
echo "Branch übertragen. Draft Pull Request #5 öffnen: $PR_URL"
|
||||
|
||||
+3
-55
@@ -10,8 +10,6 @@ import { networkInterfaces } from 'os';
|
||||
import { spawn } from 'child_process';
|
||||
import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync, unlinkSync, readdirSync, copyFileSync } from 'fs';
|
||||
import { CONFIG_PATH, readRuntimeConfig, setConfigValue, writeRuntimeConfig, updateRuntimeConfig } from './lib/config-store.mjs';
|
||||
import { createPlatformKernel } from './app/kernel/platform-kernel.mjs';
|
||||
import { getThemeContract } from './app/core/themes/theme-contract.mjs';
|
||||
import {
|
||||
createUser, getUser, getUserByEmail, getUsers, updateUser, deleteUser,
|
||||
createSession, validateSession, destroySession, destroySessionById, destroyUserSessions,
|
||||
@@ -98,9 +96,6 @@ const PORT = Number(process.env.PORT || 8124);
|
||||
ensureRuntimeDirectories();
|
||||
let shuttingDown = false;
|
||||
|
||||
const platformKernel = createPlatformKernel({ version: APP_VERSION, hasPermission });
|
||||
await platformKernel.start();
|
||||
|
||||
startAiModelJobLoop();
|
||||
startFluxPromptJobLoop();
|
||||
startImageBatchJobLoop();
|
||||
@@ -360,7 +355,7 @@ app.get('/healthz', (_req, res) => {
|
||||
res.json({ ok: true, version: APP_VERSION, uptime_seconds: Math.floor(process.uptime()), time: new Date().toISOString() });
|
||||
});
|
||||
|
||||
app.get('/readyz', async (_req, res) => {
|
||||
app.get('/readyz', (_req, res) => {
|
||||
res.setHeader('Cache-Control', 'no-store');
|
||||
const checks = [];
|
||||
try {
|
||||
@@ -370,12 +365,6 @@ app.get('/readyz', async (_req, res) => {
|
||||
checks.push({ name: 'database', ok: false, path: DB_PATH, error: error.message });
|
||||
}
|
||||
for (const item of verifyRuntimeWritable()) checks.push({ name: 'filesystem', ok: item.writable, ...item });
|
||||
try {
|
||||
const kernel = await platformKernel.health();
|
||||
checks.push({ name: 'platform-kernel', ok: kernel.ok, lifecycle: kernel.lifecycle.state, modules: kernel.modules.length });
|
||||
} catch (error) {
|
||||
checks.push({ name: 'platform-kernel', ok: false, error: error.message });
|
||||
}
|
||||
const ok = !shuttingDown && checks.every(item => item.ok !== false);
|
||||
res.status(ok ? 200 : 503).json({ ok, shutting_down: shuttingDown, version: APP_VERSION, checks, paths: runtimePathSummary(), time: new Date().toISOString() });
|
||||
});
|
||||
@@ -673,46 +662,6 @@ app.use('/auth/etsy', requireAuth, requirePermission('settings.manage'));
|
||||
// CSRF for state-changing API calls
|
||||
app.use('/api', csrfProtect);
|
||||
|
||||
// Platform kernel contracts (new routes must be deny-by-default and explicitly owned)
|
||||
platformKernel.routes.register(app, {
|
||||
id: 'platform.status.read',
|
||||
method: 'GET',
|
||||
path: '/api/security/platform',
|
||||
owner: 'vendoo.system',
|
||||
policy: 'platform.security-admin',
|
||||
audit: 'platform.status.read',
|
||||
rateLimit: 'administration',
|
||||
csrf: false,
|
||||
stability: 'internal',
|
||||
handler: async (_req, res) => res.json(platformKernel.snapshot()),
|
||||
});
|
||||
|
||||
platformKernel.routes.register(app, {
|
||||
id: 'platform.modules.read',
|
||||
method: 'GET',
|
||||
path: '/api/security/platform/modules',
|
||||
owner: 'vendoo.system',
|
||||
policy: 'platform.security-admin',
|
||||
audit: 'platform.modules.read',
|
||||
rateLimit: 'administration',
|
||||
csrf: false,
|
||||
stability: 'internal',
|
||||
handler: async (_req, res) => res.json({ modules: platformKernel.modules.snapshot() }),
|
||||
});
|
||||
|
||||
platformKernel.routes.register(app, {
|
||||
id: 'themes.contract.read',
|
||||
method: 'GET',
|
||||
path: '/api/security/platform/theme-contract',
|
||||
owner: 'vendoo.themes',
|
||||
policy: 'platform.security-admin',
|
||||
audit: 'themes.contract.read',
|
||||
rateLimit: 'administration',
|
||||
csrf: false,
|
||||
stability: 'internal',
|
||||
handler: async (_req, res) => res.json(getThemeContract()),
|
||||
});
|
||||
|
||||
// Current user info
|
||||
app.get('/api/me', (req, res) => {
|
||||
if (isSetupMode()) return res.json({ setupMode: true, role: 'admin' });
|
||||
@@ -3424,11 +3373,10 @@ function gracefulShutdown(signal) {
|
||||
console.error('Shutdown-Zeitlimit erreicht. Prozess wird beendet.');
|
||||
process.exit(1);
|
||||
}, 10_000);
|
||||
server.close(async () => {
|
||||
try { await platformKernel.stop(); } catch (error) { console.error('Platform-Kernel-Shutdown fehlgeschlagen:', error.message || error); }
|
||||
server.close(() => {
|
||||
clearTimeout(forceTimer);
|
||||
try { db.pragma('wal_checkpoint(TRUNCATE)'); } catch {}
|
||||
try { db.close(); } catch {}
|
||||
clearTimeout(forceTimer);
|
||||
console.log('Vendoo wurde sauber beendet.');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
# Vendoo 1.35.0 – grafischer Zero-Edit-Installationsassistent
|
||||
# Vendoo 1.34.3 – grafischer Zero-Edit-Installationsassistent
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$VendooDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$SetupScript = Join-Path $VendooDir 'setup.ps1'
|
||||
@@ -90,7 +90,7 @@ function Set-Configuration([string]$Target, [string]$Access, [int]$Port, [string
|
||||
}
|
||||
|
||||
$form = New-Object System.Windows.Forms.Form
|
||||
$form.Text = 'Vendoo 1.35.0 – Installation'
|
||||
$form.Text = 'Vendoo 1.34.3 – Installation'
|
||||
$form.StartPosition = 'CenterScreen'
|
||||
$form.Size = New-Object System.Drawing.Size(720, 620)
|
||||
$form.MinimumSize = New-Object System.Drawing.Size(720, 620)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$VendooDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$Version = '1.35.0'
|
||||
$Version = '1.34.3'
|
||||
$Port = 8124
|
||||
$InstallModeFile = Join-Path $VendooDir '.vendoo-install-mode'
|
||||
|
||||
@@ -831,7 +831,7 @@ function Start-VendooLocal {
|
||||
Write-Host ''
|
||||
Write-Step '...' 'Starte Vendoo-Server...'
|
||||
Push-Location $VendooDir
|
||||
Start-Process "http://localhost:$Port/?build=1.35.0"
|
||||
Start-Process "http://localhost:$Port/?build=1.34.3"
|
||||
& node bootstrap.mjs
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
APP_VERSION="1.35.0"
|
||||
APP_VERSION="1.34.3"
|
||||
ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
MODE_FILE="$ROOT_DIR/.vendoo-install-mode"
|
||||
ENV_FILE="$ROOT_DIR/.env"
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import { dirname, join, resolve } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const sourcePath = join(root, 'public', 'design-system', 'tokens', 'source.json');
|
||||
const cssPath = join(root, 'public', 'design-system', 'tokens.css');
|
||||
const contractPath = join(root, 'public', 'design-system', 'theme-contract.json');
|
||||
const source = JSON.parse(readFileSync(sourcePath, 'utf8'));
|
||||
const failures = [];
|
||||
|
||||
if (source.schemaVersion !== 1) failures.push('schemaVersion muss 1 sein');
|
||||
if (!/^\d+\.\d+\.\d+$/.test(String(source.contractVersion || ''))) failures.push('contractVersion ist ungültig');
|
||||
if (!Array.isArray(source.themes) || !source.themes.includes('light') || !source.themes.includes('dark')) failures.push('light und dark müssen definiert sein');
|
||||
|
||||
const cssVars = new Set();
|
||||
for (const [name, token] of Object.entries(source.tokens || {})) {
|
||||
if (!/^[a-z][A-Za-z0-9]*(?:\.[a-z][A-Za-z0-9]*)+$/.test(name)) failures.push(`Ungültiger Tokenname: ${name}`);
|
||||
if (!/^--vd-[a-z0-9-]+$/.test(String(token.css || ''))) failures.push(`Ungültige CSS-Variable: ${name}`);
|
||||
if (cssVars.has(token.css)) failures.push(`Doppelte CSS-Variable: ${token.css}`);
|
||||
cssVars.add(token.css);
|
||||
for (const theme of source.themes) if (typeof token[theme] !== 'string' || !token[theme].trim()) failures.push(`${name} fehlt in Theme ${theme}`);
|
||||
}
|
||||
if (failures.length) {
|
||||
console.error(`Design-Token-Generierung fehlgeschlagen (${failures.length}):`);
|
||||
failures.forEach(item => console.error(`- ${item}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function block(selector, theme) {
|
||||
const lines = Object.values(source.tokens).map(token => ` ${token.css}: ${token[theme]};`);
|
||||
return `${selector} {\n${lines.join('\n')}\n}`;
|
||||
}
|
||||
|
||||
const css = `/* Automatisch generiert aus design-system/tokens/source.json. Nicht direkt bearbeiten. */\n` +
|
||||
`/* Contract ${source.contractVersion} */\n` +
|
||||
`${block(':root', 'light')}\n\n${block('[data-theme="dark"]', 'dark')}\n\n${block('[data-theme="contrast"]', 'contrast')}\n\n` +
|
||||
`@media (prefers-reduced-motion: reduce) {\n :root { --vd-motion-fast: 0ms; --vd-motion-normal: 0ms; }\n}\n`;
|
||||
writeFileSync(cssPath, css, 'utf8');
|
||||
|
||||
const contract = {
|
||||
schemaVersion: source.schemaVersion,
|
||||
contractVersion: source.contractVersion,
|
||||
themes: source.themes,
|
||||
tokens: Object.fromEntries(Object.entries(source.tokens).map(([name, token]) => [name, {
|
||||
css: token.css,
|
||||
type: token.type,
|
||||
customizable: Boolean(token.customizable),
|
||||
...(token.min !== undefined ? { min: token.min } : {}),
|
||||
...(token.max !== undefined ? { max: token.max } : {}),
|
||||
}])),
|
||||
};
|
||||
writeFileSync(contractPath, `${JSON.stringify(contract, null, 2)}\n`, 'utf8');
|
||||
console.log(`Design Tokens erzeugt: ${Object.keys(source.tokens).length} Tokens, ${source.themes.length} Themes.`);
|
||||
@@ -1,129 +0,0 @@
|
||||
import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
|
||||
import { dirname, join, relative, sep } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
||||
const read = rel => readFileSync(join(root, rel), 'utf8');
|
||||
const failures = [];
|
||||
const expect = (text, marker, label = marker) => { if (!text.includes(marker)) failures.push(`Fehlt: ${label}`); };
|
||||
const forbid = (text, marker, label = marker) => { if (text.includes(marker)) failures.push(`Darf nicht enthalten sein: ${label}`); };
|
||||
|
||||
const pkg = JSON.parse(read('package.json'));
|
||||
const lock = JSON.parse(read('package-lock.json'));
|
||||
const manifest = JSON.parse(read('update-manifest.json'));
|
||||
const html = read('public/index.html');
|
||||
const app = read('public/app.js');
|
||||
const bootstrap = read('bootstrap.mjs');
|
||||
const configStore = read('lib/config-store.mjs');
|
||||
const runtimePaths = read('lib/runtime-paths.mjs');
|
||||
const setupPs = read('setup.ps1');
|
||||
const setupGui = read('setup-gui.ps1');
|
||||
const setupBat = read('setup.bat');
|
||||
const setupSh = read('setup.sh');
|
||||
const compose = read('compose.yaml');
|
||||
const composeVps = read('compose.vps.yaml');
|
||||
const caddy = read('deploy/Caddyfile');
|
||||
const env = read('.env.example');
|
||||
const gitignore = read('.gitignore');
|
||||
const ci = read('.github/workflows/ci.yml');
|
||||
const release = read('.github/workflows/release.yml');
|
||||
const gitDeploy = read('scripts/github-deploy.ps1');
|
||||
const gitSafety = read('tools/verify-git-safety.mjs');
|
||||
const gitSafetyRegression = read('tools/verify-git-safety-regression.mjs');
|
||||
const releaseBuilder = read('tools/build-release.mjs');
|
||||
const installerShellGate = read('tools/verify-installer-shell.sh');
|
||||
const gitStaging = read('tools/prepare-git-staging.mjs');
|
||||
const gitStagingRegression = read('tools/verify-git-staging.mjs');
|
||||
const architectureGate = read('tools/verify-platform-architecture-1.35.0.mjs');
|
||||
const tokenSource = read('public/design-system/tokens/source.json');
|
||||
const tokenCss = read('public/design-system/tokens.css');
|
||||
const moduleSchema = read('app/contracts/module.schema.json');
|
||||
const boundaries = read('app/architecture-boundaries.json');
|
||||
|
||||
for (const [label, value] of [
|
||||
['package.json', pkg.version], ['package-lock.json', lock.version],
|
||||
['package-lock root', lock.packages?.['']?.version], ['update-manifest', manifest.version],
|
||||
]) if (value !== '1.35.0') failures.push(`${label}-Version ist ${value}`);
|
||||
|
||||
for (const marker of ['content="1.35.0"', 'style.css?v=1.35.0', 'app.js?v=1.35.0']) expect(html, marker, `UI-Build ${marker}`);
|
||||
if (!app.startsWith("const VENDOO_UI_BUILD = '1.35.0';")) failures.push('UI-Build ist nicht 1.35.0');
|
||||
for (const marker of ['VENDOO_CONFIG_PATH', "await import('./server.mjs')", 'override: true']) expect(bootstrap, marker, `Bootstrap ${marker}`);
|
||||
for (const marker of ['CONFIG_PATH', 'writeRuntimeConfig', 'updateRuntimeConfig', 'mode: 0o600']) expect(configStore, marker, `Konfigurationsspeicher ${marker}`);
|
||||
for (const marker of ['VENDOO_CONFIG_PATH', 'runtime_config: CONFIG_PATH']) expect(runtimePaths, marker, `Runtime-Pfade ${marker}`);
|
||||
for (const marker of ['readRuntimeConfig()', 'writeRuntimeConfig(envContent)', 'updateRuntimeConfig({ LOCAL_IMAGE_TOKEN: token })', 'runtime_config_path: CONFIG_PATH']) expect(read('server.mjs'), marker, `Server-Konfiguration ${marker}`);
|
||||
forbid(read('server.mjs'), "join(__dirname, '.env')", 'direktes Schreiben in schreibgeschütztes App-.env');
|
||||
|
||||
for (const marker of ["$Version = '1.35.0'", '?build=1.35.0', "'install-local'", "'install-docker'", 'Configure-VendooConsole', 'Initialize-VendooEnvironment']) expect(setupPs, marker, `Windows-Setup ${marker}`);
|
||||
|
||||
for (const marker of [
|
||||
'Vendoo 1.35.0 – Installation', 'Lokal auf diesem Windows-PC', 'Docker Desktop auf diesem Windows-PC',
|
||||
'Set-Configuration', 'VENDOO_SETUP_TOKEN', 'Clipboard', 'AI- und Marktplatz-Schlüssel werden später',
|
||||
]) expect(setupGui, marker, `Windows-GUI ${marker}`);
|
||||
forbid(setupGui, 'PlaceholderText', 'nicht mit Windows PowerShell 5.1 kompatible PlaceholderText-Eigenschaft');
|
||||
for (const marker of ['setup-gui.ps1', '-STA', 'Parser]::ParseFile']) expect(setupBat, marker, `setup.bat ${marker}`);
|
||||
|
||||
for (const marker of [
|
||||
'Zero-Edit Deployment Assistant', 'configure_wizard', 'VPS / öffentlicher Server mit automatischem HTTPS',
|
||||
'VENDOO_DOMAIN', 'compose.vps.yaml', 'Caddy übernimmt HTTPS automatisch', 'keine Datei musste manuell bearbeitet werden',
|
||||
'Aktualisieren (mit Sicherheitsbackup)', 'docker-backup.sh',
|
||||
]) expect(setupSh, marker, `Linux-Assistent ${marker}`);
|
||||
|
||||
for (const marker of ['VENDOO_CONFIG_PATH: /app/data/config/vendoo.env', 'vendoo_config:/app/data/config']) expect(compose, marker, `Basis-Compose-Konfiguration ${marker}`);
|
||||
for (const marker of ['caddy:2-alpine', '80:80', '443:443', './deploy/Caddyfile', 'condition: service_healthy', 'read_only: true', 'vendoo_config:/app/data/config']) expect(composeVps, marker, `VPS-Compose ${marker}`);
|
||||
for (const marker of ['{$VENDOO_DOMAIN}', 'reverse_proxy vendoo:8124', 'Strict-Transport-Security']) expect(caddy, marker, `Caddy ${marker}`);
|
||||
for (const marker of ['VENDOO_DOMAIN=', 'VENDOO_SETUP_TOKEN=', 'VENDOO_COOKIE_SECURE=', 'LOCAL_IMAGE_ENABLED=false']) expect(env, marker, `.env.example ${marker}`);
|
||||
|
||||
for (const marker of ['name: Vendoo CI', 'npm ci', 'npm run verify:git', 'npm run verify:git-regression', 'npm run verify:git-staging', 'npm run verify:deployment', 'npm run verify:config', 'npm run verify:installer-shell', 'docker build --pull', 'compose.vps.yaml']) expect(ci, marker, `CI ${marker}`);
|
||||
for (const marker of ['name: Vendoo Release', 'Release-Tag gegen Paketversion prüfen', 'npm run verify:git-staging', 'npm run verify:config', 'npm run verify:installer-shell', 'release-output', 'gh release upload', 'ghcr.io', 'docker/build-push-action@v6', 'actions/attest@v4', 'attestations: write', 'id-token: write', 'steps.push.outputs.digest', 'linux/amd64,linux/arm64']) expect(release, marker, `Release-Workflow ${marker}`);
|
||||
for (const marker of ['release/1.35.0-platform-kernel', 'verify-git-safety.mjs', 'prepare-git-staging.mjs', 'RedirectStandardOutput', 'github-deploy-safety-report.txt', 'Zum Fortfahren PUSH eingeben', 'vendoo/pull/7']) expect(gitDeploy, marker, `GitHub-Deploy ${marker}`);
|
||||
for (const marker of ['fileURLToPath', 'kompletten Arbeitsbaum', 'forbiddenPathPatterns', 'secretPatterns', 'Git-Sicherheitsgate erfolgreich']) expect(gitSafety, marker, `Git-Sicherheitsgate ${marker}`);
|
||||
for (const marker of ['unversioniert', '.env.local', 'untracked-secret.txt', 'Git-Sicherheitsgate-Regressionstest erfolgreich']) expect(gitSafetyRegression, marker, `Git-Sicherheitsgate-Regression ${marker}`);
|
||||
for (const marker of ['prepareGitStaging', 'shouldSkipStagingEntry', 'allowedEnvTemplates', 'skippedTopLevelDirs', 'operations', 'updates', 'Ziel ist kein geklontes Git-Repository']) expect(gitStaging, marker, `Git-Staging ${marker}`);
|
||||
for (const marker of ['.env.local', 'operations/staging/job.json', 'updates/backups/slice22_1_20260707-194953/setup.ps1', '.git/marker', 'Git-Staging-Regressionstest erfolgreich']) expect(gitStagingRegression, marker, `Git-Staging-Regression ${marker}`);
|
||||
for (const marker of ['release-output', 'FILE_CHECKSUMS_SHA256.txt', 'createHash', 'shouldSkipStagingEntry']) expect(releaseBuilder, marker, `Release-Builder ${marker}`);
|
||||
for (const marker of ['run_mode docker', 'run_mode nas', 'run_mode vps', 'VENDOO_SETUP_TOKEN', 'stat -c']) expect(installerShellGate, marker, `Shell-Installer-Gate ${marker}`);
|
||||
|
||||
for (const marker of ['.env', 'runtime/', 'updates/', 'local-ai/comfyui/', '.vendoo-install-mode', 'release-output/']) expect(gitignore, marker, `.gitignore ${marker}`);
|
||||
|
||||
for (const marker of ['createPlatformKernel', "id: 'platform.status.read'", "policy: 'platform.security-admin'", 'await platformKernel.stop()']) expect(read('server.mjs'), marker, `Platform-Kernel ${marker}`);
|
||||
for (const marker of ['Platform-Architecture-Gate 1.35.0 erfolgreich', 'MODULE_DEPENDENCY_CYCLE', 'ROUTE_POLICY_REQUIRED', 'THEME_VALUE_INVALID']) expect(architectureGate, marker, `Architektur-Gate ${marker}`);
|
||||
for (const marker of ['contractVersion', 'color.brand', 'density.controlHeight', 'motion.normal']) expect(tokenSource, marker, `Tokenquelle ${marker}`);
|
||||
for (const marker of [':root {', '[data-theme="dark"]', '[data-theme="contrast"]', '--vd-brand:']) expect(tokenCss, marker, `Token-CSS ${marker}`);
|
||||
for (const marker of ['Vendoo Module Manifest', 'legacy-bridge', 'extension-host']) expect(moduleSchema, marker, `Modulschema ${marker}`);
|
||||
for (const marker of ['modular-monolith', 'denyImplicitRoutes', 'thirdPartyExtensionsInMainProcess', 'strangler']) expect(boundaries, marker, `Architekturgrenzen ${marker}`);
|
||||
for (const required of ['app/kernel/platform-kernel.mjs', 'app/kernel/module-registry.mjs', 'app/kernel/route-registry.mjs', 'app/kernel/policy-engine.mjs', 'app/core/themes/theme-contract.mjs', 'public/design-system/theme-runtime.js', 'public/design-system/theme-contract.json', 'public/core/frontend-module-registry.js', 'public/core/safe-dom.js', 'public/core/api-client.js', 'docs/architecture/MODULE_SYSTEM_1_35_0.md', 'docs/architecture/DESIGN_SYSTEM_1_35_0.md', 'docs/architecture/SECURITY_ARCHITECTURE_1_35_0.md']) if (!existsSync(join(root, required))) failures.push(`Architektur-Pflichtdatei fehlt: ${required}`);
|
||||
for (const marker of ['read_only: true', 'no-new-privileges:true', 'cap_drop:', '/readyz', 'host.docker.internal:host-gateway']) expect(compose, marker, `Basis-Compose ${marker}`);
|
||||
|
||||
for (const required of [
|
||||
'.github/workflows/ci.yml', '.github/workflows/release.yml', '.github/dependabot.yml', '.github/CODEOWNERS',
|
||||
'GitHub-Deploy-Assistent.bat', 'scripts/github-deploy.ps1', 'scripts/github-deploy.sh',
|
||||
'setup-gui.ps1', 'bootstrap.mjs', 'lib/config-store.mjs', 'compose.vps.yaml', 'deploy/Caddyfile', 'tools/verify-git-safety.mjs', 'tools/verify-git-safety-regression.mjs', 'tools/prepare-git-staging.mjs', 'tools/verify-git-staging.mjs', 'tools/verify-installer-shell.sh', 'tools/build-release.mjs',
|
||||
]) if (!existsSync(join(root, required))) failures.push(`Pflichtdatei fehlt: ${required}`);
|
||||
|
||||
function walk(dir, out = []) {
|
||||
for (const name of readdirSync(dir)) {
|
||||
if (name === 'node_modules' || name === 'release-output' || name === '.git') continue;
|
||||
const abs = join(dir, name);
|
||||
const rel = relative(root, abs).split(sep).join('/');
|
||||
const stat = lstatSync(abs);
|
||||
if (stat.isDirectory()) walk(abs, out); else out.push(rel);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
const files = walk(root);
|
||||
const forbiddenRuntime = files.filter(rel =>
|
||||
rel === '.env' || rel === '.vendoo-install-mode' ||
|
||||
/(^|\/)(runtime|node_modules|uploads|backups|logs|updates)(\/|$)/i.test(rel) ||
|
||||
/^db\/.*\.(db|sqlite|sqlite3)(-wal|-shm)?$/i.test(rel) ||
|
||||
/^local-ai\/(comfyui|downloads|extract-temp)\//i.test(rel)
|
||||
);
|
||||
if (forbiddenRuntime.length) failures.push(`Verbotene Laufzeitdaten im Paket: ${forbiddenRuntime.slice(0, 10).join(', ')}`);
|
||||
|
||||
forbid(read('README.md'), '.env.example` nach `.env` kopieren', 'alte manuelle .env-Anweisung im README');
|
||||
|
||||
if (failures.length) {
|
||||
console.error(`Deployment-Gate 1.35.0 fehlgeschlagen (${failures.length}):`);
|
||||
failures.forEach(item => console.error(`- ${item}`));
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('Deployment-Gate 1.35.0 erfolgreich: Plattformkernel, Modul-/Policy-/Routenverträge, Design Tokens, Legacy-Bridges, Update-Archiv-Ausschluss, Zero-Edit-Installer, CI und Release-Workflow geprüft.');
|
||||
@@ -1,118 +0,0 @@
|
||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs';
|
||||
import { tmpdir } from 'os';
|
||||
import { dirname, join, resolve } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { spawnSync } from 'child_process';
|
||||
import { createPlatformKernel } from '../app/kernel/platform-kernel.mjs';
|
||||
import { ModuleRegistry } from '../app/kernel/module-registry.mjs';
|
||||
import { PolicyEngine } from '../app/kernel/policy-engine.mjs';
|
||||
import { RouteRegistry } from '../app/kernel/route-registry.mjs';
|
||||
import { EventBus } from '../app/kernel/event-bus.mjs';
|
||||
import { validateThemeDefinition } from '../app/core/themes/theme-contract.mjs';
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const failures = [];
|
||||
const assert = (condition, message) => { if (!condition) failures.push(message); };
|
||||
const expectThrow = async (fn, code, message) => {
|
||||
try { await fn(); failures.push(message); }
|
||||
catch (error) { if (code && error.code !== code) failures.push(`${message} (erhalten: ${error.code || error.message})`); }
|
||||
};
|
||||
|
||||
const packageJson = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
||||
assert(packageJson.version === '1.35.0', `package.json ist ${packageJson.version}`);
|
||||
|
||||
const kernel = createPlatformKernel({
|
||||
version: packageJson.version,
|
||||
hasPermission: (role, permission) => role === 'admin' && permission === 'security.manage',
|
||||
});
|
||||
await kernel.start();
|
||||
const snapshot = kernel.snapshot();
|
||||
assert(snapshot.architecture === 'modular-monolith', 'Architekturtyp fehlt');
|
||||
assert(snapshot.modules.length === 12, `Erwartet 12 Module, erhalten ${snapshot.modules.length}`);
|
||||
assert(snapshot.modules.every(module => module.state === 'running'), 'Nicht alle Module laufen');
|
||||
assert(snapshot.modules.every(module => module.status === 'legacy-bridge'), 'Bestandsmodule müssen als legacy-bridge markiert sein');
|
||||
assert(snapshot.features.some(flag => flag.name === 'platform.module-kernel' && flag.value), 'Kernel-Feature-Flag fehlt');
|
||||
assert(snapshot.policies.some(policy => policy.id === 'platform.security-admin'), 'Security-Admin-Policy fehlt');
|
||||
const health = await kernel.health();
|
||||
assert(health.ok, 'Kernel-Healthcheck ist nicht erfolgreich');
|
||||
await kernel.stop();
|
||||
assert(kernel.snapshot().lifecycle.state === 'stopped', 'Kernel stoppt nicht sauber');
|
||||
|
||||
const registry = new ModuleRegistry();
|
||||
registry.register({ manifest: {
|
||||
schemaVersion: 1, id: 'vendoo.test-a', name: 'A', version: '1.0.0', type: 'feature', status: 'native',
|
||||
requires: ['vendoo.test-b'], permissions: [], provides: ['test.a'], consumes: [], owns: ['test.a'],
|
||||
} });
|
||||
registry.register({ manifest: {
|
||||
schemaVersion: 1, id: 'vendoo.test-b', name: 'B', version: '1.0.0', type: 'feature', status: 'native',
|
||||
requires: ['vendoo.test-a'], permissions: [], provides: ['test.b'], consumes: [], owns: ['test.b'],
|
||||
} });
|
||||
await expectThrow(() => registry.startAll({}), 'MODULE_DEPENDENCY_CYCLE', 'Zyklische Modulabhängigkeit wurde nicht blockiert');
|
||||
|
||||
const ownershipRegistry = new ModuleRegistry();
|
||||
ownershipRegistry.register({ manifest: { schemaVersion: 1, id: 'vendoo.owner-a', name: 'Owner A', version: '1.0.0', type: 'feature', status: 'native', requires: [], permissions: [], provides: ['owner.a'], consumes: [], owns: ['shared.resource'] } });
|
||||
await expectThrow(() => ownershipRegistry.register({ manifest: { schemaVersion: 1, id: 'vendoo.owner-b', name: 'Owner B', version: '1.0.0', type: 'feature', status: 'native', requires: [], permissions: [], provides: ['owner.b'], consumes: [], owns: ['shared.resource'] } }), 'MODULE_OWNERSHIP_CONFLICT', 'Doppelte Modul-Ownership wurde nicht blockiert');
|
||||
|
||||
const capabilityRegistry = new ModuleRegistry();
|
||||
capabilityRegistry.register({ manifest: { schemaVersion: 1, id: 'vendoo.consumer', name: 'Consumer', version: '1.0.0', type: 'feature', status: 'native', requires: [], permissions: [], provides: ['consumer.ready'], consumes: ['missing.capability'], owns: ['consumer.state'] } });
|
||||
await expectThrow(() => capabilityRegistry.startAll({}), 'MODULE_CAPABILITY_MISSING', 'Fehlende Capability wurde nicht blockiert');
|
||||
|
||||
const policies = new PolicyEngine();
|
||||
policies.register('test.admin', ({ user }) => user?.role === 'admin');
|
||||
const routes = new RouteRegistry({ policyEngine: policies });
|
||||
const fakeApp = { get(path, handler) { this.path = path; this.handler = handler; } };
|
||||
await expectThrow(() => routes.register(fakeApp, { id: 'test.no-policy', method: 'GET', path: '/x', owner: 'vendoo.test', handler() {} }), 'ROUTE_POLICY_REQUIRED', 'Route ohne Policy wurde akzeptiert');
|
||||
routes.register(fakeApp, { id: 'test.route.read', method: 'GET', path: '/x', owner: 'vendoo.test', policy: 'test.admin', csrf: false, handler(_req, res) { res.json({ ok: true }); } });
|
||||
let deniedStatus = null;
|
||||
await fakeApp.handler({ user: { role: 'viewer' }, requestId: 'test' }, { headersSent: false, status(code) { deniedStatus = code; return this; }, json() {} }, () => {});
|
||||
assert(deniedStatus === 403, `Deny-by-default-Route gab ${deniedStatus} statt 403 zurück`);
|
||||
|
||||
const events = new EventBus();
|
||||
let eventCalls = 0;
|
||||
events.on('test.created', () => { eventCalls += 1; }, { owner: 'vendoo.test' });
|
||||
await events.emit('test.created');
|
||||
assert(eventCalls === 1, 'Event Bus liefert Event nicht aus');
|
||||
assert(events.removeOwner('vendoo.test') === 1, 'Event-Owner-Cleanup funktioniert nicht');
|
||||
|
||||
const validTheme = validateThemeDefinition({ id: 'markus-theme', name: 'Markus Theme', values: { 'color.brand': '#123456', 'radius.md': '14px' } });
|
||||
assert(validTheme.values['color.brand'] === '#123456', 'Gültiges Theme wurde nicht akzeptiert');
|
||||
await expectThrow(() => validateThemeDefinition({ id: 'unsafe', name: 'Unsafe', values: { 'shadow.float': 'url(javascript:alert(1))' } }), 'THEME_TOKEN_FORBIDDEN', 'Nicht anpassbarer Token wurde akzeptiert');
|
||||
await expectThrow(() => validateThemeDefinition({ id: 'unsafe', name: 'Unsafe', values: { 'color.brand': 'url(javascript:alert(1))' } }), 'THEME_VALUE_INVALID', 'Unsicherer Farbwert wurde akzeptiert');
|
||||
await expectThrow(() => validateThemeDefinition({ id: 'unsafe-rgb', name: 'Unsafe RGB', values: { 'color.brand': 'rgb(999,0,0)' } }), 'THEME_VALUE_INVALID', 'Farbkanal außerhalb 0-255 wurde akzeptiert');
|
||||
|
||||
const tokenCssBefore = readFileSync(join(root, 'public', 'design-system', 'tokens.css'), 'utf8');
|
||||
const generated = spawnSync(process.execPath, [join(root, 'tools', 'generate-design-tokens.mjs')], { cwd: root, encoding: 'utf8' });
|
||||
assert(generated.status === 0, `Token-Generator fehlgeschlagen: ${generated.stderr || generated.stdout}`);
|
||||
const tokenCssAfter = readFileSync(join(root, 'public', 'design-system', 'tokens.css'), 'utf8');
|
||||
assert(tokenCssBefore === tokenCssAfter, 'Token-Generator ist nicht deterministisch');
|
||||
assert(tokenCssAfter.includes('[data-theme="contrast"]'), 'Kontrast-Theme fehlt');
|
||||
|
||||
const style = readFileSync(join(root, 'public', 'style.css'), 'utf8');
|
||||
assert(style.startsWith("@import url('./design-system/tokens.css?v=1.35.0');"), 'style.css bindet Tokenvertrag nicht zuerst ein');
|
||||
assert(!style.includes('Vendoo UI Foundation 2026'), 'Alte eingebettete Tokenquelle ist noch vorhanden');
|
||||
const frontendRegistry = readFileSync(join(root, 'public', 'core', 'frontend-module-registry.js'), 'utf8');
|
||||
const safeDom = readFileSync(join(root, 'public', 'core', 'safe-dom.js'), 'utf8');
|
||||
const apiClient = readFileSync(join(root, 'public', 'core', 'api-client.js'), 'utf8');
|
||||
assert(frontendRegistry.includes('AbortController') && frontendRegistry.includes('async unmount()'), 'Frontend-Modul-Lifecycle fehlt');
|
||||
assert(safeDom.includes('textContent') && !safeDom.includes('innerHTML'), 'Safe-DOM-Vertrag ist unsicher');
|
||||
assert(apiClient.includes("credentials: 'same-origin'") && apiClient.includes('X-CSRF-Token'), 'API-Client-Sicherheitsvertrag fehlt');
|
||||
|
||||
const html = readFileSync(join(root, 'public', 'index.html'), 'utf8');
|
||||
assert(html.includes('design-system/theme-runtime.js?v=1.35.0'), 'Theme Runtime ist nicht eingebunden');
|
||||
const server = readFileSync(join(root, 'server.mjs'), 'utf8');
|
||||
for (const marker of ['createPlatformKernel', "id: 'platform.status.read'", "policy: 'platform.security-admin'", 'await platformKernel.stop()']) {
|
||||
assert(server.includes(marker), `Serverintegration fehlt: ${marker}`);
|
||||
}
|
||||
|
||||
const temp = mkdtempSync(join(tmpdir(), 'vendoo-architecture-'));
|
||||
try {
|
||||
writeFileSync(join(temp, 'proof.txt'), 'ok');
|
||||
assert(readFileSync(join(temp, 'proof.txt'), 'utf8') === 'ok', 'Temporäre Testumgebung ist nicht schreibbar');
|
||||
} finally { rmSync(temp, { recursive: true, force: true }); }
|
||||
|
||||
if (failures.length) {
|
||||
console.error(`Platform-Architecture-Gate 1.35.0 fehlgeschlagen (${failures.length}):`);
|
||||
failures.forEach(failure => console.error(`- ${failure}`));
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Platform-Architecture-Gate 1.35.0 erfolgreich: ${snapshot.modules.length} Module, ${snapshot.features.length} Feature Flags, ${snapshot.policies.length} Policies, Tokenvertrag und Deny-by-default-Routen geprüft.`);
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"schema": "vendoo-update-manifest-v1",
|
||||
"version": "1.35.0",
|
||||
"name": "Vendoo Modular Platform Kernel & Design-System Foundation",
|
||||
"version": "1.34.3",
|
||||
"name": "Vendoo Git-Staging Update-Archiv-Ausschluss Hotfix",
|
||||
"channel": "stable",
|
||||
"minimum_node": "18",
|
||||
"notes": "Modularer Plattformkernel, validierte Modulmanifeste, Deny-by-default-Routenverträge, Feature Flags, Lifecycle/Event Bus sowie versionierte Design Tokens mit sicherem Theme-Vertrag. Bestehendes Verhalten bleibt über Legacy-Bridges erhalten.",
|
||||
"notes": "Lokale updates/backups-, Slice- und Rollback-Artefakte werden vor dem Git-Sicherheitsgate automatisch aus dem temporären Staging ausgeschlossen; aktive Root- und scripts-Dateien bleiben enthalten.",
|
||||
"download_url": "",
|
||||
"sha256": ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user