@actuate-media/cli 0.2.5 → 0.3.1
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +15 -0
- package/CHANGELOG.md +12 -0
- package/dist/__tests__/init.test.d.ts +2 -0
- package/dist/__tests__/init.test.d.ts.map +1 -0
- package/dist/__tests__/init.test.js +25 -0
- package/dist/__tests__/init.test.js.map +1 -0
- package/dist/__tests__/seed.test.d.ts +2 -0
- package/dist/__tests__/seed.test.d.ts.map +1 -0
- package/dist/__tests__/seed.test.js +115 -0
- package/dist/__tests__/seed.test.js.map +1 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +31 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/seed.d.ts +18 -0
- package/dist/commands/seed.d.ts.map +1 -1
- package/dist/commands/seed.js +217 -97
- package/dist/commands/seed.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
- package/src/__tests__/init.test.ts +30 -0
- package/src/__tests__/seed.test.ts +126 -0
- package/src/commands/init.ts +36 -0
- package/src/commands/seed.ts +277 -96
- package/src/index.ts +2 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import type { Command } from 'commander';
|
|
3
|
+
import { logger } from '../utils/logger.js';
|
|
4
|
+
|
|
5
|
+
export function buildCreateActuateArgs(projectName?: string): string[] {
|
|
6
|
+
const args = ['create', 'actuate-cms@latest'];
|
|
7
|
+
if (projectName) args.push(projectName);
|
|
8
|
+
return args;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function npmCommand(): string {
|
|
12
|
+
return process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function runInit(projectName?: string): Promise<void> {
|
|
16
|
+
const child = spawn(npmCommand(), buildCreateActuateArgs(projectName), {
|
|
17
|
+
stdio: 'inherit',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const exitCode = await new Promise<number | null>((resolve, reject) => {
|
|
21
|
+
child.once('error', reject);
|
|
22
|
+
child.once('exit', resolve);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (exitCode && exitCode !== 0) {
|
|
26
|
+
logger.error(`Project initialization failed with exit code ${exitCode}.`);
|
|
27
|
+
process.exit(exitCode);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function registerInitCommand(program: Command): void {
|
|
32
|
+
program
|
|
33
|
+
.command('init [project-name]')
|
|
34
|
+
.description('Scaffold a new Actuate CMS project')
|
|
35
|
+
.action(runInit);
|
|
36
|
+
}
|
package/src/commands/seed.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import path from "node:path";
|
|
4
6
|
import { createInterface } from "node:readline/promises";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
5
8
|
import ora from "ora";
|
|
6
9
|
import { logger } from "../utils/logger.js";
|
|
7
10
|
|
|
@@ -173,15 +176,116 @@ interface SeedOptions {
|
|
|
173
176
|
reset?: boolean;
|
|
174
177
|
}
|
|
175
178
|
|
|
179
|
+
export interface NormalizedSeedDocument {
|
|
180
|
+
collection: string;
|
|
181
|
+
data: Record<string, unknown>;
|
|
182
|
+
status: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface NormalizedSeedGlobal {
|
|
186
|
+
slug: string;
|
|
187
|
+
data: Record<string, unknown>;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface NormalizedSeedPayload {
|
|
191
|
+
documents: NormalizedSeedDocument[];
|
|
192
|
+
globals: NormalizedSeedGlobal[];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const SEED_FILE_CANDIDATES = [
|
|
196
|
+
"actuate.seed.json",
|
|
197
|
+
"actuate.seed.ts",
|
|
198
|
+
"actuate.seed.js",
|
|
199
|
+
"actuate.seed.mjs",
|
|
200
|
+
"cms.seed.json",
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
function asRecord(value: unknown): Record<string, unknown> {
|
|
204
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
205
|
+
? value as Record<string, unknown>
|
|
206
|
+
: {};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function normalizeDocument(collection: string, doc: unknown): NormalizedSeedDocument {
|
|
210
|
+
const record = asRecord(doc);
|
|
211
|
+
return {
|
|
212
|
+
collection,
|
|
213
|
+
data: asRecord(record.data ?? record),
|
|
214
|
+
status: typeof record.status === "string" ? record.status : "DRAFT",
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function normalizeSeedPayload(seedData: unknown): NormalizedSeedPayload {
|
|
219
|
+
const documents: NormalizedSeedDocument[] = [];
|
|
220
|
+
const globals: NormalizedSeedGlobal[] = [];
|
|
221
|
+
|
|
222
|
+
if (Array.isArray(seedData)) {
|
|
223
|
+
for (const doc of seedData) {
|
|
224
|
+
const record = asRecord(doc);
|
|
225
|
+
documents.push(normalizeDocument(
|
|
226
|
+
typeof record.collection === "string" ? record.collection : "imported",
|
|
227
|
+
record.data ? record : { data: record },
|
|
228
|
+
));
|
|
229
|
+
}
|
|
230
|
+
return { documents, globals };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const root = asRecord(seedData);
|
|
234
|
+
|
|
235
|
+
const globalEntries = asRecord(root.globals);
|
|
236
|
+
for (const [slug, value] of Object.entries(globalEntries)) {
|
|
237
|
+
globals.push({ slug, data: asRecord(value) });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const collections = root.collections ? asRecord(root.collections) : root;
|
|
241
|
+
for (const [collection, docs] of Object.entries(collections)) {
|
|
242
|
+
if (collection === "globals" || collection === "collections") continue;
|
|
243
|
+
if (!Array.isArray(docs)) continue;
|
|
244
|
+
for (const doc of docs) {
|
|
245
|
+
documents.push(normalizeDocument(collection, doc));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return { documents, globals };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function findConventionSeedFile(): string | null {
|
|
253
|
+
for (const candidate of SEED_FILE_CANDIDATES) {
|
|
254
|
+
if (existsSync(candidate)) return candidate;
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function loadSeedFile(filePath: string): Promise<unknown> {
|
|
260
|
+
const extension = path.extname(filePath);
|
|
261
|
+
if (extension === ".json" || extension === "") {
|
|
262
|
+
const raw = await readFile(filePath, "utf-8");
|
|
263
|
+
return JSON.parse(raw);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const fileUrl = pathToFileURL(path.resolve(filePath)).href;
|
|
267
|
+
const mod = extension === ".ts"
|
|
268
|
+
? await import("tsx/esm/api").then(({ tsImport }) => tsImport(fileUrl, import.meta.url))
|
|
269
|
+
: await import(fileUrl);
|
|
270
|
+
|
|
271
|
+
return (mod as { default?: unknown; seed?: unknown }).default
|
|
272
|
+
?? (mod as { seed?: unknown }).seed;
|
|
273
|
+
}
|
|
274
|
+
|
|
176
275
|
async function runSeed(options: SeedOptions): Promise<void> {
|
|
177
|
-
|
|
178
|
-
|
|
276
|
+
const conventionFile = !options.demo && !options.file ? findConventionSeedFile() : null;
|
|
277
|
+
const file = options.file ?? conventionFile ?? undefined;
|
|
278
|
+
|
|
279
|
+
if (!options.demo && !file) {
|
|
280
|
+
logger.error("Specify --demo, --file <path>, or add actuate.seed.json in the project root.");
|
|
179
281
|
process.exit(1);
|
|
180
282
|
}
|
|
181
283
|
|
|
284
|
+
let seededDb: { db: any; disconnect: () => Promise<void> } | null = null;
|
|
285
|
+
|
|
182
286
|
try {
|
|
183
|
-
|
|
184
|
-
const db =
|
|
287
|
+
seededDb = await getSeedDatabase();
|
|
288
|
+
const db = seededDb.db;
|
|
185
289
|
|
|
186
290
|
if (options.reset) {
|
|
187
291
|
const yes = await confirm(
|
|
@@ -203,78 +307,178 @@ async function runSeed(options: SeedOptions): Promise<void> {
|
|
|
203
307
|
await seedDemoData(db);
|
|
204
308
|
}
|
|
205
309
|
|
|
206
|
-
if (
|
|
207
|
-
await seedFromFile(db,
|
|
310
|
+
if (file) {
|
|
311
|
+
await seedFromFile(db, file);
|
|
208
312
|
}
|
|
209
313
|
} catch (err) {
|
|
210
314
|
const message = err instanceof Error ? err.message : String(err);
|
|
211
315
|
logger.error(`Seed failed: ${message}`);
|
|
212
316
|
process.exit(1);
|
|
317
|
+
} finally {
|
|
318
|
+
await seededDb?.disconnect();
|
|
213
319
|
}
|
|
214
320
|
}
|
|
215
321
|
|
|
216
|
-
async function
|
|
217
|
-
const
|
|
322
|
+
async function getSeedDatabase(): Promise<{ db: any; disconnect: () => Promise<void> }> {
|
|
323
|
+
const { getDB, initDB, isDBInitialized } = await import("@actuate-media/cms-core");
|
|
324
|
+
|
|
325
|
+
if (isDBInitialized()) {
|
|
326
|
+
return { db: getDB<any>(), disconnect: async () => {} };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const db = await createProjectPrismaClient();
|
|
330
|
+
initDB(db);
|
|
331
|
+
return {
|
|
332
|
+
db,
|
|
333
|
+
disconnect: async () => {
|
|
334
|
+
if (typeof db.$disconnect === "function") {
|
|
335
|
+
await db.$disconnect();
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function createProjectPrismaClient(): Promise<any> {
|
|
342
|
+
if (!process.env.DATABASE_URL) {
|
|
343
|
+
throw new Error("DATABASE_URL is required to run seed/populate.");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const requireFromProject = createRequire(path.join(process.cwd(), "package.json"));
|
|
347
|
+
const generatedClient = path.resolve("generated", "prisma", "client.ts");
|
|
348
|
+
|
|
349
|
+
if (existsSync(generatedClient)) {
|
|
350
|
+
const [{ tsImport }, adapterModule, pgModule] = await Promise.all([
|
|
351
|
+
import("tsx/esm/api"),
|
|
352
|
+
import(pathToFileURL(requireFromProject.resolve("@prisma/adapter-pg")).href),
|
|
353
|
+
import(pathToFileURL(requireFromProject.resolve("pg")).href),
|
|
354
|
+
]);
|
|
355
|
+
const { PrismaClient } = await tsImport(pathToFileURL(generatedClient).href, import.meta.url) as {
|
|
356
|
+
PrismaClient: new (options?: unknown) => any;
|
|
357
|
+
};
|
|
358
|
+
const { PrismaPg } = adapterModule as { PrismaPg: new (pool: unknown) => unknown };
|
|
359
|
+
const pg = (pgModule as { default?: typeof pgModule }).default ?? pgModule;
|
|
360
|
+
const pool = new (pg as any).Pool({ connectionString: process.env.DATABASE_URL });
|
|
361
|
+
const adapter = new PrismaPg(pool);
|
|
362
|
+
return new PrismaClient({ adapter } as any);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const clientModule = await import(pathToFileURL(requireFromProject.resolve("@prisma/client")).href) as {
|
|
366
|
+
PrismaClient: new () => any;
|
|
367
|
+
};
|
|
368
|
+
return new clientModule.PrismaClient();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export async function ensureSeedAdmin(db: any): Promise<{ id: string }> {
|
|
372
|
+
const existing = await db.user.findFirst({ where: { role: "ADMIN" } });
|
|
373
|
+
if (existing) return existing;
|
|
374
|
+
|
|
375
|
+
const email = process.env.CMS_ADMIN_EMAIL;
|
|
376
|
+
const password = process.env.CMS_ADMIN_PASSWORD;
|
|
377
|
+
const name = process.env.CMS_ADMIN_NAME ?? "Admin";
|
|
378
|
+
|
|
379
|
+
if (!email || !password) {
|
|
380
|
+
throw new Error(
|
|
381
|
+
"No admin user exists. Set CMS_ADMIN_EMAIL and CMS_ADMIN_PASSWORD before running seed, or complete the setup wizard first.",
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const { createInitialAdmin } = await import("@actuate-media/cms-core");
|
|
386
|
+
const result = await createInitialAdmin(db, { email, password, name });
|
|
387
|
+
if (!result.success || !result.userId) {
|
|
388
|
+
throw new Error(result.error ?? "Failed to create initial admin user");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return { id: result.userId };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function sanitizeSeedData(value: unknown, sanitizeHtml: (html: string) => string): unknown {
|
|
395
|
+
if (Array.isArray(value)) {
|
|
396
|
+
return value.map((item) => sanitizeSeedData(item, sanitizeHtml));
|
|
397
|
+
}
|
|
398
|
+
if (value && typeof value === "object") {
|
|
399
|
+
return Object.fromEntries(
|
|
400
|
+
Object.entries(value as Record<string, unknown>).map(([key, item]) => [
|
|
401
|
+
key,
|
|
402
|
+
sanitizeSeedData(item, sanitizeHtml),
|
|
403
|
+
]),
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
if (typeof value === "string" && /<[a-z][\s\S]*>/i.test(value)) {
|
|
407
|
+
return sanitizeHtml(value);
|
|
408
|
+
}
|
|
409
|
+
return value;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export async function createSeedDocument(
|
|
413
|
+
db: any,
|
|
414
|
+
userId: string,
|
|
415
|
+
doc: NormalizedSeedDocument,
|
|
416
|
+
): Promise<void> {
|
|
417
|
+
const { extractPlainText, hashContent, sanitizeHtml } = await import("@actuate-media/cms-core");
|
|
418
|
+
const data = sanitizeSeedData(doc.data, sanitizeHtml) as Record<string, unknown>;
|
|
419
|
+
const serialized = JSON.stringify(data);
|
|
420
|
+
const plainText = extractPlainText(serialized);
|
|
421
|
+
const contentHash = await hashContent(serialized);
|
|
218
422
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
adminUser = await db.user.create({
|
|
423
|
+
await db.$transaction(async (tx: any) => {
|
|
424
|
+
const created = await tx.document.create({
|
|
222
425
|
data: {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
426
|
+
collection: doc.collection,
|
|
427
|
+
title: typeof data.title === "string" ? data.title : null,
|
|
428
|
+
slug: typeof data.slug === "string" ? data.slug : null,
|
|
429
|
+
data,
|
|
430
|
+
status: doc.status,
|
|
431
|
+
publishedAt: doc.status === "PUBLISHED" ? new Date() : null,
|
|
432
|
+
createdById: userId,
|
|
433
|
+
updatedById: userId,
|
|
434
|
+
plainText,
|
|
435
|
+
contentHash,
|
|
229
436
|
},
|
|
230
437
|
});
|
|
231
|
-
|
|
438
|
+
|
|
439
|
+
await tx.version.create({
|
|
440
|
+
data: {
|
|
441
|
+
documentId: created.id,
|
|
442
|
+
data,
|
|
443
|
+
changedById: userId,
|
|
444
|
+
changeType: "CREATE",
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async function seedDemoData(db: any): Promise<void> {
|
|
451
|
+
const spinner = ora("Seeding demo data…").start();
|
|
452
|
+
|
|
453
|
+
const adminUser = await ensureSeedAdmin(db);
|
|
232
454
|
const userId = adminUser.id;
|
|
233
455
|
|
|
234
456
|
let pagesCreated = 0;
|
|
235
457
|
for (const page of DEMO_PAGES) {
|
|
236
|
-
await db
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
status: "PUBLISHED",
|
|
241
|
-
publishedAt: new Date(),
|
|
242
|
-
createdById: userId,
|
|
243
|
-
updatedById: userId,
|
|
244
|
-
plainText: `${page.title} ${page.slug}`,
|
|
245
|
-
},
|
|
458
|
+
await createSeedDocument(db, userId, {
|
|
459
|
+
collection: "pages",
|
|
460
|
+
data: page,
|
|
461
|
+
status: "PUBLISHED",
|
|
246
462
|
});
|
|
247
463
|
pagesCreated++;
|
|
248
464
|
}
|
|
249
465
|
|
|
250
466
|
let postsCreated = 0;
|
|
251
467
|
for (const post of DEMO_POSTS) {
|
|
252
|
-
await db
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
},
|
|
468
|
+
await createSeedDocument(db, userId, {
|
|
469
|
+
collection: "posts",
|
|
470
|
+
data: post,
|
|
471
|
+
status: post.status,
|
|
262
472
|
});
|
|
263
473
|
postsCreated++;
|
|
264
474
|
}
|
|
265
475
|
|
|
266
476
|
let formsCreated = 0;
|
|
267
477
|
for (const form of DEMO_FORMS) {
|
|
268
|
-
await db
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
status: "PUBLISHED",
|
|
273
|
-
publishedAt: new Date(),
|
|
274
|
-
createdById: userId,
|
|
275
|
-
updatedById: userId,
|
|
276
|
-
plainText: `${form.title} ${form.slug}`,
|
|
277
|
-
},
|
|
478
|
+
await createSeedDocument(db, userId, {
|
|
479
|
+
collection: "forms",
|
|
480
|
+
data: form,
|
|
481
|
+
status: "PUBLISHED",
|
|
278
482
|
});
|
|
279
483
|
formsCreated++;
|
|
280
484
|
}
|
|
@@ -312,12 +516,11 @@ async function seedFromFile(db: any, filePath: string): Promise<void> {
|
|
|
312
516
|
|
|
313
517
|
const spinner = ora(`Seeding from ${filePath}…`).start();
|
|
314
518
|
|
|
315
|
-
const raw = await readFile(filePath, "utf-8");
|
|
316
519
|
let seedData: any;
|
|
317
520
|
try {
|
|
318
|
-
seedData =
|
|
521
|
+
seedData = await loadSeedFile(filePath);
|
|
319
522
|
} catch {
|
|
320
|
-
spinner.fail("Invalid
|
|
523
|
+
spinner.fail("Invalid seed file.");
|
|
321
524
|
process.exit(1);
|
|
322
525
|
}
|
|
323
526
|
|
|
@@ -326,55 +529,26 @@ async function seedFromFile(db: any, filePath: string): Promise<void> {
|
|
|
326
529
|
process.exit(1);
|
|
327
530
|
}
|
|
328
531
|
|
|
329
|
-
|
|
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
|
-
}
|
|
532
|
+
const adminUser = await ensureSeedAdmin(db);
|
|
342
533
|
const userId = adminUser.id;
|
|
343
534
|
|
|
344
|
-
|
|
535
|
+
const normalized = normalizeSeedPayload(seedData);
|
|
536
|
+
const { updateGlobal } = await import("@actuate-media/cms-core");
|
|
537
|
+
const ctx = { userId, role: "ADMIN", db };
|
|
345
538
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
}
|
|
539
|
+
let documentCount = 0;
|
|
540
|
+
for (const doc of normalized.documents) {
|
|
541
|
+
await createSeedDocument(db, userId, doc);
|
|
542
|
+
documentCount++;
|
|
375
543
|
}
|
|
376
544
|
|
|
377
|
-
|
|
545
|
+
let globalCount = 0;
|
|
546
|
+
for (const global of normalized.globals) {
|
|
547
|
+
await updateGlobal(global.slug, global.data, ctx);
|
|
548
|
+
globalCount++;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
spinner.succeed(`Seeded ${documentCount} documents and ${globalCount} globals from ${filePath}.`);
|
|
378
552
|
}
|
|
379
553
|
|
|
380
554
|
export function registerSeedCommand(program: Command): void {
|
|
@@ -382,7 +556,14 @@ export function registerSeedCommand(program: Command): void {
|
|
|
382
556
|
.command("seed")
|
|
383
557
|
.description("Seed the database with demo or custom data")
|
|
384
558
|
.option("--demo", "Seed demo content (pages, posts, forms, users)")
|
|
385
|
-
.option("--file <path>", "Seed from a JSON file")
|
|
559
|
+
.option("--file <path>", "Seed from a JSON, JavaScript, or TypeScript file")
|
|
560
|
+
.option("--reset", "Clear existing data before seeding")
|
|
561
|
+
.action(runSeed);
|
|
562
|
+
|
|
563
|
+
program
|
|
564
|
+
.command("populate")
|
|
565
|
+
.description("Populate the database from actuate.seed.json or a custom seed file")
|
|
566
|
+
.option("--file <path>", "Seed from a JSON, JavaScript, or TypeScript file")
|
|
386
567
|
.option("--reset", "Clear existing data before seeding")
|
|
387
568
|
.action(runSeed);
|
|
388
569
|
}
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { registerUpgradeCommand } from "./commands/upgrade.js";
|
|
|
9
9
|
import { registerUpdateCheckCommand } from "./commands/update-check.js";
|
|
10
10
|
import { registerDbInitCommand } from "./commands/db-init.js";
|
|
11
11
|
import { registerDbStatusCommand } from "./commands/db-status.js";
|
|
12
|
+
import { registerInitCommand } from "./commands/init.js";
|
|
12
13
|
|
|
13
14
|
const program = new Command();
|
|
14
15
|
|
|
@@ -26,5 +27,6 @@ registerUpgradeCommand(program);
|
|
|
26
27
|
registerUpdateCheckCommand(program);
|
|
27
28
|
registerDbInitCommand(program);
|
|
28
29
|
registerDbStatusCommand(program);
|
|
30
|
+
registerInitCommand(program);
|
|
29
31
|
|
|
30
32
|
program.parse();
|