includio-cms 0.20.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.
Files changed (101) hide show
  1. package/API.md +22 -21
  2. package/CHANGELOG.md +147 -0
  3. package/DOCS.md +1 -1
  4. package/README.md +138 -32
  5. package/ROADMAP.md +11 -4
  6. package/dist/admin/api/rest/handler.d.ts +13 -1
  7. package/dist/admin/api/rest/handler.js +13 -1
  8. package/dist/admin/api/rest/middleware/apiKey.js +9 -1
  9. package/dist/admin/api/rest/middleware/generateApiKey.d.ts +16 -0
  10. package/dist/admin/api/rest/middleware/generateApiKey.js +19 -0
  11. package/dist/admin/client/collection/collection-entries.svelte +1 -1
  12. package/dist/admin/client/collection/empty-state.svelte +1 -1
  13. package/dist/admin/client/collection/row-actions.svelte +3 -3
  14. package/dist/admin/client/collection/table-toolbar.svelte +3 -1
  15. package/dist/admin/client/entry/entry-header.svelte +3 -1
  16. package/dist/admin/client/users/create-user-dialog.svelte +4 -4
  17. package/dist/admin/client/users/delete-user-dialog.svelte +4 -2
  18. package/dist/admin/client/users/lang.d.ts +10 -2
  19. package/dist/admin/client/users/lang.js +10 -4
  20. package/dist/admin/client/users/users-page.svelte +3 -2
  21. package/dist/admin/components/media/file-upload.svelte +2 -0
  22. package/dist/ai-claude/index.d.ts +9 -1
  23. package/dist/ai-claude/index.js +9 -1
  24. package/dist/ai-openai/index.d.ts +9 -1
  25. package/dist/ai-openai/index.js +9 -1
  26. package/dist/cli/index.js +115 -13
  27. package/dist/cms/runtime/schema.d.ts +2 -0
  28. package/dist/cms/runtime/schema.js +4 -0
  29. package/dist/cms/runtime/types.d.ts +1 -1
  30. package/dist/core/cms.d.ts +13 -1
  31. package/dist/core/cms.js +13 -1
  32. package/dist/core/errors.d.ts +71 -0
  33. package/dist/core/errors.js +179 -0
  34. package/dist/core/server/consentLogs/operations/create.d.ts +13 -1
  35. package/dist/core/server/consentLogs/operations/create.js +13 -1
  36. package/dist/core/server/entries/operations/create.js +6 -1
  37. package/dist/core/server/entries/operations/get.js +14 -3
  38. package/dist/core/server/entries/operations/resolveEntry.d.ts +32 -1
  39. package/dist/core/server/entries/operations/resolveEntry.js +36 -4
  40. package/dist/core/server/entries/operations/update.js +5 -1
  41. package/dist/core/server/fields/utils/resolveMedia.d.ts +18 -1
  42. package/dist/core/server/fields/utils/resolveMedia.js +13 -1
  43. package/dist/core/server/forms/submissions/operations/create.d.ts +21 -1
  44. package/dist/core/server/forms/submissions/operations/create.js +18 -2
  45. package/dist/core/server/forms/submissions/utils/parseMultipart.d.ts +15 -1
  46. package/dist/core/server/forms/submissions/utils/parseMultipart.js +15 -1
  47. package/dist/core/server/media/operations/uploadFile.js +4 -3
  48. package/dist/core/server/media/styles/sharp/generateImageStyle.js +3 -2
  49. package/dist/core/server/media/utils/generateAdminThumbnail.js +3 -2
  50. package/dist/core/server/media/utils/generateBlurDataUrl.js +2 -1
  51. package/dist/db-postgres/index.d.ts +10 -0
  52. package/dist/db-postgres/index.js +10 -0
  53. package/dist/email-nodemailer/index.d.ts +13 -1
  54. package/dist/email-nodemailer/index.js +13 -1
  55. package/dist/entity/index.d.ts +16 -1
  56. package/dist/entity/index.js +16 -1
  57. package/dist/files-local/index.d.ts +12 -1
  58. package/dist/files-local/index.js +12 -1
  59. package/dist/paraglide/messages/_index.d.ts +3 -36
  60. package/dist/paraglide/messages/_index.js +3 -71
  61. package/dist/paraglide/messages/hello_world.d.ts +5 -0
  62. package/dist/paraglide/messages/hello_world.js +33 -0
  63. package/dist/paraglide/messages/login_hello.d.ts +16 -0
  64. package/dist/paraglide/messages/login_hello.js +34 -0
  65. package/dist/paraglide/messages/login_please_login.d.ts +16 -0
  66. package/dist/paraglide/messages/login_please_login.js +34 -0
  67. package/dist/server/auth.d.ts +11 -0
  68. package/dist/server/auth.js +11 -0
  69. package/dist/server/security/csp.d.ts +16 -0
  70. package/dist/server/security/csp.js +33 -0
  71. package/dist/server/security/csrf.d.ts +13 -0
  72. package/dist/server/security/csrf.js +49 -0
  73. package/dist/server/security/index.d.ts +3 -0
  74. package/dist/server/security/index.js +3 -0
  75. package/dist/server/security/rate-limit.d.ts +44 -0
  76. package/dist/server/security/rate-limit.js +97 -0
  77. package/dist/server/utils/withTimeout.d.ts +21 -0
  78. package/dist/server/utils/withTimeout.js +37 -0
  79. package/dist/sveltekit/config.d.ts +67 -4
  80. package/dist/sveltekit/config.js +73 -4
  81. package/dist/sveltekit/server/handle.d.ts +15 -1
  82. package/dist/sveltekit/server/handle.js +22 -1
  83. package/dist/sveltekit/server/index.d.ts +1 -0
  84. package/dist/sveltekit/server/index.js +1 -0
  85. package/dist/sveltekit/server/layout.d.ts +12 -1
  86. package/dist/sveltekit/server/layout.js +12 -1
  87. package/dist/sveltekit/server/preview.d.ts +21 -1
  88. package/dist/sveltekit/server/preview.js +21 -1
  89. package/dist/types/cms.d.ts +4 -0
  90. package/dist/types/cms.schema.d.ts +452 -0
  91. package/dist/types/cms.schema.js +629 -0
  92. package/dist/updates/0.21.0/index.d.ts +2 -0
  93. package/dist/updates/0.21.0/index.js +55 -0
  94. package/dist/updates/0.22.0/index.d.ts +2 -0
  95. package/dist/updates/0.22.0/index.js +75 -0
  96. package/dist/updates/index.js +3 -1
  97. package/package.json +12 -2
  98. package/dist/paraglide/messages/en.d.ts +0 -5
  99. package/dist/paraglide/messages/en.js +0 -14
  100. package/dist/paraglide/messages/pl.d.ts +0 -5
  101. package/dist/paraglide/messages/pl.js +0 -14
@@ -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>
@@ -82,7 +82,7 @@
82
82
  {lang.deleteWarningTitle}
83
83
  </p>
84
84
  <p class="mt-1 text-sm" style="color: var(--muted-foreground);">
85
- {@html lang.deleteWarningDesc(user.name)}
85
+ {lang.deleteWarningDesc.before}<strong>{user.name}</strong>{lang.deleteWarningDesc.after}
86
86
  </p>
87
87
  </div>
88
88
  </div>
@@ -90,13 +90,14 @@
90
90
  <!-- Confirm input -->
91
91
  <div class="space-y-2">
92
92
  <Label for="delete-confirm">
93
- {@html lang.deleteConfirmType}
93
+ {lang.deleteConfirmType.before}<strong>{lang.deleteConfirmWord}</strong>{lang.deleteConfirmType.after}
94
94
  </Label>
95
95
  <Input
96
96
  id="delete-confirm"
97
97
  bind:value={confirmInput}
98
98
  placeholder={lang.deleteConfirmWord}
99
99
  autocomplete="off"
100
+ data-testid="delete-confirm-input"
100
101
  />
101
102
  </div>
102
103
 
@@ -108,6 +109,7 @@
108
109
  variant="destructive"
109
110
  disabled={!confirmed || loading}
110
111
  onclick={handleDelete}
112
+ data-testid="delete-user-submit"
111
113
  >
112
114
  {lang.deleteUser}
113
115
  </Button>
@@ -24,8 +24,16 @@ export declare const usersLang: Record<InterfaceLanguage, {
24
24
  deleteConfirmTitle: string;
25
25
  deleteConfirmDescription: string;
26
26
  deleteWarningTitle: string;
27
- deleteWarningDesc: (name: string) => string;
28
- deleteConfirmType: string;
27
+ /** Split into `before` / `after`; the user name is rendered between them in template (avoids `{@html}`). */
28
+ deleteWarningDesc: {
29
+ before: string;
30
+ after: string;
31
+ };
32
+ /** Split into `before` / `after`; `deleteConfirmWord` is rendered between them. */
33
+ deleteConfirmType: {
34
+ before: string;
35
+ after: string;
36
+ };
29
37
  deleteConfirmWord: string;
30
38
  userCreated: string;
31
39
  userUpdated: string;
@@ -24,8 +24,11 @@ export const usersLang = {
24
24
  deleteConfirmTitle: 'Delete user?',
25
25
  deleteConfirmDescription: 'This action cannot be undone. The user will be permanently deleted.',
26
26
  deleteWarningTitle: 'This action cannot be undone',
27
- deleteWarningDesc: (name) => `User <strong>${name}</strong> will be permanently deleted along with all their data.`,
28
- deleteConfirmType: 'Type <strong>DELETE</strong> to confirm',
27
+ deleteWarningDesc: {
28
+ before: 'User ',
29
+ after: ' will be permanently deleted along with all their data.'
30
+ },
31
+ deleteConfirmType: { before: 'Type ', after: ' to confirm' },
29
32
  deleteConfirmWord: 'DELETE',
30
33
  userCreated: 'User created',
31
34
  userUpdated: 'User updated',
@@ -107,8 +110,11 @@ export const usersLang = {
107
110
  deleteConfirmTitle: 'Usunąć użytkownika?',
108
111
  deleteConfirmDescription: 'Ta akcja jest nieodwracalna. Użytkownik zostanie trwale usunięty.',
109
112
  deleteWarningTitle: 'Tej operacji nie można cofnąć',
110
- deleteWarningDesc: (name) => `Użytkownik <strong>${name}</strong> zostanie trwale usunięty wraz ze wszystkimi danymi.`,
111
- deleteConfirmType: 'Wpisz <strong>USUŃ</strong>, żeby potwierdzić',
113
+ deleteWarningDesc: {
114
+ before: 'Użytkownik ',
115
+ after: ' zostanie trwale usunięty wraz ze wszystkimi danymi.'
116
+ },
117
+ deleteConfirmType: { before: 'Wpisz ', after: ', żeby potwierdzić' },
112
118
  deleteConfirmWord: 'USUŃ',
113
119
  userCreated: 'Użytkownik utworzony',
114
120
  userUpdated: 'Użytkownik zaktualizowany',
@@ -360,7 +360,7 @@
360
360
  {lang.invite.inviteUser}
361
361
  </Button>
362
362
  {/if}
363
- <Button variant="default" size="sm" onclick={() => (createOpen = true)}>
363
+ <Button variant="default" size="sm" onclick={() => (createOpen = true)} data-testid="create-user-button">
364
364
  <Plus class="size-4" />
365
365
  {lang.createUser}
366
366
  </Button>
@@ -384,7 +384,7 @@
384
384
  <p class="mb-5 max-w-sm text-sm" style="color: var(--muted-foreground);">
385
385
  {lang.emptyDescription}
386
386
  </p>
387
- <Button onclick={() => (createOpen = true)}>
387
+ <Button onclick={() => (createOpen = true)} data-testid="create-user-button">
388
388
  <Plus class="size-4" />
389
389
  {lang.addUser}
390
390
  </Button>
@@ -518,6 +518,7 @@
518
518
  class="text-destructive h-8 w-8"
519
519
  onclick={() => openDelete(user)}
520
520
  title={lang.deleteUser}
521
+ data-testid="delete-user-row-button"
521
522
  >
522
523
  <Trash class="size-4" />
523
524
  </Button>
@@ -218,6 +218,7 @@
218
218
  type="button"
219
219
  class="inline-flex items-center gap-2 rounded-lg bg-primary px-4 py-1.5 text-[13px] font-semibold text-primary-foreground transition-colors hover:bg-plum-dark whitespace-nowrap"
220
220
  onclick={() => inputElement.click()}
221
+ data-testid="file-upload-button"
221
222
  >
222
223
  <Upload class="h-4 w-4" />
223
224
  {lang[interfaceLanguage.current].addFiles}
@@ -229,6 +230,7 @@
229
230
  type="file"
230
231
  {accept}
231
232
  onchange={handleUpload}
233
+ data-testid="file-input"
232
234
  />
233
235
 
234
236
  <!-- Drag overlay -->
@@ -4,8 +4,16 @@ import type { AIAdapter, AIConfig } from '../types/adapters/ai.js';
4
4
  *
5
5
  * `@anthropic-ai/sdk` is an **optional peer dependency** — install it in your
6
6
  * project (`pnpm add @anthropic-ai/sdk`) when using this adapter. SDK loads
7
- * lazily on first call; missing peer throws a clear error.
7
+ * lazily on first call; a missing peer throws a clear error.
8
8
  *
9
+ * @param config - `apiKey` is required.
10
+ * @returns An `AIAdapter` ready to use in `defineConfig({ ai })`.
9
11
  * @public
12
+ * @example
13
+ * ```ts
14
+ * import { claudeAdapter } from 'includio-cms/ai-claude';
15
+ *
16
+ * const ai = claudeAdapter({ apiKey: process.env.ANTHROPIC_API_KEY! });
17
+ * ```
10
18
  */
11
19
  export declare function claudeAdapter(config: AIConfig): AIAdapter;
@@ -5,9 +5,17 @@ import sharp from 'sharp';
5
5
  *
6
6
  * `@anthropic-ai/sdk` is an **optional peer dependency** — install it in your
7
7
  * project (`pnpm add @anthropic-ai/sdk`) when using this adapter. SDK loads
8
- * lazily on first call; missing peer throws a clear error.
8
+ * lazily on first call; a missing peer throws a clear error.
9
9
  *
10
+ * @param config - `apiKey` is required.
11
+ * @returns An `AIAdapter` ready to use in `defineConfig({ ai })`.
10
12
  * @public
13
+ * @example
14
+ * ```ts
15
+ * import { claudeAdapter } from 'includio-cms/ai-claude';
16
+ *
17
+ * const ai = claudeAdapter({ apiKey: process.env.ANTHROPIC_API_KEY! });
18
+ * ```
11
19
  */
12
20
  export function claudeAdapter(config) {
13
21
  let client = null;
@@ -4,8 +4,16 @@ import type { AIAdapter, AIConfig } from '../types/adapters/ai.js';
4
4
  *
5
5
  * `openai` is an **optional peer dependency** — install it in your project
6
6
  * (`pnpm add openai`) when using this adapter. SDK loads lazily on first call;
7
- * missing peer throws a clear error.
7
+ * a missing peer throws a clear error.
8
8
  *
9
+ * @param config - `apiKey` is required.
10
+ * @returns An `AIAdapter` ready to use in `defineConfig({ ai })`.
9
11
  * @public
12
+ * @example
13
+ * ```ts
14
+ * import { openAIAdapter } from 'includio-cms/ai-openai';
15
+ *
16
+ * const ai = openAIAdapter({ apiKey: process.env.OPENAI_API_KEY! });
17
+ * ```
10
18
  */
11
19
  export declare function openAIAdapter(config: AIConfig): AIAdapter;
@@ -6,9 +6,17 @@ import sharp from 'sharp';
6
6
  *
7
7
  * `openai` is an **optional peer dependency** — install it in your project
8
8
  * (`pnpm add openai`) when using this adapter. SDK loads lazily on first call;
9
- * missing peer throws a clear error.
9
+ * a missing peer throws a clear error.
10
10
  *
11
+ * @param config - `apiKey` is required.
12
+ * @returns An `AIAdapter` ready to use in `defineConfig({ ai })`.
11
13
  * @public
14
+ * @example
15
+ * ```ts
16
+ * import { openAIAdapter } from 'includio-cms/ai-openai';
17
+ *
18
+ * const ai = openAIAdapter({ apiKey: process.env.OPENAI_API_KEY! });
19
+ * ```
12
20
  */
13
21
  export function openAIAdapter(config) {
14
22
  let openai = null;
package/dist/cli/index.js CHANGED
@@ -4,30 +4,111 @@ import { installPeers } from './install-peers.js';
4
4
  import { createUser } from './create-user.js';
5
5
  import fs from 'node:fs';
6
6
  import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
7
8
  const args = process.argv.slice(2);
8
9
  const command = args[0];
9
10
  const subcommand = args[1];
10
- function printUsage() {
11
- console.log(`Usage: includio <command>
11
+ const HELP_FLAGS = new Set(['--help', '-h']);
12
+ const VERSION_FLAGS = new Set(['--version', '-v']);
13
+ function readPackageVersion() {
14
+ try {
15
+ const here = path.dirname(fileURLToPath(import.meta.url));
16
+ // In dist: dist/cli/index.js → ../../package.json
17
+ // In src dev: src/lib/cli/index.ts → ../../../package.json
18
+ const candidates = [
19
+ path.resolve(here, '../../package.json'),
20
+ path.resolve(here, '../../../package.json')
21
+ ];
22
+ for (const c of candidates) {
23
+ if (fs.existsSync(c)) {
24
+ const pkg = JSON.parse(fs.readFileSync(c, 'utf-8'));
25
+ if (pkg?.name === 'includio-cms' && typeof pkg.version === 'string') {
26
+ return pkg.version;
27
+ }
28
+ }
29
+ }
30
+ }
31
+ catch {
32
+ // fall through
33
+ }
34
+ return '0.0.0';
35
+ }
36
+ function printTopLevelHelp() {
37
+ console.log(`includio — CLI for includio-cms
38
+
39
+ Usage: includio <command> [options]
12
40
 
13
41
  Commands:
14
- scaffold admin Generate admin route files
15
- install-peers Install missing peer dependencies
16
- create-user Create a new admin/user account
42
+ scaffold admin Generate admin route files (/admin and /api/admin)
43
+ install-peers Install missing optional peer dependencies
44
+ create-user Create a new admin/user account interactively
45
+
46
+ Global options:
47
+ --help, -h Show this help
48
+ --version, -v Print the package version
49
+
50
+ Run \`includio <command> --help\` for command-specific options.
51
+ `);
52
+ }
53
+ function printScaffoldAdminHelp() {
54
+ console.log(`includio scaffold admin — generate admin route files
55
+
56
+ Usage: includio scaffold admin [options]
17
57
 
18
58
  Options:
19
- --force Overwrite existing files
20
- --routes-dir Path to routes directory (default: src/routes)
21
- --cms-config Path to cms config (default: src/lib/cms/cms.config.ts)
22
- --shop Force shop routes ON (default: auto-detect from cms config)
23
- --no-shop Force shop routes OFF
24
- --dry-run Show what would be installed (install-peers)
59
+ --force Overwrite existing files
60
+ --routes-dir <DIR> Path to SvelteKit routes (default: src/routes)
61
+ --cms-config <FILE> Path to CMS config (default: src/lib/cms/cms.config.ts)
62
+ --shop Force shop routes ON (overrides auto-detect)
63
+ --no-shop Force shop routes OFF
64
+ --help, -h Show this help
65
+
66
+ Examples:
67
+ includio scaffold admin
68
+ includio scaffold admin --force --shop
69
+ includio scaffold admin --routes-dir apps/web/src/routes
70
+ `);
71
+ }
72
+ function printInstallPeersHelp() {
73
+ console.log(`includio install-peers — install missing optional peer deps
74
+
75
+ Reads your project's installed packages and offers to install any optional
76
+ peers used by the parts of includio-cms you have configured (e.g. \`openai\`,
77
+ \`@anthropic-ai/sdk\`, \`nodemailer\`).
78
+
79
+ Usage: includio install-peers [options]
80
+
81
+ Options:
82
+ --dry-run List what would be installed, don't run the package manager
83
+ --help, -h Show this help
84
+
85
+ Examples:
86
+ includio install-peers --dry-run
87
+ includio install-peers
88
+ `);
89
+ }
90
+ function printCreateUserHelp() {
91
+ console.log(`includio create-user — create an admin/user account
92
+
93
+ Interactive prompt — asks for email, password, name, and role. Requires
94
+ \`DATABASE_URL\` in the environment (or .env loaded by the project).
95
+
96
+ Usage: includio create-user [options]
97
+
98
+ Options:
99
+ --help, -h Show this help
100
+
101
+ Example:
102
+ includio create-user
25
103
  `);
26
104
  }
27
105
  function flagValue(name) {
28
106
  const idx = args.indexOf(name);
29
107
  return idx !== -1 ? args[idx + 1] : undefined;
30
108
  }
109
+ function hasHelpFlag() {
110
+ return args.some((a) => HELP_FLAGS.has(a));
111
+ }
31
112
  /**
32
113
  * Best-effort detection: scan cms config text for an active `shop: defineShop(`
33
114
  * or `shop: <var>` property inside `defineCMS({ ... })`. Returns null if the
@@ -43,7 +124,20 @@ function detectShopUsage(cmsConfigPath) {
43
124
  .replace(/\/\/[^\n]*/g, '');
44
125
  return /\bshop\s*:\s*\S/.test(stripped);
45
126
  }
127
+ // Top-level: no command, --help, or --version
128
+ if (!command || HELP_FLAGS.has(command)) {
129
+ printTopLevelHelp();
130
+ process.exit(0);
131
+ }
132
+ if (VERSION_FLAGS.has(command)) {
133
+ console.log(readPackageVersion());
134
+ process.exit(0);
135
+ }
46
136
  if (command === 'scaffold' && subcommand === 'admin') {
137
+ if (hasHelpFlag()) {
138
+ printScaffoldAdminHelp();
139
+ process.exit(0);
140
+ }
47
141
  const force = args.includes('--force');
48
142
  const cwd = process.cwd();
49
143
  const routesDir = flagValue('--routes-dir') ?? path.join(cwd, 'src', 'routes');
@@ -73,13 +167,21 @@ if (command === 'scaffold' && subcommand === 'admin') {
73
167
  scaffoldAdmin({ routesDir, force, shop });
74
168
  }
75
169
  else if (command === 'install-peers') {
170
+ if (hasHelpFlag()) {
171
+ printInstallPeersHelp();
172
+ process.exit(0);
173
+ }
76
174
  const dryRun = args.includes('--dry-run');
77
175
  installPeers({ dryRun });
78
176
  }
79
177
  else if (command === 'create-user') {
178
+ if (hasHelpFlag()) {
179
+ printCreateUserHelp();
180
+ process.exit(0);
181
+ }
80
182
  createUser();
81
183
  }
82
184
  else {
83
- printUsage();
84
- process.exit(command ? 1 : 0);
185
+ printTopLevelHelp();
186
+ process.exit(1);
85
187
  }
@@ -0,0 +1,2 @@
1
+ export * from 'includio-cms/db-postgres/schema-core';
2
+ export * from 'includio-cms/auth-schema';
@@ -0,0 +1,4 @@
1
+ // This file is auto-generated. Do not edit directly.
2
+ // Point your drizzle.config.ts schema field at this file.
3
+ export * from 'includio-cms/db-postgres/schema-core';
4
+ export * from 'includio-cms/auth-schema';
@@ -44,7 +44,7 @@ export interface BlogPost {
44
44
  slug?: string;
45
45
  cover?: ImageFieldData | VideoFieldData | null;
46
46
  rating: number;
47
- category?: 'technology' | 'design' | 'business' | 'tutorial';
47
+ category: 'technology' | 'design' | 'business' | 'tutorial';
48
48
  publishedAt?: string;
49
49
  thumbnail?: ImageFieldData | VideoFieldData | null;
50
50
  content?: StructuredContentDoc;
@@ -39,7 +39,19 @@ export declare class CMS implements ICMS {
39
39
  }
40
40
  export declare function initCMS(config: CMSConfig): CMS;
41
41
  /**
42
- * Returns the singleton CMS instance. Must be called after `includioCMS()` initializes the CMS in `hooks.server.ts`.
42
+ * Returns the singleton CMS instance. Must be called after `includioCMS()`
43
+ * initializes the CMS in `hooks.server.ts`.
44
+ *
45
+ * @returns The active `CMS` instance (collections, adapters, plugins, ...).
46
+ * @throws {Error} when called before `includioCMS()` has run (e.g. in plain
47
+ * client code that imports `$lib/...` without going through SvelteKit hooks).
43
48
  * @public
49
+ * @example
50
+ * ```ts
51
+ * import { getCMS } from 'includio-cms';
52
+ *
53
+ * const cms = getCMS();
54
+ * await cms.databaseAdapter.getEntries({ slug: 'posts' });
55
+ * ```
44
56
  */
45
57
  export declare function getCMS(): CMS;
package/dist/core/cms.js CHANGED
@@ -160,8 +160,20 @@ export function initCMS(config) {
160
160
  return cms;
161
161
  }
162
162
  /**
163
- * Returns the singleton CMS instance. Must be called after `includioCMS()` initializes the CMS in `hooks.server.ts`.
163
+ * Returns the singleton CMS instance. Must be called after `includioCMS()`
164
+ * initializes the CMS in `hooks.server.ts`.
165
+ *
166
+ * @returns The active `CMS` instance (collections, adapters, plugins, ...).
167
+ * @throws {Error} when called before `includioCMS()` has run (e.g. in plain
168
+ * client code that imports `$lib/...` without going through SvelteKit hooks).
164
169
  * @public
170
+ * @example
171
+ * ```ts
172
+ * import { getCMS } from 'includio-cms';
173
+ *
174
+ * const cms = getCMS();
175
+ * await cms.databaseAdapter.getEntries({ slug: 'posts' });
176
+ * ```
165
177
  */
166
178
  export function getCMS() {
167
179
  if (!cms) {
@@ -0,0 +1,71 @@
1
+ import type { ZodError } from 'zod';
2
+ /**
3
+ * Base error class for CMS runtime failures. Carries a stable `code` and a
4
+ * `context` bag so callers (and clients) can branch on the failure mode without
5
+ * string-matching on `message`.
6
+ *
7
+ * `code` is a SCREAMING_SNAKE_CASE constant; see callers for the canonical list
8
+ * (`ENTRY_NOT_FOUND`, `ENTRY_VERSION_NOT_FOUND`, `INVALID_DATA`,
9
+ * `MISSING_REQUIRED_PARAM`, `CONFIG_VALIDATION_FAILED`).
10
+ *
11
+ * @public
12
+ * @example
13
+ * ```ts
14
+ * try { await resolveEntry({ id }); }
15
+ * catch (e) {
16
+ * if (e instanceof CmsError && e.code === 'ENTRY_NOT_FOUND') { ... }
17
+ * }
18
+ * ```
19
+ */
20
+ export declare class CmsError extends Error {
21
+ readonly code: string;
22
+ readonly context: Record<string, unknown>;
23
+ constructor(code: string, message: string, context?: Record<string, unknown>, options?: {
24
+ cause?: unknown;
25
+ });
26
+ toString(): string;
27
+ }
28
+ /**
29
+ * One concrete validation issue extracted from a Zod error, with a friendly
30
+ * hint when the path matches a known config shape.
31
+ * @public
32
+ */
33
+ export interface ConfigValidationIssue {
34
+ /** Dot/bracket-formatted path, e.g. `languages[0].code` or `collections[2].fields[0].slug`. */
35
+ path: string;
36
+ /** The validator's message, lightly normalized. */
37
+ message: string;
38
+ /** Optional hint describing how to fix this specific issue. */
39
+ hint?: string;
40
+ }
41
+ /**
42
+ * Thrown by {@link defineConfig} when the runtime config fails Zod validation.
43
+ * Aggregates ALL issues (not just the first) so the user can fix everything in
44
+ * one pass.
45
+ *
46
+ * @public
47
+ */
48
+ export declare class ConfigValidationError extends CmsError {
49
+ readonly issues: ConfigValidationIssue[];
50
+ constructor(issues: ConfigValidationIssue[]);
51
+ }
52
+ /**
53
+ * Render a Zod issue path (`(string | number)[]`) into JS-style notation:
54
+ * - string segments → `.foo`
55
+ * - numeric segments → `[3]`
56
+ * - leading string segment has no leading dot.
57
+ */
58
+ export declare function formatIssuePath(path: ReadonlyArray<string | number>): string;
59
+ /**
60
+ * Render a `ZodError` from entry/form data validation as a list of
61
+ * `path: message` lines (one per issue). Used by data-write operations to give
62
+ * callers a readable dump instead of `JSON.stringify(error.flatten())`.
63
+ *
64
+ * @public
65
+ */
66
+ export declare function formatZodDataIssues(error: ZodError): string;
67
+ /**
68
+ * Convert a `ZodError` into a {@link ConfigValidationError} with friendly,
69
+ * path-prefixed messages and per-issue hints.
70
+ */
71
+ export declare function formatConfigError(error: ZodError): ConfigValidationError;