11import { invariant } from '@zenstackhq/common-helpers' ;
2- import { isPlugin , LiteralExpr , Plugin , type Model } from '@zenstackhq/language/ast' ;
2+ import { ZModelLanguageMetaData } from '@zenstackhq/language' ;
3+ import { isPlugin , isDataModel , type DataModel , LiteralExpr , Plugin , type Model } from '@zenstackhq/language/ast' ;
34import { getLiteral , getLiteralArray } from '@zenstackhq/language/utils' ;
45import { type CliPlugin } from '@zenstackhq/sdk' ;
56import colors from 'colors' ;
@@ -16,6 +17,7 @@ type Options = {
1617 schema ?: string ;
1718 output ?: string ;
1819 silent : boolean ;
20+ watch : boolean ;
1921 lite : boolean ;
2022 liteOnly : boolean ;
2123} ;
@@ -24,6 +26,92 @@ type Options = {
2426 * CLI action for generating code from schema
2527 */
2628export async function run ( options : Options ) {
29+ const model = await pureGenerate ( options , false ) ;
30+
31+ if ( options . watch ) {
32+ const logsEnabled = ! options . silent ;
33+
34+ if ( logsEnabled ) {
35+ console . log ( colors . green ( `\nEnable watch mode!` ) ) ;
36+ }
37+
38+ const schemaExtensions = ZModelLanguageMetaData . fileExtensions . join ( ', ' ) ;
39+
40+ // Get real models file path (cuz its merged into single document -> we need use cst nodes)
41+ const getModelAllPaths = ( model : Model ) => new Set (
42+ (
43+ model . declarations . filter (
44+ ( v ) =>
45+ isDataModel ( v ) &&
46+ v . $cstNode ?. parent ?. element . $type === 'Model' &&
47+ ! ! v . $cstNode . parent . element . $document ?. uri ?. fsPath ,
48+ ) as DataModel [ ]
49+ ) . map ( ( v ) => v . $cstNode ! . parent ! . element . $document ! . uri ! . fsPath ) ,
50+ ) ;
51+
52+ const { watch } = await import ( 'chokidar' ) ;
53+
54+ const watchedPaths = getModelAllPaths ( model ) ;
55+ let reGenerateSchemaTimeout : ReturnType < typeof setTimeout > | undefined ;
56+
57+ if ( logsEnabled ) {
58+ const logPaths = [ ...watchedPaths ] . map ( ( at ) => `- ${ at } ` ) . join ( '\n' ) ;
59+ console . log ( `Watched file paths:\n${ logPaths } ` ) ;
60+ }
61+
62+ const watcher = watch ( [ ...watchedPaths ] , {
63+ alwaysStat : false ,
64+ ignoreInitial : true ,
65+ ignorePermissionErrors : true ,
66+ ignored : ( at ) => ! schemaExtensions . includes ( path . extname ( at ) ) ,
67+ } ) ;
68+
69+ const reGenerateSchema = ( ) => {
70+ clearTimeout ( reGenerateSchemaTimeout ) ;
71+
72+ // prevent save multiple files and run multiple times
73+ reGenerateSchemaTimeout = setTimeout ( async ( ) => {
74+ if ( logsEnabled ) {
75+ console . log ( 'Got changes, run generation!' ) ;
76+ }
77+
78+ try {
79+ const newModel = await pureGenerate ( options , true ) ;
80+ const allModelsPaths = getModelAllPaths ( newModel ) ;
81+ const newModelPaths = [ ...allModelsPaths ] . filter ( ( at ) => ! watchedPaths . has ( at ) ) ;
82+
83+ if ( newModelPaths . length ) {
84+ if ( logsEnabled ) {
85+ const logPaths = [ ...newModelPaths ] . map ( ( at ) => `- ${ at } ` ) . join ( '\n' ) ;
86+ console . log ( `Add file(s) to watch:\n${ logPaths } ` ) ;
87+ }
88+
89+ watcher . add ( newModelPaths ) ;
90+ }
91+ } catch ( e ) {
92+ console . error ( e ) ;
93+ }
94+ } , 500 ) ;
95+ } ;
96+
97+ watcher . on ( 'unlink' , ( pathAt ) => {
98+ if ( logsEnabled ) {
99+ console . log ( `Remove file from watch: ${ pathAt } ` ) ;
100+ }
101+
102+ watcher . unwatch ( pathAt ) ;
103+ watchedPaths . delete ( pathAt ) ;
104+
105+ reGenerateSchema ( ) ;
106+ } ) ;
107+
108+ watcher . on ( 'change' , ( ) => {
109+ reGenerateSchema ( ) ;
110+ } ) ;
111+ }
112+ }
113+
114+ async function pureGenerate ( options : Options , fromWatch : boolean ) {
27115 const start = Date . now ( ) ;
28116
29117 const schemaFile = getSchemaFile ( options . schema ) ;
@@ -35,7 +123,9 @@ export async function run(options: Options) {
35123
36124 if ( ! options . silent ) {
37125 console . log ( colors . green ( `Generation completed successfully in ${ Date . now ( ) - start } ms.\n` ) ) ;
38- console . log ( `You can now create a ZenStack client with it.
126+
127+ if ( ! fromWatch ) {
128+ console . log ( `You can now create a ZenStack client with it.
39129
40130\`\`\`ts
41131import { ZenStackClient } from '@zenstackhq/orm';
@@ -47,7 +137,10 @@ const client = new ZenStackClient(schema, {
47137\`\`\`
48138
49139Check documentation: https://zenstack.dev/docs/` ) ;
140+ }
50141 }
142+
143+ return model ;
51144}
52145
53146function getOutputPath ( options : Options , schemaFile : string ) {
0 commit comments