@actuate-media/cli 0.11.2 โ†’ 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
1
 
2
- > @actuate-media/cli@0.11.2 build /home/runner/work/actuatecms/actuatecms/packages/cli
2
+ > @actuate-media/cli@0.12.0 build /home/runner/work/actuatecms/actuatecms/packages/cli
3
3
  > tsc
4
4
 
@@ -1,60 +1,60 @@
1
1
 
2
- > @actuate-media/cli@0.11.2 test /home/runner/work/actuatecms/actuatecms/packages/cli
2
+ > @actuate-media/cli@0.12.0 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) 353ms
9
- โœ“ dist/__tests__/seed.test.js (10 tests) 117ms
10
- โœ“ src/__tests__/db-sync.test.ts (10 tests) 495ms
11
- โœ“ dist/__tests__/deployment-diagnostics.test.js (9 tests) 99ms
12
- โœ“ src/__tests__/seed.test.ts (10 tests) 146ms
13
- โœ“ src/__tests__/deployment-diagnostics.test.ts (9 tests) 60ms
8
+ โœ“ dist/__tests__/db-sync.test.js (10 tests) 233ms
9
+ โœ“ dist/__tests__/seed.test.js (10 tests) 128ms
10
+ โœ“ src/__tests__/db-sync.test.ts (10 tests) 219ms
11
+ โœ“ dist/__tests__/deployment-diagnostics.test.js (9 tests) 35ms
12
+ โœ“ src/__tests__/seed.test.ts (10 tests) 164ms
13
+ โœ“ src/__tests__/deployment-diagnostics.test.ts (9 tests) 49ms
14
14
  - Reading canonical Actuate schema...
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
15
  โœ” Actuate CMS models added to schema.
17
16
  - Running prisma generate...
17
+ 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
18
18
  โœ” Prisma client generated.
19
19
  โ„น Run `npx prisma migrate dev --name actuate-cms` to create the migration.
20
20
 
21
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
22
- โ„น Install it first: `npm install @actuate-media/cms-core`.
23
22
  - Reading canonical Actuate schema...
24
-
25
23
  โœ– Could not locate @actuate-media/cms-core.
24
+ โ„น Install it first: `npm install @actuate-media/cms-core`.
25
+
26
26
  - Reading canonical Actuate schema...
27
27
  โœ” Actuate CMS models added to schema.
28
- - Running prisma generate...
29
28
  stdout | dist/__tests__/db-init.test.js > db:init (WS-D D5 โ€” canonical schema, no stale fragment) > command > is idempotent without --force
30
29
  โœ” Prisma client generated.
30
+ - Running prisma generate...
31
31
  โ„น Run `npx prisma migrate dev --name actuate-cms` to create the migration.
32
32
 
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) 265ms
35
+ โœ“ dist/__tests__/db-init.test.js (4 tests) 228ms
36
36
  - Reading canonical Actuate schema...
37
- โœ” Actuate CMS models added to schema.
38
- - Running prisma generate...
39
37
  stdout | src/__tests__/db-init.test.ts > db:init (WS-D D5 โ€” canonical schema, no stale fragment) > command > injects canonical models with @@map and no duplicate datasource/generator
40
38
  โœ” Prisma client generated.
41
39
  โ„น Run `npx prisma migrate dev --name actuate-cms` to create the migration.
42
40
 
41
+ โœ” Actuate CMS models added to schema.
42
+ - Running prisma generate...
43
+ - Reading canonical Actuate schema...
43
44
  stdout | src/__tests__/db-init.test.ts > db:init (WS-D D5 โ€” canonical schema, no stale fragment) > command > fails clearly when cms-core cannot be located
45
+ โœ– Could not locate @actuate-media/cms-core.
44
46
  โ„น Install it first: `npm install @actuate-media/cms-core`.
45
- - Reading canonical Actuate schema...
46
47
 
47
- โœ– Could not locate @actuate-media/cms-core.
48
48
  - Reading canonical Actuate schema...
49
49
  stdout | src/__tests__/db-init.test.ts > db:init (WS-D D5 โ€” canonical schema, no stale fragment) > command > is idempotent without --force
50
50
  โœ” Prisma client generated.
51
- โ„น Run `npx prisma migrate dev --name actuate-cms` to create the migration.
52
-
53
51
  โœ” Actuate CMS models added to schema.
52
+ โ„น Run `npx prisma migrate dev --name actuate-cms` to create the migration.
54
53
  - Running prisma generate...
54
+
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) 203ms
57
+ โœ“ src/__tests__/db-init.test.ts (4 tests) 246ms
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) 330ms
186
+ โœ“ dist/__tests__/mcp-init.test.js (6 tests) 249ms
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,13 +312,13 @@ 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) 523ms
316
- โœ“ dist/__tests__/form-seed.test.js (10 tests) 66ms
317
- โœ“ src/__tests__/form-seed.test.ts (10 tests) 31ms
318
- โœ“ dist/__tests__/vercel-env-matrix.test.js (6 tests) 50ms
319
- โœ“ src/__tests__/vercel-env-matrix.test.ts (6 tests) 61ms
320
- โœ“ dist/__tests__/load-dotenv.test.js (3 tests) 75ms
321
- โœ“ src/__tests__/load-dotenv.test.ts (3 tests) 100ms
315
+ โœ“ src/__tests__/mcp-init.test.ts (6 tests) 134ms
316
+ โœ“ dist/__tests__/form-seed.test.js (10 tests) 31ms
317
+ โœ“ src/__tests__/form-seed.test.ts (10 tests) 34ms
318
+ โœ“ dist/__tests__/vercel-env-matrix.test.js (6 tests) 28ms
319
+ โœ“ src/__tests__/vercel-env-matrix.test.ts (6 tests) 30ms
320
+ โœ“ dist/__tests__/load-dotenv.test.js (3 tests) 37ms
321
+ โœ“ src/__tests__/load-dotenv.test.ts (3 tests) 44ms
322
322
  - Reading canonical Actuate schema...
323
323
  โœ” Actuate CMS models added to schema.
324
324
  - Running prisma generate...
@@ -326,20 +326,20 @@ Next steps
326
326
  โœ” Prisma client generated.
327
327
  โ„น Run `npx prisma migrate dev --name actuate-cms` to create the migration.
328
328
 
329
- โœ“ dist/__tests__/schema-fragment.test.js (1 test) 119ms
329
+ โœ“ dist/__tests__/schema-fragment.test.js (1 test) 80ms
330
330
  - Reading canonical Actuate schema...
331
331
  โœ” Actuate CMS models added to schema.
332
- - Running prisma generate...
333
332
  stdout | src/__tests__/schema-fragment.test.ts > db:init schema fragment > injects all deploy-critical Actuate models
334
333
  โœ” Prisma client generated.
334
+ - Running prisma generate...
335
335
  โ„น Run `npx prisma migrate dev --name actuate-cms` to create the migration.
336
336
 
337
- โœ“ src/__tests__/schema-fragment.test.ts (1 test) 114ms
338
- โœ“ dist/__tests__/init.test.js (2 tests) 33ms
339
- โœ“ src/__tests__/init.test.ts (2 tests) 30ms
337
+ โœ“ src/__tests__/schema-fragment.test.ts (1 test) 66ms
338
+ โœ“ dist/__tests__/init.test.js (2 tests) 16ms
339
+ โœ“ src/__tests__/init.test.ts (2 tests) 15ms
340
340
 
341
341
   Test Files  20 passed (20)
342
342
   Tests  122 passed (122)
343
-  Start at  18:10:11
344
-  Duration  28.76s (transform 2.79s, setup 0ms, import 7.35s, tests 3.27s, environment 13ms)
343
+  Start at  05:27:27
344
+  Duration  21.87s (transform 2.20s, setup 0ms, import 5.83s, tests 2.07s, environment 21ms)
345
345
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @actuate-media/cli
2
2
 
3
+ ## 0.12.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 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.
8
+
9
+ ## 0.11.3
10
+
11
+ ### Patch Changes
12
+
13
+ - 9d4e24c: Hide the redundant site name headline on the login screen and in the admin sidebar when a logo image is displayed.
14
+
15
+ Serve favicons as properly sized 32ร—32 PNGs (and 180ร—180 apple-touch icons) from dedicated public routes so SVG and oversized sources render correctly in browser tabs.
16
+
17
+ Align SEO audit schema/canonical detection with public page rendering, add `actuate migrate:schema` to backfill structuredDataType on existing content, and fix route-contract tests for new favicon endpoints.
18
+
3
19
  ## 0.11.2
4
20
 
5
21
  ### Patch Changes
@@ -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":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,GAAG,MAAM,KAAK,CAAA;AACrB,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAQ3C,KAAK,UAAU,iBAAiB,CAAC,UAAkB;IACjD,IAAI,CAAC;QACH,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,yBAAyB,CAAC,CAAA;QACpE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAA;QAChD,MAAM,GAAG,GAAG,aAAa,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAA;QACnD,MAAM,GAAG,GAAG,CAAC,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAGhD,CAAA;QACD,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,MAAM,CAAA;QACxC,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YACzC,gBAAgB,CAAC,MAAe,CAAC,CAAA;QACnC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,CAAC,IAAI,CACT,kBAAkB,UAAU,4DAA4D,CACzF,CAAA;IACH,CAAC;AACH,CAAC;AAED,wFAAwF;AACxF,KAAK,UAAU,cAAc,CAAC,OAA2B;IACvD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,KAAK,CAAA;IACtC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACxF,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,CAAC,CAAC,EAAE,CAAC;QAC/E,MAAM,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAA;QACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;QAC/C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/C,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC;gBAC1D,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;YAC1B,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAA;IACF,MAAM,iBAAiB,CAAC,OAAO,CAAC,MAAM,IAAI,mBAAmB,CAAC,CAAA;IAE9D,IAAI,UAAU,GAA4D,IAAI,CAAA;IAC9E,MAAM,OAAO,GAAG,GAAG,CACjB,MAAM,CAAC,CAAC,CAAC,uCAAuC,CAAC,CAAC,CAAC,qCAAqC,CACzF,CAAC,KAAK,EAAE,CAAA;IAET,IAAI,CAAC;QACH,UAAU,GAAG,MAAM,sBAAsB,EAAE,CAAA;QAC3C,MAAM,EAAE,GAAG,UAAU,CAAC,EAErB,CAAA;QAED,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC,yBAAyB,CAAC,CAAA;QAErE,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,CAAA;QACnE,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,oEAAoE,CAAC,CAAA;YAClF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QAED,MAAM,GAAG,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;QAChF,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAA;QAElE,OAAO,CAAC,OAAO,CACb,MAAM;YACJ,CAAC,CAAC,sBAAsB,MAAM,CAAC,QAAQ,gCAAgC;YACvE,CAAC,CAAC,WAAW,MAAM,CAAC,QAAQ,eAAe,CAC9C,CAAA;QACD,MAAM,CAAC,IAAI,CAAC,eAAe,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;QAC5C,MAAM,CAAC,IAAI,CAAC,eAAe,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAA;QAC7C,MAAM,CAAC,IAAI,CAAC,eAAe,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;QAC5C,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC,eAAe,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAC7D,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACf,MAAM,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAA;IACtE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAChE,OAAO,CAAC,IAAI,CAAC,0BAA0B,OAAO,EAAE,CAAC,CAAA;QACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;YAAS,CAAC;QACT,MAAM,UAAU,EAAE,UAAU,EAAE,CAAA;IAChC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,OAAgB;IACzD,OAAO;SACJ,OAAO,CAAC,cAAc,CAAC;SACvB,WAAW,CACV,wFAAwF,CACzF;SACA,MAAM,CAAC,WAAW,EAAE,0CAA0C,CAAC;SAC/D,MAAM,CAAC,kBAAkB,EAAE,6CAA6C,CAAC;SACzE,MAAM,CAAC,iBAAiB,EAAE,2BAA2B,EAAE,mBAAmB,CAAC;SAC3E,MAAM,CAAC,cAAc,CAAC,CAAA;AAC3B,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerMigrateSchemaCommand(program: Command): void;
3
+ //# sourceMappingURL=migrate-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-schema.d.ts","sourceRoot":"","sources":["../../src/commands/migrate-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAmGnC,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAUnE"}
@@ -0,0 +1,84 @@
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} โ€” schema defaults will use collection slug heuristics only.`);
20
+ }
21
+ }
22
+ /**
23
+ * `actuate migrate:schema` โ€” backfill structuredDataType/schemaType and JSON-LD
24
+ * on published content documents. Idempotent.
25
+ */
26
+ async function runMigrateSchema(options) {
27
+ const dryRun = options.dryRun ?? false;
28
+ const batchSize = options.batchSize ? Number.parseInt(options.batchSize, 10) : undefined;
29
+ if (batchSize !== undefined && (!Number.isFinite(batchSize) || batchSize <= 0)) {
30
+ logger.error('--batch-size must be a positive integer.');
31
+ process.exit(1);
32
+ }
33
+ await loadProjectEnv(process.cwd()).then((env) => {
34
+ for (const [key, value] of Object.entries(env)) {
35
+ if (value !== undefined && process.env[key] === undefined) {
36
+ process.env[key] = value;
37
+ }
38
+ }
39
+ });
40
+ await loadProjectConfig(options.config ?? 'actuate.config.ts');
41
+ let connection = null;
42
+ const spinner = ora(dryRun ? 'Scanning content documents (dry run)โ€ฆ' : 'Backfilling structured data fieldsโ€ฆ').start();
43
+ try {
44
+ connection = await connectProjectDatabase();
45
+ const db = connection.db;
46
+ const { migrateStructuredDataFields } = await import('@actuate-media/cms-core');
47
+ const admin = await db.user.findFirst({ where: { role: 'ADMIN' } });
48
+ if (!admin) {
49
+ spinner.fail('No ADMIN user found. Create an admin (setup wizard or seed) first.');
50
+ process.exit(1);
51
+ }
52
+ const ctx = { userId: admin.id, role: 'ADMIN', db: connection.db, locale: 'en' };
53
+ const result = await migrateStructuredDataFields(ctx, { dryRun, batchSize });
54
+ spinner.succeed(dryRun
55
+ ? `Dry run complete โ€” ${result.migrated} document(s) would be updated.`
56
+ : `Updated ${result.migrated} document(s).`);
57
+ logger.info(` Scanned: ${result.scanned}`);
58
+ logger.info(` Migrated: ${result.migrated}`);
59
+ logger.info(` Skipped: ${result.skipped}`);
60
+ if (result.migratedIds.length > 0) {
61
+ logger.info(` IDs: ${result.migratedIds.join(', ')}`);
62
+ }
63
+ logger.info('');
64
+ logger.info('Next: re-run the SEO audit in Settings โ†’ SEO โ†’ Audit.');
65
+ }
66
+ catch (err) {
67
+ const message = err instanceof Error ? err.message : String(err);
68
+ spinner.fail(`Schema migration failed: ${message}`);
69
+ process.exit(1);
70
+ }
71
+ finally {
72
+ await connection?.disconnect();
73
+ }
74
+ }
75
+ export function registerMigrateSchemaCommand(program) {
76
+ program
77
+ .command('migrate:schema')
78
+ .description('Backfill structuredDataType/schemaType (and published JSON-LD) on content documents. Idempotent.')
79
+ .option('--dry-run', 'Report what would change without writing')
80
+ .option('--batch-size <n>', 'Documents to scan per DB page (default 100)')
81
+ .option('--config <path>', 'Path to actuate.config.ts', 'actuate.config.ts')
82
+ .action(runMigrateSchema);
83
+ }
84
+ //# sourceMappingURL=migrate-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-schema.js","sourceRoot":"","sources":["../../src/commands/migrate-schema.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,6 +2,8 @@
2
2
  import { Command } from 'commander';
3
3
  import { registerMigrateCommand } from './commands/migrate.js';
4
4
  import { registerMigrateSectionsCommand } from './commands/migrate-sections.js';
5
+ import { registerMigrateSchemaCommand } from './commands/migrate-schema.js';
6
+ import { registerMigrateMetaCommand } from './commands/migrate-meta.js';
5
7
  import { registerGenerateCommand } from './commands/generate.js';
6
8
  import { registerSeedCommand } from './commands/seed.js';
7
9
  import { registerImportCommand } from './commands/import.js';
@@ -22,6 +24,8 @@ program
22
24
  .version('0.1.0');
23
25
  registerMigrateCommand(program);
24
26
  registerMigrateSectionsCommand(program);
27
+ registerMigrateSchemaCommand(program);
28
+ registerMigrateMetaCommand(program);
25
29
  registerGenerateCommand(program);
26
30
  registerSeedCommand(program);
27
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,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,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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actuate-media/cli",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
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.76.3"
31
+ "@actuate-media/cms-core": "0.78.0"
32
32
  },
33
33
  "scripts": {
34
34
  "build": "tsc",
@@ -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
+ }
@@ -0,0 +1,110 @@
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 MigrateSchemaOptions {
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} โ€” schema defaults will use collection slug heuristics only.`,
31
+ )
32
+ }
33
+ }
34
+
35
+ /**
36
+ * `actuate migrate:schema` โ€” backfill structuredDataType/schemaType and JSON-LD
37
+ * on published content documents. Idempotent.
38
+ */
39
+ async function runMigrateSchema(options: MigrateSchemaOptions): Promise<void> {
40
+ const dryRun = options.dryRun ?? false
41
+ const batchSize = options.batchSize ? Number.parseInt(options.batchSize, 10) : undefined
42
+ if (batchSize !== undefined && (!Number.isFinite(batchSize) || batchSize <= 0)) {
43
+ logger.error('--batch-size must be a positive integer.')
44
+ process.exit(1)
45
+ }
46
+
47
+ await loadProjectEnv(process.cwd()).then((env) => {
48
+ for (const [key, value] of Object.entries(env)) {
49
+ if (value !== undefined && process.env[key] === undefined) {
50
+ process.env[key] = value
51
+ }
52
+ }
53
+ })
54
+ await loadProjectConfig(options.config ?? 'actuate.config.ts')
55
+
56
+ let connection: { db: unknown; disconnect: () => Promise<void> } | null = null
57
+ const spinner = ora(
58
+ dryRun ? 'Scanning content documents (dry run)โ€ฆ' : 'Backfilling structured data fieldsโ€ฆ',
59
+ ).start()
60
+
61
+ try {
62
+ connection = await connectProjectDatabase()
63
+ const db = connection.db as {
64
+ user: { findFirst: (args: unknown) => Promise<{ id: string } | null> }
65
+ }
66
+
67
+ const { migrateStructuredDataFields } = await import('@actuate-media/cms-core')
68
+
69
+ const admin = await db.user.findFirst({ where: { role: 'ADMIN' } })
70
+ if (!admin) {
71
+ spinner.fail('No ADMIN user found. Create an admin (setup wizard or seed) first.')
72
+ process.exit(1)
73
+ }
74
+
75
+ const ctx = { userId: admin.id, role: 'ADMIN', db: connection.db, locale: 'en' }
76
+ const result = await migrateStructuredDataFields(ctx, { dryRun, batchSize })
77
+
78
+ spinner.succeed(
79
+ dryRun
80
+ ? `Dry run complete โ€” ${result.migrated} document(s) would be updated.`
81
+ : `Updated ${result.migrated} document(s).`,
82
+ )
83
+ logger.info(` Scanned: ${result.scanned}`)
84
+ logger.info(` Migrated: ${result.migrated}`)
85
+ logger.info(` Skipped: ${result.skipped}`)
86
+ if (result.migratedIds.length > 0) {
87
+ logger.info(` IDs: ${result.migratedIds.join(', ')}`)
88
+ }
89
+ logger.info('')
90
+ logger.info('Next: re-run the SEO audit in Settings โ†’ SEO โ†’ Audit.')
91
+ } catch (err) {
92
+ const message = err instanceof Error ? err.message : String(err)
93
+ spinner.fail(`Schema migration failed: ${message}`)
94
+ process.exit(1)
95
+ } finally {
96
+ await connection?.disconnect()
97
+ }
98
+ }
99
+
100
+ export function registerMigrateSchemaCommand(program: Command): void {
101
+ program
102
+ .command('migrate:schema')
103
+ .description(
104
+ 'Backfill structuredDataType/schemaType (and published JSON-LD) on content documents. Idempotent.',
105
+ )
106
+ .option('--dry-run', 'Report what would change without writing')
107
+ .option('--batch-size <n>', 'Documents to scan per DB page (default 100)')
108
+ .option('--config <path>', 'Path to actuate.config.ts', 'actuate.config.ts')
109
+ .action(runMigrateSchema)
110
+ }
package/src/index.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  import { Command } from 'commander'
3
3
  import { registerMigrateCommand } from './commands/migrate.js'
4
4
  import { registerMigrateSectionsCommand } from './commands/migrate-sections.js'
5
+ import { registerMigrateSchemaCommand } from './commands/migrate-schema.js'
6
+ import { registerMigrateMetaCommand } from './commands/migrate-meta.js'
5
7
  import { registerGenerateCommand } from './commands/generate.js'
6
8
  import { registerSeedCommand } from './commands/seed.js'
7
9
  import { registerImportCommand } from './commands/import.js'
@@ -29,6 +31,8 @@ program
29
31
 
30
32
  registerMigrateCommand(program)
31
33
  registerMigrateSectionsCommand(program)
34
+ registerMigrateSchemaCommand(program)
35
+ registerMigrateMetaCommand(program)
32
36
  registerGenerateCommand(program)
33
37
  registerSeedCommand(program)
34
38
  registerImportCommand(program)