@actuate-media/cli 0.4.0 → 0.4.2
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 +21 -10
- package/CHANGELOG.md +34 -0
- package/dist/__tests__/deployment-diagnostics.test.js +40 -0
- package/dist/__tests__/deployment-diagnostics.test.js.map +1 -1
- package/dist/__tests__/init.test.js.map +1 -1
- package/dist/__tests__/schema-fragment.test.js +1 -1
- package/dist/__tests__/schema-fragment.test.js.map +1 -1
- package/dist/__tests__/seed.test.js.map +1 -1
- package/dist/commands/db-init.d.ts +2 -2
- package/dist/commands/db-init.d.ts.map +1 -1
- package/dist/commands/db-init.js +32 -32
- package/dist/commands/db-init.js.map +1 -1
- package/dist/commands/db-status.d.ts +1 -1
- package/dist/commands/db-status.d.ts.map +1 -1
- package/dist/commands/db-status.js +33 -33
- package/dist/commands/db-status.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +55 -38
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/export.d.ts +1 -1
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +32 -32
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/generate.d.ts +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +8 -8
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/import.d.ts +1 -1
- package/dist/commands/import.d.ts.map +1 -1
- package/dist/commands/import.js +55 -58
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/migrate.d.ts +1 -1
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +18 -24
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/seed.d.ts +1 -1
- package/dist/commands/seed.d.ts.map +1 -1
- package/dist/commands/seed.js +156 -157
- package/dist/commands/seed.js.map +1 -1
- package/dist/commands/update-check.d.ts +1 -1
- package/dist/commands/update-check.d.ts.map +1 -1
- package/dist/commands/update-check.js +34 -27
- package/dist/commands/update-check.js.map +1 -1
- package/dist/commands/upgrade.d.ts +1 -1
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +41 -34
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/deployment/diagnostics.d.ts +2 -0
- package/dist/deployment/diagnostics.d.ts.map +1 -1
- package/dist/deployment/diagnostics.js +50 -1
- package/dist/deployment/diagnostics.js.map +1 -1
- package/dist/index.js +15 -15
- package/dist/index.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +5 -5
- package/dist/utils/logger.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/deployment-diagnostics.test.ts +100 -50
- package/src/__tests__/init.test.ts +17 -17
- package/src/__tests__/schema-fragment.test.ts +29 -25
- package/src/__tests__/seed.test.ts +25 -25
- package/src/commands/db-init.ts +59 -59
- package/src/commands/db-status.ts +70 -68
- package/src/commands/doctor.ts +110 -86
- package/src/commands/export.ts +65 -75
- package/src/commands/generate.ts +14 -16
- package/src/commands/import.ts +125 -140
- package/src/commands/init.ts +14 -14
- package/src/commands/migrate.ts +29 -35
- package/src/commands/seed.ts +294 -300
- package/src/commands/update-check.ts +77 -72
- package/src/commands/upgrade.ts +92 -85
- package/src/deployment/diagnostics.ts +124 -61
- package/src/index.ts +30 -30
- package/src/utils/logger.ts +10 -10
package/src/commands/export.ts
CHANGED
|
@@ -1,131 +1,121 @@
|
|
|
1
|
-
import { Command } from
|
|
2
|
-
import { writeFile } from
|
|
3
|
-
import ora from
|
|
4
|
-
import { logger } from
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import { writeFile } from 'node:fs/promises'
|
|
3
|
+
import ora from 'ora'
|
|
4
|
+
import { logger } from '../utils/logger.js'
|
|
5
5
|
|
|
6
6
|
interface ExportOptions {
|
|
7
|
-
collection?: string
|
|
8
|
-
format: string
|
|
9
|
-
output?: string
|
|
10
|
-
includeDrafts?: boolean
|
|
7
|
+
collection?: string
|
|
8
|
+
format: string
|
|
9
|
+
output?: string
|
|
10
|
+
includeDrafts?: boolean
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
function buildOutputPath(options: ExportOptions): string {
|
|
14
|
-
if (options.output) return options.output
|
|
15
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g,
|
|
16
|
-
const ext = options.format ===
|
|
17
|
-
return `actuate-export-${timestamp}.${ext}
|
|
14
|
+
if (options.output) return options.output
|
|
15
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
|
|
16
|
+
const ext = options.format === 'csv' ? 'csv' : 'json'
|
|
17
|
+
return `actuate-export-${timestamp}.${ext}`
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function jsonToCsv(docs: any[]): string {
|
|
21
|
-
if (docs.length === 0) return
|
|
21
|
+
if (docs.length === 0) return ''
|
|
22
22
|
|
|
23
|
-
const keySet = new Set<string>()
|
|
23
|
+
const keySet = new Set<string>()
|
|
24
24
|
for (const doc of docs) {
|
|
25
|
-
keySet.add(
|
|
26
|
-
keySet.add(
|
|
27
|
-
keySet.add(
|
|
28
|
-
keySet.add(
|
|
29
|
-
keySet.add(
|
|
30
|
-
if (doc.data && typeof doc.data ===
|
|
25
|
+
keySet.add('id')
|
|
26
|
+
keySet.add('collection')
|
|
27
|
+
keySet.add('status')
|
|
28
|
+
keySet.add('createdAt')
|
|
29
|
+
keySet.add('updatedAt')
|
|
30
|
+
if (doc.data && typeof doc.data === 'object') {
|
|
31
31
|
for (const key of Object.keys(doc.data)) {
|
|
32
|
-
keySet.add(`data.${key}`)
|
|
32
|
+
keySet.add(`data.${key}`)
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
const headers = Array.from(keySet)
|
|
36
|
+
const headers = Array.from(keySet)
|
|
37
37
|
|
|
38
38
|
const escapeCsvValue = (val: unknown): string => {
|
|
39
|
-
if (val === null || val === undefined) return
|
|
40
|
-
const str = typeof val ===
|
|
41
|
-
if (str.includes(
|
|
42
|
-
return `"${str.replace(/"/g, '""')}"
|
|
39
|
+
if (val === null || val === undefined) return ''
|
|
40
|
+
const str = typeof val === 'object' ? JSON.stringify(val) : String(val)
|
|
41
|
+
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
|
|
42
|
+
return `"${str.replace(/"/g, '""')}"`
|
|
43
43
|
}
|
|
44
|
-
return str
|
|
45
|
-
}
|
|
44
|
+
return str
|
|
45
|
+
}
|
|
46
46
|
|
|
47
47
|
const rows = docs.map((doc) => {
|
|
48
48
|
return headers
|
|
49
49
|
.map((header) => {
|
|
50
|
-
if (header.startsWith(
|
|
51
|
-
const field = header.slice(5)
|
|
52
|
-
return escapeCsvValue(doc.data?.[field])
|
|
50
|
+
if (header.startsWith('data.')) {
|
|
51
|
+
const field = header.slice(5)
|
|
52
|
+
return escapeCsvValue(doc.data?.[field])
|
|
53
53
|
}
|
|
54
|
-
return escapeCsvValue(doc[header])
|
|
54
|
+
return escapeCsvValue(doc[header])
|
|
55
55
|
})
|
|
56
|
-
.join(
|
|
57
|
-
})
|
|
56
|
+
.join(',')
|
|
57
|
+
})
|
|
58
58
|
|
|
59
|
-
return [headers.join(
|
|
59
|
+
return [headers.join(','), ...rows].join('\n')
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
async function runExport(options: ExportOptions): Promise<void> {
|
|
63
|
-
const target = options.collection ??
|
|
64
|
-
const spinner = ora(`Exporting ${target}…`).start()
|
|
63
|
+
const target = options.collection ?? 'all collections'
|
|
64
|
+
const spinner = ora(`Exporting ${target}…`).start()
|
|
65
65
|
|
|
66
66
|
try {
|
|
67
|
-
const { getDB } = await import(
|
|
68
|
-
const db = getDB<any>()
|
|
67
|
+
const { getDB } = await import('@actuate-media/cms-core')
|
|
68
|
+
const db = getDB<any>()
|
|
69
69
|
|
|
70
|
-
const where: any = { deletedAt: null }
|
|
70
|
+
const where: any = { deletedAt: null }
|
|
71
71
|
|
|
72
72
|
if (options.collection) {
|
|
73
|
-
where.collection = options.collection
|
|
73
|
+
where.collection = options.collection
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
if (!options.includeDrafts) {
|
|
77
|
-
where.status =
|
|
77
|
+
where.status = 'PUBLISHED'
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
const docs = await db.document.findMany({
|
|
81
81
|
where,
|
|
82
|
-
orderBy: { createdAt:
|
|
83
|
-
})
|
|
82
|
+
orderBy: { createdAt: 'desc' },
|
|
83
|
+
})
|
|
84
84
|
|
|
85
85
|
if (docs.length === 0) {
|
|
86
|
-
spinner.warn(
|
|
87
|
-
return
|
|
86
|
+
spinner.warn('No documents found matching the criteria.')
|
|
87
|
+
return
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
const outputPath = buildOutputPath(options)
|
|
91
|
-
let content: string
|
|
90
|
+
const outputPath = buildOutputPath(options)
|
|
91
|
+
let content: string
|
|
92
92
|
|
|
93
|
-
if (options.format ===
|
|
94
|
-
content = jsonToCsv(docs)
|
|
93
|
+
if (options.format === 'csv') {
|
|
94
|
+
content = jsonToCsv(docs)
|
|
95
95
|
} else {
|
|
96
|
-
content = JSON.stringify(docs, null, 2)
|
|
96
|
+
content = JSON.stringify(docs, null, 2)
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
await writeFile(outputPath, content,
|
|
100
|
-
spinner.succeed(`Exported ${docs.length} documents to ${outputPath}`)
|
|
99
|
+
await writeFile(outputPath, content, 'utf-8')
|
|
100
|
+
spinner.succeed(`Exported ${docs.length} documents to ${outputPath}`)
|
|
101
101
|
} catch (err) {
|
|
102
|
-
spinner.fail(
|
|
103
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
104
|
-
logger.error(message)
|
|
105
|
-
process.exit(1)
|
|
102
|
+
spinner.fail('Export failed.')
|
|
103
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
104
|
+
logger.error(message)
|
|
105
|
+
process.exit(1)
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
export function registerExportCommand(program: Command): void {
|
|
110
110
|
program
|
|
111
|
-
.command(
|
|
112
|
-
.description(
|
|
113
|
-
.option(
|
|
114
|
-
|
|
115
|
-
"Export only the specified collection (default: all)",
|
|
116
|
-
)
|
|
117
|
-
.option(
|
|
118
|
-
"-f, --format <type>",
|
|
119
|
-
"Output format: json or csv",
|
|
120
|
-
"json",
|
|
121
|
-
)
|
|
122
|
-
.option(
|
|
123
|
-
"-o, --output <path>",
|
|
124
|
-
"Output file path (default: actuate-export-{timestamp}.{format})",
|
|
125
|
-
)
|
|
111
|
+
.command('export')
|
|
112
|
+
.description('Export content to JSON or CSV files')
|
|
113
|
+
.option('-c, --collection <slug>', 'Export only the specified collection (default: all)')
|
|
114
|
+
.option('-f, --format <type>', 'Output format: json or csv', 'json')
|
|
126
115
|
.option(
|
|
127
|
-
|
|
128
|
-
|
|
116
|
+
'-o, --output <path>',
|
|
117
|
+
'Output file path (default: actuate-export-{timestamp}.{format})',
|
|
129
118
|
)
|
|
130
|
-
.
|
|
119
|
+
.option('--include-drafts', 'Include draft documents (default: published only)')
|
|
120
|
+
.action(runExport)
|
|
131
121
|
}
|
package/src/commands/generate.ts
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
1
|
-
import { Command } from
|
|
2
|
-
import ora from
|
|
3
|
-
import { logger } from
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import ora from 'ora'
|
|
3
|
+
import { logger } from '../utils/logger.js'
|
|
4
4
|
|
|
5
5
|
async function runGenerate(): Promise<void> {
|
|
6
|
-
const spinner = ora(
|
|
6
|
+
const spinner = ora('Generating TypeScript types from schema…').start()
|
|
7
7
|
|
|
8
8
|
try {
|
|
9
9
|
// Dynamically import the codegen module from cms-core
|
|
10
|
-
const { runCodegen } = await import(
|
|
11
|
-
await runCodegen()
|
|
12
|
-
spinner.succeed(
|
|
10
|
+
const { runCodegen } = await import('@actuate-media/cms-core/codegen')
|
|
11
|
+
await runCodegen()
|
|
12
|
+
spinner.succeed('Type generation complete.')
|
|
13
13
|
} catch (err) {
|
|
14
|
-
spinner.fail(
|
|
15
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
16
|
-
logger.error(message)
|
|
17
|
-
process.exitCode = 1
|
|
14
|
+
spinner.fail('Type generation failed.')
|
|
15
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
16
|
+
logger.error(message)
|
|
17
|
+
process.exitCode = 1
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function registerGenerateCommand(program: Command): void {
|
|
22
22
|
program
|
|
23
|
-
.command(
|
|
24
|
-
.description(
|
|
25
|
-
|
|
26
|
-
)
|
|
27
|
-
.action(runGenerate);
|
|
23
|
+
.command('generate')
|
|
24
|
+
.description('Run the TypeScript type generator from the cms-core codegen module')
|
|
25
|
+
.action(runGenerate)
|
|
28
26
|
}
|
package/src/commands/import.ts
CHANGED
|
@@ -1,243 +1,228 @@
|
|
|
1
|
-
import { Command } from
|
|
2
|
-
import { readFile } from
|
|
3
|
-
import { existsSync } from
|
|
4
|
-
import ora from
|
|
5
|
-
import { logger } from
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import { existsSync } from 'node:fs'
|
|
4
|
+
import ora from 'ora'
|
|
5
|
+
import { logger } from '../utils/logger.js'
|
|
6
6
|
|
|
7
7
|
interface ImportOptions {
|
|
8
|
-
source: string
|
|
9
|
-
format?: string
|
|
10
|
-
collection?: string
|
|
11
|
-
dryRun?: boolean
|
|
8
|
+
source: string
|
|
9
|
+
format?: string
|
|
10
|
+
collection?: string
|
|
11
|
+
dryRun?: boolean
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
function detectFormat(filePath: string): string {
|
|
15
|
-
const ext = filePath.split(
|
|
16
|
-
if (ext ===
|
|
17
|
-
if (ext ===
|
|
18
|
-
if (ext ===
|
|
19
|
-
return
|
|
15
|
+
const ext = filePath.split('.').pop()?.toLowerCase()
|
|
16
|
+
if (ext === 'json') return 'json'
|
|
17
|
+
if (ext === 'csv') return 'csv'
|
|
18
|
+
if (ext === 'xml' || ext === 'wxr') return 'wordpress'
|
|
19
|
+
return 'json'
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function parseCsv(content: string): Record<string, string>[] {
|
|
23
|
-
const lines = content.split(
|
|
24
|
-
if (lines.length < 2) return []
|
|
23
|
+
const lines = content.split('\n').filter((line) => line.trim())
|
|
24
|
+
if (lines.length < 2) return []
|
|
25
25
|
|
|
26
26
|
const parseRow = (row: string): string[] => {
|
|
27
|
-
const values: string[] = []
|
|
28
|
-
let current =
|
|
29
|
-
let inQuotes = false
|
|
27
|
+
const values: string[] = []
|
|
28
|
+
let current = ''
|
|
29
|
+
let inQuotes = false
|
|
30
30
|
|
|
31
31
|
for (let i = 0; i < row.length; i++) {
|
|
32
|
-
const char = row[i]
|
|
32
|
+
const char = row[i]
|
|
33
33
|
if (char === '"') {
|
|
34
34
|
if (inQuotes && row[i + 1] === '"') {
|
|
35
|
-
current += '"'
|
|
36
|
-
i
|
|
35
|
+
current += '"'
|
|
36
|
+
i++
|
|
37
37
|
} else {
|
|
38
|
-
inQuotes = !inQuotes
|
|
38
|
+
inQuotes = !inQuotes
|
|
39
39
|
}
|
|
40
|
-
} else if (char ===
|
|
41
|
-
values.push(current.trim())
|
|
42
|
-
current =
|
|
40
|
+
} else if (char === ',' && !inQuotes) {
|
|
41
|
+
values.push(current.trim())
|
|
42
|
+
current = ''
|
|
43
43
|
} else {
|
|
44
|
-
current += char
|
|
44
|
+
current += char
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
values.push(current.trim())
|
|
48
|
-
return values
|
|
49
|
-
}
|
|
47
|
+
values.push(current.trim())
|
|
48
|
+
return values
|
|
49
|
+
}
|
|
50
50
|
|
|
51
|
-
const headers = parseRow(lines[0]!)
|
|
51
|
+
const headers = parseRow(lines[0]!)
|
|
52
52
|
return lines.slice(1).map((line) => {
|
|
53
|
-
const values = parseRow(line)
|
|
54
|
-
const obj: Record<string, string> = {}
|
|
53
|
+
const values = parseRow(line)
|
|
54
|
+
const obj: Record<string, string> = {}
|
|
55
55
|
headers.forEach((header, i) => {
|
|
56
|
-
obj[header] = values[i] ??
|
|
57
|
-
})
|
|
58
|
-
return obj
|
|
59
|
-
})
|
|
56
|
+
obj[header] = values[i] ?? ''
|
|
57
|
+
})
|
|
58
|
+
return obj
|
|
59
|
+
})
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
function parseWordPressXml(content: string): any[] {
|
|
63
|
-
const items: any[] = []
|
|
64
|
-
const itemRegex = /<item>([\s\S]*?)<\/item>/g
|
|
65
|
-
let match: RegExpExecArray | null
|
|
63
|
+
const items: any[] = []
|
|
64
|
+
const itemRegex = /<item>([\s\S]*?)<\/item>/g
|
|
65
|
+
let match: RegExpExecArray | null
|
|
66
66
|
|
|
67
67
|
while ((match = itemRegex.exec(content)) !== null) {
|
|
68
|
-
const itemXml = match[1]
|
|
68
|
+
const itemXml = match[1]
|
|
69
69
|
|
|
70
70
|
const extract = (tag: string): string => {
|
|
71
|
-
const tagRegex = new RegExp(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
const tagRegex = new RegExp(
|
|
72
|
+
`<${tag}><!\\[CDATA\\[([\\s\\S]*?)\\]\\]><\\/${tag}>|<${tag}>([\\s\\S]*?)<\\/${tag}>`,
|
|
73
|
+
)
|
|
74
|
+
const m = tagRegex.exec(itemXml!)
|
|
75
|
+
return m?.[1] ?? m?.[2] ?? ''
|
|
76
|
+
}
|
|
75
77
|
|
|
76
|
-
const title = extract(
|
|
77
|
-
const body = extract(
|
|
78
|
-
const slug = extract(
|
|
79
|
-
const status = extract(
|
|
80
|
-
const postDate = extract(
|
|
81
|
-
const postType = extract(
|
|
78
|
+
const title = extract('title')
|
|
79
|
+
const body = extract('content:encoded')
|
|
80
|
+
const slug = extract('wp:post_name')
|
|
81
|
+
const status = extract('wp:status')
|
|
82
|
+
const postDate = extract('wp:post_date')
|
|
83
|
+
const postType = extract('wp:post_type')
|
|
82
84
|
|
|
83
|
-
if (postType && postType !==
|
|
85
|
+
if (postType && postType !== 'post' && postType !== 'page') continue
|
|
84
86
|
|
|
85
87
|
items.push({
|
|
86
88
|
title,
|
|
87
|
-
slug: slug || title.toLowerCase().replace(/[^a-z0-9]+/g,
|
|
89
|
+
slug: slug || title.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
|
|
88
90
|
content: body,
|
|
89
91
|
wpStatus: status,
|
|
90
92
|
wpPostDate: postDate,
|
|
91
|
-
collection: postType ===
|
|
92
|
-
})
|
|
93
|
+
collection: postType === 'page' ? 'pages' : 'posts',
|
|
94
|
+
})
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
return items
|
|
97
|
+
return items
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
function printPreviewTable(records: any[], collection: string | undefined): void {
|
|
99
|
-
console.log(
|
|
100
|
-
console.log(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
"Status",
|
|
105
|
-
);
|
|
106
|
-
console.log(" " + "-".repeat(66));
|
|
107
|
-
|
|
108
|
-
const shown = records.slice(0, 20);
|
|
101
|
+
console.log('\n Dry-run preview:\n')
|
|
102
|
+
console.log(' ' + 'Collection'.padEnd(16) + 'Title / Slug'.padEnd(40) + 'Status')
|
|
103
|
+
console.log(' ' + '-'.repeat(66))
|
|
104
|
+
|
|
105
|
+
const shown = records.slice(0, 20)
|
|
109
106
|
for (const rec of shown) {
|
|
110
|
-
const col = (rec.collection ?? collection ??
|
|
111
|
-
const label = (rec.title ?? rec.slug ??
|
|
112
|
-
const status = rec.status ?? rec.wpStatus ??
|
|
113
|
-
console.log(` ${col}${label}${status}`)
|
|
107
|
+
const col = (rec.collection ?? collection ?? 'imported').padEnd(16)
|
|
108
|
+
const label = (rec.title ?? rec.slug ?? '(untitled)').slice(0, 38).padEnd(40)
|
|
109
|
+
const status = rec.status ?? rec.wpStatus ?? 'DRAFT'
|
|
110
|
+
console.log(` ${col}${label}${status}`)
|
|
114
111
|
}
|
|
115
112
|
|
|
116
113
|
if (records.length > 20) {
|
|
117
|
-
console.log(` ... and ${records.length - 20} more`)
|
|
114
|
+
console.log(` ... and ${records.length - 20} more`)
|
|
118
115
|
}
|
|
119
|
-
console.log()
|
|
116
|
+
console.log()
|
|
120
117
|
}
|
|
121
118
|
|
|
122
119
|
async function runImport(options: ImportOptions): Promise<void> {
|
|
123
|
-
const { source, collection, dryRun } = options
|
|
120
|
+
const { source, collection, dryRun } = options
|
|
124
121
|
|
|
125
122
|
if (!existsSync(source)) {
|
|
126
|
-
logger.error(`File not found: ${source}`)
|
|
127
|
-
process.exit(1)
|
|
123
|
+
logger.error(`File not found: ${source}`)
|
|
124
|
+
process.exit(1)
|
|
128
125
|
}
|
|
129
126
|
|
|
130
|
-
const format = options.format ?? detectFormat(source)
|
|
131
|
-
const spinner = ora(`Parsing ${format} file: ${source}…`).start()
|
|
127
|
+
const format = options.format ?? detectFormat(source)
|
|
128
|
+
const spinner = ora(`Parsing ${format} file: ${source}…`).start()
|
|
132
129
|
|
|
133
130
|
try {
|
|
134
|
-
const raw = await readFile(source,
|
|
135
|
-
let records: any[]
|
|
131
|
+
const raw = await readFile(source, 'utf-8')
|
|
132
|
+
let records: any[]
|
|
136
133
|
|
|
137
|
-
if (format ===
|
|
138
|
-
const parsed = JSON.parse(raw)
|
|
134
|
+
if (format === 'json') {
|
|
135
|
+
const parsed = JSON.parse(raw)
|
|
139
136
|
if (!Array.isArray(parsed)) {
|
|
140
|
-
spinner.fail(
|
|
141
|
-
process.exit(1)
|
|
137
|
+
spinner.fail('JSON file must contain an array of documents.')
|
|
138
|
+
process.exit(1)
|
|
142
139
|
}
|
|
143
|
-
records = parsed
|
|
144
|
-
} else if (format ===
|
|
140
|
+
records = parsed
|
|
141
|
+
} else if (format === 'csv') {
|
|
145
142
|
if (!collection) {
|
|
146
|
-
spinner.fail(
|
|
147
|
-
process.exit(1)
|
|
143
|
+
spinner.fail('The --collection option is required for CSV imports.')
|
|
144
|
+
process.exit(1)
|
|
148
145
|
}
|
|
149
146
|
records = parseCsv(raw).map((row) => ({
|
|
150
147
|
collection,
|
|
151
148
|
...row,
|
|
152
|
-
}))
|
|
153
|
-
} else if (format ===
|
|
154
|
-
records = parseWordPressXml(raw)
|
|
149
|
+
}))
|
|
150
|
+
} else if (format === 'wordpress') {
|
|
151
|
+
records = parseWordPressXml(raw)
|
|
155
152
|
} else {
|
|
156
|
-
spinner.fail(`Unsupported format: ${format}`)
|
|
157
|
-
process.exit(1)
|
|
153
|
+
spinner.fail(`Unsupported format: ${format}`)
|
|
154
|
+
process.exit(1)
|
|
158
155
|
}
|
|
159
156
|
|
|
160
|
-
spinner.succeed(`Parsed ${records.length} records from ${source}.`)
|
|
157
|
+
spinner.succeed(`Parsed ${records.length} records from ${source}.`)
|
|
161
158
|
|
|
162
159
|
if (records.length === 0) {
|
|
163
|
-
logger.warn(
|
|
164
|
-
return
|
|
160
|
+
logger.warn('No records found in file.')
|
|
161
|
+
return
|
|
165
162
|
}
|
|
166
163
|
|
|
167
164
|
if (dryRun) {
|
|
168
|
-
printPreviewTable(records, collection)
|
|
169
|
-
logger.info(
|
|
170
|
-
return
|
|
165
|
+
printPreviewTable(records, collection)
|
|
166
|
+
logger.info('Dry run — no data was written.')
|
|
167
|
+
return
|
|
171
168
|
}
|
|
172
169
|
|
|
173
|
-
const writeSpinner = ora(`Importing ${records.length} records…`).start()
|
|
170
|
+
const writeSpinner = ora(`Importing ${records.length} records…`).start()
|
|
174
171
|
|
|
175
|
-
const { getDB } = await import(
|
|
176
|
-
const db = getDB<any>()
|
|
172
|
+
const { getDB } = await import('@actuate-media/cms-core')
|
|
173
|
+
const db = getDB<any>()
|
|
177
174
|
|
|
178
|
-
let adminUser = await db.user.findFirst({ where: { role:
|
|
175
|
+
let adminUser = await db.user.findFirst({ where: { role: 'ADMIN' } })
|
|
179
176
|
if (!adminUser) {
|
|
180
177
|
adminUser = await db.user.create({
|
|
181
178
|
data: {
|
|
182
|
-
email:
|
|
183
|
-
name:
|
|
184
|
-
role:
|
|
179
|
+
email: 'admin@actuatecms.dev',
|
|
180
|
+
name: 'Admin',
|
|
181
|
+
role: 'ADMIN',
|
|
185
182
|
isActive: true,
|
|
186
183
|
isApproved: true,
|
|
187
184
|
emailVerified: true,
|
|
188
185
|
},
|
|
189
|
-
})
|
|
186
|
+
})
|
|
190
187
|
}
|
|
191
|
-
const userId = adminUser.id
|
|
188
|
+
const userId = adminUser.id
|
|
192
189
|
|
|
193
|
-
let imported = 0
|
|
190
|
+
let imported = 0
|
|
194
191
|
for (const rec of records) {
|
|
195
|
-
const docCollection = rec.collection ?? collection ??
|
|
196
|
-
const data: any = { ...rec }
|
|
197
|
-
delete data.collection
|
|
198
|
-
delete data.wpStatus
|
|
199
|
-
delete data.wpPostDate
|
|
192
|
+
const docCollection = rec.collection ?? collection ?? 'imported'
|
|
193
|
+
const data: any = { ...rec }
|
|
194
|
+
delete data.collection
|
|
195
|
+
delete data.wpStatus
|
|
196
|
+
delete data.wpPostDate
|
|
200
197
|
|
|
201
198
|
await db.document.create({
|
|
202
199
|
data: {
|
|
203
200
|
collection: docCollection,
|
|
204
201
|
data,
|
|
205
|
-
status:
|
|
202
|
+
status: 'DRAFT',
|
|
206
203
|
createdById: userId,
|
|
207
204
|
updatedById: userId,
|
|
208
205
|
},
|
|
209
|
-
})
|
|
210
|
-
imported
|
|
206
|
+
})
|
|
207
|
+
imported++
|
|
211
208
|
}
|
|
212
209
|
|
|
213
|
-
writeSpinner.succeed(`Imported ${imported} documents as DRAFT.`)
|
|
210
|
+
writeSpinner.succeed(`Imported ${imported} documents as DRAFT.`)
|
|
214
211
|
} catch (err) {
|
|
215
|
-
spinner.fail(
|
|
216
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
217
|
-
logger.error(message)
|
|
218
|
-
process.exit(1)
|
|
212
|
+
spinner.fail('Import failed.')
|
|
213
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
214
|
+
logger.error(message)
|
|
215
|
+
process.exit(1)
|
|
219
216
|
}
|
|
220
217
|
}
|
|
221
218
|
|
|
222
219
|
export function registerImportCommand(program: Command): void {
|
|
223
220
|
program
|
|
224
|
-
.command(
|
|
225
|
-
.description(
|
|
226
|
-
.requiredOption(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
)
|
|
230
|
-
.
|
|
231
|
-
"-f, --format <type>",
|
|
232
|
-
"File format: json, csv, or wordpress (default: auto-detect)",
|
|
233
|
-
)
|
|
234
|
-
.option(
|
|
235
|
-
"-c, --collection <slug>",
|
|
236
|
-
"Target collection (required for csv/wordpress)",
|
|
237
|
-
)
|
|
238
|
-
.option(
|
|
239
|
-
"--dry-run",
|
|
240
|
-
"Preview import without writing to the database",
|
|
241
|
-
)
|
|
242
|
-
.action(runImport);
|
|
221
|
+
.command('import')
|
|
222
|
+
.description('Import content from JSON, CSV, or WordPress XML files')
|
|
223
|
+
.requiredOption('-s, --source <path>', 'Path to the import file')
|
|
224
|
+
.option('-f, --format <type>', 'File format: json, csv, or wordpress (default: auto-detect)')
|
|
225
|
+
.option('-c, --collection <slug>', 'Target collection (required for csv/wordpress)')
|
|
226
|
+
.option('--dry-run', 'Preview import without writing to the database')
|
|
227
|
+
.action(runImport)
|
|
243
228
|
}
|
package/src/commands/init.ts
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process'
|
|
2
|
-
import type { Command } from 'commander'
|
|
3
|
-
import { logger } from '../utils/logger.js'
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import type { Command } from 'commander'
|
|
3
|
+
import { logger } from '../utils/logger.js'
|
|
4
4
|
|
|
5
5
|
export function buildCreateActuateArgs(projectName?: string): string[] {
|
|
6
|
-
const args = ['create', 'actuate-cms@latest']
|
|
7
|
-
if (projectName) args.push(projectName)
|
|
8
|
-
return args
|
|
6
|
+
const args = ['create', 'actuate-cms@latest']
|
|
7
|
+
if (projectName) args.push(projectName)
|
|
8
|
+
return args
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
function npmCommand(): string {
|
|
12
|
-
return process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
12
|
+
return process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
async function runInit(projectName?: string): Promise<void> {
|
|
16
16
|
const child = spawn(npmCommand(), buildCreateActuateArgs(projectName), {
|
|
17
17
|
stdio: 'inherit',
|
|
18
|
-
})
|
|
18
|
+
})
|
|
19
19
|
|
|
20
20
|
const exitCode = await new Promise<number | null>((resolve, reject) => {
|
|
21
|
-
child.once('error', reject)
|
|
22
|
-
child.once('exit', resolve)
|
|
23
|
-
})
|
|
21
|
+
child.once('error', reject)
|
|
22
|
+
child.once('exit', resolve)
|
|
23
|
+
})
|
|
24
24
|
|
|
25
25
|
if (exitCode && exitCode !== 0) {
|
|
26
|
-
logger.error(`Project initialization failed with exit code ${exitCode}.`)
|
|
27
|
-
process.exit(exitCode)
|
|
26
|
+
logger.error(`Project initialization failed with exit code ${exitCode}.`)
|
|
27
|
+
process.exit(exitCode)
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -32,5 +32,5 @@ export function registerInitCommand(program: Command): void {
|
|
|
32
32
|
program
|
|
33
33
|
.command('init [project-name]')
|
|
34
34
|
.description('Scaffold a new Actuate CMS project')
|
|
35
|
-
.action(runInit)
|
|
35
|
+
.action(runInit)
|
|
36
36
|
}
|