includio-cms 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +20 -20
- package/CHANGELOG.md +90 -0
- package/DOCS.md +1 -1
- package/README.md +138 -32
- package/ROADMAP.md +4 -0
- package/dist/admin/api/rest/handler.d.ts +13 -1
- package/dist/admin/api/rest/handler.js +13 -1
- package/dist/admin/api/rest/middleware/generateApiKey.d.ts +9 -0
- package/dist/admin/api/rest/middleware/generateApiKey.js +9 -0
- package/dist/admin/client/collection/collection-entries.svelte +1 -1
- package/dist/admin/client/collection/empty-state.svelte +1 -1
- package/dist/admin/client/collection/row-actions.svelte +3 -3
- package/dist/admin/client/collection/table-toolbar.svelte +3 -1
- package/dist/admin/client/entry/entry-header.svelte +3 -1
- package/dist/admin/client/users/create-user-dialog.svelte +4 -4
- package/dist/admin/client/users/delete-user-dialog.svelte +2 -0
- package/dist/admin/client/users/users-page.svelte +3 -2
- package/dist/admin/components/media/file-upload.svelte +2 -0
- package/dist/ai-claude/index.d.ts +9 -1
- package/dist/ai-claude/index.js +9 -1
- package/dist/ai-openai/index.d.ts +9 -1
- package/dist/ai-openai/index.js +9 -1
- package/dist/cli/index.js +115 -13
- package/dist/cms/runtime/schema.d.ts +2 -0
- package/dist/cms/runtime/schema.js +4 -0
- package/dist/cms/runtime/types.d.ts +1 -1
- package/dist/core/cms.d.ts +13 -1
- package/dist/core/cms.js +13 -1
- package/dist/core/errors.d.ts +71 -0
- package/dist/core/errors.js +179 -0
- package/dist/core/server/consentLogs/operations/create.d.ts +13 -1
- package/dist/core/server/consentLogs/operations/create.js +13 -1
- package/dist/core/server/entries/operations/create.js +6 -1
- package/dist/core/server/entries/operations/get.js +14 -3
- package/dist/core/server/entries/operations/resolveEntry.d.ts +32 -1
- package/dist/core/server/entries/operations/resolveEntry.js +36 -4
- package/dist/core/server/entries/operations/update.js +5 -1
- package/dist/core/server/fields/utils/resolveMedia.d.ts +18 -1
- package/dist/core/server/fields/utils/resolveMedia.js +13 -1
- package/dist/core/server/forms/submissions/operations/create.d.ts +21 -1
- package/dist/core/server/forms/submissions/operations/create.js +18 -2
- package/dist/core/server/forms/submissions/utils/parseMultipart.d.ts +15 -1
- package/dist/core/server/forms/submissions/utils/parseMultipart.js +15 -1
- package/dist/db-postgres/index.d.ts +10 -0
- package/dist/db-postgres/index.js +10 -0
- package/dist/email-nodemailer/index.d.ts +13 -1
- package/dist/email-nodemailer/index.js +13 -1
- package/dist/entity/index.d.ts +16 -1
- package/dist/entity/index.js +16 -1
- package/dist/files-local/index.d.ts +12 -1
- package/dist/files-local/index.js +12 -1
- package/dist/paraglide/messages/_index.d.ts +3 -36
- package/dist/paraglide/messages/_index.js +3 -71
- package/dist/paraglide/messages/hello_world.d.ts +5 -0
- package/dist/paraglide/messages/hello_world.js +33 -0
- package/dist/paraglide/messages/login_hello.d.ts +16 -0
- package/dist/paraglide/messages/login_hello.js +34 -0
- package/dist/paraglide/messages/login_please_login.d.ts +16 -0
- package/dist/paraglide/messages/login_please_login.js +34 -0
- package/dist/server/auth.d.ts +11 -0
- package/dist/server/auth.js +11 -0
- package/dist/sveltekit/config.d.ts +67 -4
- package/dist/sveltekit/config.js +73 -4
- package/dist/sveltekit/server/handle.d.ts +15 -1
- package/dist/sveltekit/server/handle.js +15 -1
- package/dist/sveltekit/server/layout.d.ts +12 -1
- package/dist/sveltekit/server/layout.js +12 -1
- package/dist/sveltekit/server/preview.d.ts +21 -1
- package/dist/sveltekit/server/preview.js +21 -1
- package/dist/types/cms.schema.d.ts +452 -0
- package/dist/types/cms.schema.js +629 -0
- package/dist/updates/0.22.0/index.d.ts +2 -0
- package/dist/updates/0.22.0/index.js +75 -0
- package/dist/updates/index.js +2 -1
- package/package.json +4 -1
- package/dist/paraglide/messages/en.d.ts +0 -5
- package/dist/paraglide/messages/en.js +0 -14
- package/dist/paraglide/messages/pl.d.ts +0 -5
- package/dist/paraglide/messages/pl.js +0 -14
package/API.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# includio-cms — Public API v0.
|
|
1
|
+
# includio-cms — Public API v0.22.0
|
|
2
2
|
|
|
3
3
|
> Auto-generated by `scripts/generate-api-md.ts`. Do not edit by hand.
|
|
4
4
|
|
|
@@ -13,19 +13,19 @@ Tags:
|
|
|
13
13
|
|
|
14
14
|
### `includio-cms`
|
|
15
15
|
|
|
16
|
-
- `createEntityAPI(cms: CMS, opts?: EntityAPIOptions): <inferred>` — Creates a high-level Entity API (CRUD + publish/archive) bound to a CMS
|
|
16
|
+
- `createEntityAPI(cms: CMS, opts?: EntityAPIOptions): <inferred>` — Creates a high-level Entity API (CRUD + publish/archive) bound to a CMS
|
|
17
17
|
- `getAuth(): <inferred>` — Returns the underlying `better-auth` instance from the initialized CMS.
|
|
18
|
-
- `getCMS(): CMS` — Returns the singleton CMS instance. Must be called after `includioCMS()`
|
|
19
|
-
- `interface ResolvedMedia`
|
|
20
|
-
- `resolveMediaWithStyles(mediaIds: string[], styles?: ImageFieldStyle[]): Promise<Record<string, ResolvedMedia>>` — Resolve media files by IDs and generate image styles. Useful for plugins
|
|
18
|
+
- `getCMS(): CMS` — Returns the singleton CMS instance. Must be called after `includioCMS()`
|
|
19
|
+
- `interface ResolvedMedia` — Resolved media file plus its image styles + blur placeholder. Returned by
|
|
20
|
+
- `resolveMediaWithStyles(mediaIds: string[], styles?: ImageFieldStyle[]): Promise<Record<string, ResolvedMedia>>` — Resolve media files by IDs and generate image styles. Useful for plugins or
|
|
21
21
|
|
|
22
22
|
### `includio-cms/core`
|
|
23
23
|
|
|
24
|
-
- `createEntityAPI(cms: CMS, opts?: EntityAPIOptions): <inferred>` — Creates a high-level Entity API (CRUD + publish/archive) bound to a CMS
|
|
24
|
+
- `createEntityAPI(cms: CMS, opts?: EntityAPIOptions): <inferred>` — Creates a high-level Entity API (CRUD + publish/archive) bound to a CMS
|
|
25
25
|
- `getAuth(): <inferred>` — Returns the underlying `better-auth` instance from the initialized CMS.
|
|
26
|
-
- `getCMS(): CMS` — Returns the singleton CMS instance. Must be called after `includioCMS()`
|
|
27
|
-
- `interface ResolvedMedia`
|
|
28
|
-
- `resolveMediaWithStyles(mediaIds: string[], styles?: ImageFieldStyle[]): Promise<Record<string, ResolvedMedia>>` — Resolve media files by IDs and generate image styles. Useful for plugins
|
|
26
|
+
- `getCMS(): CMS` — Returns the singleton CMS instance. Must be called after `includioCMS()`
|
|
27
|
+
- `interface ResolvedMedia` — Resolved media file plus its image styles + blur placeholder. Returned by
|
|
28
|
+
- `resolveMediaWithStyles(mediaIds: string[], styles?: ImageFieldStyle[]): Promise<Record<string, ResolvedMedia>>` — Resolve media files by IDs and generate image styles. Useful for plugins or
|
|
29
29
|
|
|
30
30
|
### `includio-cms/types`
|
|
31
31
|
|
|
@@ -239,11 +239,11 @@ Tags:
|
|
|
239
239
|
### `includio-cms/sveltekit`
|
|
240
240
|
|
|
241
241
|
- `const CmsProvider: LegacyComponentType`
|
|
242
|
-
- `defineCollection(config: CollectionInput): CollectionConfig` — Defines a collection (multi-entry content type).
|
|
242
|
+
- `defineCollection(config: CollectionInput): CollectionConfig` — Defines a collection (multi-entry content type, e.g. blog posts, products).
|
|
243
243
|
- `defineConfig(config: CMSConfig): CMSConfig` — Defines the root CMS configuration. Pass to `includioCMS()` in `hooks.server.ts`.
|
|
244
|
-
- `defineForm(config: FormConfig): FormConfig` — Defines a public form (submitted via
|
|
245
|
-
- `defineObject(config: Omit<ObjectField, 'type'>): ObjectField` — Defines a reusable object field (nested record). Use inside `fields[]
|
|
246
|
-
- `defineSingle(config: SingleInput): SingleConfig` — Defines a singleton (single-entry content type, e.g. site settings).
|
|
244
|
+
- `defineForm(config: FormConfig): FormConfig` — Defines a public form (submitted via `POST /api/forms/[slug]/submit`).
|
|
245
|
+
- `defineObject(config: Omit<ObjectField, 'type'>): ObjectField` — Defines a reusable object field (nested record). Use inline inside `fields[]`
|
|
246
|
+
- `defineSingle(config: SingleInput): SingleConfig` — Defines a singleton (single-entry content type, e.g. site settings, homepage).
|
|
247
247
|
- `enableHybridEditing(): <inferred>` — Call in a layout/component script to enable data-hybrid-path rendering for all descendant HybridTarget/Image/Video.
|
|
248
248
|
- `extractBlocks(doc: StructuredContentDoc, type?: string): SCNode[]` — Extract block-level nodes, optionally filtered by type.
|
|
249
249
|
- `extractInlineBlocks(doc: StructuredContentDoc, blockType?: string): SCInlineBlockAttrs[]` — Extract inline block nodes, optionally filtered by blockType.
|
|
@@ -267,16 +267,16 @@ Tags:
|
|
|
267
267
|
|
|
268
268
|
### `includio-cms/sveltekit/server`
|
|
269
269
|
|
|
270
|
-
- `cmsLayoutLoad(event: RequestEvent): <inferred>` — Returns `cmsContext` from `event.locals` for use in a SvelteKit layout
|
|
271
|
-
- `countEntries(opts: CountEntriesOptions): Promise<number>` — Count entries matching the same filters as `resolveEntries`, without
|
|
270
|
+
- `cmsLayoutLoad(event: RequestEvent): <inferred>` — Returns `cmsContext` from `event.locals` for use in a SvelteKit layout
|
|
271
|
+
- `countEntries(opts: CountEntriesOptions): Promise<number>` — Count entries matching the same filters as `resolveEntries`, without
|
|
272
272
|
- `type CountEntriesOptions = Omit< ResolveEntriesOptions, 'limit' | 'offset' | 'populate' | 'orderBy' | 'dataOrderBy' >`
|
|
273
273
|
- `const createConsentLog: <inferred>`
|
|
274
274
|
- `const createFormSubmission: <inferred>`
|
|
275
|
-
- `createRestApiHandler(): <inferred>` — REST API handler factory. Returns `{ GET, POST, PUT, DELETE }`
|
|
275
|
+
- `createRestApiHandler(): <inferred>` — REST API handler factory. Returns `{ GET, POST, PUT, DELETE }`
|
|
276
276
|
- `generateApiKey(): string` — Generate a cryptographically random API key (32 bytes, base64url-encoded).
|
|
277
|
-
- `getPreviewEntry(event: RequestEvent, options: { language: string }): Promise<Entry | null>` — Resolves the preview entry from a `?preview=<versionId>` query param.
|
|
278
|
-
- `includioCMS(cmsConfig: CMSConfig): Handle[]` — SvelteKit `Handle[]` array that initializes the CMS, generates runtime
|
|
279
|
-
- `parseFormDataForSubmission(formData: FormData, fields: FormField[]): Promise<Record<string, unknown>>` — Parses multipart `FormData` into a typed record of field values, handling
|
|
277
|
+
- `getPreviewEntry(event: RequestEvent, options: { language: string }): Promise<Entry | null>` — Resolves the preview entry from a `?preview=<versionId>` query param.
|
|
278
|
+
- `includioCMS(cmsConfig: CMSConfig): Handle[]` — SvelteKit `Handle[]` array that initializes the CMS, generates runtime
|
|
279
|
+
- `parseFormDataForSubmission(formData: FormData, fields: FormField[]): Promise<Record<string, unknown>>` — Parses multipart `FormData` into a typed record of field values, handling
|
|
280
280
|
- `type PopulateConfig = { /** Hard cap on relation depth. Default 5. Use `0` to keep all relations as raw IDs. */ maxDept...` — Recursion + per-field opt-out config for relation population.
|
|
281
281
|
- `resolveEntries(opts: ResolveEntriesOptions): Promise<Entry[]>` — Fetch a list of populated Entries from a collection (or singleton with multiple instances).
|
|
282
282
|
- `interface ResolveEntriesOptions`
|
|
@@ -387,7 +387,7 @@ Tags:
|
|
|
387
387
|
### `includio-cms/files-local`
|
|
388
388
|
|
|
389
389
|
- `const fullDir: <inferred>`
|
|
390
|
-
- `local(config?: LocalFilesConfig): FilesAdapter` — Local-disk files adapter. Stores uploads under `./static/uploads` (dev) or
|
|
390
|
+
- `local(config?: LocalFilesConfig): FilesAdapter` — Local-disk files adapter. Stores uploads under `./static/uploads` (dev) or
|
|
391
391
|
- `interface LocalFilesConfig`
|
|
392
392
|
|
|
393
393
|
### `includio-cms/email-nodemailer`
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,96 @@
|
|
|
3
3
|
All notable changes to includio-cms are documented here.
|
|
4
4
|
Generated from `src/lib/updates/` — do not edit manually.
|
|
5
5
|
|
|
6
|
+
## 0.22.0 — 2026-04-30
|
|
7
|
+
|
|
8
|
+
Faza 9 — DX & config validation pass. `defineConfig()` waliduje config strict Zodem z czytelnymi błędami (path + hint), resolvery / operacje throwują typowane `CmsError` z `code` + `context`, CLI ma `--help` per subcommand i `--version`, README przepisany pod nowych userów (system requirements + 5-min quickstart), `.env.example` rozszerzony o wszystkie `INCLUDIO_*` envy, JSDoc na każdym `@public` symbolu (opis + `@param` + `@returns` + `@example`).
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `defineConfig()` (`includio-cms/sveltekit`) — runtime Zod validation z agregacją wszystkich błędów. Invalid config → `ConfigValidationError` (extends `CmsError`) z `issues[]` (path + message + hint), bullet-list message ready-to-paste do PR. `superRefine`: unique slugs (collections/singles/forms), default locale ≤ 1, relation targets w deklarowanych kolekcjach, `apiKeys[].permissions` referuje istniejące collection slugs.
|
|
12
|
+
- `CmsError` + `ConfigValidationError` (`includio-cms` → `core/errors`) — bazowa klasa z `code` (SCREAMING_SNAKE_CASE), `context: Record<string, unknown>`, `cause?`. Stabilne kody: `ENTRY_NOT_FOUND`, `ENTRY_VERSION_NOT_FOUND`, `INVALID_DATA`, `MISSING_REQUIRED_PARAM`, `CONFIG_VALIDATION_FAILED`. `toString()` = `[CODE] message (k=v, k=v)`.
|
|
13
|
+
- `formatZodDataIssues(error)` helper — render Zod errora z entry/form data jako lista `path: message`. Używany w `createEntryVersion`, `updateEntryVersion`, `createFormSubmission`.
|
|
14
|
+
- CLI: `includio --help`, `includio scaffold admin --help`, `includio install-peers --help`, `includio create-user --help` — exit 0 + Usage/Options/Example block. `includio --version` / `-v` z `package.json`. Unknown command → exit 1 + top-level help.
|
|
15
|
+
- `.env.example` rozszerzony — 11 envów z komentarzami i sekcjami (Required / Media & Uploads / Security & Rate Limits). Demo vars usunięte (były tylko dla `src/lib/demo/` skasowanego w Fazie 1).
|
|
16
|
+
- README rebuild: TOC, sekcja **System requirements** (Node 18+, PG 14+, ffmpeg optional), 5-step **Quick start** (install → .env → config → scaffold → create-user → dev), poprawiony link do `DOCS.md`, dodana tabelka adapterów, sekcja CLI z subcommandami.
|
|
17
|
+
- JSDoc bodies (opis + `@param` + `@returns` + `@example`) na każdym `@public` symbolu — `defineConfig`, `defineCollection`, `defineSingle`, `defineForm`, `defineObject`, `getCMS`, `resolveEntry`, `resolveEntries`, `countEntries`, `createFormSubmission`, `parseFormDataForSubmission`, `createConsentLog`, `resolveMediaWithStyles`, `createEntityAPI`, `createRestApiHandler`, `generateApiKey`, `getAuth`, `includioCMS`, `getPreviewEntry`, `cmsLayoutLoad`, factory adapterów (`pg`, `local`, `nodemailerAdapter`, `openAIAdapter`, `claudeAdapter`).
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- `MISSING_REQUIRED_PARAM` zamiast `throw new Error('... is required')` w `resolveEntry` / `resolveEntries` / `countEntries`. Context zawiera `op` + `missing`.
|
|
21
|
+
- `INVALID_DATA` zamiast `throw Error('Invalid data: ' + JSON.stringify(parsedData.error.flatten()))` w `createEntryVersion` / `updateEntryVersion` / `createFormSubmission`. Context zawiera `collection`/`entryId`/`lang` (entries) lub `formSlug` (forms). Body errora to czytelna lista `field.path: message`.
|
|
22
|
+
- `ENTRY_NOT_FOUND` z `{ collection, id }` w `_getDbEntryOrThrow` / `_getRawEntryOrThrow`; `ENTRY_VERSION_NOT_FOUND` z `{ versionId, entryId, lang }` w `_getDbEntryVersionOrThrow`.
|
|
23
|
+
|
|
24
|
+
### Breaking
|
|
25
|
+
- **Strict config validation w `defineConfig`** — invalid configs które wcześniej "działały" w runtime (np. duplicate slug, brak default locale, relation do nieistniejącej kolekcji, brak metody w adapterze) **teraz throwują `ConfigValidationError` na starcie**. Najczęstsze przypadki + fix:
|
|
26
|
+
- **Duplicate slug**: dwie kolekcje/singles/forms z tym samym `slug` → unique slug per kategoria.
|
|
27
|
+
- **Invalid language code**: `code: 'ENGLISH'` → `code: 'en'` (ISO-639-1, opcjonalnie z regionem `'pl-PL'`).
|
|
28
|
+
- **Multiple default locales**: dwa `{ default: true }` → tylko jeden.
|
|
29
|
+
- **Missing adapter method**: stub adaptera bez `getEntries` etc. → zaimportuj pełen `pg()` / `local()` lub doimplementuj brakujące metody.
|
|
30
|
+
- **Invalid relation target**: `{ type: 'relation', collection: 'authors' }` przy braku kolekcji `authors` → dodaj kolekcję lub popraw target.
|
|
31
|
+
- **Resolver / operation error format** — błędy z `resolveEntry` / `resolveEntries` / `countEntries` / `createEntryVersion` / `updateEntryVersion` / `createFormSubmission` rzucają teraz `CmsError` (extends `Error`). `error.message` zachowuje stary opis (przy `toString()` poprzedzony `[CODE]`) — string-matching `error.message === 'Entry not found'` może się rozjechać. **Migracja:**
|
|
32
|
+
```ts
|
|
33
|
+
import { CmsError } from 'includio-cms';
|
|
34
|
+
|
|
35
|
+
try { await resolveEntry({ id }); }
|
|
36
|
+
catch (e) {
|
|
37
|
+
if (e instanceof CmsError && e.code === 'ENTRY_NOT_FOUND') { ... }
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
- **`.env.example` reformatted** — `DEMO_USER_EMAIL`, `DEMO_USER_PASSWORD`, `DEMO_RESET_KEY` usunięte (były tylko dla wewnętrznego demo skasowanego w Fazie 1). Jeśli polegałeś na nich → trzymaj w lokalnym `.env`, nie w `.env.example`.
|
|
41
|
+
|
|
42
|
+
### Notes
|
|
43
|
+
|
|
44
|
+
## Czytelność błędów config
|
|
45
|
+
|
|
46
|
+
Po update — invalid config wyrzuci na starcie:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
ConfigValidationError: CMSConfig validation failed (2 issues):
|
|
50
|
+
- languages[0]: must be a 2-letter ISO code (e.g. 'en' or 'pl-PL')
|
|
51
|
+
- collections[1].slug: duplicate collection slug 'posts' — Hint: each collection/single/form must have a unique `slug`
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Każdy issue ma `path` (JS-notation: `collections[1].slug`, `apiKeys[0].permissions[2]`) i `message`. Tam gdzie da się rozsądnie zgadnąć, dochodzi `Hint:` z fix-suggestion.
|
|
55
|
+
|
|
56
|
+
## Pattern: branchowanie po error code
|
|
57
|
+
|
|
58
|
+
Stary kod string-matchujący po `error.message` musi się przepisać:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// PRZED
|
|
62
|
+
catch (e) {
|
|
63
|
+
if ((e as Error).message === 'Entry not found') { /* 404 */ }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// PO
|
|
67
|
+
import { CmsError } from 'includio-cms';
|
|
68
|
+
catch (e) {
|
|
69
|
+
if (e instanceof CmsError && e.code === 'ENTRY_NOT_FOUND') { /* 404 */ }
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Dostępne kody w v0.22.0: `ENTRY_NOT_FOUND`, `ENTRY_VERSION_NOT_FOUND`, `INVALID_DATA`, `MISSING_REQUIRED_PARAM`, `CONFIG_VALIDATION_FAILED`.
|
|
74
|
+
|
|
75
|
+
## Nowe envy (opt-in)
|
|
76
|
+
|
|
77
|
+
Skopiuj `node_modules/includio-cms/.env.example` do swojego `.env` — domyślne wartości działają OOTB:
|
|
78
|
+
|
|
79
|
+
- `INCLUDIO_SHARP_TIMEOUT_MS` (default 30000)
|
|
80
|
+
- `INCLUDIO_RATE_LIMIT_MAX` / `INCLUDIO_RATE_LIMIT_WINDOW_MS` (default 200/60000)
|
|
81
|
+
- `INCLUDIO_FORM_RATE_LIMIT_MAX` / `INCLUDIO_FORM_RATE_LIMIT_WINDOW_MS` (default 5/3600000)
|
|
82
|
+
- `INCLUDIO_CSRF_ALLOWED_ORIGINS` (default: same-origin only)
|
|
83
|
+
|
|
84
|
+
## CLI helpy
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
includio --help
|
|
88
|
+
includio scaffold admin --help
|
|
89
|
+
includio install-peers --help
|
|
90
|
+
includio create-user --help
|
|
91
|
+
includio --version
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Brak SQL migration.
|
|
95
|
+
|
|
6
96
|
## 0.21.0 — 2026-04-30
|
|
7
97
|
|
|
8
98
|
Faza 5 część 2 — security finish: form rate-limit DRY refactor, API keys `expiresAt` / `rotatedAt`, sharp timeout 30s, `{@html}` audit close. `KNOWN-RISKS.md` w root jako single source of truth dla zaakceptowanych ryzyk v1.0. Plus: faza 6 setup — vitest coverage (info-only), docker test profile, integration test scaffolding (`tests/`).
|
package/DOCS.md
CHANGED
package/README.md
CHANGED
|
@@ -1,57 +1,149 @@
|
|
|
1
|
-
#
|
|
1
|
+
# includio-cms
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/includio/includio-cms/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
A headless CMS built for SvelteKit. Type-safe, extensible, with a modern admin interface and pluggable adapters for database, files, email, and AI.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
- [Features](#features)
|
|
10
|
+
- [System requirements](#system-requirements)
|
|
11
|
+
- [Quick start (5 minutes)](#quick-start-5-minutes)
|
|
12
|
+
- [Configuration](#configuration)
|
|
13
|
+
- [Adapters](#adapters)
|
|
14
|
+
- [Writing your own adapter](#writing-your-own-adapter)
|
|
15
|
+
- [Optional peer dependencies](#optional-peer-dependencies)
|
|
16
|
+
- [CLI](#cli)
|
|
17
|
+
- [Links](#links)
|
|
18
|
+
- [For AI assistants](#for-ai-assistants)
|
|
19
|
+
- [License](#license)
|
|
4
20
|
|
|
5
21
|
## Features
|
|
6
22
|
|
|
7
|
-
- **
|
|
23
|
+
- **20 field types** — text, structured content, media, relations, blocks, SEO, URL, custom plugins, and more
|
|
8
24
|
- **Structured content** — ProseMirror-based rich text with inline blocks, tables, media embeds
|
|
9
25
|
- **Media management** — image styles (Sharp), video transcoding (ffmpeg), focal points, blur placeholders
|
|
10
|
-
- **Per-language versioning** — independent draft/published/scheduled per language
|
|
26
|
+
- **Per-language versioning** — independent draft / published / scheduled per language
|
|
11
27
|
- **Layout DSL** — organize admin editor with sections, columns, cards, accordions
|
|
12
28
|
- **Frontend components** — `<Image>`, `<Video>`, `<StructuredContent>`, `<Media>` for SvelteKit
|
|
13
29
|
- **REST API** — API key auth, CRUD endpoints, schema introspection
|
|
14
30
|
- **Entity API** — server-side programmatic CRUD for scripts, migrations, webhooks
|
|
15
31
|
- **Code generation** — TypeScript types and Zod schemas from your content schema
|
|
16
|
-
- **Pluggable adapters** — swap database, file storage, email,
|
|
32
|
+
- **Pluggable adapters** — swap database, file storage, email, AI provider
|
|
17
33
|
- **Plugin system** — lifecycle hooks and custom field types
|
|
18
|
-
- **
|
|
34
|
+
- **Strict config validation** — invalid configs throw a `ConfigValidationError` listing every issue with a path and a fix hint
|
|
35
|
+
|
|
36
|
+
## System requirements
|
|
37
|
+
|
|
38
|
+
- **Node.js** 18+ (20 LTS recommended)
|
|
39
|
+
- **PostgreSQL** 14+ — required by the bundled `db-postgres` adapter; or write your own [`DatabaseAdapter`](#writing-your-own-adapter)
|
|
40
|
+
- **ffmpeg + ffprobe** — optional, only required for video transcoding (set `FFMPEG_PATH` / `FFPROBE_PATH` if not on `PATH`)
|
|
41
|
+
- A SvelteKit project (Kit ≥ 2.48, Svelte ≥ 5.43, Vite ≥ 7)
|
|
42
|
+
|
|
43
|
+
## Quick start (5 minutes)
|
|
19
44
|
|
|
20
|
-
|
|
45
|
+
The five steps below take you from `pnpm add` to a running admin at `/admin`.
|
|
46
|
+
|
|
47
|
+
**1. Install:**
|
|
21
48
|
|
|
22
49
|
```bash
|
|
23
50
|
pnpm add includio-cms
|
|
51
|
+
pnpm dlx includio install-peers
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**2. Configure environment:**
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
cp node_modules/includio-cms/.env.example .env
|
|
58
|
+
# edit .env — set DATABASE_URL and BETTER_AUTH_SECRET
|
|
24
59
|
```
|
|
25
60
|
|
|
26
|
-
|
|
61
|
+
Generate a secret:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
openssl rand -hex 32
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**3. Create `src/lib/cms/cms.config.ts`:**
|
|
68
|
+
|
|
69
|
+
```ts
|
|
27
70
|
import { defineConfig } from 'includio-cms/sveltekit';
|
|
28
71
|
import { pg } from 'includio-cms/db-postgres';
|
|
29
72
|
import { local } from 'includio-cms/files-local';
|
|
30
73
|
|
|
31
74
|
export default defineConfig({
|
|
32
75
|
languages: ['en'],
|
|
33
|
-
db: pg({ databaseUrl: process.env.DATABASE_URL }),
|
|
76
|
+
db: pg({ databaseUrl: process.env.DATABASE_URL! }),
|
|
34
77
|
files: local(),
|
|
35
|
-
|
|
78
|
+
auth: { secret: process.env.BETTER_AUTH_SECRET! },
|
|
79
|
+
collections: []
|
|
36
80
|
});
|
|
37
81
|
```
|
|
38
82
|
|
|
39
|
-
|
|
83
|
+
**4. Scaffold admin routes + create a user:**
|
|
40
84
|
|
|
41
|
-
|
|
85
|
+
```bash
|
|
86
|
+
pnpm dlx includio scaffold admin
|
|
87
|
+
pnpm dlx includio create-user
|
|
88
|
+
```
|
|
42
89
|
|
|
43
|
-
|
|
90
|
+
**5. Run the dev server:**
|
|
44
91
|
|
|
45
|
-
|
|
92
|
+
```bash
|
|
93
|
+
pnpm dev
|
|
94
|
+
```
|
|
46
95
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
96
|
+
Open `http://localhost:5173/admin` and sign in with the user you just created.
|
|
97
|
+
|
|
98
|
+
> Need more? Full reference in [`DOCS.md`](./DOCS.md). API surface in [`API.md`](./API.md).
|
|
99
|
+
|
|
100
|
+
## Configuration
|
|
101
|
+
|
|
102
|
+
`defineConfig({ ... })` is your single source of truth. The shape (abridged):
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
defineConfig({
|
|
106
|
+
languages: ['en', 'pl'], // first entry is the default locale
|
|
107
|
+
db, files, // required adapters
|
|
108
|
+
email, ai, // optional adapters
|
|
109
|
+
auth: { secret }, // required for /admin
|
|
110
|
+
collections: [postsCollection],
|
|
111
|
+
singles: [siteSettings],
|
|
112
|
+
forms: [contactForm],
|
|
113
|
+
apiKeys: [{ key: '...', role: 'admin' }],
|
|
114
|
+
media: { /* sharp / video / maintenance options */ },
|
|
115
|
+
typography: { fixOrphans: true },
|
|
116
|
+
shop, cmp // optional modules
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Invalid configs throw immediately at startup with every issue listed:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
ConfigValidationError: CMSConfig validation failed (2 issues):
|
|
124
|
+
- languages[0]: must be a 2-letter ISO code (e.g. 'en' or 'pl-PL')
|
|
125
|
+
- collections[1].slug: duplicate collection slug 'posts'
|
|
126
|
+
— Hint: each collection/single/form must have a unique `slug`
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
See [`DOCS.md`](./DOCS.md) for every field type and option.
|
|
130
|
+
|
|
131
|
+
## Adapters
|
|
132
|
+
|
|
133
|
+
`includio-cms` ships four pluggable contracts. Each has a default implementation; bring your own when you need to.
|
|
134
|
+
|
|
135
|
+
| Contract | Default | Lazy peer dep |
|
|
136
|
+
|--------------------|------------------------|------------------------------|
|
|
137
|
+
| `DatabaseAdapter` | `includio-cms/db-postgres` | `postgres`, `drizzle-orm` |
|
|
138
|
+
| `FilesAdapter` | `includio-cms/files-local` | — |
|
|
139
|
+
| `EmailAdapter` | `includio-cms/email-nodemailer` | `nodemailer` |
|
|
140
|
+
| `AIAdapter` | `includio-cms/ai-openai`, `includio-cms/ai-claude` | `openai`, `@anthropic-ai/sdk` |
|
|
51
141
|
|
|
52
142
|
Source of truth: [`src/lib/types/adapters/`](src/lib/types/adapters/).
|
|
53
143
|
|
|
54
|
-
###
|
|
144
|
+
### Writing your own adapter
|
|
145
|
+
|
|
146
|
+
Each contract is a TypeScript interface — implement every required method (optionals are marked `?`). TypeScript will enforce the surface.
|
|
55
147
|
|
|
56
148
|
```ts
|
|
57
149
|
import type { DatabaseAdapter } from 'includio-cms/types';
|
|
@@ -68,7 +160,7 @@ export function myDbAdapter(config: { connection: string }): DatabaseAdapter {
|
|
|
68
160
|
createEntryVersion: async (data) => { /* ... */ },
|
|
69
161
|
updateEntryVersion: async (data) => { /* ... */ },
|
|
70
162
|
getEntryVersions: async (data) => { /* ... */ },
|
|
71
|
-
deleteEntryVersion: async (data) => { /* ... */ }
|
|
163
|
+
deleteEntryVersion: async (data) => { /* ... */ }
|
|
72
164
|
// ...form submissions, media files, image/video styles, tags, consent logs...
|
|
73
165
|
};
|
|
74
166
|
}
|
|
@@ -90,25 +182,39 @@ Reference implementation: [`src/lib/db-postgres/index.ts`](src/lib/db-postgres/i
|
|
|
90
182
|
|
|
91
183
|
### Optional peer dependencies
|
|
92
184
|
|
|
93
|
-
The default email + AI adapters depend on third-party SDKs that **are not installed by default**.
|
|
185
|
+
The default email + AI adapters depend on third-party SDKs that **are not installed by default**. Install the peer once you use the adapter:
|
|
186
|
+
|
|
187
|
+
| Adapter | Required peer | Install |
|
|
188
|
+
|--------------------|---------------------|-------------------------------|
|
|
189
|
+
| `email-nodemailer` | `nodemailer` | `pnpm add nodemailer` |
|
|
190
|
+
| `ai-openai` | `openai` | `pnpm add openai` |
|
|
191
|
+
| `ai-claude` | `@anthropic-ai/sdk` | `pnpm add @anthropic-ai/sdk` |
|
|
192
|
+
|
|
193
|
+
Each SDK is loaded lazily on first call. A missing peer throws a clear error at runtime — install it and the adapter wakes up. `pnpm dlx includio install-peers` automates this for the parts of the CMS you have configured.
|
|
194
|
+
|
|
195
|
+
## CLI
|
|
196
|
+
|
|
197
|
+
The `includio` binary ships with three subcommands. Run any of them with `--help` for details.
|
|
94
198
|
|
|
95
|
-
|
|
|
96
|
-
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
199
|
+
| Command | What it does |
|
|
200
|
+
|--------------------------|-----------------------------------------------------------|
|
|
201
|
+
| `includio scaffold admin`| Generates `/admin` and `/api/admin` route files |
|
|
202
|
+
| `includio install-peers` | Installs missing optional peer SDKs based on your config |
|
|
203
|
+
| `includio create-user` | Creates an admin/user account interactively |
|
|
100
204
|
|
|
101
|
-
|
|
205
|
+
Global flags: `--help, -h` and `--version, -v`.
|
|
102
206
|
|
|
103
207
|
## Links
|
|
104
208
|
|
|
105
|
-
- [Full
|
|
106
|
-
- [
|
|
107
|
-
- [
|
|
209
|
+
- [Full documentation](./DOCS.md)
|
|
210
|
+
- [Public API surface](./API.md)
|
|
211
|
+
- [Changelog](./CHANGELOG.md)
|
|
212
|
+
- [Roadmap](./ROADMAP.md)
|
|
213
|
+
- [Known risks](./KNOWN-RISKS.md)
|
|
108
214
|
|
|
109
|
-
## For AI
|
|
215
|
+
## For AI assistants
|
|
110
216
|
|
|
111
|
-
Full documentation is in [`DOCS.md`](DOCS.md) (shipped with the npm package). When working on a project using includio-cms, read `node_modules/includio-cms/DOCS.md` for the complete API reference, available components, and migration guides.
|
|
217
|
+
Full documentation is in [`DOCS.md`](./DOCS.md) (shipped with the npm package). When working on a project using includio-cms, read `node_modules/includio-cms/DOCS.md` for the complete API reference, available components, and migration guides.
|
|
112
218
|
|
|
113
219
|
## License
|
|
114
220
|
|
package/ROADMAP.md
CHANGED
|
@@ -339,6 +339,10 @@
|
|
|
339
339
|
> 13 faz w ~16 sesjach, droga 0.16 → 1.0.0. Lean scope: stabilizacja, security, testy, docs, audit + polish shopa. Bez nowych feature'ów.
|
|
340
340
|
|
|
341
341
|
- [x] `[breaking]` `[P0]` API surface lock — exports trim 26→15, JSDoc tagi (@public/@internal/@experimental), `API.md` autogenerated (0.20.0) <!-- files: package.json, scripts/generate-api-md.ts -->
|
|
342
|
+
- [x] `[chore]` `[P1]` Faza 6 — Test foundation: docker postgres + integration project + 43 cases (30 REST + 6 shop checkout + 7 webhook signature/idempotency dla PayU/InPost), coverage report <!-- files: tests/integration/rest/, tests/helpers/{rest,shop}.ts, tests/integration/fixtures/shopConfig.ts, tests/setup.ts -->
|
|
343
|
+
- [x] `[chore]` `[P1]` Faza 7 — E2E foundation: Playwright config (storageState reuse, setup project), `db_test_e2e` na 5435, `e2e/setup/global-setup.ts` (drizzle push), `e2e/admin/auth.setup.ts` (sign-up + admin role + login), 4 specy zielone w ~23s: login, collection-crud (blog-post create→edit→archive→permanent-delete), media-upload, user-mgmt (create→delete); data-testid na critical buttons admin <!-- files: playwright.config.ts, e2e/admin/*, src/lib/admin/client/{collection,entry,users}/, src/lib/admin/components/media/file-upload.svelte -->
|
|
344
|
+
- [x] `[chore]` `[P1]` Faza 8 — CI na PR: `.github/workflows/ci.yml` z jobs lint/typecheck/unit/integration (postgres :5434)/e2e (postgres :5435 + playwright cache)/build, pnpm cache, trace artifacts on fail, README CI badge <!-- files: .github/workflows/ci.yml, README.md -->
|
|
345
|
+
- [x] `[breaking]` `[P0]` Faza 9 — DX & config validation (0.22.0): `defineConfig` Zod strict z field path + hint, `CmsError` + `ConfigValidationError` z code/context, resolver throw sites zmigrowane, CLI `--help` per subcommand + `--version`, `.env.example` rozszerzony o `INCLUDIO_*`, README rebuild (TOC + system req + 5-min quickstart), JSDoc body (opis + @param + @returns + @example) na każdym `@public` <!-- files: src/lib/core/errors.ts, src/lib/types/cms.schema.ts, src/lib/sveltekit/config.ts, src/lib/cli/index.ts, README.md, .env.example, src/lib/updates/0.22.0/ -->
|
|
342
346
|
- [ ] `[fix]` `[P1]` Select field — `defaultValue` propagacja do zod schema (full repro: `ideas/post-v1/select-field-defaultvalue-bug.md`); fix planowany w Fazie 12 (RC)
|
|
343
347
|
|
|
344
348
|
## v1.x — Post-v1.0 deferred
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { type RequestHandler } from '@sveltejs/kit';
|
|
2
2
|
/**
|
|
3
|
-
* REST API handler factory. Returns `{ GET, POST, PUT, DELETE }`
|
|
3
|
+
* REST API handler factory. Returns `{ GET, POST, PUT, DELETE }`
|
|
4
|
+
* `RequestHandler`s authenticated via the `x-api-key` header. Mount in
|
|
5
|
+
* `src/routes/admin/api/rest/[...restPath]/+server.ts`.
|
|
6
|
+
*
|
|
7
|
+
* @returns An object with `GET`, `POST`, `PUT`, `DELETE` SvelteKit
|
|
8
|
+
* `RequestHandler`s, ready to re-export from a `+server.ts` route.
|
|
4
9
|
* @public
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // src/routes/admin/api/rest/[...restPath]/+server.ts
|
|
13
|
+
* import { createRestApiHandler } from 'includio-cms/admin/remote';
|
|
14
|
+
*
|
|
15
|
+
* export const { GET, POST, PUT, DELETE } = createRestApiHandler();
|
|
16
|
+
* ```
|
|
5
17
|
*/
|
|
6
18
|
export declare function createRestApiHandler(): {
|
|
7
19
|
GET: RequestHandler;
|
|
@@ -50,8 +50,20 @@ function matchRoute(method, path) {
|
|
|
50
50
|
return null;
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
* REST API handler factory. Returns `{ GET, POST, PUT, DELETE }`
|
|
53
|
+
* REST API handler factory. Returns `{ GET, POST, PUT, DELETE }`
|
|
54
|
+
* `RequestHandler`s authenticated via the `x-api-key` header. Mount in
|
|
55
|
+
* `src/routes/admin/api/rest/[...restPath]/+server.ts`.
|
|
56
|
+
*
|
|
57
|
+
* @returns An object with `GET`, `POST`, `PUT`, `DELETE` SvelteKit
|
|
58
|
+
* `RequestHandler`s, ready to re-export from a `+server.ts` route.
|
|
54
59
|
* @public
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* // src/routes/admin/api/rest/[...restPath]/+server.ts
|
|
63
|
+
* import { createRestApiHandler } from 'includio-cms/admin/remote';
|
|
64
|
+
*
|
|
65
|
+
* export const { GET, POST, PUT, DELETE } = createRestApiHandler();
|
|
66
|
+
* ```
|
|
55
67
|
*/
|
|
56
68
|
export function createRestApiHandler() {
|
|
57
69
|
function handle(method) {
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
* Generate a cryptographically random API key (32 bytes, base64url-encoded).
|
|
3
3
|
* Use the result for `ApiKeyConfig.key`. Full rotation requires updating
|
|
4
4
|
* `cms.config.ts` and redeploying — see `KNOWN-RISKS.md` §2.
|
|
5
|
+
*
|
|
6
|
+
* @returns A 43-character base64url string (no padding).
|
|
5
7
|
* @public
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { generateApiKey } from 'includio-cms/admin/remote';
|
|
11
|
+
*
|
|
12
|
+
* const key = generateApiKey();
|
|
13
|
+
* console.log(key); // 'A1b2C3...xYz'
|
|
14
|
+
* ```
|
|
6
15
|
*/
|
|
7
16
|
export declare function generateApiKey(): string;
|
|
@@ -3,7 +3,16 @@ import { randomBytes } from 'node:crypto';
|
|
|
3
3
|
* Generate a cryptographically random API key (32 bytes, base64url-encoded).
|
|
4
4
|
* Use the result for `ApiKeyConfig.key`. Full rotation requires updating
|
|
5
5
|
* `cms.config.ts` and redeploying — see `KNOWN-RISKS.md` §2.
|
|
6
|
+
*
|
|
7
|
+
* @returns A 43-character base64url string (no padding).
|
|
6
8
|
* @public
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { generateApiKey } from 'includio-cms/admin/remote';
|
|
12
|
+
*
|
|
13
|
+
* const key = generateApiKey();
|
|
14
|
+
* console.log(key); // 'A1b2C3...xYz'
|
|
15
|
+
* ```
|
|
7
16
|
*/
|
|
8
17
|
export function generateApiKey() {
|
|
9
18
|
return randomBytes(32).toString('base64url');
|
|
@@ -897,7 +897,7 @@
|
|
|
897
897
|
>
|
|
898
898
|
<AlertDialog.Footer>
|
|
899
899
|
<AlertDialog.Cancel>{lang[interfaceLanguage.current].cancel}</AlertDialog.Cancel>
|
|
900
|
-
<AlertDialog.Action onclick={confirmDelete}
|
|
900
|
+
<AlertDialog.Action onclick={confirmDelete} data-testid="confirm-delete-entry"
|
|
901
901
|
>{lang[interfaceLanguage.current].delete}</AlertDialog.Action
|
|
902
902
|
>
|
|
903
903
|
</AlertDialog.Footer>
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
<h3 class="text-lg font-bold text-foreground mb-2">{title}</h3>
|
|
21
21
|
<p class="text-sm text-muted-foreground max-w-[360px] mb-6">{description}</p>
|
|
22
22
|
{#if ctaLabel && onCta}
|
|
23
|
-
<Button variant="default" onclick={onCta}>
|
|
23
|
+
<Button variant="default" onclick={onCta} data-testid="create-entry-button">
|
|
24
24
|
<Plus class="size-4 mr-1.5" />
|
|
25
25
|
{ctaLabel}
|
|
26
26
|
</Button>
|
|
@@ -51,18 +51,18 @@
|
|
|
51
51
|
</DropdownMenu.Item>
|
|
52
52
|
<DropdownMenu.Separator />
|
|
53
53
|
{#if onRestore}
|
|
54
|
-
<DropdownMenu.Item onclick={onRestore}>
|
|
54
|
+
<DropdownMenu.Item onclick={onRestore} data-testid="row-action-restore">
|
|
55
55
|
<ArchiveOff class="mr-2 size-4" />
|
|
56
56
|
{t.restore}
|
|
57
57
|
</DropdownMenu.Item>
|
|
58
58
|
{:else}
|
|
59
|
-
<DropdownMenu.Item onclick={onArchive}>
|
|
59
|
+
<DropdownMenu.Item onclick={onArchive} data-testid="row-action-archive">
|
|
60
60
|
<Archive class="mr-2 size-4" />
|
|
61
61
|
{t.archive}
|
|
62
62
|
</DropdownMenu.Item>
|
|
63
63
|
{/if}
|
|
64
64
|
{#if onDelete}
|
|
65
|
-
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={onDelete}>
|
|
65
|
+
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={onDelete} data-testid="row-action-delete">
|
|
66
66
|
<Trash class="mr-2 size-4" />
|
|
67
67
|
{t.delete}
|
|
68
68
|
</DropdownMenu.Item>
|
|
@@ -131,6 +131,7 @@
|
|
|
131
131
|
class="gap-1.5 {hasActiveFilter
|
|
132
132
|
? 'border-primary/30 bg-lavender-lighter text-primary'
|
|
133
133
|
: ''}"
|
|
134
|
+
data-testid="status-filter-trigger"
|
|
134
135
|
>
|
|
135
136
|
<Filter class="size-3.5" />
|
|
136
137
|
{t.status}{hasActiveFilter ? `: ${activeFilterLabel}` : ''}
|
|
@@ -148,6 +149,7 @@
|
|
|
148
149
|
onStatusFilterChange(option.value);
|
|
149
150
|
statusPopoverOpen = false;
|
|
150
151
|
}}
|
|
152
|
+
data-testid="status-filter-option-{option.value ?? 'all'}"
|
|
151
153
|
>
|
|
152
154
|
{option.label()}
|
|
153
155
|
</button>
|
|
@@ -228,7 +230,7 @@
|
|
|
228
230
|
|
|
229
231
|
<div class="flex-1"></div>
|
|
230
232
|
|
|
231
|
-
<Button variant="default" size="sm" onclick={onCreateEntry}>
|
|
233
|
+
<Button variant="default" size="sm" onclick={onCreateEntry} data-testid="create-entry-button">
|
|
232
234
|
<Plus class="mr-1 size-4" />
|
|
233
235
|
{createLabel}
|
|
234
236
|
</Button>
|
|
@@ -166,6 +166,7 @@
|
|
|
166
166
|
type="button"
|
|
167
167
|
class="bg-primary text-primary-foreground hover:bg-primary/90 inline-flex items-center gap-1.5 rounded-l-lg px-4 py-1.5 text-[13px] font-semibold transition-colors"
|
|
168
168
|
onclick={() => onSave('published-now')}
|
|
169
|
+
data-testid="publish-button"
|
|
169
170
|
>
|
|
170
171
|
<SendIcon class="size-3.5" />
|
|
171
172
|
{primaryButtonLabel}
|
|
@@ -179,13 +180,14 @@
|
|
|
179
180
|
type="button"
|
|
180
181
|
class="bg-primary text-primary-foreground hover:bg-primary/90 inline-flex items-center rounded-r-lg border-l border-white/20 px-1.5 py-1.5 transition-colors"
|
|
181
182
|
aria-label={lang[interfaceLanguage.current].saveDraft}
|
|
183
|
+
data-testid="save-draft-trigger"
|
|
182
184
|
>
|
|
183
185
|
<ChevronDownIcon class="size-3.5" />
|
|
184
186
|
</button>
|
|
185
187
|
{/snippet}
|
|
186
188
|
</DropdownMenu.Trigger>
|
|
187
189
|
<DropdownMenu.Content align="end" class="w-48">
|
|
188
|
-
<DropdownMenu.Item onclick={onSaveDraft}>
|
|
190
|
+
<DropdownMenu.Item onclick={onSaveDraft} data-testid="save-draft-button">
|
|
189
191
|
{lang[interfaceLanguage.current].saveDraft}
|
|
190
192
|
<DropdownMenu.Shortcut>{shortcutLabel}</DropdownMenu.Shortcut>
|
|
191
193
|
</DropdownMenu.Item>
|
|
@@ -141,12 +141,12 @@
|
|
|
141
141
|
<div class="space-y-2">
|
|
142
142
|
<Label>{lang.role}</Label>
|
|
143
143
|
<Select.Root type="single" value={role} onValueChange={(v) => v && (role = v as UserRole)}>
|
|
144
|
-
<Select.Trigger class="w-full" aria-describedby="create-role-hint">
|
|
144
|
+
<Select.Trigger class="w-full" aria-describedby="create-role-hint" data-testid="role-select">
|
|
145
145
|
{role === 'admin' ? lang.roleAdmin : lang.roleUser}
|
|
146
146
|
</Select.Trigger>
|
|
147
147
|
<Select.Content>
|
|
148
|
-
<Select.Item value="user">{lang.roleUser}</Select.Item>
|
|
149
|
-
<Select.Item value="admin">{lang.roleAdmin}</Select.Item>
|
|
148
|
+
<Select.Item value="user" data-testid="role-option-user">{lang.roleUser}</Select.Item>
|
|
149
|
+
<Select.Item value="admin" data-testid="role-option-admin">{lang.roleAdmin}</Select.Item>
|
|
150
150
|
</Select.Content>
|
|
151
151
|
</Select.Root>
|
|
152
152
|
<p id="create-role-hint" class="text-xs" style="color: var(--text-light);">
|
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
{/if}
|
|
159
159
|
<Dialog.Footer>
|
|
160
160
|
<Button type="button" variant="outline" onclick={() => onOpenChange(false)}>{lang.cancel}</Button>
|
|
161
|
-
<Button type="submit" disabled={loading}>{lang.createUser}</Button>
|
|
161
|
+
<Button type="submit" disabled={loading} data-testid="create-user-submit">{lang.createUser}</Button>
|
|
162
162
|
</Dialog.Footer>
|
|
163
163
|
</form>
|
|
164
164
|
</Dialog.Content>
|