Skip to content

Commit 68708b4

Browse files
committed
feat(cli): Improves database introspection and syncing
Enhances the `db pull` command with a spinner for better UX. Adds color-coded logging to highlight important steps. Provides more detailed output on schema changes, including deleted models, enums, added fields, and deleted attributes. Also includes minor improvements to enum mapping and constraint handling.
1 parent 1dc45fa commit 68708b4

File tree

2 files changed

+73
-20
lines changed

2 files changed

+73
-20
lines changed

packages/cli/src/actions/db.ts

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import { config } from '@dotenvx/dotenvx';
22
import { ZModelCodeGenerator } from '@zenstackhq/language';
33
import { DataModel, Enum, type Model } from '@zenstackhq/language/ast';
4+
import colors from 'colors';
45
import fs from 'node:fs';
56
import path from 'node:path';
7+
import ora from 'ora';
68
import { execPrisma } from '../utils/exec-utils';
79
import {
810
generateTempPrismaSchema,
911
getSchemaFile,
1012
handleSubProcessError,
11-
requireDataSourceUrl,
1213
loadSchemaDocument,
14+
requireDataSourceUrl,
1315
} from './action-utils';
1416
import { syncEnums, syncRelation, syncTable, type Relation } from './pull';
1517
import { providers } from './pull/provider';
@@ -77,6 +79,7 @@ async function runPush(options: PushOptions) {
7779
}
7880

7981
async function runPull(options: PullOptions) {
82+
const spinner = ora();
8083
try {
8184
const schemaFile = getSchemaFile(options.schema);
8285
const { model, services } = await loadSchemaDocument(schemaFile, { returnServices: true });
@@ -98,15 +101,20 @@ async function runPull(options: PullOptions) {
98101
if (!provider) {
99102
throw new Error(`No introspection provider found for: ${datasource.provider}`);
100103
}
101-
console.log('Starging introspect the database...');
104+
105+
spinner.start('Introspecting database...');
102106
const { enums: allEnums, tables: allTables } = await provider.introspect(datasource.url);
107+
spinner.succeed('Database introspected');
108+
103109
const enums = provider.isSupportedFeature('Schema')
104110
? allEnums.filter((e) => datasource.allSchemas.includes(e.schema_name))
105111
: allEnums;
106112
const tables = provider.isSupportedFeature('Schema')
107113
? allTables.filter((t) => datasource.allSchemas.includes(t.schema))
108114
: allTables;
109115

116+
console.log(colors.blue('Syncing schema...'));
117+
110118
const newModel: Model = {
111119
$type: 'Model',
112120
$container: undefined,
@@ -165,12 +173,22 @@ async function runPull(options: PullOptions) {
165173
});
166174
}
167175

176+
console.log(colors.blue('Schema synced'));
177+
168178
const cwd = new URL(`file://${process.cwd()}`).pathname;
169179
const docs = services.shared.workspace.LangiumDocuments.all
170180
.filter(({ uri }) => uri.path.toLowerCase().startsWith(cwd.toLowerCase()))
171181
.toArray();
172182
const docsSet = new Set(docs.map((d) => d.uri.toString()));
173183

184+
console.log(colors.bold('\nApplying changes to ZModel...'));
185+
186+
const deletedModels: string[] = [];
187+
const deletedEnums: string[] = [];
188+
const addedFields: string[] = [];
189+
const deletedAttributes: string[] = [];
190+
const deletedFields: string[] = [];
191+
174192
//Delete models
175193
services.shared.workspace.IndexManager.allElements('DataModel', docsSet)
176194
.filter(
@@ -181,7 +199,7 @@ async function runPull(options: PullOptions) {
181199
const model = decl.node!.$container as Model;
182200
const index = model.declarations.findIndex((d) => d === decl.node);
183201
model.declarations.splice(index, 1);
184-
console.log(`Delete model ${decl.name}`);
202+
deletedModels.push(colors.red(`- Model ${decl.name} deleted`));
185203
});
186204

187205
// Delete Enums
@@ -195,7 +213,7 @@ async function runPull(options: PullOptions) {
195213
const model = decl.node!.$container as Model;
196214
const index = model.declarations.findIndex((d) => d === decl.node);
197215
model.declarations.splice(index, 1);
198-
console.log(`Delete enum ${decl.name}`);
216+
deletedEnums.push(colors.red(`- Enum ${decl.name} deleted`));
199217
});
200218
//
201219
newModel.declarations
@@ -239,14 +257,16 @@ async function runPull(options: PullOptions) {
239257

240258
if (originalFields.length > 1) {
241259
console.warn(
242-
`Found more original fields, need to tweak the search algorith. ${originalDataModel.name}->[${originalFields.map((of) => of.name).join(', ')}](${f.name})`,
260+
colors.yellow(
261+
`Found more original fields, need to tweak the search algorith. ${originalDataModel.name}->[${originalFields.map((of) => of.name).join(', ')}](${f.name})`,
262+
),
243263
);
244264
return;
245265
}
246266
const originalField = originalFields.at(0);
247267
Object.freeze(originalField);
248268
if (!originalField) {
249-
console.log(`Added field ${f.name} to ${originalDataModel.name}`);
269+
addedFields.push(colors.green(`+ Field ${f.name} added to ${originalDataModel.name}`));
250270
(f as any).$container = originalDataModel;
251271
originalDataModel.fields.push(f as any);
252272
if (f.$type === 'DataField' && f.type.reference?.ref) {
@@ -260,7 +280,7 @@ async function runPull(options: PullOptions) {
260280
}
261281
return;
262282
}
263-
if (f.name === 'profiles') console.log(f.attributes.length);
283+
264284
originalField.attributes
265285
.filter(
266286
(attr) =>
@@ -271,7 +291,9 @@ async function runPull(options: PullOptions) {
271291
const field = attr.$container;
272292
const index = field.attributes.findIndex((d) => d === attr);
273293
field.attributes.splice(index, 1);
274-
console.log(`Delete attribute from field:${field.name} ${attr.decl.$refText}`);
294+
deletedAttributes.push(
295+
colors.yellow(`- Attribute ${attr.decl.$refText} deleted from field: ${field.name}`),
296+
);
275297
});
276298
});
277299
originalDataModel.fields
@@ -295,10 +317,35 @@ async function runPull(options: PullOptions) {
295317
const _model = f.$container;
296318
const index = _model.fields.findIndex((d) => d === f);
297319
_model.fields.splice(index, 1);
298-
console.log(`Delete field ${f.name}`);
320+
deletedFields.push(colors.red(`- Field ${f.name} deleted from ${_model.name}`));
299321
});
300322
});
301323

324+
if (deletedModels.length > 0) {
325+
console.log(colors.bold('\nDeleted Models:'));
326+
deletedModels.forEach((msg) => console.log(msg));
327+
}
328+
329+
if (deletedEnums.length > 0) {
330+
console.log(colors.bold('\nDeleted Enums:'));
331+
deletedEnums.forEach((msg) => console.log(msg));
332+
}
333+
334+
if (addedFields.length > 0) {
335+
console.log(colors.bold('\nAdded Fields:'));
336+
addedFields.forEach((msg) => console.log(msg));
337+
}
338+
339+
if (deletedAttributes.length > 0) {
340+
console.log(colors.bold('\nDeleted Attributes:'));
341+
deletedAttributes.forEach((msg) => console.log(msg));
342+
}
343+
344+
if (deletedFields.length > 0) {
345+
console.log(colors.bold('\nDeleted Fields:'));
346+
deletedFields.forEach((msg) => console.log(msg));
347+
}
348+
302349
if (options.out && !fs.lstatSync(options.out).isFile()) {
303350
throw new Error(`Output path ${options.out} is not a file`);
304351
}
@@ -311,20 +358,23 @@ async function runPull(options: PullOptions) {
311358
if (options.out) {
312359
const zmodelSchema = generator.generate(newModel);
313360

314-
console.log(`Writing to ${options.out}`);
361+
console.log(colors.blue(`Writing to ${options.out}`));
315362

316363
const outPath = options.out ? path.resolve(options.out) : schemaFile;
317364

318365
fs.writeFileSync(outPath, zmodelSchema);
319366
} else {
320367
docs.forEach(({ uri, parseResult: { value: model } }) => {
321368
const zmodelSchema = generator.generate(model);
322-
console.log(`Writing to ${uri.path}`);
369+
console.log(colors.blue(`Writing to ${uri.path}`));
323370
fs.writeFileSync(uri.fsPath, zmodelSchema);
324371
});
325372
}
373+
374+
console.log(colors.green.bold('\nPull completed successfully!'));
326375
} catch (error) {
327-
console.log(error);
376+
spinner.fail('Pull failed');
377+
console.error(error);
328378
throw error;
329379
}
330-
}
380+
}

packages/cli/src/actions/pull/index.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ZModelServices } from '@zenstackhq/language';
2+
import colors from 'colors';
23
import {
34
isEnum,
45
type Attribute,
@@ -38,7 +39,7 @@ export function syncEnums({
3839
if (provider.isSupportedFeature('NativeEnum')) {
3940
for (const dbEnum of dbEnums) {
4041
const { modified, name } = resolveNameCasing(options.modelCasing, dbEnum.enum_type);
41-
if (modified) console.log(`Mapping enum ${dbEnum.enum_type} to ${name}`);
42+
if (modified) console.log(colors.gray(`Mapping enum ${dbEnum.enum_type} to ${name}`));
4243
const factory = new EnumFactory().setName(name);
4344
if (modified || options.alwaysMap)
4445
factory.addAttribute((builder) =>
@@ -344,16 +345,18 @@ export function syncTable({
344345
table.indexes.forEach((index) => {
345346
if (index.predicate) {
346347
//These constraints are not supported by Zenstack, because Zenstack currently does not fully support check constraints. Read more: https://pris.ly/d/check-constraints
347-
console.log(
348-
'These constraints are not supported by Zenstack. Read more: https://pris.ly/d/check-constraints',
349-
`- Model: "${table.name}", constraint: "${index.name}"`,
348+
console.warn(
349+
colors.yellow(
350+
`These constraints are not supported by Zenstack. Read more: https://pris.ly/d/check-constraints\n- Model: "${table.name}", constraint: "${index.name}"`,
351+
),
350352
);
351353
return;
352354
}
353355
if (index.columns.find((c) => c.expression)) {
354-
console.log(
355-
'These constraints are not supported by Zenstack. Read more: https://pris.ly/d/check-constraints',
356-
`- Model: "${table.name}", constraint: "${index.name}"`,
356+
console.warn(
357+
colors.yellow(
358+
`These constraints are not supported by Zenstack. Read more: https://pris.ly/d/check-constraints\n- Model: "${table.name}", constraint: "${index.name}"`,
359+
),
357360
);
358361
return;
359362
}

0 commit comments

Comments
 (0)