mirror of
https://github.com/zenstackhq/zenstack
synced 2026-05-24 10:08:55 +00:00
feat(cli): add "format" command (#419)
* feat(cli): add "format" command * better-auth: format generated schema * update
This commit is contained in:
parent
e8f979067c
commit
fcd557312d
7 changed files with 110 additions and 6 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { lowerCaseFirst, upperCaseFirst } from '@zenstackhq/common-helpers';
|
||||
import { loadDocument, ZModelCodeGenerator } from '@zenstackhq/language';
|
||||
import { formatDocument, loadDocument, ZModelCodeGenerator } from '@zenstackhq/language';
|
||||
import {
|
||||
Argument,
|
||||
ArrayExpr,
|
||||
|
|
@ -111,7 +111,13 @@ async function updateSchema(
|
|||
}
|
||||
|
||||
const generator = new ZModelCodeGenerator();
|
||||
const content = generator.generate(zmodel);
|
||||
let content = generator.generate(zmodel);
|
||||
|
||||
try {
|
||||
content = await formatDocument(content);
|
||||
} catch {
|
||||
// ignore formatting errors
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
|
|
|||
27
packages/cli/src/actions/format.ts
Normal file
27
packages/cli/src/actions/format.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { formatDocument } from '@zenstackhq/language';
|
||||
import colors from 'colors';
|
||||
import fs from 'node:fs';
|
||||
import { getSchemaFile } from './action-utils';
|
||||
|
||||
type Options = {
|
||||
schema?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* CLI action for formatting a ZModel schema file.
|
||||
*/
|
||||
export async function run(options: Options) {
|
||||
const schemaFile = getSchemaFile(options.schema);
|
||||
let formattedContent: string;
|
||||
|
||||
try {
|
||||
formattedContent = await formatDocument(fs.readFileSync(schemaFile, 'utf-8'));
|
||||
} catch (error) {
|
||||
console.error(colors.red('✗ Schema formatting failed.'));
|
||||
// Re-throw to maintain CLI exit code behavior
|
||||
throw error;
|
||||
}
|
||||
|
||||
fs.writeFileSync(schemaFile, formattedContent, 'utf-8');
|
||||
console.log(colors.green('✓ Schema formatting completed successfully.'));
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import { run as check } from './check';
|
||||
import { run as db } from './db';
|
||||
import { run as format } from './format';
|
||||
import { run as generate } from './generate';
|
||||
import { run as info } from './info';
|
||||
import { run as init } from './init';
|
||||
import { run as migrate } from './migrate';
|
||||
import { run as check } from './check';
|
||||
|
||||
export { db, generate, info, init, migrate, check };
|
||||
export { check, db, format, generate, info, init, migrate };
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ const checkAction = async (options: Parameters<typeof actions.check>[0]): Promis
|
|||
await telemetry.trackCommand('check', () => actions.check(options));
|
||||
};
|
||||
|
||||
const formatAction = async (options: Parameters<typeof actions.format>[0]): Promise<void> => {
|
||||
await telemetry.trackCommand('format', () => actions.format(options));
|
||||
};
|
||||
|
||||
function createProgram() {
|
||||
const program = new Command('zen')
|
||||
.alias('zenstack')
|
||||
|
|
@ -145,6 +149,13 @@ function createProgram() {
|
|||
.addOption(noVersionCheckOption)
|
||||
.action(checkAction);
|
||||
|
||||
program
|
||||
.command('format')
|
||||
.description('Format a ZModel schema file')
|
||||
.addOption(schemaOption)
|
||||
.addOption(noVersionCheckOption)
|
||||
.action(formatAction);
|
||||
|
||||
program.addHelpCommand('help [command]', 'Display help for a command');
|
||||
|
||||
program.hook('preAction', async (_thisCommand, actionCommand) => {
|
||||
|
|
|
|||
33
packages/cli/test/format.test.ts
Normal file
33
packages/cli/test/format.test.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { createProject, runCli } from './utils';
|
||||
import fs from 'node:fs';
|
||||
|
||||
const model = `
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
}
|
||||
`;
|
||||
|
||||
describe('CLI format command test', () => {
|
||||
it('should format a valid schema successfully', () => {
|
||||
const workDir = createProject(model);
|
||||
expect(() => runCli('format', workDir)).not.toThrow();
|
||||
const updatedContent = fs.readFileSync(`${workDir}/zenstack/schema.zmodel`, 'utf-8');
|
||||
expect(
|
||||
updatedContent.includes(`model User {
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
}`),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should silently ignore invalid schema', () => {
|
||||
const invalidModel = `
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
`;
|
||||
const workDir = createProject(invalidModel);
|
||||
expect(() => runCli('format', workDir)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,4 +1,12 @@
|
|||
import { isAstNode, URI, type AstNode, type LangiumDocument, type LangiumDocuments, type Mutable } from 'langium';
|
||||
import {
|
||||
isAstNode,
|
||||
TextDocument,
|
||||
URI,
|
||||
type AstNode,
|
||||
type LangiumDocument,
|
||||
type LangiumDocuments,
|
||||
type Mutable,
|
||||
} from 'langium';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
|
@ -6,6 +14,7 @@ import { isDataSource, type Model } from './ast';
|
|||
import { STD_LIB_MODULE_NAME } from './constants';
|
||||
import { createZModelServices } from './module';
|
||||
import { getDataModelAndTypeDefs, getDocument, hasAttribute, resolveImport, resolveTransitiveImports } from './utils';
|
||||
import type { ZModelFormatter } from './zmodel-formatter';
|
||||
|
||||
/**
|
||||
* Loads ZModel document from the given file name. Include the additional document
|
||||
|
|
@ -200,3 +209,20 @@ function validationAfterImportMerge(model: Model) {
|
|||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the given ZModel content.
|
||||
*/
|
||||
export async function formatDocument(content: string) {
|
||||
const services = createZModelServices().ZModelLanguage;
|
||||
const langiumDocuments = services.shared.workspace.LangiumDocuments;
|
||||
const document = langiumDocuments.createDocument(URI.parse('memory://schema.zmodel'), content);
|
||||
const formatter = services.lsp.Formatter as ZModelFormatter;
|
||||
const identifier = { uri: document.uri.toString() };
|
||||
const options = formatter.getFormatOptions() ?? {
|
||||
insertSpaces: true,
|
||||
tabSize: 4,
|
||||
};
|
||||
const edits = await formatter.formatDocument(document, { options, textDocument: identifier });
|
||||
return TextDocument.applyEdits(document.textDocument, edits);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
export { loadDocument } from './document';
|
||||
export { formatDocument, loadDocument } from './document';
|
||||
export * from './module';
|
||||
export { ZModelCodeGenerator } from './zmodel-code-generator';
|
||||
|
|
|
|||
Loading…
Reference in a new issue