diff --git a/__generated__/types.ts b/__generated__/types.ts new file mode 100644 index 0000000..dff8961 --- /dev/null +++ b/__generated__/types.ts @@ -0,0 +1,40 @@ +// NOTE: this file is generated with datx generator + +export type Flight = { + type: 'flights'; + id?: string; + lid?: string; + attributes: { + airplaneModel: string; + departsAt: string; + arrivesAt: string; + basePrice: string; + currentSeatPrice: string; + name: string; + }; +}; + +export type Session = { + type: 'sessions'; + id?: string; + lid?: string; + attributes: { + email: string; + password: string; + }; + relationships: { + user: { + data: User; + }; + }; +}; + +export type User = { + type: 'users'; + id?: string; + lid?: string; + attributes: { + firstName: string; + lastName: string; + }; +}; diff --git a/datx-generator.config.js b/datx-generator.config.js new file mode 100644 index 0000000..4e216d4 --- /dev/null +++ b/datx-generator.config.js @@ -0,0 +1,4 @@ +module.exports = { + models: './src/models/*.ts', + output: './__generated__', +}; diff --git a/package-lock.json b/package-lock.json index 8e48e32..b5adb74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,8 @@ "swr": "~1.3.0" }, "devDependencies": { + "@babel/generator": "~7.20.5", + "@babel/parser": "~7.20.5", "@chakra-ui/cli": "~2.1.8", "@chakra-ui/storybook-addon": "~4.0.12", "@infinum/plop-next-ts-generators": "~0.0.1-5", @@ -145,11 +147,11 @@ } }, "node_modules/@babel/generator": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.3.tgz", - "integrity": "sha512-fqVZnmp1ncvZU757UzDheKZpfPgatqY59XtW2/j/18H7u76akb8xqvjw82f+i2UKd/ksYsSick/BCLQUUtJ/qQ==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", + "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", "dependencies": { - "@babel/types": "^7.19.3", + "@babel/types": "^7.20.5", "@jridgewell/gen-mapping": "^0.3.2", "jsesc": "^2.5.1" }, @@ -439,9 +441,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", "engines": { "node": ">=6.9.0" } @@ -504,9 +506,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.3.tgz", - "integrity": "sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1962,11 +1964,11 @@ } }, "node_modules/@babel/types": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.3.tgz", - "integrity": "sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", + "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", "dependencies": { - "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-string-parser": "^7.19.4", "@babel/helper-validator-identifier": "^7.19.1", "to-fast-properties": "^2.0.0" }, @@ -44745,11 +44747,11 @@ } }, "@babel/generator": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.3.tgz", - "integrity": "sha512-fqVZnmp1ncvZU757UzDheKZpfPgatqY59XtW2/j/18H7u76akb8xqvjw82f+i2UKd/ksYsSick/BCLQUUtJ/qQ==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", + "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", "requires": { - "@babel/types": "^7.19.3", + "@babel/types": "^7.20.5", "@jridgewell/gen-mapping": "^0.3.2", "jsesc": "^2.5.1" }, @@ -44963,9 +44965,9 @@ } }, "@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==" + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" }, "@babel/helper-validator-identifier": { "version": "7.19.1", @@ -45010,9 +45012,9 @@ } }, "@babel/parser": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.3.tgz", - "integrity": "sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ==" + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", + "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.18.6", @@ -45982,11 +45984,11 @@ } }, "@babel/types": { - "version": "7.19.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.3.tgz", - "integrity": "sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", + "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", "requires": { - "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-string-parser": "^7.19.4", "@babel/helper-validator-identifier": "^7.19.1", "to-fast-properties": "^2.0.0" } diff --git a/package.json b/package.json index 894b00f..09b2a25 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "postinstall": "npm run gen:theme-typings", "i18n:generate": "node ./scripts/i18next-cli.js", "gen:component": "plop component", - "gen:theme": "plop theme" + "gen:theme": "plop theme", + "gen:datx": "node ./scripts/datx-generator.js" }, "dependencies": { "@chakra-ui/react": "~2.3.4", @@ -50,6 +51,8 @@ "swr": "~1.3.0" }, "devDependencies": { + "@babel/generator": "~7.20.5", + "@babel/parser": "~7.20.5", "@chakra-ui/cli": "~2.1.8", "@chakra-ui/storybook-addon": "~4.0.12", "@infinum/plop-next-ts-generators": "~0.0.1-5", diff --git a/scripts/datx-generator.js b/scripts/datx-generator.js new file mode 100644 index 0000000..ebabd26 --- /dev/null +++ b/scripts/datx-generator.js @@ -0,0 +1,115 @@ +const path = require('path'); +const fs = require('fs'); +const glob = require('glob'); +const parser = require('@babel/parser'); +const babelGenerate = require('@babel/generator').default; + +const configFileName = 'datx-generator.config.js'; + +const parserConfig = { + sourceType: 'module', + plugins: ['typescript', 'decorators-legacy'], +}; + +const { models: modelsGlob, output } = require(`${process.cwd()}/${configFileName}`); + +// getType is a function that returns the type of the model based on the source code using babel parser +const getType = (source) => { + const ast = parser.parse(source, parserConfig); + + const type = ast.program.body.find((node) => node.type === 'ExportNamedDeclaration'); + + return type.declaration.body.body.find((node) => node.key.name === 'type').value.value; +}; + +// getAttributes is a function that returns the attributes of the model based on the source code using babel parser +const getAttributes = (source) => { + const ast = parser.parse(source, parserConfig); + + // find all props with @Attribute decorator without any options + const arguments = ast.program.body + .filter((node) => node.type === 'ExportNamedDeclaration') + .map((node) => node.declaration.body.body) + .flat() + .filter((node) => node.decorators) + .filter((node) => node.decorators[0].expression.callee.name === 'Attribute') + .filter((node) => node.decorators[0].expression.arguments.length === 0); + + if (arguments.length === 0) { + return ''; + } + + return ` + attributes: { + ${arguments + .map((node) => `${node.key.name}: ${babelGenerate(node.typeAnnotation.typeAnnotation).code};`) + .join('\n\t\t')} + };`; +}; + +// getRelationships is a function that returns the relationships of the model based on the source code using babel parser finding all props with @Attribute decorator with toOne or toMany options +const getRelationships = (source) => { + const ast = parser.parse(source, parserConfig); + + // find all props with @Attribute decorator with toOne or toMany options + const arguments = ast.program.body + .filter((node) => node.type === 'ExportNamedDeclaration') + .map((node) => node.declaration.body.body) + .flat() + .filter((node) => node.decorators) + .filter((node) => node.decorators[0].expression.callee.name === 'Attribute') + .filter((node) => node.decorators[0].expression.arguments.length > 0) + .filter( + (node) => + node.decorators[0].expression.arguments[0]?.properties[0].key.name === 'toOne' || + node.decorators[0].expression.arguments[0]?.properties[0].key.name === 'toMany' + ); + + if (arguments.length === 0) { + return ''; + } + + return ` + relationships: { + ${arguments + .map( + (node) => `${node.key.name}: { + data: ${babelGenerate(node.typeAnnotation.typeAnnotation).code}; + };` + ) + .join('\n\t\t')} + };`; +}; + +const generate = (models) => `// NOTE: this file is generated with datx generator + +${models + .map( + ({ basename, source }) => `export type ${basename} = { + type: '${getType(source)}'; + id?: string; + lid?: string;${getAttributes(source)}${getRelationships(source)} +} +` + ) + .join('\n')} +`; + +const models = glob.sync(modelsGlob).map((model) => ({ + basename: path.basename(model, '.ts'), + source: fs.readFileSync(model, 'utf8'), +})); + +const template = generate(models); + +if (!fs.existsSync(path.join(process.cwd(), output))) { + fs.mkdirSync(path.join(process.cwd(), output)); +} + +fs.writeFile(path.join(process.cwd(), output, 'types.ts'), template, (err) => { + if (err) { + return console.log(`Unable to save: ${err}`); + } + + console.log('The file was saved!'); +});