Skip to content

Commit bf85ceb

Browse files
authored
feat:Support ZModel format command in CLI (#869)
1 parent b9f2e10 commit bf85ceb

File tree

6 files changed

+108
-4
lines changed

6 files changed

+108
-4
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { getVersion } from '@zenstackhq/runtime';
2+
import { formatDocument } from '../cli-util';
3+
import colors from 'colors';
4+
import ora from 'ora';
5+
import { writeFile } from 'fs/promises';
6+
7+
export async function format(projectPath: string, options: { schema: string }) {
8+
const version = getVersion();
9+
console.log(colors.bold(`⌛️ ZenStack CLI v${version}`));
10+
11+
const schemaFile = options.schema;
12+
const spinner = ora(`Formatting ${schemaFile}`).start();
13+
try {
14+
const formattedDoc = await formatDocument(schemaFile);
15+
await writeFile(schemaFile, formattedDoc);
16+
spinner.succeed();
17+
} catch (e) {
18+
spinner.fail();
19+
throw e;
20+
}
21+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './generate';
22
export * from './info';
33
export * from './init';
44
export * from './repl';
5+
export * from './format';

packages/schema/src/cli/cli-util.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { createZModelServices, ZModelServices } from '../language-server/zmodel-
1313
import { mergeBaseModel, resolveImport, resolveTransitiveImports } from '../utils/ast-utils';
1414
import { getVersion } from '../utils/version-utils';
1515
import { CliError } from './cli-error';
16+
import { ZModelFormatter } from '../language-server/zmodel-formatter';
17+
import { TextDocument } from 'vscode-languageserver-textdocument';
1618

1719
// required minimal version of Prisma
1820
export const requiredPrismaVersion = '4.8.0';
@@ -251,3 +253,26 @@ export async function checkNewVersion() {
251253
console.log(`A newer version ${colors.cyan(latestVersion)} is available.`);
252254
}
253255
}
256+
257+
export async function formatDocument(fileName: string) {
258+
const services = createZModelServices(NodeFileSystem).ZModel;
259+
const extensions = services.LanguageMetaData.fileExtensions;
260+
if (!extensions.includes(path.extname(fileName))) {
261+
console.error(colors.yellow(`Please choose a file with extension: ${extensions}.`));
262+
throw new CliError('invalid schema file');
263+
}
264+
265+
const langiumDocuments = services.shared.workspace.LangiumDocuments;
266+
const document = langiumDocuments.getOrCreateDocument(URI.file(path.resolve(fileName)));
267+
268+
const formatter = services.lsp.Formatter as ZModelFormatter;
269+
270+
const identifier = { uri: document.uri.toString() };
271+
const options = formatter.getFormatOptions() ?? {
272+
insertSpaces: true,
273+
tabSize: 4,
274+
};
275+
276+
const edits = await formatter.formatDocument(document, { options, textDocument: identifier });
277+
return TextDocument.applyEdits(document.textDocument, edits);
278+
}

packages/schema/src/cli/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ export const replAction = async (options: Parameters<typeof actions.repl>[1]): P
5050
);
5151
};
5252

53+
export const formatAction = async (options: Parameters<typeof actions.format>[1]): Promise<void> => {
54+
await telemetry.trackSpan(
55+
'cli:command:start',
56+
'cli:command:complete',
57+
'cli:command:error',
58+
{ command: 'format' },
59+
() => actions.format(process.cwd(), options)
60+
);
61+
};
62+
5363
export function createProgram() {
5464
const program = new Command('zenstack');
5565

@@ -116,6 +126,12 @@ export function createProgram() {
116126
.option('--table', 'enable table format output')
117127
.action(replAction);
118128

129+
program
130+
.command('format')
131+
.description('Format a ZenStack schema file.')
132+
.addOption(schemaOption)
133+
.action(formatAction);
134+
119135
// make sure config is loaded before actions run
120136
program.hook('preAction', async (_, actionCommand) => {
121137
let configFile: string | undefined = actionCommand.opts().config;

packages/schema/tests/schema/formatter.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@ const services = createZModelServices({ ...EmptyFileSystem }).ZModel;
77
const formatting = expectFormatting(services);
88

99
describe('ZModelFormatter', () => {
10-
// eslint-disable-next-line jest/no-disabled-tests
11-
test.skip('declaration formatting', async () => {
10+
it('declaration formatting', async () => {
1211
await formatting({
1312
before: `datasource db { provider = 'postgresql' url = env('DATABASE_URL')} generator js {provider = 'prisma-client-js'}
1413
plugin swrHooks {provider = '@zenstackhq/swr'output = 'lib/hooks'}
15-
model User {id:id String @id name String? }
14+
model User {id String @id name String? }
1615
enum Role {ADMIN USER}`,
1716
after: `datasource db {
1817
provider = 'postgresql'
@@ -26,7 +25,6 @@ plugin swrHooks {
2625
output = 'lib/hooks'
2726
}
2827
model User {
29-
id
3028
id String @id
3129
name String?
3230
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as fs from 'fs';
2+
import * as tmp from 'tmp';
3+
import { createProgram } from '../../../../packages/schema/src/cli';
4+
describe('CLI format test', () => {
5+
let origDir: string;
6+
7+
beforeEach(() => {
8+
origDir = process.cwd();
9+
const r = tmp.dirSync({ unsafeCleanup: true });
10+
console.log(`Project dir: ${r.name}`);
11+
process.chdir(r.name);
12+
});
13+
14+
afterEach(() => {
15+
process.chdir(origDir);
16+
});
17+
18+
it('basic format', async () => {
19+
const model = `
20+
datasource db {provider="sqlite" url="file:./dev.db"}
21+
generator client {provider = "prisma-client-js"}
22+
model Post {id Int @id() @default(autoincrement())users User[]}`;
23+
24+
const formattedModel = `
25+
datasource db {
26+
provider="sqlite"
27+
url="file:./dev.db"
28+
}
29+
generator client {
30+
provider = "prisma-client-js"
31+
}
32+
model Post {
33+
id Int @id() @default(autoincrement())
34+
users User[]
35+
}`;
36+
// set up schema
37+
fs.writeFileSync('schema.zmodel', model, 'utf-8');
38+
const program = createProgram();
39+
await program.parseAsync(['format'], { from: 'user' });
40+
41+
expect(fs.readFileSync('schema.zmodel', 'utf-8')).toEqual(formattedModel);
42+
});
43+
});

0 commit comments

Comments
 (0)