@@ -4,6 +4,32 @@ import path from "path";
44import chalk from "chalk" ;
55import Handlebars from "handlebars" ;
66import { fileURLToPath } from 'url' ;
7+ import { parse } from "@babel/parser" ;
8+ import * as recast from "recast" ;
9+ import { namedTypes as n , builders as b } from "ast-types" ;
10+
11+ const DATA_SOURCE_RE = / d a t a S o u r c e : \s * [ " ' ] ( .+ ?) [ " ' ] / ;
12+ const ADMINFORTH_DATA_TYPE_KEYS = {
13+ string : "STRING" ,
14+ integer : "INTEGER" ,
15+ float : "FLOAT" ,
16+ decimal : "DECIMAL" ,
17+ boolean : "BOOLEAN" ,
18+ date : "DATE" ,
19+ datetime : "DATETIME" ,
20+ time : "TIME" ,
21+ text : "TEXT" ,
22+ json : "JSON" ,
23+ } ;
24+
25+ const parser = {
26+ parse ( source ) {
27+ return parse ( source , {
28+ sourceType : "module" ,
29+ plugins : [ "typescript" ] ,
30+ } ) ;
31+ } ,
32+ } ;
733
834export async function renderHBSTemplate ( templatePath , data ) {
935 const templateContent = await fs . readFile ( templatePath , "utf-8" ) ;
@@ -22,14 +48,35 @@ export async function generateResourceFile({
2248
2349 if ( fsSync . existsSync ( baseFilePath ) ) {
2450 const content = await fs . readFile ( baseFilePath , "utf-8" ) ;
25- const match = content . match ( / d a t a S o u r c e : \s * [ " ' ] ( . + ? ) [ " ' ] / ) ;
51+ const match = content . match ( DATA_SOURCE_RE ) ;
2652 const existingDataSource = match ?. [ 1 ] ;
2753 if ( existingDataSource === dataSource ) {
28- console . log ( chalk . yellow ( `⚠️ File already exists with same dataSource: ${ baseFilePath } ` ) ) ;
29- return { alreadyExists : true , path : baseFilePath , fileName : baseFileName , resourceId : table } ;
54+ const syncedColumnsCount = await syncResourceColumns ( baseFilePath , content , columns ) ;
55+ return {
56+ alreadyExists : true ,
57+ path : baseFilePath ,
58+ fileName : baseFileName ,
59+ resourceId : table ,
60+ syncedColumnsCount,
61+ } ;
3062 } else {
3163 const suffixedFileName = `${ table } _${ dataSource } .ts` ;
3264 const suffixedFilePath = path . resolve ( process . cwd ( ) , resourcesDir , suffixedFileName ) ;
65+ if ( fsSync . existsSync ( suffixedFilePath ) ) {
66+ const suffixedContent = await fs . readFile ( suffixedFilePath , "utf-8" ) ;
67+ const suffixedMatch = suffixedContent . match ( DATA_SOURCE_RE ) ;
68+ const suffixedDataSource = suffixedMatch ?. [ 1 ] ;
69+ if ( suffixedDataSource === dataSource ) {
70+ const syncedColumnsCount = await syncResourceColumns ( suffixedFilePath , suffixedContent , columns ) ;
71+ return {
72+ alreadyExists : true ,
73+ path : suffixedFilePath ,
74+ fileName : suffixedFileName ,
75+ resourceId : `${ table } _${ dataSource } ` ,
76+ syncedColumnsCount,
77+ } ;
78+ }
79+ }
3380 return await writeResourceFile ( suffixedFilePath , suffixedFileName , {
3481 table,
3582 columns,
@@ -63,7 +110,7 @@ async function writeResourceFile(filePath, fileName, {
63110 dataSource,
64111 resourceId,
65112 label : table . charAt ( 0 ) . toUpperCase ( ) + table . slice ( 1 ) ,
66- columns,
113+ columns : columns . map ( normalizeColumnForTemplate ) ,
67114 } ;
68115
69116 const content = await renderHBSTemplate ( templatePath , context ) ;
@@ -74,3 +121,150 @@ async function writeResourceFile(filePath, fileName, {
74121
75122 return { alreadyExists : false , path : filePath , fileName, resourceId } ;
76123}
124+
125+ async function syncResourceColumns ( filePath , content , discoveredColumns ) {
126+ const ast = recast . parse ( content , { parser } ) ;
127+ const columnsArray = findResourceColumnsArray ( ast ) ;
128+
129+ if ( ! columnsArray ) {
130+ throw new Error ( `Could not find resource columns array in ${ filePath } ` ) ;
131+ }
132+
133+ const existingColumnNames = new Set (
134+ columnsArray . elements
135+ . filter ( ( element ) => n . ObjectExpression . check ( element ) )
136+ . map ( ( element ) => getObjectPropertyValue ( element , "name" ) )
137+ . filter ( Boolean )
138+ ) ;
139+
140+ const columnsToImport = discoveredColumns . filter ( ( column ) => ! existingColumnNames . has ( column . name ) ) ;
141+
142+ if ( ! columnsToImport . length ) {
143+ console . log ( chalk . green ( `✅ Resource is already in sync: ${ filePath } ` ) ) ;
144+ return 0 ;
145+ }
146+
147+ console . log ( chalk . cyan ( `ℹ️ Going to import ${ formatColumnsCount ( columnsToImport . length ) } : ${ columnsToImport . map ( ( column ) => column . name ) . join ( ", " ) } ` ) ) ;
148+
149+ columnsArray . elements . push ( ...columnsToImport . map ( createColumnAstNode ) ) ;
150+
151+ const newContent = recast . print ( ast , {
152+ tabWidth : 2 ,
153+ useTabs : false ,
154+ trailingComma : true ,
155+ wrapColumn : 1 ,
156+ } ) . code ;
157+
158+ await fs . writeFile ( filePath , newContent , "utf-8" ) ;
159+ console . log ( chalk . green ( `✅ Imported ${ formatColumnsCount ( columnsToImport . length ) } into resource file: ${ filePath } ` ) ) ;
160+
161+ return columnsToImport . length ;
162+ }
163+
164+ function findResourceColumnsArray ( ast ) {
165+ let columnsArray = null ;
166+
167+ recast . visit ( ast , {
168+ visitObjectExpression ( path ) {
169+ const properties = path . node . properties ;
170+ const columnsProp = findObjectProperty ( properties , "columns" ) ;
171+ const hasResourceShape = findObjectProperty ( properties , "dataSource" ) && findObjectProperty ( properties , "table" ) ;
172+
173+ if ( hasResourceShape && columnsProp && n . ArrayExpression . check ( columnsProp . value ) ) {
174+ columnsArray = columnsProp . value ;
175+ return false ;
176+ }
177+
178+ this . traverse ( path ) ;
179+ } ,
180+ } ) ;
181+
182+ return columnsArray ;
183+ }
184+
185+ function findObjectProperty ( properties , name ) {
186+ return properties . find ( ( property ) => (
187+ n . ObjectProperty . check ( property ) &&
188+ getPropertyKeyName ( property ) === name
189+ ) ) ;
190+ }
191+
192+ function getPropertyKeyName ( property ) {
193+ if ( n . Identifier . check ( property . key ) ) {
194+ return property . key . name ;
195+ }
196+ if ( n . StringLiteral . check ( property . key ) || n . Literal . check ( property . key ) ) {
197+ return property . key . value ;
198+ }
199+ return null ;
200+ }
201+
202+ function getObjectPropertyValue ( objectExpression , name ) {
203+ const property = findObjectProperty ( objectExpression . properties , name ) ;
204+ if ( ! property ) {
205+ return null ;
206+ }
207+ if ( n . StringLiteral . check ( property . value ) || n . Literal . check ( property . value ) ) {
208+ return property . value . value ;
209+ }
210+ return null ;
211+ }
212+
213+ function createColumnAstNode ( column ) {
214+ const properties = [
215+ b . objectProperty ( b . identifier ( "name" ) , b . stringLiteral ( column . name ) ) ,
216+ ] ;
217+
218+ if ( column . type ) {
219+ properties . push (
220+ b . objectProperty (
221+ b . identifier ( "type" ) ,
222+ b . memberExpression ( b . identifier ( "AdminForthDataTypes" ) , b . identifier ( getAdminForthDataTypeKey ( column . type ) ) )
223+ )
224+ ) ;
225+ }
226+
227+ if ( column . isPrimaryKey ) {
228+ properties . push ( b . objectProperty ( b . identifier ( "primaryKey" ) , b . booleanLiteral ( true ) ) ) ;
229+ }
230+
231+ if ( column . isUUID ) {
232+ properties . push (
233+ b . objectProperty (
234+ b . identifier ( "components" ) ,
235+ b . objectExpression ( [
236+ b . objectProperty ( b . identifier ( "list" ) , b . stringLiteral ( "@/renderers/CompactUUID.vue" ) ) ,
237+ ] )
238+ )
239+ ) ;
240+ }
241+
242+ properties . push (
243+ b . objectProperty (
244+ b . identifier ( "showIn" ) ,
245+ b . objectExpression ( [
246+ b . objectProperty ( b . identifier ( "all" ) , b . booleanLiteral ( true ) ) ,
247+ ] )
248+ )
249+ ) ;
250+
251+ return b . objectExpression ( properties ) ;
252+ }
253+
254+ function formatColumnsCount ( count ) {
255+ return `${ count } column${ count === 1 ? "" : "s" } ` ;
256+ }
257+
258+ function normalizeColumnForTemplate ( column ) {
259+ if ( ! column . type ) {
260+ return column ;
261+ }
262+ return {
263+ ...column ,
264+ type : getAdminForthDataTypeKey ( column . type ) ,
265+ } ;
266+ }
267+
268+ function getAdminForthDataTypeKey ( type ) {
269+ return ADMINFORTH_DATA_TYPE_KEYS [ type ] || type ;
270+ }
0 commit comments