@actuate-media/cli 0.1.3 → 0.2.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.
- package/.turbo/turbo-build.log +1 -2
- package/CHANGELOG.md +51 -0
- package/dist/commands/db-init.d.ts +3 -0
- package/dist/commands/db-init.d.ts.map +1 -0
- package/dist/commands/db-init.js +294 -0
- package/dist/commands/db-init.js.map +1 -0
- package/dist/commands/db-status.d.ts +3 -0
- package/dist/commands/db-status.d.ts.map +1 -0
- package/dist/commands/db-status.js +118 -0
- package/dist/commands/db-status.js.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/commands/db-init.ts +301 -0
- package/src/commands/db-status.ts +131 -0
- package/src/index.ts +4 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
WARN Issue while reading "/home/runner/work/actuatecms/actuatecms/.npmrc". Failed to replace env in config: ${NPM_TOKEN}
|
|
2
1
|
|
|
3
|
-
> @actuate-media/cli@0.
|
|
2
|
+
> @actuate-media/cli@0.2.0 build /home/runner/work/actuatecms/actuatecms/packages/cli
|
|
4
3
|
> tsc
|
|
5
4
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# @actuate-media/cli
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 4e3a5eb: RC2-RC4 and hardening release.
|
|
8
|
+
|
|
9
|
+
**cms-core:**
|
|
10
|
+
- Replace isomorphic-dompurify with sanitize-html (eliminates Vercel ESM crash)
|
|
11
|
+
- Remove cms-core from serverExternalPackages, move to transpilePackages
|
|
12
|
+
- Add GET /api/cms/health endpoint with model probing and diagnostics
|
|
13
|
+
- Harden db() Proxy with try/catch on property access and rawDb()
|
|
14
|
+
- Harden safeCount/safeFindMany against Proxy throws
|
|
15
|
+
- Add startup validation for missing CMS_SECRET (503 instead of 500)
|
|
16
|
+
- Bypass auth on /stats when secret is missing (returns zero-data)
|
|
17
|
+
- Loosen NextConfigLike types (experimental/images/webpack as unknown)
|
|
18
|
+
- Implement login anomaly detection (new IP, brute force, unusual hours)
|
|
19
|
+
- Implement webhook DNS resolution with private range checking
|
|
20
|
+
- Implement TOTP reauth verification
|
|
21
|
+
- Add Zod validation for secret in defineConfig (min 32 chars)
|
|
22
|
+
- Read CMS_CORE_VERSION from package.json at runtime instead of hardcoding '0.1.0'
|
|
23
|
+
|
|
24
|
+
**cms-admin:**
|
|
25
|
+
- Ship pre-compiled CSS (dist/actuate-admin.css) with all 48 responsive classes
|
|
26
|
+
- New export: @actuate-media/cms-admin/styles/precompiled.css
|
|
27
|
+
- Add CSS containment (`contain: layout style`) on .actuate-admin
|
|
28
|
+
- Fix ShortcutHelp modal using React state instead of DOM manipulation
|
|
29
|
+
- Add fixed-position layout isolation wrapper to AdminRoot
|
|
30
|
+
- Add useApiData retry limits (maxRetries=3) with exponential backoff
|
|
31
|
+
- Dashboard shows health-aware "Database Setup Required" banner
|
|
32
|
+
|
|
33
|
+
**cli:**
|
|
34
|
+
- Add `actuate db:init` command (injects CMS models into Prisma schema)
|
|
35
|
+
- Add `actuate db:status` command (checks model presence and DB connectivity)
|
|
36
|
+
|
|
37
|
+
**create-actuate-cms:**
|
|
38
|
+
- Embed full CMS schema (9 models + enum) in generated schema.prisma
|
|
39
|
+
- Add @actuate-media/cli to scaffolded devDependencies
|
|
40
|
+
- Use pre-compiled CSS import in scaffolded admin layout
|
|
41
|
+
|
|
42
|
+
### Patch Changes
|
|
43
|
+
|
|
44
|
+
- Updated dependencies [4e3a5eb]
|
|
45
|
+
- @actuate-media/cms-core@0.3.0
|
|
46
|
+
|
|
47
|
+
## 0.1.4
|
|
48
|
+
|
|
49
|
+
### Patch Changes
|
|
50
|
+
|
|
51
|
+
- Updated dependencies
|
|
52
|
+
- @actuate-media/cms-core@0.2.3
|
|
53
|
+
|
|
3
54
|
## 0.1.3
|
|
4
55
|
|
|
5
56
|
### Patch Changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-init.d.ts","sourceRoot":"","sources":["../../src/commands/db-init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgOpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA4E5D"}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { readFile, writeFile, access } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
import { logger } from "../utils/logger.js";
|
|
6
|
+
const CMS_SCHEMA_MARKER = "// ── Actuate CMS models";
|
|
7
|
+
async function fileExists(filePath) {
|
|
8
|
+
try {
|
|
9
|
+
await access(filePath);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function getCmsSchemaFragment() {
|
|
17
|
+
return `
|
|
18
|
+
// ── Actuate CMS models ─────────────────────────────────────────────────────
|
|
19
|
+
// Auto-injected by \`actuate db:init\`. Do not remove this marker comment.
|
|
20
|
+
// Schema version: 1
|
|
21
|
+
|
|
22
|
+
enum DocumentStatus {
|
|
23
|
+
DRAFT
|
|
24
|
+
PUBLISHED
|
|
25
|
+
ARCHIVED
|
|
26
|
+
SCHEDULED
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
model User {
|
|
30
|
+
id String @id @default(cuid())
|
|
31
|
+
email String @unique
|
|
32
|
+
name String @default("")
|
|
33
|
+
role String @default("EDITOR")
|
|
34
|
+
passwordHash String?
|
|
35
|
+
isActive Boolean @default(true)
|
|
36
|
+
isApproved Boolean @default(false)
|
|
37
|
+
emailVerified Boolean @default(false)
|
|
38
|
+
totpEnabled Boolean @default(false)
|
|
39
|
+
totpSecret String?
|
|
40
|
+
backupCodes Json?
|
|
41
|
+
oauthProvider String?
|
|
42
|
+
oauthId String?
|
|
43
|
+
createdAt DateTime @default(now())
|
|
44
|
+
updatedAt DateTime @updatedAt
|
|
45
|
+
|
|
46
|
+
sessions Session[]
|
|
47
|
+
documentsCreated Document[] @relation("DocumentCreatedBy")
|
|
48
|
+
documentsUpdated Document[] @relation("DocumentUpdatedBy")
|
|
49
|
+
documentsReviewed Document[] @relation("DocumentReviewer")
|
|
50
|
+
versions Version[]
|
|
51
|
+
mediaUploaded Media[] @relation("MediaUploadedBy")
|
|
52
|
+
auditLogs AuditLog[]
|
|
53
|
+
|
|
54
|
+
@@index([role])
|
|
55
|
+
@@index([isActive])
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
model Session {
|
|
59
|
+
id String @id @default(cuid())
|
|
60
|
+
userId String
|
|
61
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
62
|
+
token String? @unique
|
|
63
|
+
expiresAt DateTime
|
|
64
|
+
revokedAt DateTime?
|
|
65
|
+
ipAddress String?
|
|
66
|
+
userAgent String?
|
|
67
|
+
createdAt DateTime @default(now())
|
|
68
|
+
|
|
69
|
+
@@index([userId])
|
|
70
|
+
@@index([expiresAt])
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
model Document {
|
|
74
|
+
id String @id @default(cuid())
|
|
75
|
+
collection String
|
|
76
|
+
slug String?
|
|
77
|
+
title String?
|
|
78
|
+
data Json
|
|
79
|
+
status DocumentStatus @default(DRAFT)
|
|
80
|
+
plainText String? @db.Text
|
|
81
|
+
locale String?
|
|
82
|
+
folderId String?
|
|
83
|
+
structuredData Json?
|
|
84
|
+
workflowStage String?
|
|
85
|
+
reviewerId String?
|
|
86
|
+
reviewNote String? @db.Text
|
|
87
|
+
publishedAt DateTime?
|
|
88
|
+
scheduledAt DateTime?
|
|
89
|
+
scheduledUnpublishAt DateTime?
|
|
90
|
+
deletedAt DateTime?
|
|
91
|
+
contentHash String?
|
|
92
|
+
siteId String?
|
|
93
|
+
templateId String?
|
|
94
|
+
createdById String
|
|
95
|
+
updatedById String
|
|
96
|
+
createdAt DateTime @default(now())
|
|
97
|
+
updatedAt DateTime @updatedAt
|
|
98
|
+
|
|
99
|
+
createdBy User @relation("DocumentCreatedBy", fields: [createdById], references: [id], onDelete: Restrict)
|
|
100
|
+
updatedBy User @relation("DocumentUpdatedBy", fields: [updatedById], references: [id], onDelete: Restrict)
|
|
101
|
+
reviewer User? @relation("DocumentReviewer", fields: [reviewerId], references: [id], onDelete: SetNull)
|
|
102
|
+
folder Folder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
|
|
103
|
+
versions Version[]
|
|
104
|
+
formSubmissions FormSubmission[]
|
|
105
|
+
|
|
106
|
+
@@unique([collection, slug], name: "collection_slug")
|
|
107
|
+
@@index([collection])
|
|
108
|
+
@@index([status])
|
|
109
|
+
@@index([deletedAt])
|
|
110
|
+
@@index([publishedAt])
|
|
111
|
+
@@index([folderId])
|
|
112
|
+
@@index([locale])
|
|
113
|
+
@@index([scheduledAt])
|
|
114
|
+
@@index([scheduledUnpublishAt])
|
|
115
|
+
@@index([createdById])
|
|
116
|
+
@@index([updatedById])
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
model Media {
|
|
120
|
+
id String @id @default(cuid())
|
|
121
|
+
filename String
|
|
122
|
+
storageKey String @unique
|
|
123
|
+
mimeType String
|
|
124
|
+
fileSize Int
|
|
125
|
+
width Int?
|
|
126
|
+
height Int?
|
|
127
|
+
altText String?
|
|
128
|
+
title String?
|
|
129
|
+
blurHash String?
|
|
130
|
+
focalPointX Float?
|
|
131
|
+
focalPointY Float?
|
|
132
|
+
folderId String?
|
|
133
|
+
uploadedById String
|
|
134
|
+
createdAt DateTime @default(now())
|
|
135
|
+
updatedAt DateTime @updatedAt
|
|
136
|
+
|
|
137
|
+
uploadedBy User @relation("MediaUploadedBy", fields: [uploadedById], references: [id], onDelete: Restrict)
|
|
138
|
+
folder Folder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
|
|
139
|
+
|
|
140
|
+
@@index([folderId])
|
|
141
|
+
@@index([mimeType])
|
|
142
|
+
@@index([createdAt])
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
model Version {
|
|
146
|
+
id String @id @default(cuid())
|
|
147
|
+
documentId String
|
|
148
|
+
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
|
149
|
+
data Json
|
|
150
|
+
changedById String
|
|
151
|
+
changedBy User @relation(fields: [changedById], references: [id], onDelete: Restrict)
|
|
152
|
+
changeType String @default("UPDATE")
|
|
153
|
+
createdAt DateTime @default(now())
|
|
154
|
+
|
|
155
|
+
@@index([documentId])
|
|
156
|
+
@@index([createdAt])
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
model Folder {
|
|
160
|
+
id String @id @default(cuid())
|
|
161
|
+
name String
|
|
162
|
+
scope String
|
|
163
|
+
parentId String?
|
|
164
|
+
position Int @default(0)
|
|
165
|
+
createdAt DateTime @default(now())
|
|
166
|
+
|
|
167
|
+
parent Folder? @relation("FolderTree", fields: [parentId], references: [id], onDelete: Cascade)
|
|
168
|
+
children Folder[] @relation("FolderTree")
|
|
169
|
+
documents Document[]
|
|
170
|
+
media Media[]
|
|
171
|
+
|
|
172
|
+
@@index([scope])
|
|
173
|
+
@@index([parentId])
|
|
174
|
+
@@index([scope, parentId, position])
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
model Redirect {
|
|
178
|
+
id String @id @default(cuid())
|
|
179
|
+
source String
|
|
180
|
+
destination String
|
|
181
|
+
statusCode Int @default(301)
|
|
182
|
+
isRegex Boolean @default(false)
|
|
183
|
+
notes String?
|
|
184
|
+
createdAt DateTime @default(now())
|
|
185
|
+
updatedAt DateTime @updatedAt
|
|
186
|
+
|
|
187
|
+
@@unique([source])
|
|
188
|
+
@@index([createdAt])
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
model FormSubmission {
|
|
192
|
+
id String @id @default(cuid())
|
|
193
|
+
formId String
|
|
194
|
+
form Document @relation(fields: [formId], references: [id], onDelete: Cascade)
|
|
195
|
+
data Json
|
|
196
|
+
attribution Json?
|
|
197
|
+
submittedAt DateTime @default(now())
|
|
198
|
+
createdAt DateTime @default(now())
|
|
199
|
+
|
|
200
|
+
@@index([formId])
|
|
201
|
+
@@index([submittedAt])
|
|
202
|
+
@@index([createdAt])
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
model AuditLog {
|
|
206
|
+
id String @id @default(cuid())
|
|
207
|
+
event String
|
|
208
|
+
userId String?
|
|
209
|
+
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
|
210
|
+
details Json?
|
|
211
|
+
ipAddress String?
|
|
212
|
+
userAgent String?
|
|
213
|
+
createdAt DateTime @default(now())
|
|
214
|
+
|
|
215
|
+
@@index([event])
|
|
216
|
+
@@index([userId])
|
|
217
|
+
@@index([createdAt])
|
|
218
|
+
}
|
|
219
|
+
`;
|
|
220
|
+
}
|
|
221
|
+
export function registerDbInitCommand(program) {
|
|
222
|
+
program
|
|
223
|
+
.command("db:init")
|
|
224
|
+
.description("Add Actuate CMS models to your Prisma schema and generate the client")
|
|
225
|
+
.option("--schema <path>", "Path to schema.prisma", "prisma/schema.prisma")
|
|
226
|
+
.option("--migrate", "Run prisma migrate dev after adding models")
|
|
227
|
+
.option("--force", "Overwrite existing CMS models if present")
|
|
228
|
+
.action(async (opts) => {
|
|
229
|
+
const schemaPath = resolve(process.cwd(), opts.schema);
|
|
230
|
+
if (!(await fileExists(schemaPath))) {
|
|
231
|
+
logger.error(`Schema file not found at ${schemaPath}`);
|
|
232
|
+
logger.info("Run `npx prisma init` first, or specify --schema <path>.");
|
|
233
|
+
process.exitCode = 1;
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const spinner = ora("Reading Prisma schema...").start();
|
|
237
|
+
let content;
|
|
238
|
+
try {
|
|
239
|
+
content = await readFile(schemaPath, "utf-8");
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
spinner.fail("Failed to read schema file.");
|
|
243
|
+
logger.error(err instanceof Error ? err.message : String(err));
|
|
244
|
+
process.exitCode = 1;
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (content.includes(CMS_SCHEMA_MARKER)) {
|
|
248
|
+
if (!opts.force) {
|
|
249
|
+
spinner.info("Actuate CMS models already present in schema. Use --force to overwrite.");
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
spinner.text = "Removing existing CMS models...";
|
|
253
|
+
const markerIndex = content.indexOf(CMS_SCHEMA_MARKER);
|
|
254
|
+
content = content.substring(0, markerIndex).trimEnd() + "\n";
|
|
255
|
+
}
|
|
256
|
+
spinner.text = "Adding Actuate CMS models...";
|
|
257
|
+
const updatedContent = content.trimEnd() + "\n" + getCmsSchemaFragment();
|
|
258
|
+
try {
|
|
259
|
+
await writeFile(schemaPath, updatedContent);
|
|
260
|
+
spinner.succeed("Actuate CMS models added to schema.");
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
spinner.fail("Failed to write schema file.");
|
|
264
|
+
logger.error(err instanceof Error ? err.message : String(err));
|
|
265
|
+
process.exitCode = 1;
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const execOpts = { stdio: "inherit", cwd: process.cwd() };
|
|
269
|
+
const genSpinner = ora("Running prisma generate...").start();
|
|
270
|
+
try {
|
|
271
|
+
genSpinner.stop();
|
|
272
|
+
execSync("npx prisma generate", execOpts);
|
|
273
|
+
logger.success("Prisma client generated.");
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
logger.warn("prisma generate failed. You may need to set DATABASE_URL first.");
|
|
277
|
+
}
|
|
278
|
+
if (opts.migrate) {
|
|
279
|
+
const migSpinner = ora("Running prisma migrate dev...").start();
|
|
280
|
+
try {
|
|
281
|
+
migSpinner.stop();
|
|
282
|
+
execSync("npx prisma migrate dev --name actuate-cms-init", execOpts);
|
|
283
|
+
logger.success("Migration created and applied.");
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
logger.warn("prisma migrate dev failed. Run it manually after setting DATABASE_URL.");
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
logger.info("Run `npx prisma migrate dev --name actuate-cms` to create the migration.");
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=db-init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-init.js","sourceRoot":"","sources":["../../src/commands/db-init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-status.d.ts","sourceRoot":"","sources":["../../src/commands/db-status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+BpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmG9D"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { readFile, access } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { logger } from "../utils/logger.js";
|
|
7
|
+
const CMS_EXPECTED_MODELS = [
|
|
8
|
+
"User",
|
|
9
|
+
"Session",
|
|
10
|
+
"Document",
|
|
11
|
+
"Media",
|
|
12
|
+
"Version",
|
|
13
|
+
"Folder",
|
|
14
|
+
"Redirect",
|
|
15
|
+
"FormSubmission",
|
|
16
|
+
"AuditLog",
|
|
17
|
+
];
|
|
18
|
+
const CMS_SCHEMA_MARKER = "// ── Actuate CMS models";
|
|
19
|
+
async function fileExists(filePath) {
|
|
20
|
+
try {
|
|
21
|
+
await access(filePath);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function registerDbStatusCommand(program) {
|
|
29
|
+
program
|
|
30
|
+
.command("db:status")
|
|
31
|
+
.description("Check which Actuate CMS models are present in your Prisma schema")
|
|
32
|
+
.option("--schema <path>", "Path to schema.prisma", "prisma/schema.prisma")
|
|
33
|
+
.action(async (opts) => {
|
|
34
|
+
const schemaPath = resolve(process.cwd(), opts.schema);
|
|
35
|
+
if (!(await fileExists(schemaPath))) {
|
|
36
|
+
logger.error(`Schema file not found at ${schemaPath}`);
|
|
37
|
+
logger.info("Run `npx prisma init` first, or specify --schema <path>.");
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const spinner = ora("Checking schema...").start();
|
|
42
|
+
let content;
|
|
43
|
+
try {
|
|
44
|
+
content = await readFile(schemaPath, "utf-8");
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
spinner.fail("Failed to read schema file.");
|
|
48
|
+
logger.error(err instanceof Error ? err.message : String(err));
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
spinner.stop();
|
|
53
|
+
const hasMarker = content.includes(CMS_SCHEMA_MARKER);
|
|
54
|
+
const modelRegex = /^model\s+(\w+)\s*\{/gm;
|
|
55
|
+
const schemaModels = new Set();
|
|
56
|
+
let match;
|
|
57
|
+
while ((match = modelRegex.exec(content)) !== null) {
|
|
58
|
+
schemaModels.add(match[1]);
|
|
59
|
+
}
|
|
60
|
+
console.log();
|
|
61
|
+
console.log(chalk.bold(" Actuate CMS Model Status"));
|
|
62
|
+
console.log(chalk.dim(" ─────────────────────────────────"));
|
|
63
|
+
console.log();
|
|
64
|
+
let present = 0;
|
|
65
|
+
let missing = 0;
|
|
66
|
+
for (const model of CMS_EXPECTED_MODELS) {
|
|
67
|
+
if (schemaModels.has(model)) {
|
|
68
|
+
console.log(` ${chalk.green("✓")} ${model}`);
|
|
69
|
+
present++;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.log(` ${chalk.red("✗")} ${model} ${chalk.dim("— missing")}`);
|
|
73
|
+
missing++;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
console.log();
|
|
77
|
+
console.log(chalk.dim(" ─────────────────────────────────"));
|
|
78
|
+
console.log(` ${chalk.green(present)} present, ${missing > 0 ? chalk.red(missing) : chalk.green(missing)} missing`);
|
|
79
|
+
if (!hasMarker) {
|
|
80
|
+
console.log();
|
|
81
|
+
logger.warn("CMS schema marker not found. Models may have been added manually.");
|
|
82
|
+
logger.info("Run `actuate db:init` to add CMS models with proper markers.");
|
|
83
|
+
}
|
|
84
|
+
if (missing > 0) {
|
|
85
|
+
console.log();
|
|
86
|
+
logger.info("Run `actuate db:init` to add missing models.");
|
|
87
|
+
}
|
|
88
|
+
let dbConnected = false;
|
|
89
|
+
try {
|
|
90
|
+
spinner.start("Checking database connection...");
|
|
91
|
+
execSync("npx prisma db execute --stdin --schema " + JSON.stringify(schemaPath), {
|
|
92
|
+
input: "SELECT 1;",
|
|
93
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
94
|
+
cwd: process.cwd(),
|
|
95
|
+
});
|
|
96
|
+
spinner.succeed("Database connection OK.");
|
|
97
|
+
dbConnected = true;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
spinner.warn("Could not connect to database. Check DATABASE_URL.");
|
|
101
|
+
}
|
|
102
|
+
if (dbConnected && missing === 0) {
|
|
103
|
+
try {
|
|
104
|
+
spinner.start("Checking migration status...");
|
|
105
|
+
spinner.stop();
|
|
106
|
+
execSync("npx prisma migrate status", {
|
|
107
|
+
stdio: "inherit",
|
|
108
|
+
cwd: process.cwd(),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
logger.warn("Could not check migration status.");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
console.log();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=db-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-status.js","sourceRoot":"","sources":["../../src/commands/db-status.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,8 @@ import { registerImportCommand } from "./commands/import.js";
|
|
|
7
7
|
import { registerExportCommand } from "./commands/export.js";
|
|
8
8
|
import { registerUpgradeCommand } from "./commands/upgrade.js";
|
|
9
9
|
import { registerUpdateCheckCommand } from "./commands/update-check.js";
|
|
10
|
+
import { registerDbInitCommand } from "./commands/db-init.js";
|
|
11
|
+
import { registerDbStatusCommand } from "./commands/db-status.js";
|
|
10
12
|
const program = new Command();
|
|
11
13
|
program
|
|
12
14
|
.name("actuate")
|
|
@@ -19,5 +21,7 @@ registerImportCommand(program);
|
|
|
19
21
|
registerExportCommand(program);
|
|
20
22
|
registerUpgradeCommand(program);
|
|
21
23
|
registerUpdateCheckCommand(program);
|
|
24
|
+
registerDbInitCommand(program);
|
|
25
|
+
registerDbStatusCommand(program);
|
|
22
26
|
program.parse();
|
|
23
27
|
//# sourceMappingURL=index.js.map
|
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,CAAC;AACpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAElE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,6DAA6D,CAAC;KAC1E,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,sBAAsB,CAAC,OAAO,CAAC,CAAC;AAChC,uBAAuB,CAAC,OAAO,CAAC,CAAC;AACjC,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,sBAAsB,CAAC,OAAO,CAAC,CAAC;AAChC,0BAA0B,CAAC,OAAO,CAAC,CAAC;AACpC,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,uBAAuB,CAAC,OAAO,CAAC,CAAC;AAEjC,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actuate-media/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "CLI for Actuate CMS — migrations, codegen, imports, exports, and upgrades",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"commander": "^13.1.0",
|
|
20
20
|
"chalk": "^5.4.1",
|
|
21
21
|
"ora": "^8.2.0",
|
|
22
|
-
"@actuate-media/cms-core": "0.
|
|
22
|
+
"@actuate-media/cms-core": "0.3.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"typescript": "^5.7.0"
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { readFile, writeFile, access } from "node:fs/promises";
|
|
3
|
+
import { resolve, join } from "node:path";
|
|
4
|
+
import { execSync, type ExecSyncOptions } from "node:child_process";
|
|
5
|
+
import ora from "ora";
|
|
6
|
+
import { logger } from "../utils/logger.js";
|
|
7
|
+
|
|
8
|
+
const CMS_SCHEMA_MARKER = "// ── Actuate CMS models";
|
|
9
|
+
|
|
10
|
+
async function fileExists(filePath: string): Promise<boolean> {
|
|
11
|
+
try {
|
|
12
|
+
await access(filePath);
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getCmsSchemaFragment(): string {
|
|
20
|
+
return `
|
|
21
|
+
// ── Actuate CMS models ─────────────────────────────────────────────────────
|
|
22
|
+
// Auto-injected by \`actuate db:init\`. Do not remove this marker comment.
|
|
23
|
+
// Schema version: 1
|
|
24
|
+
|
|
25
|
+
enum DocumentStatus {
|
|
26
|
+
DRAFT
|
|
27
|
+
PUBLISHED
|
|
28
|
+
ARCHIVED
|
|
29
|
+
SCHEDULED
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
model User {
|
|
33
|
+
id String @id @default(cuid())
|
|
34
|
+
email String @unique
|
|
35
|
+
name String @default("")
|
|
36
|
+
role String @default("EDITOR")
|
|
37
|
+
passwordHash String?
|
|
38
|
+
isActive Boolean @default(true)
|
|
39
|
+
isApproved Boolean @default(false)
|
|
40
|
+
emailVerified Boolean @default(false)
|
|
41
|
+
totpEnabled Boolean @default(false)
|
|
42
|
+
totpSecret String?
|
|
43
|
+
backupCodes Json?
|
|
44
|
+
oauthProvider String?
|
|
45
|
+
oauthId String?
|
|
46
|
+
createdAt DateTime @default(now())
|
|
47
|
+
updatedAt DateTime @updatedAt
|
|
48
|
+
|
|
49
|
+
sessions Session[]
|
|
50
|
+
documentsCreated Document[] @relation("DocumentCreatedBy")
|
|
51
|
+
documentsUpdated Document[] @relation("DocumentUpdatedBy")
|
|
52
|
+
documentsReviewed Document[] @relation("DocumentReviewer")
|
|
53
|
+
versions Version[]
|
|
54
|
+
mediaUploaded Media[] @relation("MediaUploadedBy")
|
|
55
|
+
auditLogs AuditLog[]
|
|
56
|
+
|
|
57
|
+
@@index([role])
|
|
58
|
+
@@index([isActive])
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
model Session {
|
|
62
|
+
id String @id @default(cuid())
|
|
63
|
+
userId String
|
|
64
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
65
|
+
token String? @unique
|
|
66
|
+
expiresAt DateTime
|
|
67
|
+
revokedAt DateTime?
|
|
68
|
+
ipAddress String?
|
|
69
|
+
userAgent String?
|
|
70
|
+
createdAt DateTime @default(now())
|
|
71
|
+
|
|
72
|
+
@@index([userId])
|
|
73
|
+
@@index([expiresAt])
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
model Document {
|
|
77
|
+
id String @id @default(cuid())
|
|
78
|
+
collection String
|
|
79
|
+
slug String?
|
|
80
|
+
title String?
|
|
81
|
+
data Json
|
|
82
|
+
status DocumentStatus @default(DRAFT)
|
|
83
|
+
plainText String? @db.Text
|
|
84
|
+
locale String?
|
|
85
|
+
folderId String?
|
|
86
|
+
structuredData Json?
|
|
87
|
+
workflowStage String?
|
|
88
|
+
reviewerId String?
|
|
89
|
+
reviewNote String? @db.Text
|
|
90
|
+
publishedAt DateTime?
|
|
91
|
+
scheduledAt DateTime?
|
|
92
|
+
scheduledUnpublishAt DateTime?
|
|
93
|
+
deletedAt DateTime?
|
|
94
|
+
contentHash String?
|
|
95
|
+
siteId String?
|
|
96
|
+
templateId String?
|
|
97
|
+
createdById String
|
|
98
|
+
updatedById String
|
|
99
|
+
createdAt DateTime @default(now())
|
|
100
|
+
updatedAt DateTime @updatedAt
|
|
101
|
+
|
|
102
|
+
createdBy User @relation("DocumentCreatedBy", fields: [createdById], references: [id], onDelete: Restrict)
|
|
103
|
+
updatedBy User @relation("DocumentUpdatedBy", fields: [updatedById], references: [id], onDelete: Restrict)
|
|
104
|
+
reviewer User? @relation("DocumentReviewer", fields: [reviewerId], references: [id], onDelete: SetNull)
|
|
105
|
+
folder Folder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
|
|
106
|
+
versions Version[]
|
|
107
|
+
formSubmissions FormSubmission[]
|
|
108
|
+
|
|
109
|
+
@@unique([collection, slug], name: "collection_slug")
|
|
110
|
+
@@index([collection])
|
|
111
|
+
@@index([status])
|
|
112
|
+
@@index([deletedAt])
|
|
113
|
+
@@index([publishedAt])
|
|
114
|
+
@@index([folderId])
|
|
115
|
+
@@index([locale])
|
|
116
|
+
@@index([scheduledAt])
|
|
117
|
+
@@index([scheduledUnpublishAt])
|
|
118
|
+
@@index([createdById])
|
|
119
|
+
@@index([updatedById])
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
model Media {
|
|
123
|
+
id String @id @default(cuid())
|
|
124
|
+
filename String
|
|
125
|
+
storageKey String @unique
|
|
126
|
+
mimeType String
|
|
127
|
+
fileSize Int
|
|
128
|
+
width Int?
|
|
129
|
+
height Int?
|
|
130
|
+
altText String?
|
|
131
|
+
title String?
|
|
132
|
+
blurHash String?
|
|
133
|
+
focalPointX Float?
|
|
134
|
+
focalPointY Float?
|
|
135
|
+
folderId String?
|
|
136
|
+
uploadedById String
|
|
137
|
+
createdAt DateTime @default(now())
|
|
138
|
+
updatedAt DateTime @updatedAt
|
|
139
|
+
|
|
140
|
+
uploadedBy User @relation("MediaUploadedBy", fields: [uploadedById], references: [id], onDelete: Restrict)
|
|
141
|
+
folder Folder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
|
|
142
|
+
|
|
143
|
+
@@index([folderId])
|
|
144
|
+
@@index([mimeType])
|
|
145
|
+
@@index([createdAt])
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
model Version {
|
|
149
|
+
id String @id @default(cuid())
|
|
150
|
+
documentId String
|
|
151
|
+
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
|
152
|
+
data Json
|
|
153
|
+
changedById String
|
|
154
|
+
changedBy User @relation(fields: [changedById], references: [id], onDelete: Restrict)
|
|
155
|
+
changeType String @default("UPDATE")
|
|
156
|
+
createdAt DateTime @default(now())
|
|
157
|
+
|
|
158
|
+
@@index([documentId])
|
|
159
|
+
@@index([createdAt])
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
model Folder {
|
|
163
|
+
id String @id @default(cuid())
|
|
164
|
+
name String
|
|
165
|
+
scope String
|
|
166
|
+
parentId String?
|
|
167
|
+
position Int @default(0)
|
|
168
|
+
createdAt DateTime @default(now())
|
|
169
|
+
|
|
170
|
+
parent Folder? @relation("FolderTree", fields: [parentId], references: [id], onDelete: Cascade)
|
|
171
|
+
children Folder[] @relation("FolderTree")
|
|
172
|
+
documents Document[]
|
|
173
|
+
media Media[]
|
|
174
|
+
|
|
175
|
+
@@index([scope])
|
|
176
|
+
@@index([parentId])
|
|
177
|
+
@@index([scope, parentId, position])
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
model Redirect {
|
|
181
|
+
id String @id @default(cuid())
|
|
182
|
+
source String
|
|
183
|
+
destination String
|
|
184
|
+
statusCode Int @default(301)
|
|
185
|
+
isRegex Boolean @default(false)
|
|
186
|
+
notes String?
|
|
187
|
+
createdAt DateTime @default(now())
|
|
188
|
+
updatedAt DateTime @updatedAt
|
|
189
|
+
|
|
190
|
+
@@unique([source])
|
|
191
|
+
@@index([createdAt])
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
model FormSubmission {
|
|
195
|
+
id String @id @default(cuid())
|
|
196
|
+
formId String
|
|
197
|
+
form Document @relation(fields: [formId], references: [id], onDelete: Cascade)
|
|
198
|
+
data Json
|
|
199
|
+
attribution Json?
|
|
200
|
+
submittedAt DateTime @default(now())
|
|
201
|
+
createdAt DateTime @default(now())
|
|
202
|
+
|
|
203
|
+
@@index([formId])
|
|
204
|
+
@@index([submittedAt])
|
|
205
|
+
@@index([createdAt])
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
model AuditLog {
|
|
209
|
+
id String @id @default(cuid())
|
|
210
|
+
event String
|
|
211
|
+
userId String?
|
|
212
|
+
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
|
213
|
+
details Json?
|
|
214
|
+
ipAddress String?
|
|
215
|
+
userAgent String?
|
|
216
|
+
createdAt DateTime @default(now())
|
|
217
|
+
|
|
218
|
+
@@index([event])
|
|
219
|
+
@@index([userId])
|
|
220
|
+
@@index([createdAt])
|
|
221
|
+
}
|
|
222
|
+
`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function registerDbInitCommand(program: Command): void {
|
|
226
|
+
program
|
|
227
|
+
.command("db:init")
|
|
228
|
+
.description("Add Actuate CMS models to your Prisma schema and generate the client")
|
|
229
|
+
.option("--schema <path>", "Path to schema.prisma", "prisma/schema.prisma")
|
|
230
|
+
.option("--migrate", "Run prisma migrate dev after adding models")
|
|
231
|
+
.option("--force", "Overwrite existing CMS models if present")
|
|
232
|
+
.action(async (opts: { schema: string; migrate?: boolean; force?: boolean }) => {
|
|
233
|
+
const schemaPath = resolve(process.cwd(), opts.schema);
|
|
234
|
+
|
|
235
|
+
if (!(await fileExists(schemaPath))) {
|
|
236
|
+
logger.error(`Schema file not found at ${schemaPath}`);
|
|
237
|
+
logger.info("Run `npx prisma init` first, or specify --schema <path>.");
|
|
238
|
+
process.exitCode = 1;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const spinner = ora("Reading Prisma schema...").start();
|
|
243
|
+
|
|
244
|
+
let content: string;
|
|
245
|
+
try {
|
|
246
|
+
content = await readFile(schemaPath, "utf-8");
|
|
247
|
+
} catch (err) {
|
|
248
|
+
spinner.fail("Failed to read schema file.");
|
|
249
|
+
logger.error(err instanceof Error ? err.message : String(err));
|
|
250
|
+
process.exitCode = 1;
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (content.includes(CMS_SCHEMA_MARKER)) {
|
|
255
|
+
if (!opts.force) {
|
|
256
|
+
spinner.info("Actuate CMS models already present in schema. Use --force to overwrite.");
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
spinner.text = "Removing existing CMS models...";
|
|
260
|
+
const markerIndex = content.indexOf(CMS_SCHEMA_MARKER);
|
|
261
|
+
content = content.substring(0, markerIndex).trimEnd() + "\n";
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
spinner.text = "Adding Actuate CMS models...";
|
|
265
|
+
const updatedContent = content.trimEnd() + "\n" + getCmsSchemaFragment();
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
await writeFile(schemaPath, updatedContent);
|
|
269
|
+
spinner.succeed("Actuate CMS models added to schema.");
|
|
270
|
+
} catch (err) {
|
|
271
|
+
spinner.fail("Failed to write schema file.");
|
|
272
|
+
logger.error(err instanceof Error ? err.message : String(err));
|
|
273
|
+
process.exitCode = 1;
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const execOpts: ExecSyncOptions = { stdio: "inherit", cwd: process.cwd() };
|
|
278
|
+
|
|
279
|
+
const genSpinner = ora("Running prisma generate...").start();
|
|
280
|
+
try {
|
|
281
|
+
genSpinner.stop();
|
|
282
|
+
execSync("npx prisma generate", execOpts);
|
|
283
|
+
logger.success("Prisma client generated.");
|
|
284
|
+
} catch {
|
|
285
|
+
logger.warn("prisma generate failed. You may need to set DATABASE_URL first.");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (opts.migrate) {
|
|
289
|
+
const migSpinner = ora("Running prisma migrate dev...").start();
|
|
290
|
+
try {
|
|
291
|
+
migSpinner.stop();
|
|
292
|
+
execSync("npx prisma migrate dev --name actuate-cms-init", execOpts);
|
|
293
|
+
logger.success("Migration created and applied.");
|
|
294
|
+
} catch {
|
|
295
|
+
logger.warn("prisma migrate dev failed. Run it manually after setting DATABASE_URL.");
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
logger.info("Run `npx prisma migrate dev --name actuate-cms` to create the migration.");
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { readFile, access } from "node:fs/promises";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import ora from "ora";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { logger } from "../utils/logger.js";
|
|
8
|
+
|
|
9
|
+
const CMS_EXPECTED_MODELS = [
|
|
10
|
+
"User",
|
|
11
|
+
"Session",
|
|
12
|
+
"Document",
|
|
13
|
+
"Media",
|
|
14
|
+
"Version",
|
|
15
|
+
"Folder",
|
|
16
|
+
"Redirect",
|
|
17
|
+
"FormSubmission",
|
|
18
|
+
"AuditLog",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const CMS_SCHEMA_MARKER = "// ── Actuate CMS models";
|
|
22
|
+
|
|
23
|
+
async function fileExists(filePath: string): Promise<boolean> {
|
|
24
|
+
try {
|
|
25
|
+
await access(filePath);
|
|
26
|
+
return true;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function registerDbStatusCommand(program: Command): void {
|
|
33
|
+
program
|
|
34
|
+
.command("db:status")
|
|
35
|
+
.description("Check which Actuate CMS models are present in your Prisma schema")
|
|
36
|
+
.option("--schema <path>", "Path to schema.prisma", "prisma/schema.prisma")
|
|
37
|
+
.action(async (opts: { schema: string }) => {
|
|
38
|
+
const schemaPath = resolve(process.cwd(), opts.schema);
|
|
39
|
+
|
|
40
|
+
if (!(await fileExists(schemaPath))) {
|
|
41
|
+
logger.error(`Schema file not found at ${schemaPath}`);
|
|
42
|
+
logger.info("Run `npx prisma init` first, or specify --schema <path>.");
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const spinner = ora("Checking schema...").start();
|
|
48
|
+
|
|
49
|
+
let content: string;
|
|
50
|
+
try {
|
|
51
|
+
content = await readFile(schemaPath, "utf-8");
|
|
52
|
+
} catch (err) {
|
|
53
|
+
spinner.fail("Failed to read schema file.");
|
|
54
|
+
logger.error(err instanceof Error ? err.message : String(err));
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
spinner.stop();
|
|
60
|
+
|
|
61
|
+
const hasMarker = content.includes(CMS_SCHEMA_MARKER);
|
|
62
|
+
const modelRegex = /^model\s+(\w+)\s*\{/gm;
|
|
63
|
+
const schemaModels = new Set<string>();
|
|
64
|
+
let match;
|
|
65
|
+
while ((match = modelRegex.exec(content)) !== null) {
|
|
66
|
+
schemaModels.add(match[1]!);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log();
|
|
70
|
+
console.log(chalk.bold(" Actuate CMS Model Status"));
|
|
71
|
+
console.log(chalk.dim(" ─────────────────────────────────"));
|
|
72
|
+
console.log();
|
|
73
|
+
|
|
74
|
+
let present = 0;
|
|
75
|
+
let missing = 0;
|
|
76
|
+
|
|
77
|
+
for (const model of CMS_EXPECTED_MODELS) {
|
|
78
|
+
if (schemaModels.has(model)) {
|
|
79
|
+
console.log(` ${chalk.green("✓")} ${model}`);
|
|
80
|
+
present++;
|
|
81
|
+
} else {
|
|
82
|
+
console.log(` ${chalk.red("✗")} ${model} ${chalk.dim("— missing")}`);
|
|
83
|
+
missing++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log();
|
|
88
|
+
console.log(chalk.dim(" ─────────────────────────────────"));
|
|
89
|
+
console.log(` ${chalk.green(present)} present, ${missing > 0 ? chalk.red(missing) : chalk.green(missing)} missing`);
|
|
90
|
+
|
|
91
|
+
if (!hasMarker) {
|
|
92
|
+
console.log();
|
|
93
|
+
logger.warn("CMS schema marker not found. Models may have been added manually.");
|
|
94
|
+
logger.info("Run `actuate db:init` to add CMS models with proper markers.");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (missing > 0) {
|
|
98
|
+
console.log();
|
|
99
|
+
logger.info("Run `actuate db:init` to add missing models.");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let dbConnected = false;
|
|
103
|
+
try {
|
|
104
|
+
spinner.start("Checking database connection...");
|
|
105
|
+
execSync("npx prisma db execute --stdin --schema " + JSON.stringify(schemaPath), {
|
|
106
|
+
input: "SELECT 1;",
|
|
107
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
108
|
+
cwd: process.cwd(),
|
|
109
|
+
});
|
|
110
|
+
spinner.succeed("Database connection OK.");
|
|
111
|
+
dbConnected = true;
|
|
112
|
+
} catch {
|
|
113
|
+
spinner.warn("Could not connect to database. Check DATABASE_URL.");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (dbConnected && missing === 0) {
|
|
117
|
+
try {
|
|
118
|
+
spinner.start("Checking migration status...");
|
|
119
|
+
spinner.stop();
|
|
120
|
+
execSync("npx prisma migrate status", {
|
|
121
|
+
stdio: "inherit",
|
|
122
|
+
cwd: process.cwd(),
|
|
123
|
+
});
|
|
124
|
+
} catch {
|
|
125
|
+
logger.warn("Could not check migration status.");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log();
|
|
130
|
+
});
|
|
131
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ import { registerImportCommand } from "./commands/import.js";
|
|
|
7
7
|
import { registerExportCommand } from "./commands/export.js";
|
|
8
8
|
import { registerUpgradeCommand } from "./commands/upgrade.js";
|
|
9
9
|
import { registerUpdateCheckCommand } from "./commands/update-check.js";
|
|
10
|
+
import { registerDbInitCommand } from "./commands/db-init.js";
|
|
11
|
+
import { registerDbStatusCommand } from "./commands/db-status.js";
|
|
10
12
|
|
|
11
13
|
const program = new Command();
|
|
12
14
|
|
|
@@ -22,5 +24,7 @@ registerImportCommand(program);
|
|
|
22
24
|
registerExportCommand(program);
|
|
23
25
|
registerUpgradeCommand(program);
|
|
24
26
|
registerUpdateCheckCommand(program);
|
|
27
|
+
registerDbInitCommand(program);
|
|
28
|
+
registerDbStatusCommand(program);
|
|
25
29
|
|
|
26
30
|
program.parse();
|