@actuate-media/cli 0.11.3 → 0.12.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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @actuate-media/cli@0.11.3 build /home/runner/work/actuatecms/actuatecms/packages/cli
2
+ > @actuate-media/cli@0.12.1 build /home/runner/work/actuatecms/actuatecms/packages/cli
3
3
  > tsc
4
4
 
@@ -1,38 +1,38 @@
1
1
 
2
- > @actuate-media/cli@0.11.3 test /home/runner/work/actuatecms/actuatecms/packages/cli
2
+ > @actuate-media/cli@0.12.1 test /home/runner/work/actuatecms/actuatecms/packages/cli
3
3
  > vitest run
4
4
 
5
5
 
6
6
   RUN  v4.1.8 /home/runner/work/actuatecms/actuatecms/packages/cli
7
7
 
8
- ✓ dist/__tests__/db-sync.test.js (10 tests) 442ms
9
- ✓ dist/__tests__/seed.test.js (10 tests) 78ms
10
- ✓ src/__tests__/db-sync.test.ts (10 tests) 381ms
11
- ✓ dist/__tests__/deployment-diagnostics.test.js (9 tests) 75ms
12
- ✓ src/__tests__/seed.test.ts (10 tests) 144ms
13
- ✓ src/__tests__/deployment-diagnostics.test.ts (9 tests) 61ms
8
+ ✓ dist/__tests__/db-sync.test.js (10 tests) 460ms
9
+ ✓ dist/__tests__/seed.test.js (10 tests) 199ms
10
+ ✓ src/__tests__/db-sync.test.ts (10 tests) 394ms
11
+ ✓ dist/__tests__/deployment-diagnostics.test.js (9 tests) 62ms
12
+ ✓ src/__tests__/seed.test.ts (10 tests) 166ms
13
+ ✓ src/__tests__/deployment-diagnostics.test.ts (9 tests) 49ms
14
14
  - Reading canonical Actuate schema...
15
- ✔ Actuate CMS models added to schema.
16
- - Running prisma generate...
17
15
  stdout | dist/__tests__/db-init.test.js > db:init (WS-D D5 — canonical schema, no stale fragment) > command > injects canonical models with @@map and no duplicate datasource/generator
16
+ ✔ Actuate CMS models added to schema.
18
17
  ✔ Prisma client generated.
18
+ - Running prisma generate...
19
19
  ℹ Run `npx prisma migrate dev --name actuate-cms` to create the migration.
20
20
 
21
- - Reading canonical Actuate schema...
22
- ✖ Could not locate @actuate-media/cms-core.
23
21
  stdout | dist/__tests__/db-init.test.js > db:init (WS-D D5 — canonical schema, no stale fragment) > command > fails clearly when cms-core cannot be located
24
22
  ℹ Install it first: `npm install @actuate-media/cms-core`.
23
+ - Reading canonical Actuate schema...
24
+ ✖ Could not locate @actuate-media/cms-core.
25
25
 
26
26
  - Reading canonical Actuate schema...
27
- ✔ Actuate CMS models added to schema.
28
- - Running prisma generate...
29
27
  stdout | dist/__tests__/db-init.test.js > db:init (WS-D D5 — canonical schema, no stale fragment) > command > is idempotent without --force
30
28
  ✔ Prisma client generated.
31
29
  ℹ Run `npx prisma migrate dev --name actuate-cms` to create the migration.
32
30
 
31
+ ✔ Actuate CMS models added to schema.
32
+ - Running prisma generate...
33
33
  - Reading canonical Actuate schema...
34
34
  ℹ Actuate CMS models already present in schema. Use --force to overwrite.
35
- ✓ dist/__tests__/db-init.test.js (4 tests) 170ms
35
+ ✓ dist/__tests__/db-init.test.js (4 tests) 340ms
36
36
  - Reading canonical Actuate schema...
37
37
  ✔ Actuate CMS models added to schema.
38
38
  - Running prisma generate...
@@ -46,15 +46,15 @@
46
46
  ℹ Install it first: `npm install @actuate-media/cms-core`.
47
47
 
48
48
  - Reading canonical Actuate schema...
49
- ✔ Actuate CMS models added to schema.
50
- - Running prisma generate...
51
49
  stdout | src/__tests__/db-init.test.ts > db:init (WS-D D5 — canonical schema, no stale fragment) > command > is idempotent without --force
52
50
  ✔ Prisma client generated.
53
51
  ℹ Run `npx prisma migrate dev --name actuate-cms` to create the migration.
52
+ ✔ Actuate CMS models added to schema.
54
53
 
54
+ - Running prisma generate...
55
55
  - Reading canonical Actuate schema...
56
56
  ℹ Actuate CMS models already present in schema. Use --force to overwrite.
57
- ✓ src/__tests__/db-init.test.ts (4 tests) 194ms
57
+ ✓ src/__tests__/db-init.test.ts (4 tests) 127ms
58
58
  stdout | dist/__tests__/mcp-init.test.js > mcp:init > creates both editor configs with placeholder key and git-ignores them
59
59
  ✔ Created .cursor/mcp.json
60
60
 
@@ -183,7 +183,7 @@ Next steps
183
183
  3. Restart Cursor / VS Code so the MCP server is picked up
184
184
  Tip: pass --url https://your-site.com to target a deployed CMS instead.
185
185
 
186
- ✓ dist/__tests__/mcp-init.test.js (6 tests) 255ms
186
+ ✓ dist/__tests__/mcp-init.test.js (6 tests) 484ms
187
187
  stdout | src/__tests__/mcp-init.test.ts > mcp:init > creates both editor configs with placeholder key and git-ignores them
188
188
  ✔ Created .cursor/mcp.json
189
189
 
@@ -312,21 +312,23 @@ Next steps
312
312
  3. Restart Cursor / VS Code so the MCP server is picked up
313
313
  Tip: pass --url https://your-site.com to target a deployed CMS instead.
314
314
 
315
- ✓ src/__tests__/mcp-init.test.ts (6 tests) 279ms
316
- ✓ dist/__tests__/form-seed.test.js (10 tests) 47ms
317
- ✓ src/__tests__/form-seed.test.ts (10 tests) 77ms
318
- ✓ dist/__tests__/vercel-env-matrix.test.js (6 tests) 43ms
319
- ✓ src/__tests__/vercel-env-matrix.test.ts (6 tests) 51ms
320
- ✓ dist/__tests__/load-dotenv.test.js (3 tests) 54ms
321
- ✓ src/__tests__/load-dotenv.test.ts (3 tests) 69ms
315
+ ✓ src/__tests__/mcp-init.test.ts (6 tests) 429ms
316
+ ✓ dist/__tests__/form-seed.test.js (10 tests) 60ms
317
+ ✓ src/__tests__/form-seed.test.ts (10 tests) 70ms
318
+ ✓ dist/__tests__/prisma-client-path.test.js (6 tests) 68ms
319
+ ✓ src/__tests__/prisma-client-path.test.ts (6 tests) 81ms
320
+ ✓ dist/__tests__/vercel-env-matrix.test.js (6 tests) 95ms
321
+ ✓ src/__tests__/vercel-env-matrix.test.ts (6 tests) 58ms
322
+ ✓ dist/__tests__/load-dotenv.test.js (3 tests) 105ms
323
+ ✓ src/__tests__/load-dotenv.test.ts (3 tests) 116ms
322
324
  - Reading canonical Actuate schema...
323
325
  ✔ Actuate CMS models added to schema.
324
- - Running prisma generate...
325
326
  stdout | dist/__tests__/schema-fragment.test.js > db:init schema fragment > injects all deploy-critical Actuate models
326
327
  ✔ Prisma client generated.
327
328
  ℹ Run `npx prisma migrate dev --name actuate-cms` to create the migration.
328
329
 
329
- ✓ dist/__tests__/schema-fragment.test.js (1 test) 120ms
330
+ - Running prisma generate...
331
+ ✓ dist/__tests__/schema-fragment.test.js (1 test) 132ms
330
332
  - Reading canonical Actuate schema...
331
333
  ✔ Actuate CMS models added to schema.
332
334
  - Running prisma generate...
@@ -334,12 +336,12 @@ Next steps
334
336
  ✔ Prisma client generated.
335
337
  ℹ Run `npx prisma migrate dev --name actuate-cms` to create the migration.
336
338
 
337
- ✓ src/__tests__/schema-fragment.test.ts (1 test) 80ms
338
- ✓ dist/__tests__/init.test.js (2 tests) 41ms
339
- ✓ src/__tests__/init.test.ts (2 tests) 38ms
339
+ ✓ src/__tests__/schema-fragment.test.ts (1 test) 123ms
340
+ ✓ dist/__tests__/init.test.js (2 tests) 36ms
341
+ ✓ src/__tests__/init.test.ts (2 tests) 48ms
340
342
 
341
-  Test Files  20 passed (20)
342
-  Tests  122 passed (122)
343
-  Start at  16:26:19
344
-  Duration  29.01s (transform 2.47s, setup 0ms, import 7.01s, tests 2.70s, environment 21ms)
343
+  Test Files  22 passed (22)
344
+  Tests  134 passed (134)
345
+  Start at  17:49:50
346
+  Duration  37.73s (transform 3.12s, setup 0ms, import 8.53s, tests 3.70s, environment 22ms)
345
347
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @actuate-media/cli
2
2
 
3
+ ## 0.12.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 13aae89: Fix CLI database connection for Prisma 7 projects: resolve custom generator output paths from schema.prisma, prefer lib/prisma.ts getPrisma(), and handle Windows paths for migrate:meta and migrate:schema.
8
+
9
+ ## 0.12.0
10
+
11
+ ### Minor Changes
12
+
13
+ - bc51a1d: Align SEO audit with live page meta output, expand audit checks, wire redirect/link signals, and add `actuate migrate:meta` for bulk meta backfill.
14
+
3
15
  ## 0.11.3
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=prisma-client-path.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-client-path.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/prisma-client-path.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,66 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import path from 'node:path';
4
+ import { afterEach, describe, expect, it } from 'vitest';
5
+ import { findPrismaSchemaFile, parsePrismaGeneratorOutputDir, resolveClientEntryInDir, resolveProjectPrismaClientModule, } from '../utils/prisma-client-path.js';
6
+ describe('prisma-client-path', () => {
7
+ let tempDir = null;
8
+ afterEach(() => {
9
+ if (tempDir)
10
+ rmSync(tempDir, { recursive: true, force: true });
11
+ tempDir = null;
12
+ });
13
+ function writeSchema(root, output) {
14
+ mkdirSync(path.join(root, 'prisma'), { recursive: true });
15
+ writeFileSync(path.join(root, 'prisma', 'schema.prisma'), `generator client {
16
+ provider = "prisma-client"
17
+ output = "${output}"
18
+ }
19
+
20
+ datasource db {
21
+ provider = "postgresql"
22
+ }
23
+ `);
24
+ }
25
+ function writeClient(clientPath) {
26
+ mkdirSync(path.dirname(clientPath), { recursive: true });
27
+ writeFileSync(clientPath, 'export class PrismaClient {}');
28
+ }
29
+ it('finds prisma/schema.prisma in the project root', () => {
30
+ tempDir = mkdtempSync(path.join(tmpdir(), 'actuate-cli-prisma-'));
31
+ writeSchema(tempDir, '../generated/prisma');
32
+ expect(findPrismaSchemaFile(tempDir)).toBe(path.join(tempDir, 'prisma', 'schema.prisma'));
33
+ });
34
+ it('resolves relative generator output from the schema directory', () => {
35
+ tempDir = mkdtempSync(path.join(tmpdir(), 'actuate-cli-prisma-'));
36
+ writeSchema(tempDir, '../generated/prisma');
37
+ const schemaPath = path.join(tempDir, 'prisma', 'schema.prisma');
38
+ expect(parsePrismaGeneratorOutputDir(schemaPath)).toBe(path.join(tempDir, 'generated', 'prisma'));
39
+ });
40
+ it('resolves custom output paths used by Prisma 7 scaffolds', () => {
41
+ tempDir = mkdtempSync(path.join(tmpdir(), 'actuate-cli-prisma-'));
42
+ writeSchema(tempDir, '../src/generated/prisma');
43
+ const schemaPath = path.join(tempDir, 'prisma', 'schema.prisma');
44
+ expect(parsePrismaGeneratorOutputDir(schemaPath)).toBe(path.join(tempDir, 'src', 'generated', 'prisma'));
45
+ });
46
+ it('locates client.ts via schema output before default fallbacks', () => {
47
+ tempDir = mkdtempSync(path.join(tmpdir(), 'actuate-cli-prisma-'));
48
+ writeSchema(tempDir, '../src/generated/prisma');
49
+ const clientPath = path.join(tempDir, 'src', 'generated', 'prisma', 'client.ts');
50
+ writeClient(clientPath);
51
+ expect(resolveProjectPrismaClientModule(tempDir)).toBe(clientPath);
52
+ });
53
+ it('falls back to generated/prisma/client.ts when schema output is missing', () => {
54
+ tempDir = mkdtempSync(path.join(tmpdir(), 'actuate-cli-prisma-'));
55
+ const clientPath = path.join(tempDir, 'generated', 'prisma', 'client.ts');
56
+ writeClient(clientPath);
57
+ expect(resolveProjectPrismaClientModule(tempDir)).toBe(clientPath);
58
+ });
59
+ it('resolveClientEntryInDir prefers client.ts over index.ts', () => {
60
+ tempDir = mkdtempSync(path.join(tmpdir(), 'actuate-cli-prisma-entry-'));
61
+ writeFileSync(path.join(tempDir, 'index.ts'), 'export {}');
62
+ writeFileSync(path.join(tempDir, 'client.ts'), 'export class PrismaClient {}');
63
+ expect(resolveClientEntryInDir(tempDir)).toBe(path.join(tempDir, 'client.ts'));
64
+ });
65
+ });
66
+ //# sourceMappingURL=prisma-client-path.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-client-path.test.js","sourceRoot":"","sources":["../../src/__tests__/prisma-client-path.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerMigrateMetaCommand(program: Command): void;
3
+ //# sourceMappingURL=migrate-meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-meta.d.ts","sourceRoot":"","sources":["../../src/commands/migrate-meta.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAgGnC,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAUjE"}
@@ -0,0 +1,81 @@
1
+ import { resolve } from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+ import ora from 'ora';
4
+ import { connectProjectDatabase } from '../utils/database.js';
5
+ import { loadProjectEnv } from '../utils/load-dotenv.js';
6
+ import { logger } from '../utils/logger.js';
7
+ async function loadProjectConfig(configPath) {
8
+ try {
9
+ const { setActuateConfig } = await import('@actuate-media/cms-core');
10
+ const { tsImport } = await import('tsx/esm/api');
11
+ const abs = pathToFileURL(resolve(configPath)).href;
12
+ const mod = (await tsImport(abs, import.meta.url));
13
+ const config = mod.default ?? mod.config;
14
+ if (config && typeof config === 'object') {
15
+ setActuateConfig(config);
16
+ }
17
+ }
18
+ catch {
19
+ logger.warn(`Could not load ${configPath} — meta defaults will use collection slug heuristics only.`);
20
+ }
21
+ }
22
+ /** `actuate migrate:meta` — backfill metaTitle/metaDescription on content documents. */
23
+ async function runMigrateMeta(options) {
24
+ const dryRun = options.dryRun ?? false;
25
+ const batchSize = options.batchSize ? Number.parseInt(options.batchSize, 10) : undefined;
26
+ if (batchSize !== undefined && (!Number.isFinite(batchSize) || batchSize <= 0)) {
27
+ logger.error('--batch-size must be a positive integer.');
28
+ process.exit(1);
29
+ }
30
+ await loadProjectEnv(process.cwd()).then((env) => {
31
+ for (const [key, value] of Object.entries(env)) {
32
+ if (value !== undefined && process.env[key] === undefined) {
33
+ process.env[key] = value;
34
+ }
35
+ }
36
+ });
37
+ await loadProjectConfig(options.config ?? 'actuate.config.ts');
38
+ let connection = null;
39
+ const spinner = ora(dryRun ? 'Scanning content documents (dry run)…' : 'Backfilling meta title/description…').start();
40
+ try {
41
+ connection = await connectProjectDatabase();
42
+ const db = connection.db;
43
+ const { migrateMetaFields } = await import('@actuate-media/cms-core');
44
+ const admin = await db.user.findFirst({ where: { role: 'ADMIN' } });
45
+ if (!admin) {
46
+ spinner.fail('No ADMIN user found. Create an admin (setup wizard or seed) first.');
47
+ process.exit(1);
48
+ }
49
+ const ctx = { userId: admin.id, role: 'ADMIN', db: connection.db, locale: 'en' };
50
+ const result = await migrateMetaFields(ctx, { dryRun, batchSize });
51
+ spinner.succeed(dryRun
52
+ ? `Dry run complete — ${result.migrated} document(s) would be updated.`
53
+ : `Updated ${result.migrated} document(s).`);
54
+ logger.info(` Scanned: ${result.scanned}`);
55
+ logger.info(` Migrated: ${result.migrated}`);
56
+ logger.info(` Skipped: ${result.skipped}`);
57
+ if (result.migratedIds.length > 0) {
58
+ logger.info(` IDs: ${result.migratedIds.join(', ')}`);
59
+ }
60
+ logger.info('');
61
+ logger.info('Next: re-run the SEO audit in Settings → SEO → Audit.');
62
+ }
63
+ catch (err) {
64
+ const message = err instanceof Error ? err.message : String(err);
65
+ spinner.fail(`Meta migration failed: ${message}`);
66
+ process.exit(1);
67
+ }
68
+ finally {
69
+ await connection?.disconnect();
70
+ }
71
+ }
72
+ export function registerMigrateMetaCommand(program) {
73
+ program
74
+ .command('migrate:meta')
75
+ .description('Backfill metaTitle/metaDescription from title templates and key takeaways. Idempotent.')
76
+ .option('--dry-run', 'Report what would change without writing')
77
+ .option('--batch-size <n>', 'Documents to scan per DB page (default 100)')
78
+ .option('--config <path>', 'Path to actuate.config.ts', 'actuate.config.ts')
79
+ .action(runMigrateMeta);
80
+ }
81
+ //# sourceMappingURL=migrate-meta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-meta.js","sourceRoot":"","sources":["../../src/commands/migrate-meta.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import { Command } from 'commander';
3
3
  import { registerMigrateCommand } from './commands/migrate.js';
4
4
  import { registerMigrateSectionsCommand } from './commands/migrate-sections.js';
5
5
  import { registerMigrateSchemaCommand } from './commands/migrate-schema.js';
6
+ import { registerMigrateMetaCommand } from './commands/migrate-meta.js';
6
7
  import { registerGenerateCommand } from './commands/generate.js';
7
8
  import { registerSeedCommand } from './commands/seed.js';
8
9
  import { registerImportCommand } from './commands/import.js';
@@ -24,6 +25,7 @@ program
24
25
  registerMigrateCommand(program);
25
26
  registerMigrateSectionsCommand(program);
26
27
  registerMigrateSchemaCommand(program);
28
+ registerMigrateMetaCommand(program);
27
29
  registerGenerateCommand(program);
28
30
  registerSeedCommand(program);
29
31
  registerImportCommand(program);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAA;AAC/E,OAAO,EAAE,4BAA4B,EAAE,MAAM,8BAA8B,CAAA;AAC3E,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAA;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAA;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,6BAA6B,EAAE,MAAM,gCAAgC,CAAA;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEtD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;AAE7B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,6DAA6D,CAAC;KAC1E,OAAO,CAAC,OAAO,CAAC,CAAA;AAEnB,sBAAsB,CAAC,OAAO,CAAC,CAAA;AAC/B,8BAA8B,CAAC,OAAO,CAAC,CAAA;AACvC,4BAA4B,CAAC,OAAO,CAAC,CAAA;AACrC,uBAAuB,CAAC,OAAO,CAAC,CAAA;AAChC,mBAAmB,CAAC,OAAO,CAAC,CAAA;AAC5B,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,sBAAsB,CAAC,OAAO,CAAC,CAAA;AAC/B,0BAA0B,CAAC,OAAO,CAAC,CAAA;AACnC,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,uBAAuB,CAAC,OAAO,CAAC,CAAA;AAChC,mBAAmB,CAAC,OAAO,CAAC,CAAA;AAC5B,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,0BAA0B,CAAC,OAAO,CAAC,CAAA;AACnC,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,6BAA6B,CAAC,OAAO,CAAC,CAAA;AACtC,kBAAkB,CAAC,OAAO,CAAC,CAAA;AAE3B,OAAO,CAAC,KAAK,EAAE,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAA;AAC/E,OAAO,EAAE,4BAA4B,EAAE,MAAM,8BAA8B,CAAA;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAA;AACvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAA;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAA;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,6BAA6B,EAAE,MAAM,gCAAgC,CAAA;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEtD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;AAE7B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,6DAA6D,CAAC;KAC1E,OAAO,CAAC,OAAO,CAAC,CAAA;AAEnB,sBAAsB,CAAC,OAAO,CAAC,CAAA;AAC/B,8BAA8B,CAAC,OAAO,CAAC,CAAA;AACvC,4BAA4B,CAAC,OAAO,CAAC,CAAA;AACrC,0BAA0B,CAAC,OAAO,CAAC,CAAA;AACnC,uBAAuB,CAAC,OAAO,CAAC,CAAA;AAChC,mBAAmB,CAAC,OAAO,CAAC,CAAA;AAC5B,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,sBAAsB,CAAC,OAAO,CAAC,CAAA;AAC/B,0BAA0B,CAAC,OAAO,CAAC,CAAA;AACnC,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,uBAAuB,CAAC,OAAO,CAAC,CAAA;AAChC,mBAAmB,CAAC,OAAO,CAAC,CAAA;AAC5B,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,0BAA0B,CAAC,OAAO,CAAC,CAAA;AACnC,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,6BAA6B,CAAC,OAAO,CAAC,CAAA;AACtC,kBAAkB,CAAC,OAAO,CAAC,CAAA;AAE3B,OAAO,CAAC,KAAK,EAAE,CAAA"}
@@ -5,9 +5,11 @@
5
5
  * Resolution order mirrors how a scaffolded Actuate project exposes Prisma:
6
6
  * 1. If `cms-core` already has an initialized client (e.g. set up by the host
7
7
  * app in-process), reuse it.
8
- * 2. Otherwise build a client from the project's generated Prisma client
9
- * (`generated/prisma/client.ts`, Prisma 7 + pg driver adapter).
10
- * 3. Fall back to a plain `@prisma/client` resolved from the project.
8
+ * 2. Otherwise call `getPrisma()` from `lib/prisma.ts` when the project ships
9
+ * one (Prisma 7 + pg adapter / Accelerate — same path Next.js uses).
10
+ * 3. Otherwise build a client from the schema's `output` path (custom or
11
+ * default `generated/prisma/client.ts`) via tsx + pg driver adapter.
12
+ * 4. Fall back to a plain `@prisma/client` resolved from the project.
11
13
  *
12
14
  * The returned `disconnect()` only tears down clients this helper created — a
13
15
  * pre-initialized in-process client is left untouched.
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/utils/database.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;GAaG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC;IAEtD,EAAE,EAAE,GAAG,CAAA;IACP,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAChC,CAAC,CAiBD"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/utils/database.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC;IAEtD,EAAE,EAAE,GAAG,CAAA;IACP,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAChC,CAAC,CAiBD"}
@@ -1,7 +1,7 @@
1
- import { existsSync } from 'node:fs';
2
1
  import { createRequire } from 'node:module';
3
2
  import path from 'node:path';
4
3
  import { pathToFileURL } from 'node:url';
4
+ import { findLibPrismaHelper, resolveProjectPrismaClientModule } from './prisma-client-path.js';
5
5
  /**
6
6
  * Shared helper for CLI commands that need a live connection to the consumer
7
7
  * project's database (seed, populate, data migrations…).
@@ -9,9 +9,11 @@ import { pathToFileURL } from 'node:url';
9
9
  * Resolution order mirrors how a scaffolded Actuate project exposes Prisma:
10
10
  * 1. If `cms-core` already has an initialized client (e.g. set up by the host
11
11
  * app in-process), reuse it.
12
- * 2. Otherwise build a client from the project's generated Prisma client
13
- * (`generated/prisma/client.ts`, Prisma 7 + pg driver adapter).
14
- * 3. Fall back to a plain `@prisma/client` resolved from the project.
12
+ * 2. Otherwise call `getPrisma()` from `lib/prisma.ts` when the project ships
13
+ * one (Prisma 7 + pg adapter / Accelerate — same path Next.js uses).
14
+ * 3. Otherwise build a client from the schema's `output` path (custom or
15
+ * default `generated/prisma/client.ts`) via tsx + pg driver adapter.
16
+ * 4. Fall back to a plain `@prisma/client` resolved from the project.
15
17
  *
16
18
  * The returned `disconnect()` only tears down clients this helper created — a
17
19
  * pre-initialized in-process client is left untouched.
@@ -32,27 +34,65 @@ export async function connectProjectDatabase() {
32
34
  },
33
35
  };
34
36
  }
37
+ const ACCELERATED_DB_URL_PREFIXES = ['prisma://', 'prisma+postgres://'];
38
+ function isAcceleratedDbUrl(url) {
39
+ const trimmed = url?.trim();
40
+ if (!trimmed)
41
+ return false;
42
+ return ACCELERATED_DB_URL_PREFIXES.some((prefix) => trimmed.startsWith(prefix));
43
+ }
44
+ async function importModule(specifier, requireFromProject) {
45
+ const href = pathToFileURL(requireFromProject.resolve(specifier)).href;
46
+ return import(href);
47
+ }
48
+ async function tryLibPrismaClient() {
49
+ const helperPath = findLibPrismaHelper();
50
+ if (!helperPath)
51
+ return null;
52
+ const { tsImport } = await import('tsx/esm/api');
53
+ const mod = (await tsImport(pathToFileURL(helperPath).href, import.meta.url));
54
+ const getPrisma = mod.getPrisma ?? mod.default?.getPrisma;
55
+ if (typeof getPrisma !== 'function')
56
+ return null;
57
+ return getPrisma();
58
+ }
35
59
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- see above.
36
60
  async function createProjectPrismaClient() {
37
61
  if (!process.env.DATABASE_URL) {
38
62
  throw new Error('DATABASE_URL is required to connect to the project database.');
39
63
  }
64
+ const fromLib = await tryLibPrismaClient();
65
+ if (fromLib)
66
+ return fromLib;
40
67
  const requireFromProject = createRequire(path.join(process.cwd(), 'package.json'));
41
- const generatedClient = path.resolve('generated', 'prisma', 'client.ts');
42
- if (existsSync(generatedClient)) {
68
+ const generatedClient = resolveProjectPrismaClientModule();
69
+ if (generatedClient) {
70
+ const url = process.env.DATABASE_URL;
43
71
  const [{ tsImport }, adapterModule, pgModule] = await Promise.all([
44
72
  import('tsx/esm/api'),
45
- import(pathToFileURL(requireFromProject.resolve('@prisma/adapter-pg')).href),
46
- import(pathToFileURL(requireFromProject.resolve('pg')).href),
73
+ importModule('@prisma/adapter-pg', requireFromProject),
74
+ importModule('pg', requireFromProject),
47
75
  ]);
48
76
  const { PrismaClient } = (await tsImport(pathToFileURL(generatedClient).href, import.meta.url));
77
+ if (isAcceleratedDbUrl(url)) {
78
+ let withAccelerate;
79
+ try {
80
+ const ext = (await importModule('@prisma/extension-accelerate', requireFromProject));
81
+ withAccelerate = ext.withAccelerate;
82
+ }
83
+ catch {
84
+ throw new Error('DATABASE_URL uses Prisma Accelerate but @prisma/extension-accelerate is not installed. Run `lib/prisma.ts` via adding it, or install the extension.');
85
+ }
86
+ const client = new PrismaClient({ accelerateUrl: url }).$extends(withAccelerate());
87
+ return client;
88
+ }
49
89
  const { PrismaPg } = adapterModule;
50
90
  const pg = pgModule.default ?? pgModule;
51
- const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
91
+ const pool = new pg.Pool({ connectionString: url });
52
92
  const adapter = new PrismaPg(pool);
53
93
  return new PrismaClient({ adapter });
54
94
  }
55
- const clientModule = (await import(pathToFileURL(requireFromProject.resolve('@prisma/client')).href));
95
+ const clientModule = (await importModule('@prisma/client', requireFromProject));
56
96
  return new clientModule.PrismaClient();
57
97
  }
58
98
  //# sourceMappingURL=database.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/utils/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB;IAK1C,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,yBAAyB,CAAC,CAAA;IAElF,IAAI,eAAe,EAAE,EAAE,CAAC;QACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAO,EAAE,UAAU,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC,EAAE,CAAA;IACzD,CAAC;IAED,MAAM,EAAE,GAAG,MAAM,yBAAyB,EAAE,CAAA;IAC5C,MAAM,CAAC,EAAE,CAAC,CAAA;IACV,OAAO;QACL,EAAE;QACF,UAAU,EAAE,KAAK,IAAI,EAAE;YACrB,IAAI,OAAO,EAAE,CAAC,WAAW,KAAK,UAAU,EAAE,CAAC;gBACzC,MAAM,EAAE,CAAC,WAAW,EAAE,CAAA;YACxB,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED,4EAA4E;AAC5E,KAAK,UAAU,yBAAyB;IACtC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAA;IACjF,CAAC;IAED,MAAM,kBAAkB,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,CAAC,CAAC,CAAA;IAClF,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAA;IAExE,IAAI,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QAChC,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,aAAa,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAChE,MAAM,CAAC,aAAa,CAAC;YACrB,MAAM,CAAC,aAAa,CAAC,kBAAkB,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC;YAC5E,MAAM,CAAC,aAAa,CAAC,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;SAC7D,CAAC,CAAA;QACF,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,MAAM,QAAQ,CACtC,aAAa,CAAC,eAAe,CAAC,CAAC,IAAI,EACnC,MAAM,CAAC,IAAI,CAAC,GAAG,CAChB,CAEA,CAAA;QACD,MAAM,EAAE,QAAQ,EAAE,GAAG,aAA6D,CAAA;QAClF,MAAM,EAAE,GAAI,QAA0C,CAAC,OAAO,IAAI,QAAQ,CAAA;QAC1E,MAAM,IAAI,GAAG,IAAK,EAAU,CAAC,IAAI,CAAC,EAAE,gBAAgB,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAAA;QACjF,MAAM,OAAO,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAA;QAClC,OAAO,IAAI,YAAY,CAAC,EAAE,OAAO,EAAS,CAAC,CAAA;IAC7C,CAAC;IAED,MAAM,YAAY,GAAG,CAAC,MAAM,MAAM,CAChC,aAAa,CAAC,kBAAkB,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CACjE,CAEA,CAAA;IACD,OAAO,IAAI,YAAY,CAAC,YAAY,EAAE,CAAA;AACxC,CAAC"}
1
+ {"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/utils/database.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /** Locate the consumer project's Prisma schema file. */
2
+ export declare function findPrismaSchemaFile(cwd?: string): string | null;
3
+ /**
4
+ * Parse `generator client { output = "..." }` and return the absolute output
5
+ * directory. Relative paths resolve from the schema file's directory (Prisma
6
+ * convention), including `../generated/prisma` layouts.
7
+ */
8
+ export declare function parsePrismaGeneratorOutputDir(schemaPath: string): string | null;
9
+ /** First existing Prisma client entry file inside `dir`. */
10
+ export declare function resolveClientEntryInDir(dir: string): string | null;
11
+ /**
12
+ * Resolve the generated Prisma client module path for CLI database commands.
13
+ * Reads the schema's custom `output` path before falling back to common
14
+ * scaffold locations (`generated/prisma`, `src/generated`, …).
15
+ */
16
+ export declare function resolveProjectPrismaClientModule(cwd?: string): string | null;
17
+ /** Project helpers that wrap Prisma 7 + driver adapters (preferred when present). */
18
+ export declare const LIB_PRISMA_HELPERS: readonly ["lib/prisma.ts", "lib/prisma.js", "lib/prisma.mjs"];
19
+ export declare function findLibPrismaHelper(cwd?: string): string | null;
20
+ //# sourceMappingURL=prisma-client-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-client-path.d.ts","sourceRoot":"","sources":["../../src/utils/prisma-client-path.ts"],"names":[],"mappings":"AASA,wDAAwD;AACxD,wBAAgB,oBAAoB,CAAC,GAAG,SAAgB,GAAG,MAAM,GAAG,IAAI,CAMvE;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc/E;AAED,4DAA4D;AAC5D,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMlE;AAED;;;;GAIG;AACH,wBAAgB,gCAAgC,CAAC,GAAG,SAAgB,GAAG,MAAM,GAAG,IAAI,CAgBnF;AAED,qFAAqF;AACrF,eAAO,MAAM,kBAAkB,+DAAgE,CAAA;AAE/F,wBAAgB,mBAAmB,CAAC,GAAG,SAAgB,GAAG,MAAM,GAAG,IAAI,CAMtE"}
@@ -0,0 +1,77 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ const SCHEMA_CANDIDATES = ['prisma/schema.prisma', 'schema.prisma'];
4
+ const CLIENT_ENTRY_NAMES = ['client.ts', 'client.js', 'client.mjs', 'index.ts', 'index.js'];
5
+ const OUTPUT_DIR_FALLBACKS = [['generated', 'prisma'], ['src', 'generated'], ['generated']];
6
+ /** Locate the consumer project's Prisma schema file. */
7
+ export function findPrismaSchemaFile(cwd = process.cwd()) {
8
+ for (const rel of SCHEMA_CANDIDATES) {
9
+ const candidate = path.join(cwd, rel);
10
+ if (existsSync(candidate))
11
+ return candidate;
12
+ }
13
+ return null;
14
+ }
15
+ /**
16
+ * Parse `generator client { output = "..." }` and return the absolute output
17
+ * directory. Relative paths resolve from the schema file's directory (Prisma
18
+ * convention), including `../generated/prisma` layouts.
19
+ */
20
+ export function parsePrismaGeneratorOutputDir(schemaPath) {
21
+ let content;
22
+ try {
23
+ content = readFileSync(schemaPath, 'utf8');
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ const match = content.match(/output\s*=\s*"([^"]+)"/);
29
+ if (!match?.[1])
30
+ return null;
31
+ const raw = match[1].replace(/\//g, path.sep);
32
+ if (path.isAbsolute(raw))
33
+ return raw;
34
+ return path.resolve(path.dirname(schemaPath), raw);
35
+ }
36
+ /** First existing Prisma client entry file inside `dir`. */
37
+ export function resolveClientEntryInDir(dir) {
38
+ for (const name of CLIENT_ENTRY_NAMES) {
39
+ const candidate = path.join(dir, name);
40
+ if (existsSync(candidate))
41
+ return candidate;
42
+ }
43
+ return null;
44
+ }
45
+ /**
46
+ * Resolve the generated Prisma client module path for CLI database commands.
47
+ * Reads the schema's custom `output` path before falling back to common
48
+ * scaffold locations (`generated/prisma`, `src/generated`, …).
49
+ */
50
+ export function resolveProjectPrismaClientModule(cwd = process.cwd()) {
51
+ const schemaPath = findPrismaSchemaFile(cwd);
52
+ if (schemaPath) {
53
+ const outputDir = parsePrismaGeneratorOutputDir(schemaPath);
54
+ if (outputDir) {
55
+ const entry = resolveClientEntryInDir(outputDir);
56
+ if (entry)
57
+ return entry;
58
+ }
59
+ }
60
+ for (const parts of OUTPUT_DIR_FALLBACKS) {
61
+ const entry = resolveClientEntryInDir(path.resolve(cwd, ...parts));
62
+ if (entry)
63
+ return entry;
64
+ }
65
+ return null;
66
+ }
67
+ /** Project helpers that wrap Prisma 7 + driver adapters (preferred when present). */
68
+ export const LIB_PRISMA_HELPERS = ['lib/prisma.ts', 'lib/prisma.js', 'lib/prisma.mjs'];
69
+ export function findLibPrismaHelper(cwd = process.cwd()) {
70
+ for (const rel of LIB_PRISMA_HELPERS) {
71
+ const candidate = path.join(cwd, rel);
72
+ if (existsSync(candidate))
73
+ return candidate;
74
+ }
75
+ return null;
76
+ }
77
+ //# sourceMappingURL=prisma-client-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-client-path.js","sourceRoot":"","sources":["../../src/utils/prisma-client-path.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actuate-media/cli",
3
- "version": "0.11.3",
3
+ "version": "0.12.1",
4
4
  "description": "CLI for Actuate CMS — migrations, codegen, imports, exports, and upgrades",
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,7 +28,7 @@
28
28
  "devDependencies": {
29
29
  "typescript": "^5.7.0",
30
30
  "vitest": "^4.1.0",
31
- "@actuate-media/cms-core": "0.77.3"
31
+ "@actuate-media/cms-core": "0.78.0"
32
32
  },
33
33
  "scripts": {
34
34
  "build": "tsc",
@@ -0,0 +1,87 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
2
+ import { tmpdir } from 'node:os'
3
+ import path from 'node:path'
4
+ import { afterEach, describe, expect, it } from 'vitest'
5
+
6
+ import {
7
+ findPrismaSchemaFile,
8
+ parsePrismaGeneratorOutputDir,
9
+ resolveClientEntryInDir,
10
+ resolveProjectPrismaClientModule,
11
+ } from '../utils/prisma-client-path.js'
12
+
13
+ describe('prisma-client-path', () => {
14
+ let tempDir: string | null = null
15
+
16
+ afterEach(() => {
17
+ if (tempDir) rmSync(tempDir, { recursive: true, force: true })
18
+ tempDir = null
19
+ })
20
+
21
+ function writeSchema(root: string, output: string) {
22
+ mkdirSync(path.join(root, 'prisma'), { recursive: true })
23
+ writeFileSync(
24
+ path.join(root, 'prisma', 'schema.prisma'),
25
+ `generator client {
26
+ provider = "prisma-client"
27
+ output = "${output}"
28
+ }
29
+
30
+ datasource db {
31
+ provider = "postgresql"
32
+ }
33
+ `,
34
+ )
35
+ }
36
+
37
+ function writeClient(clientPath: string) {
38
+ mkdirSync(path.dirname(clientPath), { recursive: true })
39
+ writeFileSync(clientPath, 'export class PrismaClient {}')
40
+ }
41
+
42
+ it('finds prisma/schema.prisma in the project root', () => {
43
+ tempDir = mkdtempSync(path.join(tmpdir(), 'actuate-cli-prisma-'))
44
+ writeSchema(tempDir, '../generated/prisma')
45
+ expect(findPrismaSchemaFile(tempDir)).toBe(path.join(tempDir, 'prisma', 'schema.prisma'))
46
+ })
47
+
48
+ it('resolves relative generator output from the schema directory', () => {
49
+ tempDir = mkdtempSync(path.join(tmpdir(), 'actuate-cli-prisma-'))
50
+ writeSchema(tempDir, '../generated/prisma')
51
+ const schemaPath = path.join(tempDir, 'prisma', 'schema.prisma')
52
+ expect(parsePrismaGeneratorOutputDir(schemaPath)).toBe(
53
+ path.join(tempDir, 'generated', 'prisma'),
54
+ )
55
+ })
56
+
57
+ it('resolves custom output paths used by Prisma 7 scaffolds', () => {
58
+ tempDir = mkdtempSync(path.join(tmpdir(), 'actuate-cli-prisma-'))
59
+ writeSchema(tempDir, '../src/generated/prisma')
60
+ const schemaPath = path.join(tempDir, 'prisma', 'schema.prisma')
61
+ expect(parsePrismaGeneratorOutputDir(schemaPath)).toBe(
62
+ path.join(tempDir, 'src', 'generated', 'prisma'),
63
+ )
64
+ })
65
+
66
+ it('locates client.ts via schema output before default fallbacks', () => {
67
+ tempDir = mkdtempSync(path.join(tmpdir(), 'actuate-cli-prisma-'))
68
+ writeSchema(tempDir, '../src/generated/prisma')
69
+ const clientPath = path.join(tempDir, 'src', 'generated', 'prisma', 'client.ts')
70
+ writeClient(clientPath)
71
+ expect(resolveProjectPrismaClientModule(tempDir)).toBe(clientPath)
72
+ })
73
+
74
+ it('falls back to generated/prisma/client.ts when schema output is missing', () => {
75
+ tempDir = mkdtempSync(path.join(tmpdir(), 'actuate-cli-prisma-'))
76
+ const clientPath = path.join(tempDir, 'generated', 'prisma', 'client.ts')
77
+ writeClient(clientPath)
78
+ expect(resolveProjectPrismaClientModule(tempDir)).toBe(clientPath)
79
+ })
80
+
81
+ it('resolveClientEntryInDir prefers client.ts over index.ts', () => {
82
+ tempDir = mkdtempSync(path.join(tmpdir(), 'actuate-cli-prisma-entry-'))
83
+ writeFileSync(path.join(tempDir, 'index.ts'), 'export {}')
84
+ writeFileSync(path.join(tempDir, 'client.ts'), 'export class PrismaClient {}')
85
+ expect(resolveClientEntryInDir(tempDir)).toBe(path.join(tempDir, 'client.ts'))
86
+ })
87
+ })
@@ -0,0 +1,107 @@
1
+ import { Command } from 'commander'
2
+ import { resolve } from 'node:path'
3
+ import { pathToFileURL } from 'node:url'
4
+ import ora from 'ora'
5
+ import { connectProjectDatabase } from '../utils/database.js'
6
+ import { loadProjectEnv } from '../utils/load-dotenv.js'
7
+ import { logger } from '../utils/logger.js'
8
+
9
+ interface MigrateMetaOptions {
10
+ dryRun?: boolean
11
+ batchSize?: string
12
+ config?: string
13
+ }
14
+
15
+ async function loadProjectConfig(configPath: string): Promise<void> {
16
+ try {
17
+ const { setActuateConfig } = await import('@actuate-media/cms-core')
18
+ const { tsImport } = await import('tsx/esm/api')
19
+ const abs = pathToFileURL(resolve(configPath)).href
20
+ const mod = (await tsImport(abs, import.meta.url)) as {
21
+ default?: unknown
22
+ config?: unknown
23
+ }
24
+ const config = mod.default ?? mod.config
25
+ if (config && typeof config === 'object') {
26
+ setActuateConfig(config as never)
27
+ }
28
+ } catch {
29
+ logger.warn(
30
+ `Could not load ${configPath} — meta defaults will use collection slug heuristics only.`,
31
+ )
32
+ }
33
+ }
34
+
35
+ /** `actuate migrate:meta` — backfill metaTitle/metaDescription on content documents. */
36
+ async function runMigrateMeta(options: MigrateMetaOptions): Promise<void> {
37
+ const dryRun = options.dryRun ?? false
38
+ const batchSize = options.batchSize ? Number.parseInt(options.batchSize, 10) : undefined
39
+ if (batchSize !== undefined && (!Number.isFinite(batchSize) || batchSize <= 0)) {
40
+ logger.error('--batch-size must be a positive integer.')
41
+ process.exit(1)
42
+ }
43
+
44
+ await loadProjectEnv(process.cwd()).then((env) => {
45
+ for (const [key, value] of Object.entries(env)) {
46
+ if (value !== undefined && process.env[key] === undefined) {
47
+ process.env[key] = value
48
+ }
49
+ }
50
+ })
51
+ await loadProjectConfig(options.config ?? 'actuate.config.ts')
52
+
53
+ let connection: { db: unknown; disconnect: () => Promise<void> } | null = null
54
+ const spinner = ora(
55
+ dryRun ? 'Scanning content documents (dry run)…' : 'Backfilling meta title/description…',
56
+ ).start()
57
+
58
+ try {
59
+ connection = await connectProjectDatabase()
60
+ const db = connection.db as {
61
+ user: { findFirst: (args: unknown) => Promise<{ id: string } | null> }
62
+ }
63
+
64
+ const { migrateMetaFields } = await import('@actuate-media/cms-core')
65
+
66
+ const admin = await db.user.findFirst({ where: { role: 'ADMIN' } })
67
+ if (!admin) {
68
+ spinner.fail('No ADMIN user found. Create an admin (setup wizard or seed) first.')
69
+ process.exit(1)
70
+ }
71
+
72
+ const ctx = { userId: admin.id, role: 'ADMIN', db: connection.db, locale: 'en' }
73
+ const result = await migrateMetaFields(ctx, { dryRun, batchSize })
74
+
75
+ spinner.succeed(
76
+ dryRun
77
+ ? `Dry run complete — ${result.migrated} document(s) would be updated.`
78
+ : `Updated ${result.migrated} document(s).`,
79
+ )
80
+ logger.info(` Scanned: ${result.scanned}`)
81
+ logger.info(` Migrated: ${result.migrated}`)
82
+ logger.info(` Skipped: ${result.skipped}`)
83
+ if (result.migratedIds.length > 0) {
84
+ logger.info(` IDs: ${result.migratedIds.join(', ')}`)
85
+ }
86
+ logger.info('')
87
+ logger.info('Next: re-run the SEO audit in Settings → SEO → Audit.')
88
+ } catch (err) {
89
+ const message = err instanceof Error ? err.message : String(err)
90
+ spinner.fail(`Meta migration failed: ${message}`)
91
+ process.exit(1)
92
+ } finally {
93
+ await connection?.disconnect()
94
+ }
95
+ }
96
+
97
+ export function registerMigrateMetaCommand(program: Command): void {
98
+ program
99
+ .command('migrate:meta')
100
+ .description(
101
+ 'Backfill metaTitle/metaDescription from title templates and key takeaways. Idempotent.',
102
+ )
103
+ .option('--dry-run', 'Report what would change without writing')
104
+ .option('--batch-size <n>', 'Documents to scan per DB page (default 100)')
105
+ .option('--config <path>', 'Path to actuate.config.ts', 'actuate.config.ts')
106
+ .action(runMigrateMeta)
107
+ }
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ import { Command } from 'commander'
3
3
  import { registerMigrateCommand } from './commands/migrate.js'
4
4
  import { registerMigrateSectionsCommand } from './commands/migrate-sections.js'
5
5
  import { registerMigrateSchemaCommand } from './commands/migrate-schema.js'
6
+ import { registerMigrateMetaCommand } from './commands/migrate-meta.js'
6
7
  import { registerGenerateCommand } from './commands/generate.js'
7
8
  import { registerSeedCommand } from './commands/seed.js'
8
9
  import { registerImportCommand } from './commands/import.js'
@@ -31,6 +32,7 @@ program
31
32
  registerMigrateCommand(program)
32
33
  registerMigrateSectionsCommand(program)
33
34
  registerMigrateSchemaCommand(program)
35
+ registerMigrateMetaCommand(program)
34
36
  registerGenerateCommand(program)
35
37
  registerSeedCommand(program)
36
38
  registerImportCommand(program)
@@ -1,8 +1,9 @@
1
- import { existsSync } from 'node:fs'
2
1
  import { createRequire } from 'node:module'
3
2
  import path from 'node:path'
4
3
  import { pathToFileURL } from 'node:url'
5
4
 
5
+ import { findLibPrismaHelper, resolveProjectPrismaClientModule } from './prisma-client-path.js'
6
+
6
7
  /**
7
8
  * Shared helper for CLI commands that need a live connection to the consumer
8
9
  * project's database (seed, populate, data migrations…).
@@ -10,9 +11,11 @@ import { pathToFileURL } from 'node:url'
10
11
  * Resolution order mirrors how a scaffolded Actuate project exposes Prisma:
11
12
  * 1. If `cms-core` already has an initialized client (e.g. set up by the host
12
13
  * app in-process), reuse it.
13
- * 2. Otherwise build a client from the project's generated Prisma client
14
- * (`generated/prisma/client.ts`, Prisma 7 + pg driver adapter).
15
- * 3. Fall back to a plain `@prisma/client` resolved from the project.
14
+ * 2. Otherwise call `getPrisma()` from `lib/prisma.ts` when the project ships
15
+ * one (Prisma 7 + pg adapter / Accelerate — same path Next.js uses).
16
+ * 3. Otherwise build a client from the schema's `output` path (custom or
17
+ * default `generated/prisma/client.ts`) via tsx + pg driver adapter.
18
+ * 4. Fall back to a plain `@prisma/client` resolved from the project.
16
19
  *
17
20
  * The returned `disconnect()` only tears down clients this helper created — a
18
21
  * pre-initialized in-process client is left untouched.
@@ -40,20 +43,51 @@ export async function connectProjectDatabase(): Promise<{
40
43
  }
41
44
  }
42
45
 
46
+ const ACCELERATED_DB_URL_PREFIXES = ['prisma://', 'prisma+postgres://'] as const
47
+
48
+ function isAcceleratedDbUrl(url: string | undefined): boolean {
49
+ const trimmed = url?.trim()
50
+ if (!trimmed) return false
51
+ return ACCELERATED_DB_URL_PREFIXES.some((prefix) => trimmed.startsWith(prefix))
52
+ }
53
+
54
+ async function importModule(specifier: string, requireFromProject: NodeRequire): Promise<unknown> {
55
+ const href = pathToFileURL(requireFromProject.resolve(specifier)).href
56
+ return import(href)
57
+ }
58
+
59
+ async function tryLibPrismaClient(): Promise<any | null> {
60
+ const helperPath = findLibPrismaHelper()
61
+ if (!helperPath) return null
62
+
63
+ const { tsImport } = await import('tsx/esm/api')
64
+ const mod = (await tsImport(pathToFileURL(helperPath).href, import.meta.url)) as {
65
+ getPrisma?: () => unknown
66
+ default?: { getPrisma?: () => unknown }
67
+ }
68
+ const getPrisma = mod.getPrisma ?? mod.default?.getPrisma
69
+ if (typeof getPrisma !== 'function') return null
70
+ return getPrisma()
71
+ }
72
+
43
73
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- see above.
44
74
  async function createProjectPrismaClient(): Promise<any> {
45
75
  if (!process.env.DATABASE_URL) {
46
76
  throw new Error('DATABASE_URL is required to connect to the project database.')
47
77
  }
48
78
 
79
+ const fromLib = await tryLibPrismaClient()
80
+ if (fromLib) return fromLib
81
+
49
82
  const requireFromProject = createRequire(path.join(process.cwd(), 'package.json'))
50
- const generatedClient = path.resolve('generated', 'prisma', 'client.ts')
83
+ const generatedClient = resolveProjectPrismaClientModule()
51
84
 
52
- if (existsSync(generatedClient)) {
85
+ if (generatedClient) {
86
+ const url = process.env.DATABASE_URL
53
87
  const [{ tsImport }, adapterModule, pgModule] = await Promise.all([
54
88
  import('tsx/esm/api'),
55
- import(pathToFileURL(requireFromProject.resolve('@prisma/adapter-pg')).href),
56
- import(pathToFileURL(requireFromProject.resolve('pg')).href),
89
+ importModule('@prisma/adapter-pg', requireFromProject),
90
+ importModule('pg', requireFromProject),
57
91
  ])
58
92
  const { PrismaClient } = (await tsImport(
59
93
  pathToFileURL(generatedClient).href,
@@ -61,16 +95,31 @@ async function createProjectPrismaClient(): Promise<any> {
61
95
  )) as {
62
96
  PrismaClient: new (options?: unknown) => any
63
97
  }
98
+
99
+ if (isAcceleratedDbUrl(url)) {
100
+ let withAccelerate: () => unknown
101
+ try {
102
+ const ext = (await importModule('@prisma/extension-accelerate', requireFromProject)) as {
103
+ withAccelerate: () => unknown
104
+ }
105
+ withAccelerate = ext.withAccelerate
106
+ } catch {
107
+ throw new Error(
108
+ 'DATABASE_URL uses Prisma Accelerate but @prisma/extension-accelerate is not installed. Run `lib/prisma.ts` via adding it, or install the extension.',
109
+ )
110
+ }
111
+ const client = new PrismaClient({ accelerateUrl: url } as never).$extends(withAccelerate())
112
+ return client
113
+ }
114
+
64
115
  const { PrismaPg } = adapterModule as { PrismaPg: new (pool: unknown) => unknown }
65
116
  const pg = (pgModule as { default?: typeof pgModule }).default ?? pgModule
66
- const pool = new (pg as any).Pool({ connectionString: process.env.DATABASE_URL })
117
+ const pool = new (pg as any).Pool({ connectionString: url })
67
118
  const adapter = new PrismaPg(pool)
68
119
  return new PrismaClient({ adapter } as any)
69
120
  }
70
121
 
71
- const clientModule = (await import(
72
- pathToFileURL(requireFromProject.resolve('@prisma/client')).href
73
- )) as {
122
+ const clientModule = (await importModule('@prisma/client', requireFromProject)) as {
74
123
  PrismaClient: new () => any
75
124
  }
76
125
  return new clientModule.PrismaClient()
@@ -0,0 +1,81 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import path from 'node:path'
3
+
4
+ const SCHEMA_CANDIDATES = ['prisma/schema.prisma', 'schema.prisma'] as const
5
+
6
+ const CLIENT_ENTRY_NAMES = ['client.ts', 'client.js', 'client.mjs', 'index.ts', 'index.js'] as const
7
+
8
+ const OUTPUT_DIR_FALLBACKS = [['generated', 'prisma'], ['src', 'generated'], ['generated']] as const
9
+
10
+ /** Locate the consumer project's Prisma schema file. */
11
+ export function findPrismaSchemaFile(cwd = process.cwd()): string | null {
12
+ for (const rel of SCHEMA_CANDIDATES) {
13
+ const candidate = path.join(cwd, rel)
14
+ if (existsSync(candidate)) return candidate
15
+ }
16
+ return null
17
+ }
18
+
19
+ /**
20
+ * Parse `generator client { output = "..." }` and return the absolute output
21
+ * directory. Relative paths resolve from the schema file's directory (Prisma
22
+ * convention), including `../generated/prisma` layouts.
23
+ */
24
+ export function parsePrismaGeneratorOutputDir(schemaPath: string): string | null {
25
+ let content: string
26
+ try {
27
+ content = readFileSync(schemaPath, 'utf8')
28
+ } catch {
29
+ return null
30
+ }
31
+
32
+ const match = content.match(/output\s*=\s*"([^"]+)"/)
33
+ if (!match?.[1]) return null
34
+
35
+ const raw = match[1].replace(/\//g, path.sep)
36
+ if (path.isAbsolute(raw)) return raw
37
+ return path.resolve(path.dirname(schemaPath), raw)
38
+ }
39
+
40
+ /** First existing Prisma client entry file inside `dir`. */
41
+ export function resolveClientEntryInDir(dir: string): string | null {
42
+ for (const name of CLIENT_ENTRY_NAMES) {
43
+ const candidate = path.join(dir, name)
44
+ if (existsSync(candidate)) return candidate
45
+ }
46
+ return null
47
+ }
48
+
49
+ /**
50
+ * Resolve the generated Prisma client module path for CLI database commands.
51
+ * Reads the schema's custom `output` path before falling back to common
52
+ * scaffold locations (`generated/prisma`, `src/generated`, …).
53
+ */
54
+ export function resolveProjectPrismaClientModule(cwd = process.cwd()): string | null {
55
+ const schemaPath = findPrismaSchemaFile(cwd)
56
+ if (schemaPath) {
57
+ const outputDir = parsePrismaGeneratorOutputDir(schemaPath)
58
+ if (outputDir) {
59
+ const entry = resolveClientEntryInDir(outputDir)
60
+ if (entry) return entry
61
+ }
62
+ }
63
+
64
+ for (const parts of OUTPUT_DIR_FALLBACKS) {
65
+ const entry = resolveClientEntryInDir(path.resolve(cwd, ...parts))
66
+ if (entry) return entry
67
+ }
68
+
69
+ return null
70
+ }
71
+
72
+ /** Project helpers that wrap Prisma 7 + driver adapters (preferred when present). */
73
+ export const LIB_PRISMA_HELPERS = ['lib/prisma.ts', 'lib/prisma.js', 'lib/prisma.mjs'] as const
74
+
75
+ export function findLibPrismaHelper(cwd = process.cwd()): string | null {
76
+ for (const rel of LIB_PRISMA_HELPERS) {
77
+ const candidate = path.join(cwd, rel)
78
+ if (existsSync(candidate)) return candidate
79
+ }
80
+ return null
81
+ }