@actuate-media/cli 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -3
- package/CHANGELOG.md +14 -0
- package/LICENSE +21 -21
- package/package.json +2 -2
- package/src/commands/export.ts +131 -131
- package/src/commands/generate.ts +28 -28
- package/src/commands/import.ts +243 -243
- package/src/commands/migrate.ts +62 -62
- package/src/commands/seed.ts +388 -388
- package/src/commands/update-check.ts +147 -147
- package/src/commands/upgrade.ts +173 -173
- package/src/index.ts +26 -26
- package/src/utils/logger.ts +26 -26
- package/tsconfig.json +9 -9
- package/.turbo/turbo-type-check.log +0 -5
package/src/commands/import.ts
CHANGED
|
@@ -1,243 +1,243 @@
|
|
|
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
|
-
|
|
7
|
-
interface ImportOptions {
|
|
8
|
-
source: string;
|
|
9
|
-
format?: string;
|
|
10
|
-
collection?: string;
|
|
11
|
-
dryRun?: boolean;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function detectFormat(filePath: string): string {
|
|
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
|
-
}
|
|
21
|
-
|
|
22
|
-
function parseCsv(content: string): Record<string, string>[] {
|
|
23
|
-
const lines = content.split("\n").filter((line) => line.trim());
|
|
24
|
-
if (lines.length < 2) return [];
|
|
25
|
-
|
|
26
|
-
const parseRow = (row: string): string[] => {
|
|
27
|
-
const values: string[] = [];
|
|
28
|
-
let current = "";
|
|
29
|
-
let inQuotes = false;
|
|
30
|
-
|
|
31
|
-
for (let i = 0; i < row.length; i++) {
|
|
32
|
-
const char = row[i];
|
|
33
|
-
if (char === '"') {
|
|
34
|
-
if (inQuotes && row[i + 1] === '"') {
|
|
35
|
-
current += '"';
|
|
36
|
-
i++;
|
|
37
|
-
} else {
|
|
38
|
-
inQuotes = !inQuotes;
|
|
39
|
-
}
|
|
40
|
-
} else if (char === "," && !inQuotes) {
|
|
41
|
-
values.push(current.trim());
|
|
42
|
-
current = "";
|
|
43
|
-
} else {
|
|
44
|
-
current += char;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
values.push(current.trim());
|
|
48
|
-
return values;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const headers = parseRow(lines[0]!);
|
|
52
|
-
return lines.slice(1).map((line) => {
|
|
53
|
-
const values = parseRow(line);
|
|
54
|
-
const obj: Record<string, string> = {};
|
|
55
|
-
headers.forEach((header, i) => {
|
|
56
|
-
obj[header] = values[i] ?? "";
|
|
57
|
-
});
|
|
58
|
-
return obj;
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function parseWordPressXml(content: string): any[] {
|
|
63
|
-
const items: any[] = [];
|
|
64
|
-
const itemRegex = /<item>([\s\S]*?)<\/item>/g;
|
|
65
|
-
let match: RegExpExecArray | null;
|
|
66
|
-
|
|
67
|
-
while ((match = itemRegex.exec(content)) !== null) {
|
|
68
|
-
const itemXml = match[1];
|
|
69
|
-
|
|
70
|
-
const extract = (tag: string): string => {
|
|
71
|
-
const tagRegex = new RegExp(`<${tag}><!\\[CDATA\\[([\\s\\S]*?)\\]\\]><\\/${tag}>|<${tag}>([\\s\\S]*?)<\\/${tag}>`);
|
|
72
|
-
const m = tagRegex.exec(itemXml!);
|
|
73
|
-
return m?.[1] ?? m?.[2] ?? "";
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const title = extract("title");
|
|
77
|
-
const body = extract("content:encoded");
|
|
78
|
-
const slug = extract("wp:post_name");
|
|
79
|
-
const status = extract("wp:status");
|
|
80
|
-
const postDate = extract("wp:post_date");
|
|
81
|
-
const postType = extract("wp:post_type");
|
|
82
|
-
|
|
83
|
-
if (postType && postType !== "post" && postType !== "page") continue;
|
|
84
|
-
|
|
85
|
-
items.push({
|
|
86
|
-
title,
|
|
87
|
-
slug: slug || title.toLowerCase().replace(/[^a-z0-9]+/g, "-"),
|
|
88
|
-
content: body,
|
|
89
|
-
wpStatus: status,
|
|
90
|
-
wpPostDate: postDate,
|
|
91
|
-
collection: postType === "page" ? "pages" : "posts",
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return items;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function printPreviewTable(records: any[], collection: string | undefined): void {
|
|
99
|
-
console.log("\n Dry-run preview:\n");
|
|
100
|
-
console.log(
|
|
101
|
-
" " +
|
|
102
|
-
"Collection".padEnd(16) +
|
|
103
|
-
"Title / Slug".padEnd(40) +
|
|
104
|
-
"Status",
|
|
105
|
-
);
|
|
106
|
-
console.log(" " + "-".repeat(66));
|
|
107
|
-
|
|
108
|
-
const shown = records.slice(0, 20);
|
|
109
|
-
for (const rec of shown) {
|
|
110
|
-
const col = (rec.collection ?? collection ?? "imported").padEnd(16);
|
|
111
|
-
const label = (rec.title ?? rec.slug ?? "(untitled)").slice(0, 38).padEnd(40);
|
|
112
|
-
const status = rec.status ?? rec.wpStatus ?? "DRAFT";
|
|
113
|
-
console.log(` ${col}${label}${status}`);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (records.length > 20) {
|
|
117
|
-
console.log(` ... and ${records.length - 20} more`);
|
|
118
|
-
}
|
|
119
|
-
console.log();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async function runImport(options: ImportOptions): Promise<void> {
|
|
123
|
-
const { source, collection, dryRun } = options;
|
|
124
|
-
|
|
125
|
-
if (!existsSync(source)) {
|
|
126
|
-
logger.error(`File not found: ${source}`);
|
|
127
|
-
process.exit(1);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const format = options.format ?? detectFormat(source);
|
|
131
|
-
const spinner = ora(`Parsing ${format} file: ${source}…`).start();
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
const raw = await readFile(source, "utf-8");
|
|
135
|
-
let records: any[];
|
|
136
|
-
|
|
137
|
-
if (format === "json") {
|
|
138
|
-
const parsed = JSON.parse(raw);
|
|
139
|
-
if (!Array.isArray(parsed)) {
|
|
140
|
-
spinner.fail("JSON file must contain an array of documents.");
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
143
|
-
records = parsed;
|
|
144
|
-
} else if (format === "csv") {
|
|
145
|
-
if (!collection) {
|
|
146
|
-
spinner.fail("The --collection option is required for CSV imports.");
|
|
147
|
-
process.exit(1);
|
|
148
|
-
}
|
|
149
|
-
records = parseCsv(raw).map((row) => ({
|
|
150
|
-
collection,
|
|
151
|
-
...row,
|
|
152
|
-
}));
|
|
153
|
-
} else if (format === "wordpress") {
|
|
154
|
-
records = parseWordPressXml(raw);
|
|
155
|
-
} else {
|
|
156
|
-
spinner.fail(`Unsupported format: ${format}`);
|
|
157
|
-
process.exit(1);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
spinner.succeed(`Parsed ${records.length} records from ${source}.`);
|
|
161
|
-
|
|
162
|
-
if (records.length === 0) {
|
|
163
|
-
logger.warn("No records found in file.");
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (dryRun) {
|
|
168
|
-
printPreviewTable(records, collection);
|
|
169
|
-
logger.info("Dry run — no data was written.");
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const writeSpinner = ora(`Importing ${records.length} records…`).start();
|
|
174
|
-
|
|
175
|
-
const { getDB } = await import("@actuate-media/cms-core");
|
|
176
|
-
const db = getDB<any>();
|
|
177
|
-
|
|
178
|
-
let adminUser = await db.user.findFirst({ where: { role: "ADMIN" } });
|
|
179
|
-
if (!adminUser) {
|
|
180
|
-
adminUser = await db.user.create({
|
|
181
|
-
data: {
|
|
182
|
-
email: "admin@actuatecms.dev",
|
|
183
|
-
name: "Admin",
|
|
184
|
-
role: "ADMIN",
|
|
185
|
-
isActive: true,
|
|
186
|
-
isApproved: true,
|
|
187
|
-
emailVerified: true,
|
|
188
|
-
},
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
const userId = adminUser.id;
|
|
192
|
-
|
|
193
|
-
let imported = 0;
|
|
194
|
-
for (const rec of records) {
|
|
195
|
-
const docCollection = rec.collection ?? collection ?? "imported";
|
|
196
|
-
const data: any = { ...rec };
|
|
197
|
-
delete data.collection;
|
|
198
|
-
delete data.wpStatus;
|
|
199
|
-
delete data.wpPostDate;
|
|
200
|
-
|
|
201
|
-
await db.document.create({
|
|
202
|
-
data: {
|
|
203
|
-
collection: docCollection,
|
|
204
|
-
data,
|
|
205
|
-
status: "DRAFT",
|
|
206
|
-
createdById: userId,
|
|
207
|
-
updatedById: userId,
|
|
208
|
-
},
|
|
209
|
-
});
|
|
210
|
-
imported++;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
writeSpinner.succeed(`Imported ${imported} documents as DRAFT.`);
|
|
214
|
-
} catch (err) {
|
|
215
|
-
spinner.fail("Import failed.");
|
|
216
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
217
|
-
logger.error(message);
|
|
218
|
-
process.exit(1);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export function registerImportCommand(program: Command): void {
|
|
223
|
-
program
|
|
224
|
-
.command("import")
|
|
225
|
-
.description("Import content from JSON, CSV, or WordPress XML files")
|
|
226
|
-
.requiredOption(
|
|
227
|
-
"-s, --source <path>",
|
|
228
|
-
"Path to the import file",
|
|
229
|
-
)
|
|
230
|
-
.option(
|
|
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);
|
|
243
|
-
}
|
|
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
|
+
|
|
7
|
+
interface ImportOptions {
|
|
8
|
+
source: string;
|
|
9
|
+
format?: string;
|
|
10
|
+
collection?: string;
|
|
11
|
+
dryRun?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function detectFormat(filePath: string): string {
|
|
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
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseCsv(content: string): Record<string, string>[] {
|
|
23
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
24
|
+
if (lines.length < 2) return [];
|
|
25
|
+
|
|
26
|
+
const parseRow = (row: string): string[] => {
|
|
27
|
+
const values: string[] = [];
|
|
28
|
+
let current = "";
|
|
29
|
+
let inQuotes = false;
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < row.length; i++) {
|
|
32
|
+
const char = row[i];
|
|
33
|
+
if (char === '"') {
|
|
34
|
+
if (inQuotes && row[i + 1] === '"') {
|
|
35
|
+
current += '"';
|
|
36
|
+
i++;
|
|
37
|
+
} else {
|
|
38
|
+
inQuotes = !inQuotes;
|
|
39
|
+
}
|
|
40
|
+
} else if (char === "," && !inQuotes) {
|
|
41
|
+
values.push(current.trim());
|
|
42
|
+
current = "";
|
|
43
|
+
} else {
|
|
44
|
+
current += char;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
values.push(current.trim());
|
|
48
|
+
return values;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const headers = parseRow(lines[0]!);
|
|
52
|
+
return lines.slice(1).map((line) => {
|
|
53
|
+
const values = parseRow(line);
|
|
54
|
+
const obj: Record<string, string> = {};
|
|
55
|
+
headers.forEach((header, i) => {
|
|
56
|
+
obj[header] = values[i] ?? "";
|
|
57
|
+
});
|
|
58
|
+
return obj;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseWordPressXml(content: string): any[] {
|
|
63
|
+
const items: any[] = [];
|
|
64
|
+
const itemRegex = /<item>([\s\S]*?)<\/item>/g;
|
|
65
|
+
let match: RegExpExecArray | null;
|
|
66
|
+
|
|
67
|
+
while ((match = itemRegex.exec(content)) !== null) {
|
|
68
|
+
const itemXml = match[1];
|
|
69
|
+
|
|
70
|
+
const extract = (tag: string): string => {
|
|
71
|
+
const tagRegex = new RegExp(`<${tag}><!\\[CDATA\\[([\\s\\S]*?)\\]\\]><\\/${tag}>|<${tag}>([\\s\\S]*?)<\\/${tag}>`);
|
|
72
|
+
const m = tagRegex.exec(itemXml!);
|
|
73
|
+
return m?.[1] ?? m?.[2] ?? "";
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const title = extract("title");
|
|
77
|
+
const body = extract("content:encoded");
|
|
78
|
+
const slug = extract("wp:post_name");
|
|
79
|
+
const status = extract("wp:status");
|
|
80
|
+
const postDate = extract("wp:post_date");
|
|
81
|
+
const postType = extract("wp:post_type");
|
|
82
|
+
|
|
83
|
+
if (postType && postType !== "post" && postType !== "page") continue;
|
|
84
|
+
|
|
85
|
+
items.push({
|
|
86
|
+
title,
|
|
87
|
+
slug: slug || title.toLowerCase().replace(/[^a-z0-9]+/g, "-"),
|
|
88
|
+
content: body,
|
|
89
|
+
wpStatus: status,
|
|
90
|
+
wpPostDate: postDate,
|
|
91
|
+
collection: postType === "page" ? "pages" : "posts",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return items;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function printPreviewTable(records: any[], collection: string | undefined): void {
|
|
99
|
+
console.log("\n Dry-run preview:\n");
|
|
100
|
+
console.log(
|
|
101
|
+
" " +
|
|
102
|
+
"Collection".padEnd(16) +
|
|
103
|
+
"Title / Slug".padEnd(40) +
|
|
104
|
+
"Status",
|
|
105
|
+
);
|
|
106
|
+
console.log(" " + "-".repeat(66));
|
|
107
|
+
|
|
108
|
+
const shown = records.slice(0, 20);
|
|
109
|
+
for (const rec of shown) {
|
|
110
|
+
const col = (rec.collection ?? collection ?? "imported").padEnd(16);
|
|
111
|
+
const label = (rec.title ?? rec.slug ?? "(untitled)").slice(0, 38).padEnd(40);
|
|
112
|
+
const status = rec.status ?? rec.wpStatus ?? "DRAFT";
|
|
113
|
+
console.log(` ${col}${label}${status}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (records.length > 20) {
|
|
117
|
+
console.log(` ... and ${records.length - 20} more`);
|
|
118
|
+
}
|
|
119
|
+
console.log();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function runImport(options: ImportOptions): Promise<void> {
|
|
123
|
+
const { source, collection, dryRun } = options;
|
|
124
|
+
|
|
125
|
+
if (!existsSync(source)) {
|
|
126
|
+
logger.error(`File not found: ${source}`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const format = options.format ?? detectFormat(source);
|
|
131
|
+
const spinner = ora(`Parsing ${format} file: ${source}…`).start();
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const raw = await readFile(source, "utf-8");
|
|
135
|
+
let records: any[];
|
|
136
|
+
|
|
137
|
+
if (format === "json") {
|
|
138
|
+
const parsed = JSON.parse(raw);
|
|
139
|
+
if (!Array.isArray(parsed)) {
|
|
140
|
+
spinner.fail("JSON file must contain an array of documents.");
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
records = parsed;
|
|
144
|
+
} else if (format === "csv") {
|
|
145
|
+
if (!collection) {
|
|
146
|
+
spinner.fail("The --collection option is required for CSV imports.");
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
records = parseCsv(raw).map((row) => ({
|
|
150
|
+
collection,
|
|
151
|
+
...row,
|
|
152
|
+
}));
|
|
153
|
+
} else if (format === "wordpress") {
|
|
154
|
+
records = parseWordPressXml(raw);
|
|
155
|
+
} else {
|
|
156
|
+
spinner.fail(`Unsupported format: ${format}`);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
spinner.succeed(`Parsed ${records.length} records from ${source}.`);
|
|
161
|
+
|
|
162
|
+
if (records.length === 0) {
|
|
163
|
+
logger.warn("No records found in file.");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (dryRun) {
|
|
168
|
+
printPreviewTable(records, collection);
|
|
169
|
+
logger.info("Dry run — no data was written.");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const writeSpinner = ora(`Importing ${records.length} records…`).start();
|
|
174
|
+
|
|
175
|
+
const { getDB } = await import("@actuate-media/cms-core");
|
|
176
|
+
const db = getDB<any>();
|
|
177
|
+
|
|
178
|
+
let adminUser = await db.user.findFirst({ where: { role: "ADMIN" } });
|
|
179
|
+
if (!adminUser) {
|
|
180
|
+
adminUser = await db.user.create({
|
|
181
|
+
data: {
|
|
182
|
+
email: "admin@actuatecms.dev",
|
|
183
|
+
name: "Admin",
|
|
184
|
+
role: "ADMIN",
|
|
185
|
+
isActive: true,
|
|
186
|
+
isApproved: true,
|
|
187
|
+
emailVerified: true,
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
const userId = adminUser.id;
|
|
192
|
+
|
|
193
|
+
let imported = 0;
|
|
194
|
+
for (const rec of records) {
|
|
195
|
+
const docCollection = rec.collection ?? collection ?? "imported";
|
|
196
|
+
const data: any = { ...rec };
|
|
197
|
+
delete data.collection;
|
|
198
|
+
delete data.wpStatus;
|
|
199
|
+
delete data.wpPostDate;
|
|
200
|
+
|
|
201
|
+
await db.document.create({
|
|
202
|
+
data: {
|
|
203
|
+
collection: docCollection,
|
|
204
|
+
data,
|
|
205
|
+
status: "DRAFT",
|
|
206
|
+
createdById: userId,
|
|
207
|
+
updatedById: userId,
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
imported++;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
writeSpinner.succeed(`Imported ${imported} documents as DRAFT.`);
|
|
214
|
+
} catch (err) {
|
|
215
|
+
spinner.fail("Import failed.");
|
|
216
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
217
|
+
logger.error(message);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function registerImportCommand(program: Command): void {
|
|
223
|
+
program
|
|
224
|
+
.command("import")
|
|
225
|
+
.description("Import content from JSON, CSV, or WordPress XML files")
|
|
226
|
+
.requiredOption(
|
|
227
|
+
"-s, --source <path>",
|
|
228
|
+
"Path to the import file",
|
|
229
|
+
)
|
|
230
|
+
.option(
|
|
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);
|
|
243
|
+
}
|
package/src/commands/migrate.ts
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { execSync, type ExecSyncOptions } from "node:child_process";
|
|
3
|
-
import ora from "ora";
|
|
4
|
-
import { logger } from "../utils/logger.js";
|
|
5
|
-
|
|
6
|
-
const execOpts: ExecSyncOptions = { stdio: "inherit", cwd: process.cwd() };
|
|
7
|
-
|
|
8
|
-
function runPendingMigrations(): void {
|
|
9
|
-
const spinner = ora("Running pending migrations…").start();
|
|
10
|
-
try {
|
|
11
|
-
spinner.stop();
|
|
12
|
-
execSync("npx prisma migrate deploy", execOpts);
|
|
13
|
-
logger.success("All pending migrations applied.");
|
|
14
|
-
} catch {
|
|
15
|
-
spinner.fail("Migration run failed.");
|
|
16
|
-
process.exitCode = 1;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function createMigration(name: string): void {
|
|
21
|
-
const spinner = ora(`Creating migration "${name}"…`).start();
|
|
22
|
-
try {
|
|
23
|
-
spinner.stop();
|
|
24
|
-
execSync(`npx prisma migrate dev --name ${name}`, execOpts);
|
|
25
|
-
logger.success(`Migration "${name}" created.`);
|
|
26
|
-
} catch {
|
|
27
|
-
spinner.fail("Migration creation failed.");
|
|
28
|
-
process.exitCode = 1;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function showMigrationStatus(): void {
|
|
33
|
-
const spinner = ora("Checking migration status…").start();
|
|
34
|
-
try {
|
|
35
|
-
spinner.stop();
|
|
36
|
-
execSync("npx prisma migrate status", execOpts);
|
|
37
|
-
} catch {
|
|
38
|
-
spinner.fail("Could not retrieve migration status.");
|
|
39
|
-
process.exitCode = 1;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function registerMigrateCommand(program: Command): void {
|
|
44
|
-
const migrate = program
|
|
45
|
-
.command("migrate")
|
|
46
|
-
.description("Database migration utilities powered by Prisma");
|
|
47
|
-
|
|
48
|
-
migrate
|
|
49
|
-
.command("run")
|
|
50
|
-
.description("Apply all pending migrations")
|
|
51
|
-
.action(runPendingMigrations);
|
|
52
|
-
|
|
53
|
-
migrate
|
|
54
|
-
.command("create <name>")
|
|
55
|
-
.description("Create a new migration with the given name")
|
|
56
|
-
.action(createMigration);
|
|
57
|
-
|
|
58
|
-
migrate
|
|
59
|
-
.command("status")
|
|
60
|
-
.description("Show current migration status")
|
|
61
|
-
.action(showMigrationStatus);
|
|
62
|
-
}
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { execSync, type ExecSyncOptions } from "node:child_process";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import { logger } from "../utils/logger.js";
|
|
5
|
+
|
|
6
|
+
const execOpts: ExecSyncOptions = { stdio: "inherit", cwd: process.cwd() };
|
|
7
|
+
|
|
8
|
+
function runPendingMigrations(): void {
|
|
9
|
+
const spinner = ora("Running pending migrations…").start();
|
|
10
|
+
try {
|
|
11
|
+
spinner.stop();
|
|
12
|
+
execSync("npx prisma migrate deploy", execOpts);
|
|
13
|
+
logger.success("All pending migrations applied.");
|
|
14
|
+
} catch {
|
|
15
|
+
spinner.fail("Migration run failed.");
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createMigration(name: string): void {
|
|
21
|
+
const spinner = ora(`Creating migration "${name}"…`).start();
|
|
22
|
+
try {
|
|
23
|
+
spinner.stop();
|
|
24
|
+
execSync(`npx prisma migrate dev --name ${name}`, execOpts);
|
|
25
|
+
logger.success(`Migration "${name}" created.`);
|
|
26
|
+
} catch {
|
|
27
|
+
spinner.fail("Migration creation failed.");
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function showMigrationStatus(): void {
|
|
33
|
+
const spinner = ora("Checking migration status…").start();
|
|
34
|
+
try {
|
|
35
|
+
spinner.stop();
|
|
36
|
+
execSync("npx prisma migrate status", execOpts);
|
|
37
|
+
} catch {
|
|
38
|
+
spinner.fail("Could not retrieve migration status.");
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function registerMigrateCommand(program: Command): void {
|
|
44
|
+
const migrate = program
|
|
45
|
+
.command("migrate")
|
|
46
|
+
.description("Database migration utilities powered by Prisma");
|
|
47
|
+
|
|
48
|
+
migrate
|
|
49
|
+
.command("run")
|
|
50
|
+
.description("Apply all pending migrations")
|
|
51
|
+
.action(runPendingMigrations);
|
|
52
|
+
|
|
53
|
+
migrate
|
|
54
|
+
.command("create <name>")
|
|
55
|
+
.description("Create a new migration with the given name")
|
|
56
|
+
.action(createMigration);
|
|
57
|
+
|
|
58
|
+
migrate
|
|
59
|
+
.command("status")
|
|
60
|
+
.description("Show current migration status")
|
|
61
|
+
.action(showMigrationStatus);
|
|
62
|
+
}
|