@actuate-media/cli 0.1.1 → 0.1.3

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.
@@ -1,388 +1,388 @@
1
- import { Command } from "commander";
2
- import { readFile } from "node:fs/promises";
3
- import { existsSync } from "node:fs";
4
- import { createInterface } from "node:readline/promises";
5
- import ora from "ora";
6
- import { logger } from "../utils/logger.js";
7
-
8
- async function confirm(question: string): Promise<boolean> {
9
- const rl = createInterface({ input: process.stdin, output: process.stdout });
10
- const answer = await rl.question(`${question} (y/N) `);
11
- rl.close();
12
- return answer.trim().toLowerCase() === "y";
13
- }
14
-
15
- const DEMO_PAGES = [
16
- {
17
- title: "Home",
18
- slug: "home",
19
- content: "<h1>Welcome</h1><p>Your homepage content goes here.</p>",
20
- },
21
- {
22
- title: "About",
23
- slug: "about",
24
- content:
25
- "<h1>About Us</h1><p>Learn more about our team and mission.</p>",
26
- },
27
- {
28
- title: "Contact",
29
- slug: "contact",
30
- content:
31
- "<h1>Contact</h1><p>Get in touch with us via the form below.</p>",
32
- },
33
- {
34
- title: "Privacy Policy",
35
- slug: "privacy-policy",
36
- content:
37
- "<h1>Privacy Policy</h1><p>Your privacy matters to us. Read our full policy.</p>",
38
- },
39
- {
40
- title: "Terms of Service",
41
- slug: "terms",
42
- content:
43
- "<h1>Terms of Service</h1><p>Please read the following terms carefully.</p>",
44
- },
45
- ];
46
-
47
- const DEMO_POSTS = [
48
- {
49
- title: "Getting Started with Actuate CMS",
50
- slug: "getting-started",
51
- excerpt: "Learn how to set up and configure your new CMS.",
52
- content:
53
- "<h1>Getting Started</h1><p>Welcome to Actuate CMS. This guide walks you through initial setup.</p>",
54
- status: "PUBLISHED",
55
- },
56
- {
57
- title: "Content Modeling Best Practices",
58
- slug: "content-modeling",
59
- excerpt: "Design your collections and fields for maximum flexibility.",
60
- content:
61
- "<h1>Content Modeling</h1><p>Effective content modeling is the foundation of a great CMS.</p>",
62
- status: "PUBLISHED",
63
- },
64
- {
65
- title: "Working with Media",
66
- slug: "working-with-media",
67
- excerpt: "Upload, organize, and optimize your media assets.",
68
- content:
69
- "<h1>Working with Media</h1><p>Actuate CMS provides powerful media management.</p>",
70
- status: "PUBLISHED",
71
- },
72
- {
73
- title: "SEO Optimization Tips",
74
- slug: "seo-optimization",
75
- excerpt: "Boost your search rankings with built-in SEO tools.",
76
- content:
77
- "<h1>SEO Optimization</h1><p>Follow these tips to improve your site visibility.</p>",
78
- status: "PUBLISHED",
79
- },
80
- {
81
- title: "Building Custom Plugins",
82
- slug: "building-plugins",
83
- excerpt: "Extend Actuate CMS with your own plugins.",
84
- content:
85
- "<h1>Building Plugins</h1><p>The plugin system lets you add custom functionality.</p>",
86
- status: "PUBLISHED",
87
- },
88
- {
89
- title: "API Reference Overview",
90
- slug: "api-reference",
91
- excerpt: "A comprehensive guide to the Actuate CMS REST API.",
92
- content:
93
- "<h1>API Reference</h1><p>Use the API to integrate your content anywhere.</p>",
94
- status: "PUBLISHED",
95
- },
96
- {
97
- title: "Deployment Guide",
98
- slug: "deployment-guide",
99
- excerpt: "Deploy Actuate CMS to Vercel, AWS, or self-hosted.",
100
- content:
101
- "<h1>Deployment Guide</h1><p>Multiple deployment options for every use case.</p>",
102
- status: "PUBLISHED",
103
- },
104
- {
105
- title: "Multi-language Content",
106
- slug: "multi-language",
107
- excerpt: "Set up localization and manage translated content.",
108
- content:
109
- "<h1>Multi-language Content</h1><p>Reach a global audience with localized content.</p>",
110
- status: "PUBLISHED",
111
- },
112
- {
113
- title: "Webhooks and Integrations",
114
- slug: "webhooks-integrations",
115
- excerpt: "Connect Actuate CMS to external services with webhooks.",
116
- content:
117
- "<h1>Webhooks</h1><p>Automate workflows by connecting to third-party services.</p>",
118
- status: "DRAFT",
119
- },
120
- {
121
- title: "Advanced Access Control",
122
- slug: "access-control",
123
- excerpt: "Fine-tune permissions with role-based access control.",
124
- content:
125
- "<h1>Access Control</h1><p>Protect your content with granular permissions.</p>",
126
- status: "DRAFT",
127
- },
128
- ];
129
-
130
- const DEMO_FORMS = [
131
- {
132
- title: "Contact Form",
133
- slug: "contact-form",
134
- fields: [
135
- { name: "name", type: "text", required: true },
136
- { name: "email", type: "email", required: true },
137
- { name: "message", type: "textarea", required: true },
138
- ],
139
- submitLabel: "Send Message",
140
- successMessage: "Thanks for reaching out! We'll get back to you soon.",
141
- },
142
- {
143
- title: "Newsletter Signup",
144
- slug: "newsletter",
145
- fields: [
146
- { name: "email", type: "email", required: true },
147
- { name: "firstName", type: "text", required: false },
148
- ],
149
- submitLabel: "Subscribe",
150
- successMessage: "You're subscribed! Check your inbox for confirmation.",
151
- },
152
- {
153
- title: "Feedback Form",
154
- slug: "feedback",
155
- fields: [
156
- { name: "name", type: "text", required: false },
157
- { name: "rating", type: "select", options: ["1", "2", "3", "4", "5"], required: true },
158
- { name: "comments", type: "textarea", required: false },
159
- ],
160
- submitLabel: "Submit Feedback",
161
- successMessage: "Thank you for your feedback!",
162
- },
163
- ];
164
-
165
- const DEMO_USERS = [
166
- { email: "editor@example.com", name: "Demo Editor", role: "EDITOR" as const },
167
- { email: "author@example.com", name: "Demo Author", role: "AUTHOR" as const },
168
- ];
169
-
170
- interface SeedOptions {
171
- demo?: boolean;
172
- file?: string;
173
- reset?: boolean;
174
- }
175
-
176
- async function runSeed(options: SeedOptions): Promise<void> {
177
- if (!options.demo && !options.file) {
178
- logger.error("Specify --demo to seed demo content or --file <path> to seed from a JSON file.");
179
- process.exit(1);
180
- }
181
-
182
- try {
183
- const { getDB } = await import("@actuate-media/cms-core");
184
- const db = getDB<any>();
185
-
186
- if (options.reset) {
187
- const yes = await confirm(
188
- "This will delete ALL existing documents and versions. Continue?",
189
- );
190
- if (!yes) {
191
- logger.warn("Seed cancelled.");
192
- return;
193
- }
194
-
195
- const resetSpinner = ora("Clearing existing data…").start();
196
- await db.version.deleteMany({});
197
- await db.mediaUsage.deleteMany({});
198
- await db.document.deleteMany({});
199
- resetSpinner.succeed("Existing data cleared.");
200
- }
201
-
202
- if (options.demo) {
203
- await seedDemoData(db);
204
- }
205
-
206
- if (options.file) {
207
- await seedFromFile(db, options.file);
208
- }
209
- } catch (err) {
210
- const message = err instanceof Error ? err.message : String(err);
211
- logger.error(`Seed failed: ${message}`);
212
- process.exit(1);
213
- }
214
- }
215
-
216
- async function seedDemoData(db: any): Promise<void> {
217
- const spinner = ora("Seeding demo data…").start();
218
-
219
- let adminUser = await db.user.findFirst({ where: { role: "ADMIN" } });
220
- if (!adminUser) {
221
- adminUser = await db.user.create({
222
- data: {
223
- email: "admin@actuatecms.dev",
224
- name: "Admin",
225
- role: "ADMIN",
226
- isActive: true,
227
- isApproved: true,
228
- emailVerified: true,
229
- },
230
- });
231
- }
232
- const userId = adminUser.id;
233
-
234
- let pagesCreated = 0;
235
- for (const page of DEMO_PAGES) {
236
- await db.document.create({
237
- data: {
238
- collection: "pages",
239
- data: page,
240
- status: "PUBLISHED",
241
- publishedAt: new Date(),
242
- createdById: userId,
243
- updatedById: userId,
244
- plainText: `${page.title} ${page.slug}`,
245
- },
246
- });
247
- pagesCreated++;
248
- }
249
-
250
- let postsCreated = 0;
251
- for (const post of DEMO_POSTS) {
252
- await db.document.create({
253
- data: {
254
- collection: "posts",
255
- data: post,
256
- status: post.status,
257
- publishedAt: post.status === "PUBLISHED" ? new Date() : null,
258
- createdById: userId,
259
- updatedById: userId,
260
- plainText: `${post.title} ${post.excerpt}`,
261
- },
262
- });
263
- postsCreated++;
264
- }
265
-
266
- let formsCreated = 0;
267
- for (const form of DEMO_FORMS) {
268
- await db.document.create({
269
- data: {
270
- collection: "forms",
271
- data: form,
272
- status: "PUBLISHED",
273
- publishedAt: new Date(),
274
- createdById: userId,
275
- updatedById: userId,
276
- plainText: `${form.title} ${form.slug}`,
277
- },
278
- });
279
- formsCreated++;
280
- }
281
-
282
- let usersCreated = 0;
283
- for (const user of DEMO_USERS) {
284
- const exists = await db.user.findFirst({ where: { email: user.email } });
285
- if (!exists) {
286
- await db.user.create({
287
- data: {
288
- email: user.email,
289
- name: user.name,
290
- role: user.role,
291
- isActive: true,
292
- isApproved: true,
293
- emailVerified: true,
294
- },
295
- });
296
- usersCreated++;
297
- }
298
- }
299
-
300
- spinner.succeed("Demo data seeded successfully.");
301
- logger.info(` Pages: ${pagesCreated}`);
302
- logger.info(` Posts: ${postsCreated}`);
303
- logger.info(` Forms: ${formsCreated}`);
304
- logger.info(` Users: ${usersCreated} (+ existing admin)`);
305
- }
306
-
307
- async function seedFromFile(db: any, filePath: string): Promise<void> {
308
- if (!existsSync(filePath)) {
309
- logger.error(`File not found: ${filePath}`);
310
- process.exit(1);
311
- }
312
-
313
- const spinner = ora(`Seeding from ${filePath}…`).start();
314
-
315
- const raw = await readFile(filePath, "utf-8");
316
- let seedData: any;
317
- try {
318
- seedData = JSON.parse(raw);
319
- } catch {
320
- spinner.fail("Invalid JSON file.");
321
- process.exit(1);
322
- }
323
-
324
- if (!Array.isArray(seedData) && typeof seedData !== "object") {
325
- spinner.fail("Seed file must contain a JSON array or an object with collection keys.");
326
- process.exit(1);
327
- }
328
-
329
- let adminUser = await db.user.findFirst({ where: { role: "ADMIN" } });
330
- if (!adminUser) {
331
- adminUser = await db.user.create({
332
- data: {
333
- email: "admin@actuatecms.dev",
334
- name: "Admin",
335
- role: "ADMIN",
336
- isActive: true,
337
- isApproved: true,
338
- emailVerified: true,
339
- },
340
- });
341
- }
342
- const userId = adminUser.id;
343
-
344
- let count = 0;
345
-
346
- if (Array.isArray(seedData)) {
347
- for (const doc of seedData) {
348
- await db.document.create({
349
- data: {
350
- collection: doc.collection ?? "imported",
351
- data: doc.data ?? doc,
352
- status: doc.status ?? "DRAFT",
353
- createdById: userId,
354
- updatedById: userId,
355
- },
356
- });
357
- count++;
358
- }
359
- } else {
360
- for (const [collection, docs] of Object.entries(seedData)) {
361
- if (!Array.isArray(docs)) continue;
362
- for (const doc of docs as any[]) {
363
- await db.document.create({
364
- data: {
365
- collection,
366
- data: doc.data ?? doc,
367
- status: doc.status ?? "DRAFT",
368
- createdById: userId,
369
- updatedById: userId,
370
- },
371
- });
372
- count++;
373
- }
374
- }
375
- }
376
-
377
- spinner.succeed(`Seeded ${count} documents from ${filePath}.`);
378
- }
379
-
380
- export function registerSeedCommand(program: Command): void {
381
- program
382
- .command("seed")
383
- .description("Seed the database with demo or custom data")
384
- .option("--demo", "Seed demo content (pages, posts, forms, users)")
385
- .option("--file <path>", "Seed from a JSON file")
386
- .option("--reset", "Clear existing data before seeding")
387
- .action(runSeed);
388
- }
1
+ import { Command } from "commander";
2
+ import { readFile } from "node:fs/promises";
3
+ import { existsSync } from "node:fs";
4
+ import { createInterface } from "node:readline/promises";
5
+ import ora from "ora";
6
+ import { logger } from "../utils/logger.js";
7
+
8
+ async function confirm(question: string): Promise<boolean> {
9
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
10
+ const answer = await rl.question(`${question} (y/N) `);
11
+ rl.close();
12
+ return answer.trim().toLowerCase() === "y";
13
+ }
14
+
15
+ const DEMO_PAGES = [
16
+ {
17
+ title: "Home",
18
+ slug: "home",
19
+ content: "<h1>Welcome</h1><p>Your homepage content goes here.</p>",
20
+ },
21
+ {
22
+ title: "About",
23
+ slug: "about",
24
+ content:
25
+ "<h1>About Us</h1><p>Learn more about our team and mission.</p>",
26
+ },
27
+ {
28
+ title: "Contact",
29
+ slug: "contact",
30
+ content:
31
+ "<h1>Contact</h1><p>Get in touch with us via the form below.</p>",
32
+ },
33
+ {
34
+ title: "Privacy Policy",
35
+ slug: "privacy-policy",
36
+ content:
37
+ "<h1>Privacy Policy</h1><p>Your privacy matters to us. Read our full policy.</p>",
38
+ },
39
+ {
40
+ title: "Terms of Service",
41
+ slug: "terms",
42
+ content:
43
+ "<h1>Terms of Service</h1><p>Please read the following terms carefully.</p>",
44
+ },
45
+ ];
46
+
47
+ const DEMO_POSTS = [
48
+ {
49
+ title: "Getting Started with Actuate CMS",
50
+ slug: "getting-started",
51
+ excerpt: "Learn how to set up and configure your new CMS.",
52
+ content:
53
+ "<h1>Getting Started</h1><p>Welcome to Actuate CMS. This guide walks you through initial setup.</p>",
54
+ status: "PUBLISHED",
55
+ },
56
+ {
57
+ title: "Content Modeling Best Practices",
58
+ slug: "content-modeling",
59
+ excerpt: "Design your collections and fields for maximum flexibility.",
60
+ content:
61
+ "<h1>Content Modeling</h1><p>Effective content modeling is the foundation of a great CMS.</p>",
62
+ status: "PUBLISHED",
63
+ },
64
+ {
65
+ title: "Working with Media",
66
+ slug: "working-with-media",
67
+ excerpt: "Upload, organize, and optimize your media assets.",
68
+ content:
69
+ "<h1>Working with Media</h1><p>Actuate CMS provides powerful media management.</p>",
70
+ status: "PUBLISHED",
71
+ },
72
+ {
73
+ title: "SEO Optimization Tips",
74
+ slug: "seo-optimization",
75
+ excerpt: "Boost your search rankings with built-in SEO tools.",
76
+ content:
77
+ "<h1>SEO Optimization</h1><p>Follow these tips to improve your site visibility.</p>",
78
+ status: "PUBLISHED",
79
+ },
80
+ {
81
+ title: "Building Custom Plugins",
82
+ slug: "building-plugins",
83
+ excerpt: "Extend Actuate CMS with your own plugins.",
84
+ content:
85
+ "<h1>Building Plugins</h1><p>The plugin system lets you add custom functionality.</p>",
86
+ status: "PUBLISHED",
87
+ },
88
+ {
89
+ title: "API Reference Overview",
90
+ slug: "api-reference",
91
+ excerpt: "A comprehensive guide to the Actuate CMS REST API.",
92
+ content:
93
+ "<h1>API Reference</h1><p>Use the API to integrate your content anywhere.</p>",
94
+ status: "PUBLISHED",
95
+ },
96
+ {
97
+ title: "Deployment Guide",
98
+ slug: "deployment-guide",
99
+ excerpt: "Deploy Actuate CMS to Vercel, AWS, or self-hosted.",
100
+ content:
101
+ "<h1>Deployment Guide</h1><p>Multiple deployment options for every use case.</p>",
102
+ status: "PUBLISHED",
103
+ },
104
+ {
105
+ title: "Multi-language Content",
106
+ slug: "multi-language",
107
+ excerpt: "Set up localization and manage translated content.",
108
+ content:
109
+ "<h1>Multi-language Content</h1><p>Reach a global audience with localized content.</p>",
110
+ status: "PUBLISHED",
111
+ },
112
+ {
113
+ title: "Webhooks and Integrations",
114
+ slug: "webhooks-integrations",
115
+ excerpt: "Connect Actuate CMS to external services with webhooks.",
116
+ content:
117
+ "<h1>Webhooks</h1><p>Automate workflows by connecting to third-party services.</p>",
118
+ status: "DRAFT",
119
+ },
120
+ {
121
+ title: "Advanced Access Control",
122
+ slug: "access-control",
123
+ excerpt: "Fine-tune permissions with role-based access control.",
124
+ content:
125
+ "<h1>Access Control</h1><p>Protect your content with granular permissions.</p>",
126
+ status: "DRAFT",
127
+ },
128
+ ];
129
+
130
+ const DEMO_FORMS = [
131
+ {
132
+ title: "Contact Form",
133
+ slug: "contact-form",
134
+ fields: [
135
+ { name: "name", type: "text", required: true },
136
+ { name: "email", type: "email", required: true },
137
+ { name: "message", type: "textarea", required: true },
138
+ ],
139
+ submitLabel: "Send Message",
140
+ successMessage: "Thanks for reaching out! We'll get back to you soon.",
141
+ },
142
+ {
143
+ title: "Newsletter Signup",
144
+ slug: "newsletter",
145
+ fields: [
146
+ { name: "email", type: "email", required: true },
147
+ { name: "firstName", type: "text", required: false },
148
+ ],
149
+ submitLabel: "Subscribe",
150
+ successMessage: "You're subscribed! Check your inbox for confirmation.",
151
+ },
152
+ {
153
+ title: "Feedback Form",
154
+ slug: "feedback",
155
+ fields: [
156
+ { name: "name", type: "text", required: false },
157
+ { name: "rating", type: "select", options: ["1", "2", "3", "4", "5"], required: true },
158
+ { name: "comments", type: "textarea", required: false },
159
+ ],
160
+ submitLabel: "Submit Feedback",
161
+ successMessage: "Thank you for your feedback!",
162
+ },
163
+ ];
164
+
165
+ const DEMO_USERS = [
166
+ { email: "editor@example.com", name: "Demo Editor", role: "EDITOR" as const },
167
+ { email: "author@example.com", name: "Demo Author", role: "AUTHOR" as const },
168
+ ];
169
+
170
+ interface SeedOptions {
171
+ demo?: boolean;
172
+ file?: string;
173
+ reset?: boolean;
174
+ }
175
+
176
+ async function runSeed(options: SeedOptions): Promise<void> {
177
+ if (!options.demo && !options.file) {
178
+ logger.error("Specify --demo to seed demo content or --file <path> to seed from a JSON file.");
179
+ process.exit(1);
180
+ }
181
+
182
+ try {
183
+ const { getDB } = await import("@actuate-media/cms-core");
184
+ const db = getDB<any>();
185
+
186
+ if (options.reset) {
187
+ const yes = await confirm(
188
+ "This will delete ALL existing documents and versions. Continue?",
189
+ );
190
+ if (!yes) {
191
+ logger.warn("Seed cancelled.");
192
+ return;
193
+ }
194
+
195
+ const resetSpinner = ora("Clearing existing data…").start();
196
+ await db.version.deleteMany({});
197
+ await db.mediaUsage.deleteMany({});
198
+ await db.document.deleteMany({});
199
+ resetSpinner.succeed("Existing data cleared.");
200
+ }
201
+
202
+ if (options.demo) {
203
+ await seedDemoData(db);
204
+ }
205
+
206
+ if (options.file) {
207
+ await seedFromFile(db, options.file);
208
+ }
209
+ } catch (err) {
210
+ const message = err instanceof Error ? err.message : String(err);
211
+ logger.error(`Seed failed: ${message}`);
212
+ process.exit(1);
213
+ }
214
+ }
215
+
216
+ async function seedDemoData(db: any): Promise<void> {
217
+ const spinner = ora("Seeding demo data…").start();
218
+
219
+ let adminUser = await db.user.findFirst({ where: { role: "ADMIN" } });
220
+ if (!adminUser) {
221
+ adminUser = await db.user.create({
222
+ data: {
223
+ email: "admin@actuatecms.dev",
224
+ name: "Admin",
225
+ role: "ADMIN",
226
+ isActive: true,
227
+ isApproved: true,
228
+ emailVerified: true,
229
+ },
230
+ });
231
+ }
232
+ const userId = adminUser.id;
233
+
234
+ let pagesCreated = 0;
235
+ for (const page of DEMO_PAGES) {
236
+ await db.document.create({
237
+ data: {
238
+ collection: "pages",
239
+ data: page,
240
+ status: "PUBLISHED",
241
+ publishedAt: new Date(),
242
+ createdById: userId,
243
+ updatedById: userId,
244
+ plainText: `${page.title} ${page.slug}`,
245
+ },
246
+ });
247
+ pagesCreated++;
248
+ }
249
+
250
+ let postsCreated = 0;
251
+ for (const post of DEMO_POSTS) {
252
+ await db.document.create({
253
+ data: {
254
+ collection: "posts",
255
+ data: post,
256
+ status: post.status,
257
+ publishedAt: post.status === "PUBLISHED" ? new Date() : null,
258
+ createdById: userId,
259
+ updatedById: userId,
260
+ plainText: `${post.title} ${post.excerpt}`,
261
+ },
262
+ });
263
+ postsCreated++;
264
+ }
265
+
266
+ let formsCreated = 0;
267
+ for (const form of DEMO_FORMS) {
268
+ await db.document.create({
269
+ data: {
270
+ collection: "forms",
271
+ data: form,
272
+ status: "PUBLISHED",
273
+ publishedAt: new Date(),
274
+ createdById: userId,
275
+ updatedById: userId,
276
+ plainText: `${form.title} ${form.slug}`,
277
+ },
278
+ });
279
+ formsCreated++;
280
+ }
281
+
282
+ let usersCreated = 0;
283
+ for (const user of DEMO_USERS) {
284
+ const exists = await db.user.findFirst({ where: { email: user.email } });
285
+ if (!exists) {
286
+ await db.user.create({
287
+ data: {
288
+ email: user.email,
289
+ name: user.name,
290
+ role: user.role,
291
+ isActive: true,
292
+ isApproved: true,
293
+ emailVerified: true,
294
+ },
295
+ });
296
+ usersCreated++;
297
+ }
298
+ }
299
+
300
+ spinner.succeed("Demo data seeded successfully.");
301
+ logger.info(` Pages: ${pagesCreated}`);
302
+ logger.info(` Posts: ${postsCreated}`);
303
+ logger.info(` Forms: ${formsCreated}`);
304
+ logger.info(` Users: ${usersCreated} (+ existing admin)`);
305
+ }
306
+
307
+ async function seedFromFile(db: any, filePath: string): Promise<void> {
308
+ if (!existsSync(filePath)) {
309
+ logger.error(`File not found: ${filePath}`);
310
+ process.exit(1);
311
+ }
312
+
313
+ const spinner = ora(`Seeding from ${filePath}…`).start();
314
+
315
+ const raw = await readFile(filePath, "utf-8");
316
+ let seedData: any;
317
+ try {
318
+ seedData = JSON.parse(raw);
319
+ } catch {
320
+ spinner.fail("Invalid JSON file.");
321
+ process.exit(1);
322
+ }
323
+
324
+ if (!Array.isArray(seedData) && typeof seedData !== "object") {
325
+ spinner.fail("Seed file must contain a JSON array or an object with collection keys.");
326
+ process.exit(1);
327
+ }
328
+
329
+ let adminUser = await db.user.findFirst({ where: { role: "ADMIN" } });
330
+ if (!adminUser) {
331
+ adminUser = await db.user.create({
332
+ data: {
333
+ email: "admin@actuatecms.dev",
334
+ name: "Admin",
335
+ role: "ADMIN",
336
+ isActive: true,
337
+ isApproved: true,
338
+ emailVerified: true,
339
+ },
340
+ });
341
+ }
342
+ const userId = adminUser.id;
343
+
344
+ let count = 0;
345
+
346
+ if (Array.isArray(seedData)) {
347
+ for (const doc of seedData) {
348
+ await db.document.create({
349
+ data: {
350
+ collection: doc.collection ?? "imported",
351
+ data: doc.data ?? doc,
352
+ status: doc.status ?? "DRAFT",
353
+ createdById: userId,
354
+ updatedById: userId,
355
+ },
356
+ });
357
+ count++;
358
+ }
359
+ } else {
360
+ for (const [collection, docs] of Object.entries(seedData)) {
361
+ if (!Array.isArray(docs)) continue;
362
+ for (const doc of docs as any[]) {
363
+ await db.document.create({
364
+ data: {
365
+ collection,
366
+ data: doc.data ?? doc,
367
+ status: doc.status ?? "DRAFT",
368
+ createdById: userId,
369
+ updatedById: userId,
370
+ },
371
+ });
372
+ count++;
373
+ }
374
+ }
375
+ }
376
+
377
+ spinner.succeed(`Seeded ${count} documents from ${filePath}.`);
378
+ }
379
+
380
+ export function registerSeedCommand(program: Command): void {
381
+ program
382
+ .command("seed")
383
+ .description("Seed the database with demo or custom data")
384
+ .option("--demo", "Seed demo content (pages, posts, forms, users)")
385
+ .option("--file <path>", "Seed from a JSON file")
386
+ .option("--reset", "Clear existing data before seeding")
387
+ .action(runSeed);
388
+ }