+
+ :class="maxWidthClass">
diff --git a/examples/laravel10-app/resources/js/Components/NavLink.vue b/examples/laravel10-app/resources/js/Components/NavLink.vue
index 592ff74..b344ef7 100644
--- a/examples/laravel10-app/resources/js/Components/NavLink.vue
+++ b/examples/laravel10-app/resources/js/Components/NavLink.vue
@@ -1,13 +1,13 @@
diff --git a/examples/laravel10-app/resources/js/Components/PrimaryButton.vue b/examples/laravel10-app/resources/js/Components/PrimaryButton.vue
index b3bb738..5311385 100644
--- a/examples/laravel10-app/resources/js/Components/PrimaryButton.vue
+++ b/examples/laravel10-app/resources/js/Components/PrimaryButton.vue
@@ -1,6 +1,6 @@
diff --git a/examples/laravel10-app/resources/js/Components/ResponsiveNavLink.vue b/examples/laravel10-app/resources/js/Components/ResponsiveNavLink.vue
index 6305f42..24d7f48 100644
--- a/examples/laravel10-app/resources/js/Components/ResponsiveNavLink.vue
+++ b/examples/laravel10-app/resources/js/Components/ResponsiveNavLink.vue
@@ -1,13 +1,13 @@
diff --git a/examples/laravel10-app/resources/js/Components/SecondaryButton.vue b/examples/laravel10-app/resources/js/Components/SecondaryButton.vue
index 20fa3bb..6af8368 100644
--- a/examples/laravel10-app/resources/js/Components/SecondaryButton.vue
+++ b/examples/laravel10-app/resources/js/Components/SecondaryButton.vue
@@ -1,6 +1,6 @@
diff --git a/examples/laravel10-app/resources/js/Components/TextInput.vue b/examples/laravel10-app/resources/js/Components/TextInput.vue
index 44651b6..919948b 100644
--- a/examples/laravel10-app/resources/js/Components/TextInput.vue
+++ b/examples/laravel10-app/resources/js/Components/TextInput.vue
@@ -1,16 +1,16 @@
diff --git a/examples/laravel10-app/resources/js/Layouts/GuestLayout.vue b/examples/laravel10-app/resources/js/Layouts/GuestLayout.vue
index ab07b47..054d221 100644
--- a/examples/laravel10-app/resources/js/Layouts/GuestLayout.vue
+++ b/examples/laravel10-app/resources/js/Layouts/GuestLayout.vue
@@ -1,6 +1,6 @@
diff --git a/examples/laravel10-app/resources/js/Pages/Auth/ConfirmPassword.vue b/examples/laravel10-app/resources/js/Pages/Auth/ConfirmPassword.vue
index a929f87..4ae1461 100644
--- a/examples/laravel10-app/resources/js/Pages/Auth/ConfirmPassword.vue
+++ b/examples/laravel10-app/resources/js/Pages/Auth/ConfirmPassword.vue
@@ -1,19 +1,19 @@
diff --git a/examples/laravel10-app/resources/js/Pages/Auth/ForgotPassword.vue b/examples/laravel10-app/resources/js/Pages/Auth/ForgotPassword.vue
index 09a7df1..4f29c5b 100644
--- a/examples/laravel10-app/resources/js/Pages/Auth/ForgotPassword.vue
+++ b/examples/laravel10-app/resources/js/Pages/Auth/ForgotPassword.vue
@@ -1,21 +1,21 @@
diff --git a/examples/laravel10-app/resources/js/Pages/Auth/Login.vue b/examples/laravel10-app/resources/js/Pages/Auth/Login.vue
index 300f2f2..a790031 100644
--- a/examples/laravel10-app/resources/js/Pages/Auth/Login.vue
+++ b/examples/laravel10-app/resources/js/Pages/Auth/Login.vue
@@ -1,27 +1,27 @@
diff --git a/examples/laravel10-app/resources/js/Pages/Auth/Register.vue b/examples/laravel10-app/resources/js/Pages/Auth/Register.vue
index 283347a..59d36f4 100644
--- a/examples/laravel10-app/resources/js/Pages/Auth/Register.vue
+++ b/examples/laravel10-app/resources/js/Pages/Auth/Register.vue
@@ -1,23 +1,23 @@
diff --git a/examples/laravel10-app/resources/js/Pages/Auth/ResetPassword.vue b/examples/laravel10-app/resources/js/Pages/Auth/ResetPassword.vue
index 922f2e2..ec057d9 100644
--- a/examples/laravel10-app/resources/js/Pages/Auth/ResetPassword.vue
+++ b/examples/laravel10-app/resources/js/Pages/Auth/ResetPassword.vue
@@ -1,27 +1,27 @@
diff --git a/examples/laravel10-app/resources/js/Pages/Auth/VerifyEmail.vue b/examples/laravel10-app/resources/js/Pages/Auth/VerifyEmail.vue
index 97c1d7f..025a09c 100644
--- a/examples/laravel10-app/resources/js/Pages/Auth/VerifyEmail.vue
+++ b/examples/laravel10-app/resources/js/Pages/Auth/VerifyEmail.vue
@@ -1,20 +1,22 @@
diff --git a/examples/laravel10-app/resources/js/Pages/Dashboard.vue b/examples/laravel10-app/resources/js/Pages/Dashboard.vue
index 597fb4c..676b920 100644
--- a/examples/laravel10-app/resources/js/Pages/Dashboard.vue
+++ b/examples/laravel10-app/resources/js/Pages/Dashboard.vue
@@ -1,11 +1,10 @@
diff --git a/examples/laravel10-app/resources/js/Pages/Posts/Index.vue b/examples/laravel10-app/resources/js/Pages/Posts/Index.vue
index 4eb7473..842fe1c 100644
--- a/examples/laravel10-app/resources/js/Pages/Posts/Index.vue
+++ b/examples/laravel10-app/resources/js/Pages/Posts/Index.vue
@@ -1,10 +1,9 @@
diff --git a/examples/laravel10-app/resources/js/Pages/Posts/Show.vue b/examples/laravel10-app/resources/js/Pages/Posts/Show.vue
index 0e6ef4d..6cafb61 100644
--- a/examples/laravel10-app/resources/js/Pages/Posts/Show.vue
+++ b/examples/laravel10-app/resources/js/Pages/Posts/Show.vue
@@ -1,9 +1,9 @@
diff --git a/examples/laravel10-app/resources/js/Pages/Profile/Edit.vue b/examples/laravel10-app/resources/js/Pages/Profile/Edit.vue
index 674f7c6..9fd0db1 100644
--- a/examples/laravel10-app/resources/js/Pages/Profile/Edit.vue
+++ b/examples/laravel10-app/resources/js/Pages/Profile/Edit.vue
@@ -1,13 +1,13 @@
diff --git a/examples/laravel10-app/resources/js/Pages/Profile/Partials/DeleteUserForm.vue b/examples/laravel10-app/resources/js/Pages/Profile/Partials/DeleteUserForm.vue
index cc60695..f18d9a3 100644
--- a/examples/laravel10-app/resources/js/Pages/Profile/Partials/DeleteUserForm.vue
+++ b/examples/laravel10-app/resources/js/Pages/Profile/Partials/DeleteUserForm.vue
@@ -1,39 +1,39 @@
diff --git a/examples/laravel10-app/resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue b/examples/laravel10-app/resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue
index 6eb502b..ee7d2b9 100644
--- a/examples/laravel10-app/resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue
+++ b/examples/laravel10-app/resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue
@@ -1,35 +1,35 @@
diff --git a/examples/laravel10-app/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue b/examples/laravel10-app/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue
index fdf43cb..613af45 100644
--- a/examples/laravel10-app/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue
+++ b/examples/laravel10-app/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue
@@ -1,25 +1,25 @@
diff --git a/examples/laravel10-app/resources/js/Pages/Welcome.vue b/examples/laravel10-app/resources/js/Pages/Welcome.vue
index a7ac892..1bf5de3 100644
--- a/examples/laravel10-app/resources/js/Pages/Welcome.vue
+++ b/examples/laravel10-app/resources/js/Pages/Welcome.vue
@@ -1,20 +1,19 @@
diff --git a/examples/laravel10-app/resources/js/app.ts b/examples/laravel10-app/resources/js/app.ts
index fccd5fe..26dff06 100644
--- a/examples/laravel10-app/resources/js/app.ts
+++ b/examples/laravel10-app/resources/js/app.ts
@@ -1,23 +1,28 @@
-import './bootstrap';
-import '../css/app.css';
+import "./bootstrap";
+import "../css/app.css";
-import { createApp, h } from 'vue';
-import { createInertiaApp } from '@inertiajs/vue3';
-import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
-import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m';
-import { Ziggy } from './ziggy';
+import { createInertiaApp } from "@inertiajs/vue3";
+import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers";
+import { createApp, h } from "vue";
+import { ZiggyVue } from "../../vendor/tightenco/ziggy/dist/vue.m";
+import { Ziggy } from "./ziggy";
-const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';
+const appName =
+ window.document.getElementsByTagName("title")[0]?.innerText || "Laravel";
createInertiaApp({
- progress: { color: '#4B5563' },
- title: (title) => `${title} - ${appName}`,
- resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('../js/Pages/**/*.vue') as any),
- setup({ el, App, props, plugin }) {
- createApp({ render: () => h(App, props) })
- .use(plugin)
- .use(ZiggyVue, Ziggy)
- .mount(el);
- },
+ progress: { color: "#4B5563" },
+ title: (title) => `${title} - ${appName}`,
+ resolve: (name) =>
+ resolvePageComponent(
+ `./Pages/${name}.vue`,
+ // biome-ignore lint/suspicious/noExplicitAny: for example purposes
+ import.meta.glob("../js/Pages/**/*.vue") as any,
+ ),
+ setup({ el, App, props, plugin }) {
+ createApp({ render: () => h(App, props) })
+ .use(plugin)
+ .use(ZiggyVue, Ziggy)
+ .mount(el);
+ },
});
-
diff --git a/examples/laravel10-app/resources/js/bootstrap.js b/examples/laravel10-app/resources/js/bootstrap.js
index 366c49d..9e6044a 100644
--- a/examples/laravel10-app/resources/js/bootstrap.js
+++ b/examples/laravel10-app/resources/js/bootstrap.js
@@ -1,4 +1,4 @@
-import _ from 'lodash';
+import _ from "lodash";
window._ = _;
/**
@@ -7,10 +7,10 @@ window._ = _;
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
-import axios from 'axios';
+import axios from "axios";
window.axios = axios;
-window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
+window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
/**
* Echo exposes an expressive API for subscribing to channels and listening
diff --git a/examples/laravel10-app/tailwind.config.js b/examples/laravel10-app/tailwind.config.js
index 7f06c1a..28181a0 100644
--- a/examples/laravel10-app/tailwind.config.js
+++ b/examples/laravel10-app/tailwind.config.js
@@ -1,21 +1,21 @@
-const defaultTheme = require('tailwindcss/defaultTheme');
+const defaultTheme = require("tailwindcss/defaultTheme");
/** @type {import('tailwindcss').Config} */
module.exports = {
- content: [
- './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
- './storage/framework/views/*.php',
- './resources/views/**/*.blade.php',
- './resources/js/**/*.vue',
- ],
+ content: [
+ "./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php",
+ "./storage/framework/views/*.php",
+ "./resources/views/**/*.blade.php",
+ "./resources/js/**/*.vue",
+ ],
- theme: {
- extend: {
- fontFamily: {
- sans: ['Nunito', ...defaultTheme.fontFamily.sans],
- },
- },
- },
+ theme: {
+ extend: {
+ fontFamily: {
+ sans: ["Nunito", ...defaultTheme.fontFamily.sans],
+ },
+ },
+ },
- plugins: [require('@tailwindcss/forms')],
+ plugins: [require("@tailwindcss/forms")],
};
diff --git a/examples/laravel10-app/tsconfig.json b/examples/laravel10-app/tsconfig.json
index 3bcc10f..a7f12e3 100644
--- a/examples/laravel10-app/tsconfig.json
+++ b/examples/laravel10-app/tsconfig.json
@@ -1,22 +1,22 @@
{
- "compilerOptions": {
- "target": "esnext",
- "module": "esnext",
- "strict": true,
- "jsx": "preserve",
- "moduleResolution": "node",
- "baseUrl": "./",
- "skipLibCheck": true,
- "paths": {
- "@/*": ["resources/js/*"],
- "@/types/*": ["resources/js/types/*"],
- "ziggy": ["vendor/tightenco/ziggy/dist/vue.es.js"]
- },
- "types": ["vite/client"],
- "lib": ["esnext", "dom"],
- "allowJs": true,
- "strictFunctionTypes": false
- },
- "include": ["resources/js/**/*"],
- "exclude": ["node_modules", "public"]
- }
+ "compilerOptions": {
+ "target": "esnext",
+ "module": "esnext",
+ "strict": true,
+ "jsx": "preserve",
+ "moduleResolution": "node",
+ "baseUrl": "./",
+ "skipLibCheck": true,
+ "paths": {
+ "@/*": ["resources/js/*"],
+ "@/types/*": ["resources/js/types/*"],
+ "ziggy": ["vendor/tightenco/ziggy/dist/vue.es.js"]
+ },
+ "types": ["vite/client"],
+ "lib": ["esnext", "dom"],
+ "allowJs": true,
+ "strictFunctionTypes": false
+ },
+ "include": ["resources/js/**/*"],
+ "exclude": ["node_modules", "public"]
+}
diff --git a/examples/laravel10-app/vite.config.ts b/examples/laravel10-app/vite.config.ts
index 5fb66e1..8ac1b77 100644
--- a/examples/laravel10-app/vite.config.ts
+++ b/examples/laravel10-app/vite.config.ts
@@ -1,20 +1,20 @@
-import { defineConfig } from 'vite';
-import laravel from 'laravel-vite-plugin';
-import vue from '@vitejs/plugin-vue';
+import vue from "@vitejs/plugin-vue";
+import laravel from "laravel-vite-plugin";
+import { defineConfig } from "vite";
export default defineConfig({
- plugins: [
- laravel({
- input: 'resources/js/app.ts',
- refresh: true,
- }),
- vue({
- template: {
- transformAssetUrls: {
- base: null,
- includeAbsolute: false,
- },
- },
- }),
- ],
+ plugins: [
+ laravel({
+ input: "resources/js/app.ts",
+ refresh: true,
+ }),
+ vue({
+ template: {
+ transformAssetUrls: {
+ base: null,
+ includeAbsolute: false,
+ },
+ },
+ }),
+ ],
});
diff --git a/lefthook.yml b/lefthook.yml
new file mode 100644
index 0000000..76db236
--- /dev/null
+++ b/lefthook.yml
@@ -0,0 +1,9 @@
+pre-commit:
+ commands:
+ biome:
+ glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}"
+ run: npx biome check --apply --no-errors-on-unmatched --files-ignore-unknown=true ./ && git update-index --again
+
+ typescript:
+ glob: "*.{ts,tsx}"
+ run: npx tsc --noEmit --skipLibCheck
diff --git a/package.json b/package.json
index edf6a94..f69da10 100644
--- a/package.json
+++ b/package.json
@@ -1,42 +1,39 @@
{
- "name": "@7nohe/laravel-typegen",
- "version": "0.5.4",
- "description": "The library lets you generate TypeScript types from your Laravel code.",
- "bin": {
- "laravel-typegen": "dist/src/cli.js"
- },
- "exports": "./dist/src/index.js",
- "types": "./dist/src/index.d.ts",
- "scripts": {
- "build": "rm -rf dist && tsc -p tsconfig.json && cp -R templates ./dist",
- "prepublishOnly": "pnpm run build",
- "release": "npx git-ensure -a && npx bumpp --commit --tag --push"
- },
- "repository": {
- "type": "git",
- "url": "git+https://github.com/7nohe/laravel-typegen.git"
- },
- "homepage": "https://github.com/7nohe/laravel-typegen",
- "bugs": "https://github.com/7nohe/laravel-typegen/issues",
- "files": [
- "dist"
- ],
- "keywords": [
- "laravel",
- "typegen",
- "typescript"
- ],
- "author": "Daiki Urata (@7nohe)",
- "license": "MIT",
- "devDependencies": {
- "@types/node": "^20.11.16"
- },
- "dependencies": {
- "@7nohe/laravel-zodgen": "^0.1.6",
- "commander": "^12.0.0",
- "consola": "^3.2.3",
- "glob": "^10.3.12",
- "php-parser": "^3.1.5",
- "typescript": "^5.4.5"
- }
+ "name": "@7nohe/laravel-typegen",
+ "version": "0.5.4",
+ "description": "The library lets you generate TypeScript types from your Laravel code.",
+ "bin": {
+ "laravel-typegen": "dist/src/cli.js"
+ },
+ "exports": "./dist/src/index.js",
+ "types": "./dist/src/index.d.ts",
+ "scripts": {
+ "build": "rm -rf dist && tsc -p tsconfig.json && cp -R templates ./dist",
+ "prepublishOnly": "pnpm run build",
+ "release": "npx git-ensure -a && npx bumpp --commit --tag --push",
+ "typecheck": "tsc --noEmit"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/7nohe/laravel-typegen.git"
+ },
+ "homepage": "https://github.com/7nohe/laravel-typegen",
+ "bugs": "https://github.com/7nohe/laravel-typegen/issues",
+ "files": ["dist"],
+ "keywords": ["laravel", "typegen", "typescript"],
+ "author": "Daiki Urata (@7nohe)",
+ "license": "MIT",
+ "devDependencies": {
+ "@biomejs/biome": "1.7.3",
+ "@types/node": "^20.11.16",
+ "lefthook": "^1.6.12"
+ },
+ "dependencies": {
+ "@7nohe/laravel-zodgen": "^0.1.6",
+ "commander": "^12.0.0",
+ "consola": "^3.2.3",
+ "glob": "^10.3.12",
+ "php-parser": "^3.1.5",
+ "typescript": "^5.4.5"
+ }
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ee87544..8874910 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -27,9 +27,15 @@ importers:
specifier: ^5.4.5
version: 5.4.5
devDependencies:
+ '@biomejs/biome':
+ specifier: 1.7.3
+ version: 1.7.3
'@types/node':
specifier: ^20.11.16
version: 20.11.16
+ lefthook:
+ specifier: ^1.6.12
+ version: 1.6.12
packages:
@@ -37,6 +43,59 @@ packages:
resolution: {integrity: sha512-HYp1FeJ14fUnU4C2z74cCQEf6c3nS932LURg98K85KJnnHoGmyNw9hSO4/YLWCZKBtlnPyUBGgEpyuDyyhKAYg==}
hasBin: true
+ '@biomejs/biome@1.7.3':
+ resolution: {integrity: sha512-ogFQI+fpXftr+tiahA6bIXwZ7CSikygASdqMtH07J2cUzrpjyTMVc9Y97v23c7/tL1xCZhM+W9k4hYIBm7Q6cQ==}
+ engines: {node: '>=14.21.3'}
+ hasBin: true
+
+ '@biomejs/cli-darwin-arm64@1.7.3':
+ resolution: {integrity: sha512-eDvLQWmGRqrPIRY7AIrkPHkQ3visEItJKkPYSHCscSDdGvKzYjmBJwG1Gu8+QC5ed6R7eiU63LEC0APFBobmfQ==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@biomejs/cli-darwin-x64@1.7.3':
+ resolution: {integrity: sha512-JXCaIseKRER7dIURsVlAJacnm8SG5I0RpxZ4ya3dudASYUc68WGl4+FEN03ABY3KMIq7hcK1tzsJiWlmXyosZg==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@biomejs/cli-linux-arm64-musl@1.7.3':
+ resolution: {integrity: sha512-c8AlO45PNFZ1BYcwaKzdt46kYbuP6xPGuGQ6h4j3XiEDpyseRRUy/h+6gxj07XovmyxKnSX9GSZ6nVbZvcVUAw==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@biomejs/cli-linux-arm64@1.7.3':
+ resolution: {integrity: sha512-phNTBpo7joDFastnmZsFjYcDYobLTx4qR4oPvc9tJ486Bd1SfEVPHEvJdNJrMwUQK56T+TRClOQd/8X1nnjA9w==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@biomejs/cli-linux-x64-musl@1.7.3':
+ resolution: {integrity: sha512-UdEHKtYGWEX3eDmVWvQeT+z05T9/Sdt2+F/7zmMOFQ7boANeX8pcO6EkJPK3wxMudrApsNEKT26rzqK6sZRTRA==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [linux]
+
+ '@biomejs/cli-linux-x64@1.7.3':
+ resolution: {integrity: sha512-vnedYcd5p4keT3iD48oSKjOIRPYcjSNNbd8MO1bKo9ajg3GwQXZLAH+0Cvlr+eMsO67/HddWmscSQwTFrC/uPA==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [linux]
+
+ '@biomejs/cli-win32-arm64@1.7.3':
+ resolution: {integrity: sha512-unNCDqUKjujYkkSxs7gFIfdasttbDC4+z0kYmcqzRk6yWVoQBL4dNLcCbdnJS+qvVDNdI9rHp2NwpQ0WAdla4Q==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@biomejs/cli-win32-x64@1.7.3':
+ resolution: {integrity: sha512-ZmByhbrnmz/UUFYB622CECwhKIPjJLLPr5zr3edhu04LzbfcOrz16VYeNq5dpO1ADG70FORhAJkaIGdaVBG00w==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [win32]
+
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
@@ -129,6 +188,50 @@ packages:
resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
engines: {node: '>=14'}
+ lefthook-darwin-arm64@1.6.12:
+ resolution: {integrity: sha512-IJa50i+78nGxtSvnxLSDfSjBjjM7Ixl03V4+yl3Kdn+S+FwzEZet3LYTLbnKFUVy9Bg23obI3yXgwUx+tJjFXg==}
+ cpu: [arm64]
+ os: [darwin]
+
+ lefthook-darwin-x64@1.6.12:
+ resolution: {integrity: sha512-h11ByUtwM78FShgWgSUyyZtwKW6pjYfYvTygw24c/lZXKjupfowK5Ps5A73hCsjr0AEJNVpgW1S5Jd22gIJJCA==}
+ cpu: [x64]
+ os: [darwin]
+
+ lefthook-freebsd-arm64@1.6.12:
+ resolution: {integrity: sha512-Aw1+AosL8r/LFSVKG7i8GI1FpHnWFG66/6DBDUgCwNAwhNCXt7tERAM8dj9S6EqmqHCQCC0nI/6qKNBsFPk7Ow==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ lefthook-freebsd-x64@1.6.12:
+ resolution: {integrity: sha512-G8Dg7UuRstXrqaEA8MSOZikz6PpjPUQu3QmiihzcyGdzI76jFsmjJb2vkrnvMsH9u2gWb3J4sp3TULhbMHXwSw==}
+ cpu: [x64]
+ os: [freebsd]
+
+ lefthook-linux-arm64@1.6.12:
+ resolution: {integrity: sha512-fwO0i6x5EPelL66EwaySzGzvVbN2vLFZDUWuTi8nZzEgBsCBuG0mORxZg91cNCGLRPT3sgzWPraTkyzIJa7kHg==}
+ cpu: [arm64]
+ os: [linux]
+
+ lefthook-linux-x64@1.6.12:
+ resolution: {integrity: sha512-pRAZKZhSoirjRwDF0TrqxgkeXtUmJqaUi0kGmMJmutToqo9IXQcnpueVmyV9Z1m6lLJn4PpKoFydY6tFXqvyNQ==}
+ cpu: [x64]
+ os: [linux]
+
+ lefthook-windows-arm64@1.6.12:
+ resolution: {integrity: sha512-jMMIoqNKtiqGrwyWeN3JXGXi7H7iAXsGB5v4DkcUbdw9y50qhruxWz84I2PoxwYmZVeMxRR+VpYvS7nOvBmzWA==}
+ cpu: [arm64]
+ os: [win32]
+
+ lefthook-windows-x64@1.6.12:
+ resolution: {integrity: sha512-XqEBVIhp/Fd1Fs+VBlPhrSJlUkyXEJuxQmiYSYow3C18RNpQQrJFVFpz0wE/IDTn2jOXx+p5+hcdlJb+s6bnpA==}
+ cpu: [x64]
+ os: [win32]
+
+ lefthook@1.6.12:
+ resolution: {integrity: sha512-SoHhB0L1D5twH5KKsGAT1h4qF+RhGfPo/JC5z60H0RDuFWtSwFNOeFpT4Qa7XwM6J9c1fvqZzOH9/4XF7dG9Uw==}
+ hasBin: true
+
lru-cache@10.2.0:
resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==}
engines: {node: 14 || >=16.14}
@@ -226,6 +329,41 @@ snapshots:
typescript: 5.4.5
zod: 3.21.4
+ '@biomejs/biome@1.7.3':
+ optionalDependencies:
+ '@biomejs/cli-darwin-arm64': 1.7.3
+ '@biomejs/cli-darwin-x64': 1.7.3
+ '@biomejs/cli-linux-arm64': 1.7.3
+ '@biomejs/cli-linux-arm64-musl': 1.7.3
+ '@biomejs/cli-linux-x64': 1.7.3
+ '@biomejs/cli-linux-x64-musl': 1.7.3
+ '@biomejs/cli-win32-arm64': 1.7.3
+ '@biomejs/cli-win32-x64': 1.7.3
+
+ '@biomejs/cli-darwin-arm64@1.7.3':
+ optional: true
+
+ '@biomejs/cli-darwin-x64@1.7.3':
+ optional: true
+
+ '@biomejs/cli-linux-arm64-musl@1.7.3':
+ optional: true
+
+ '@biomejs/cli-linux-arm64@1.7.3':
+ optional: true
+
+ '@biomejs/cli-linux-x64-musl@1.7.3':
+ optional: true
+
+ '@biomejs/cli-linux-x64@1.7.3':
+ optional: true
+
+ '@biomejs/cli-win32-arm64@1.7.3':
+ optional: true
+
+ '@biomejs/cli-win32-x64@1.7.3':
+ optional: true
+
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
@@ -314,6 +452,41 @@ snapshots:
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
+ lefthook-darwin-arm64@1.6.12:
+ optional: true
+
+ lefthook-darwin-x64@1.6.12:
+ optional: true
+
+ lefthook-freebsd-arm64@1.6.12:
+ optional: true
+
+ lefthook-freebsd-x64@1.6.12:
+ optional: true
+
+ lefthook-linux-arm64@1.6.12:
+ optional: true
+
+ lefthook-linux-x64@1.6.12:
+ optional: true
+
+ lefthook-windows-arm64@1.6.12:
+ optional: true
+
+ lefthook-windows-x64@1.6.12:
+ optional: true
+
+ lefthook@1.6.12:
+ optionalDependencies:
+ lefthook-darwin-arm64: 1.6.12
+ lefthook-darwin-x64: 1.6.12
+ lefthook-freebsd-arm64: 1.6.12
+ lefthook-freebsd-x64: 1.6.12
+ lefthook-linux-arm64: 1.6.12
+ lefthook-linux-x64: 1.6.12
+ lefthook-windows-arm64: 1.6.12
+ lefthook-windows-x64: 1.6.12
+
lru-cache@10.2.0: {}
minimatch@8.0.4:
diff --git a/src/cli.ts b/src/cli.ts
index 6bff983..50e57fd 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -1,51 +1,51 @@
#!/usr/bin/env node
-import { generate } from "./generate";
+import fs from "node:fs";
+import path from "node:path";
+import { defaultFormRequestPath } from "@7nohe/laravel-zodgen";
import { Command } from "commander";
+import { consola } from "consola";
+import { colors } from "consola/utils";
import packageJson from "../package.json";
import {
- defaultEnumPath,
- defaultModelPath,
- defaultOutputPath,
- tmpDir,
+ defaultEnumPath,
+ defaultModelPath,
+ defaultOutputPath,
+ tmpDir,
} from "./constants";
-import fs from "fs";
-import { defaultFormRequestPath } from "@7nohe/laravel-zodgen";
-import { consola } from "consola";
-import { colors } from "consola/utils";
-import path from "path";
+import { generate } from "./generate";
export type CLIOptions = {
- output: string;
- laravelEnum: boolean;
- enumPath: string;
- modelPath: string;
- ziggy: boolean;
- vendorRoutes: boolean;
- ignoreRouteDts: boolean;
- formRequest: boolean;
- formRequestPath: string;
+ output: string;
+ laravelEnum: boolean;
+ enumPath: string;
+ modelPath: string;
+ ziggy: boolean;
+ vendorRoutes: boolean;
+ ignoreRouteDts: boolean;
+ formRequest: boolean;
+ formRequestPath: string;
};
const program = new Command();
program
- .name("laravel-typegen")
- .version(packageJson.version)
- .description("Generate TypeScript types from your Laravel code")
- .option("-o, --output ", "Output directory", defaultOutputPath)
- .option("--laravel-enum", "Use Laravel Enum", false)
- .option("--enum-path ", "Path to enum files", defaultEnumPath)
- .option("--model-path ", "Path to model files", defaultModelPath)
- .option("-z, --ziggy", "Generate types for ziggy", false)
- .option("--vendor-routes", "Include routes defined by vendor packages", false)
- .option("--ignore-route-dts", "Ignore generating route.d.ts", false)
- .option("--form-request", "Generate types for FormRequests", false)
- .option(
- "--form-request-path ",
- "Path to FormRequest files",
- defaultFormRequestPath
- )
- .parse();
+ .name("laravel-typegen")
+ .version(packageJson.version)
+ .description("Generate TypeScript types from your Laravel code")
+ .option("-o, --output ", "Output directory", defaultOutputPath)
+ .option("--laravel-enum", "Use Laravel Enum", false)
+ .option("--enum-path ", "Path to enum files", defaultEnumPath)
+ .option("--model-path ", "Path to model files", defaultModelPath)
+ .option("-z, --ziggy", "Generate types for ziggy", false)
+ .option("--vendor-routes", "Include routes defined by vendor packages", false)
+ .option("--ignore-route-dts", "Ignore generating route.d.ts", false)
+ .option("--form-request", "Generate types for FormRequests", false)
+ .option(
+ "--form-request-path ",
+ "Path to FormRequest files",
+ defaultFormRequestPath,
+ )
+ .parse();
const options = program.opts();
@@ -53,20 +53,20 @@ consola.box(`${colors.bgBlueBright(`Laravel Typegen v${packageJson.version}`)}
[Options]
${Object.entries(options)
- .map(([key, value]) => `${key}: ${value}`)
- .join("\n")}
+ .map(([key, value]) => `${key}: ${value}`)
+ .join("\n")}
`);
try {
- generate(options).then(() => {
- consola.success(
- `Done! Generated types are available in ${path.resolve(options.output)}`
- );
- });
+ generate(options).then(() => {
+ consola.success(
+ `Done! Generated types are available in ${path.resolve(options.output)}`,
+ );
+ });
} catch {
- consola.error("Failed to generate types.");
- if (fs.existsSync(tmpDir) && process.env.KEEP_LARAVEL_JSON !== "true") {
- // Clean up
- fs.rmSync(tmpDir, { recursive: true });
- }
+ consola.error("Failed to generate types.");
+ if (fs.existsSync(tmpDir) && process.env.KEEP_LARAVEL_JSON !== "true") {
+ // Clean up
+ fs.rmSync(tmpDir, { recursive: true });
+ }
}
diff --git a/src/formRequests/createFormRequestTypes.ts b/src/formRequests/createFormRequestTypes.ts
index 106e162..7e9ad90 100644
--- a/src/formRequests/createFormRequestTypes.ts
+++ b/src/formRequests/createFormRequestTypes.ts
@@ -1,117 +1,110 @@
import ts from "typescript";
-type ArrayValue =
- | Record
- | {
- name: string;
- isRequired: boolean;
- };
-
type FieldValue =
- | { name: string; isRequired: boolean; "*"?: FieldValue }
- | object;
+ | { name: string; isRequired: boolean; "*"?: FieldValue }
+ | object;
type Fields =
- | {
- [key: string]: FieldValue;
- }
- | FieldValue;
+ | {
+ [key: string]: FieldValue;
+ }
+ | FieldValue;
function getKeyword(name: string): ts.KeywordTypeSyntaxKind {
- switch (name) {
- case "string":
- return ts.SyntaxKind.StringKeyword;
- case "number":
- return ts.SyntaxKind.NumberKeyword;
- case "boolean":
- return ts.SyntaxKind.BooleanKeyword;
- case "undefined":
- return ts.SyntaxKind.UndefinedKeyword;
- default:
- return ts.SyntaxKind.AnyKeyword;
- }
+ switch (name) {
+ case "string":
+ return ts.SyntaxKind.StringKeyword;
+ case "number":
+ return ts.SyntaxKind.NumberKeyword;
+ case "boolean":
+ return ts.SyntaxKind.BooleanKeyword;
+ case "undefined":
+ return ts.SyntaxKind.UndefinedKeyword;
+ default:
+ return ts.SyntaxKind.AnyKeyword;
+ }
}
-function isArrayOfPrimitives(arrayValue: ArrayValue) {
- return (
- typeof arrayValue === "object" &&
- "name" in arrayValue &&
- typeof arrayValue.name === "string"
- );
+function isArrayOfPrimitives(arrayValue: FieldValue) {
+ return (
+ typeof arrayValue === "object" &&
+ "name" in arrayValue &&
+ typeof arrayValue.name === "string"
+ );
}
function createArrayTypeNode(arrayValue: FieldValue): ts.TypeNode {
- if (isArrayOfPrimitives(arrayValue)) {
- const arrayPrimitiveType = ts.factory.createKeywordTypeNode(
- getKeyword((arrayValue as { name: string; isRequired: boolean }).name)
- );
- return ts.factory.createArrayTypeNode(arrayPrimitiveType);
- }
- const arrayElementType = createTypeNodeFromFields(arrayValue as any);
- return ts.factory.createArrayTypeNode(arrayElementType);
+ if (isArrayOfPrimitives(arrayValue)) {
+ const arrayPrimitiveType = ts.factory.createKeywordTypeNode(
+ getKeyword((arrayValue as { name: string; isRequired: boolean }).name),
+ );
+ return ts.factory.createArrayTypeNode(arrayPrimitiveType);
+ }
+ const arrayElementType = createTypeNodeFromFields(arrayValue);
+ return ts.factory.createArrayTypeNode(arrayElementType);
}
function createTypeNodeForValue(value: FieldValue): ts.TypeNode {
- if (typeof value === "object" && !("name" in value)) {
- return createTypeNodeFromFields(value as any);
- }
+ if (typeof value === "object" && !("name" in value)) {
+ return createTypeNodeFromFields(value);
+ }
- if (typeof value === "object" && "*" in value && value["*"]) {
- return createArrayTypeNode(value["*"]);
- }
+ if (typeof value === "object" && "*" in value && value["*"]) {
+ return createArrayTypeNode(value["*"]);
+ }
- return ts.factory.createKeywordTypeNode(getKeyword(value.name as string));
+ return ts.factory.createKeywordTypeNode(getKeyword(value.name as string));
}
function createTypeNodeFromFields(fields: Fields): ts.TypeNode {
- if ("*" in fields && fields["*"]) {
- return createArrayTypeNode(fields["*"]);
- }
- const members = Object.entries(fields).flatMap<
- ts.PropertySignature | ts.TypeNode
- >(([name, value]) => {
- const typeNode = createTypeNodeForValue(value);
-
- const isRequired = !("name" in value) || (value as any).isRequired;
-
- return [
- ts.factory.createPropertySignature(
- undefined,
- name,
- isRequired
- ? undefined
- : ts.factory.createToken(ts.SyntaxKind.QuestionToken),
- typeNode
- ),
- ];
- });
-
- return ts.factory.createTypeLiteralNode(
- members.filter(Boolean) as ts.TypeElement[]
- );
+ if ("*" in fields && fields["*"]) {
+ return createArrayTypeNode(fields["*"]);
+ }
+ const members = Object.entries(fields).flatMap<
+ ts.PropertySignature | ts.TypeNode
+ >(([name, value]) => {
+ const typeNode = createTypeNodeForValue(value);
+
+ const isRequired = !("name" in value) || value.isRequired;
+
+ return [
+ ts.factory.createPropertySignature(
+ undefined,
+ name,
+ isRequired
+ ? undefined
+ : ts.factory.createToken(ts.SyntaxKind.QuestionToken),
+ typeNode,
+ ),
+ ];
+ });
+
+ return ts.factory.createTypeLiteralNode(
+ members.filter(Boolean) as ts.TypeElement[],
+ );
}
function createTypeAliasDeclaration(
- typeName: string,
- fields: Fields
+ typeName: string,
+ fields: Fields,
): ts.TypeAliasDeclaration {
- return ts.factory.createTypeAliasDeclaration(
- [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
- typeName,
- undefined,
- createTypeNodeFromFields(fields)
- );
+ return ts.factory.createTypeAliasDeclaration(
+ [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
+ typeName,
+ undefined,
+ createTypeNodeFromFields(fields),
+ );
}
export function createFormRequestTypes(rules: Fields) {
- const sourceFile = ts.factory.createSourceFile(
- Object.entries(rules).map(([key, value]) =>
- createTypeAliasDeclaration(key, value)
- ),
- ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
- ts.NodeFlags.None
- );
-
- const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
- return printer.printNode(ts.EmitHint.Unspecified, sourceFile, sourceFile);
+ const sourceFile = ts.factory.createSourceFile(
+ Object.entries(rules).map(([key, value]) =>
+ createTypeAliasDeclaration(key, value),
+ ),
+ ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
+ ts.NodeFlags.None,
+ );
+
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
+ return printer.printNode(ts.EmitHint.Unspecified, sourceFile, sourceFile);
}
diff --git a/src/generate.ts b/src/generate.ts
index 5f3bd1c..72bbcef 100644
--- a/src/generate.ts
+++ b/src/generate.ts
@@ -1,229 +1,231 @@
-import { print } from "./print";
-import { CLIOptions } from "./cli";
-import { createSource as createModelSource } from "./models/createSource";
-import { createRouteParamsSource } from "./routes/createSource";
+import { execSync } from "node:child_process";
+import fs from "node:fs";
+import path from "node:path";
+import { parseFormRequests } from "@7nohe/laravel-zodgen";
+import { consola } from "consola";
+import { colors } from "consola/utils";
import { sync } from "glob";
-import fs from "fs";
-import { execSync } from "child_process";
-import { LaravelModelType, LaravelRouteListType } from "./types";
+import type { CLIOptions } from "./cli";
import {
- defaultOutputPath,
- modelFileName,
- routeParamsFileName,
- indexDeclarationFileName,
- formRequestsFileName,
- tmpDir,
+ defaultOutputPath,
+ formRequestsFileName,
+ indexDeclarationFileName,
+ modelFileName,
+ routeParamsFileName,
+ tmpDir,
} from "./constants";
-import path from "path";
-import { parseFormRequests } from "@7nohe/laravel-zodgen";
import { createFormRequestTypes } from "./formRequests/createFormRequestTypes";
+import { createSource as createModelSource } from "./models/createSource";
+import { print } from "./print";
+import { createRouteParamsSource } from "./routes/createSource";
+import type { LaravelModelType, LaravelRouteListType } from "./types";
import { formatNamespaceForCommand, getPhpAst, getPhpNamespace } from "./utils";
-import { consola } from "consola";
-import { colors } from "consola/utils";
const modelLogPrefix = colors.bgBlueBright("[Model]");
const routeLogPrefix = colors.bgGreenBright("[Route]");
const formRequestLogPrefix = colors.bgYellowBright("[FormRequest]");
export async function generate(options: CLIOptions) {
- consola.info(
- modelLogPrefix,
- colors.blueBright("Start generating model types...")
- );
-
- const parsedModelPath = path
- .join(options.modelPath, "**", "*.php")
- .replace(/\\/g, "/");
- const modelPaths = sync(parsedModelPath).sort();
-
- const modelData: LaravelModelType[] = [];
- const parsedEnumPath = path
- .join(options.enumPath, "**", "*.php")
- .replace(/\\/g, "/");
- const enums = sync(parsedEnumPath).sort();
- if (!fs.existsSync(tmpDir)) {
- fs.mkdirSync(tmpDir);
- }
- // Generate models
- for (const modelPath of modelPaths) {
- const modelName = modelPath
- .replace(/\\/g, "/")
- .replace(options.modelPath.replace(/\\/g, "/") + "/", "")
- .replace(path.extname(modelPath), "") // remove .php extension
- .split("/")
- .at(-1)!; // get only model name without directory
-
- createModelDirectory(modelName);
-
- const namespacedModel =
- getNamespaceForCommand(modelPath) + "\\\\" + modelName;
- const outputPath = path.join(tmpDir, `${modelName}.json`);
-
- const modelShowCommand = `php artisan model:show ${namespacedModel} --json`;
-
- // Run artisan command to get model data
- try {
- if (process.env.SKIP_ARTISAN_COMMAND !== "true") {
- consola.start(
- modelLogPrefix,
- `Running '${colors.blueBright(modelShowCommand)}'`
- );
- const json = execSync(modelShowCommand).toString();
- const dir = path.dirname(outputPath);
- if (!fs.existsSync(dir)) {
- fs.mkdirSync(dir, { recursive: true });
- }
- fs.writeFileSync(outputPath, json);
- consola.success(modelLogPrefix, `Saved ${modelName} to ${outputPath}`);
- } else {
- consola.info(modelLogPrefix, `Skipping ${modelShowCommand}`);
- }
- } catch (e) {
- consola.info(
- modelLogPrefix,
- `Failed to get model data for ${modelName}'. You still can generate types by running ${modelShowCommand} manually and then run 'laravel-typegen' with SKIP_ARTISAN_COMMAND=true environment variable.`
- );
- consola.error(modelLogPrefix, e);
- }
-
- // Read model data from JSON file
- try {
- const jsonPath = path.resolve(tmpDir, `${modelName}.json`);
- consola.start(modelLogPrefix, `Reading ${jsonPath}`);
- const modelJson = JSON.parse(
- fs.readFileSync(jsonPath, "utf8")
- ) as LaravelModelType;
- modelData.push(modelJson);
- } catch {
- consola.warn(
- modelLogPrefix,
- `Failed to generate ${modelName}. Skipping...`
- );
- }
- }
-
- const modelSource = createModelSource(
- modelFileName,
- modelData,
- enums,
- options
- );
- print(modelFileName, modelSource, options.output ?? defaultOutputPath);
-
- consola.success(
- modelLogPrefix,
- colors.blueBright("Model types generated successfully.")
- );
-
- // Generate types for ziggy
- if (options.ziggy) {
- consola.info(
- routeLogPrefix,
- colors.greenBright("Start generating route types...")
- );
-
- const vendorOption = options.vendorRoutes ? "" : "--except-vendor";
- const routeListCommand = `php artisan route:list ${vendorOption} --json`;
-
- if (process.env.SKIP_ARTISAN_COMMAND !== "true") {
- consola.start(
- routeLogPrefix,
- `Running '${colors.greenBright(routeListCommand)}'`
- );
- const json = execSync(routeListCommand).toString();
- const dir = path.dirname(path.resolve(tmpDir, "route.json"));
- if (!fs.existsSync(dir)) {
- fs.mkdirSync(dir, {
- recursive: true,
- });
- }
- fs.writeFileSync(path.resolve(tmpDir, "route.json"), json);
- consola.success(
- routeLogPrefix,
- `Saved route.json to ${path.resolve(tmpDir, "route.json")}`
- );
- } else {
- consola.warn(routeLogPrefix, `Failed to get route data. Skipping...`);
- }
-
- consola.start(routeLogPrefix, "Reading route.json");
- const routeJson = JSON.parse(
- fs.readFileSync(path.resolve(tmpDir, "route.json"), "utf8")
- ) as LaravelRouteListType[];
-
- const routeSource = createRouteParamsSource(
- routeParamsFileName,
- routeJson,
- options
- );
-
- print(
- routeParamsFileName,
- routeSource,
- options.output ?? defaultOutputPath
- );
-
- consola.start(routeLogPrefix, "Copying route.d.ts...");
-
- // Copy route.d.ts
- if (!options.ignoreRouteDts) {
- fs.copyFileSync(
- path.resolve(__dirname, "..", "templates", indexDeclarationFileName),
- path.resolve(
- options.output ?? defaultOutputPath,
- indexDeclarationFileName
- )
- );
- }
-
- consola.success(
- routeLogPrefix,
- colors.greenBright("Route types generated successfully.")
- );
- }
-
- if (options.formRequest) {
- consola.start(
- formRequestLogPrefix,
- colors.yellowBright("Start generating form request types...")
- );
- consola.start(
- formRequestLogPrefix,
- `Parsing form requests from ${options.formRequestPath}...`
- );
- // Generate types for form requests
- const rules = parseFormRequests(options.formRequestPath, true);
- consola.success(formRequestLogPrefix, "Form requests parsed successfully.");
-
- const formRequestSource = createFormRequestTypes(rules);
- print(
- formRequestsFileName,
- formRequestSource,
- options.output ?? defaultOutputPath
- );
-
- consola.success(
- formRequestLogPrefix,
- colors.yellowBright("Form request types generated successfully.")
- );
- }
-
- if (fs.existsSync(tmpDir) && process.env.KEEP_LARAVEL_JSON !== "true") {
- consola.start("Removing temporary files...");
- fs.rmSync(tmpDir, { recursive: true });
- consola.success("Temporary files removed.");
- }
+ consola.info(
+ modelLogPrefix,
+ colors.blueBright("Start generating model types..."),
+ );
+
+ const parsedModelPath = path
+ .join(options.modelPath, "**", "*.php")
+ .replace(/\\/g, "/");
+ const modelPaths = sync(parsedModelPath).sort();
+
+ const modelData: LaravelModelType[] = [];
+ const parsedEnumPath = path
+ .join(options.enumPath, "**", "*.php")
+ .replace(/\\/g, "/");
+ const enums = sync(parsedEnumPath).sort();
+ if (!fs.existsSync(tmpDir)) {
+ fs.mkdirSync(tmpDir);
+ }
+ // Generate models
+ for (const modelPath of modelPaths) {
+ const modelName =
+ modelPath
+ .replace(/\\/g, "/")
+ .replace(`${options.modelPath.replace(/\\/g, "/")}/`, "")
+ .replace(path.extname(modelPath), "") // remove .php extension
+ .split("/")
+ .at(-1) ?? ""; // get only model name without directory
+
+ createModelDirectory(modelName);
+
+ const namespacedModel = `${getNamespaceForCommand(
+ modelPath,
+ )}\\\\${modelName}`;
+ const outputPath = path.join(tmpDir, `${modelName}.json`);
+
+ const modelShowCommand = `php artisan model:show ${namespacedModel} --json`;
+
+ // Run artisan command to get model data
+ try {
+ if (process.env.SKIP_ARTISAN_COMMAND !== "true") {
+ consola.start(
+ modelLogPrefix,
+ `Running '${colors.blueBright(modelShowCommand)}'`,
+ );
+ const json = execSync(modelShowCommand).toString();
+ const dir = path.dirname(outputPath);
+ if (!fs.existsSync(dir)) {
+ fs.mkdirSync(dir, { recursive: true });
+ }
+ fs.writeFileSync(outputPath, json);
+ consola.success(modelLogPrefix, `Saved ${modelName} to ${outputPath}`);
+ } else {
+ consola.info(modelLogPrefix, `Skipping ${modelShowCommand}`);
+ }
+ } catch (e) {
+ consola.info(
+ modelLogPrefix,
+ `Failed to get model data for ${modelName}'. You still can generate types by running ${modelShowCommand} manually and then run 'laravel-typegen' with SKIP_ARTISAN_COMMAND=true environment variable.`,
+ );
+ consola.error(modelLogPrefix, e);
+ }
+
+ // Read model data from JSON file
+ try {
+ const jsonPath = path.resolve(tmpDir, `${modelName}.json`);
+ consola.start(modelLogPrefix, `Reading ${jsonPath}`);
+ const modelJson = JSON.parse(
+ fs.readFileSync(jsonPath, "utf8"),
+ ) as LaravelModelType;
+ modelData.push(modelJson);
+ } catch {
+ consola.warn(
+ modelLogPrefix,
+ `Failed to generate ${modelName}. Skipping...`,
+ );
+ }
+ }
+
+ const modelSource = createModelSource(
+ modelFileName,
+ modelData,
+ enums,
+ options,
+ );
+ print(modelFileName, modelSource, options.output ?? defaultOutputPath);
+
+ consola.success(
+ modelLogPrefix,
+ colors.blueBright("Model types generated successfully."),
+ );
+
+ // Generate types for ziggy
+ if (options.ziggy) {
+ consola.info(
+ routeLogPrefix,
+ colors.greenBright("Start generating route types..."),
+ );
+
+ const vendorOption = options.vendorRoutes ? "" : "--except-vendor";
+ const routeListCommand = `php artisan route:list ${vendorOption} --json`;
+
+ if (process.env.SKIP_ARTISAN_COMMAND !== "true") {
+ consola.start(
+ routeLogPrefix,
+ `Running '${colors.greenBright(routeListCommand)}'`,
+ );
+ const json = execSync(routeListCommand).toString();
+ const dir = path.dirname(path.resolve(tmpDir, "route.json"));
+ if (!fs.existsSync(dir)) {
+ fs.mkdirSync(dir, {
+ recursive: true,
+ });
+ }
+ fs.writeFileSync(path.resolve(tmpDir, "route.json"), json);
+ consola.success(
+ routeLogPrefix,
+ `Saved route.json to ${path.resolve(tmpDir, "route.json")}`,
+ );
+ } else {
+ consola.warn(routeLogPrefix, "Failed to get route data. Skipping...");
+ }
+
+ consola.start(routeLogPrefix, "Reading route.json");
+ const routeJson = JSON.parse(
+ fs.readFileSync(path.resolve(tmpDir, "route.json"), "utf8"),
+ ) as LaravelRouteListType[];
+
+ const routeSource = createRouteParamsSource(
+ routeParamsFileName,
+ routeJson,
+ options,
+ );
+
+ print(
+ routeParamsFileName,
+ routeSource,
+ options.output ?? defaultOutputPath,
+ );
+
+ consola.start(routeLogPrefix, "Copying route.d.ts...");
+
+ // Copy route.d.ts
+ if (!options.ignoreRouteDts) {
+ fs.copyFileSync(
+ path.resolve(__dirname, "..", "templates", indexDeclarationFileName),
+ path.resolve(
+ options.output ?? defaultOutputPath,
+ indexDeclarationFileName,
+ ),
+ );
+ }
+
+ consola.success(
+ routeLogPrefix,
+ colors.greenBright("Route types generated successfully."),
+ );
+ }
+
+ if (options.formRequest) {
+ consola.start(
+ formRequestLogPrefix,
+ colors.yellowBright("Start generating form request types..."),
+ );
+ consola.start(
+ formRequestLogPrefix,
+ `Parsing form requests from ${options.formRequestPath}...`,
+ );
+ // Generate types for form requests
+ const rules = parseFormRequests(options.formRequestPath, true);
+ consola.success(formRequestLogPrefix, "Form requests parsed successfully.");
+
+ const formRequestSource = createFormRequestTypes(rules);
+ print(
+ formRequestsFileName,
+ formRequestSource,
+ options.output ?? defaultOutputPath,
+ );
+
+ consola.success(
+ formRequestLogPrefix,
+ colors.yellowBright("Form request types generated successfully."),
+ );
+ }
+
+ if (fs.existsSync(tmpDir) && process.env.KEEP_LARAVEL_JSON !== "true") {
+ consola.start("Removing temporary files...");
+ fs.rmSync(tmpDir, { recursive: true });
+ consola.success("Temporary files removed.");
+ }
}
const createModelDirectory = (modelName: string) => {
- const modelNameArray = modelName.split("/");
- modelNameArray.pop();
- if (
- modelNameArray.length > 0 &&
- !fs.existsSync(path.join(tmpDir, ...modelNameArray))
- ) {
- fs.mkdirSync(path.join(tmpDir, ...modelNameArray));
- }
+ const modelNameArray = modelName.split("/");
+ modelNameArray.pop();
+ if (
+ modelNameArray.length > 0 &&
+ !fs.existsSync(path.join(tmpDir, ...modelNameArray))
+ ) {
+ fs.mkdirSync(path.join(tmpDir, ...modelNameArray));
+ }
};
/**
@@ -233,16 +235,16 @@ const createModelDirectory = (modelName: string) => {
const namespaceDictByDir: { [key: string]: string } = {};
export const getNamespaceForCommand = (phpFilepath: string) => {
- const dir = path.dirname(phpFilepath);
+ const dir = path.dirname(phpFilepath);
- if (namespaceDictByDir[dir]) {
- return namespaceDictByDir[dir];
- }
+ if (namespaceDictByDir[dir]) {
+ return namespaceDictByDir[dir];
+ }
- const ast = getPhpAst(phpFilepath);
- const namespace = getPhpNamespace(ast).name;
- const namespaceForCommand = formatNamespaceForCommand(namespace);
- namespaceDictByDir[dir] = namespaceForCommand;
+ const ast = getPhpAst(phpFilepath);
+ const namespace = getPhpNamespace(ast).name;
+ const namespaceForCommand = formatNamespaceForCommand(namespace);
+ namespaceDictByDir[dir] = namespaceForCommand;
- return namespaceForCommand;
+ return namespaceForCommand;
};
diff --git a/src/models/createEnumTypes.ts b/src/models/createEnumTypes.ts
index 60d7d5d..7bbc983 100644
--- a/src/models/createEnumTypes.ts
+++ b/src/models/createEnumTypes.ts
@@ -1,66 +1,66 @@
-import ts from "typescript";
+import fs from "node:fs";
import {
- Class,
- Engine,
- EnumCase,
- Identifier,
- Namespace,
- Number,
- String,
+ type Class,
+ Engine,
+ type EnumCase,
+ type Identifier,
+ type Namespace,
+ type Number as NumberType,
+ type String as StringType,
} from "php-parser";
-import fs from "fs";
+import ts from "typescript";
const parser = new Engine({});
const getInitializer = (enumcase: EnumCase) => {
- const numberValue = enumcase.value as unknown as Number;
- if (numberValue.kind === "number") {
- return ts.factory.createNumericLiteral(numberValue.value);
- }
+ const numberValue = enumcase.value as unknown as NumberType;
+ if (numberValue.kind === "number") {
+ return ts.factory.createNumericLiteral(numberValue.value);
+ }
- return ts.factory.createStringLiteral(
- (enumcase.value as unknown as String).value
- );
+ return ts.factory.createStringLiteral(
+ (enumcase.value as unknown as StringType).value,
+ );
};
export const createEnumType = (enumFilePath: string) => {
- const phpFile = fs.readFileSync(enumFilePath);
- const ast = parser.parseCode(
- phpFile.toString(),
- enumFilePath.split("/").at(-1)!
- );
+ const phpFile = fs.readFileSync(enumFilePath);
+ const ast = parser.parseCode(
+ phpFile.toString(),
+ enumFilePath.split("/").at(-1) ?? "",
+ );
- // find Namespace
- const namespace = ast.children.find(
- (child) => child.kind === "namespace"
- ) as Namespace;
+ // find Namespace
+ const namespace = ast.children.find(
+ (child) => child.kind === "namespace",
+ ) as Namespace;
- // find enum Block
- const enumBlock = namespace.children.find(
- (child) => child.kind === "enum"
- ) as Class;
+ // find enum Block
+ const enumBlock = namespace.children.find(
+ (child) => child.kind === "enum",
+ ) as Class;
- const enumName = (enumBlock.name as Identifier).name;
+ const enumName = (enumBlock.name as Identifier).name;
- // get EnumCases
- const enumcases = enumBlock.body.filter(
- (declaration) => declaration.kind === "enumcase"
- ) as unknown as EnumCase[];
+ // get EnumCases
+ const enumcases = enumBlock.body.filter(
+ (declaration) => declaration.kind === "enumcase",
+ ) as unknown as EnumCase[];
- return ts.factory.createEnumDeclaration(
- [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
- ts.factory.createIdentifier(enumName),
- enumcases.map((enumcase) =>
- ts.factory.createEnumMember(
- ts.factory.createIdentifier(
- (enumcase.name as unknown as Identifier).name
- ),
- getInitializer(enumcase)
- )
- )
- );
+ return ts.factory.createEnumDeclaration(
+ [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
+ ts.factory.createIdentifier(enumName),
+ enumcases.map((enumcase) =>
+ ts.factory.createEnumMember(
+ ts.factory.createIdentifier(
+ (enumcase.name as unknown as Identifier).name,
+ ),
+ getInitializer(enumcase),
+ ),
+ ),
+ );
};
export const createEnumTypes = (enumFilePaths: string[]) => {
- return enumFilePaths.map((path) => createEnumType(path));
+ return enumFilePaths.map((path) => createEnumType(path));
};
diff --git a/src/models/createLarvelEnumTypes.ts b/src/models/createLarvelEnumTypes.ts
index 21b5248..c331033 100644
--- a/src/models/createLarvelEnumTypes.ts
+++ b/src/models/createLarvelEnumTypes.ts
@@ -1,69 +1,67 @@
-import ts from "typescript";
+import fs from "node:fs";
import {
- Class,
- ClassConstant,
- Constant,
- Engine,
- Identifier,
- Namespace,
- Number,
- String,
+ type Class,
+ type ClassConstant,
+ type Constant,
+ Engine,
+ type Identifier,
+ type Namespace,
+ type Number as NumberType,
+ type String as StringType,
} from "php-parser";
-import fs from "fs";
+import ts from "typescript";
const parser = new Engine({});
const getInitializer = (constant: Constant) => {
- const numberValue = constant.value as Number;
- if (numberValue.kind === "number") {
- return ts.factory.createNumericLiteral(numberValue.value);
- }
+ const numberValue = constant.value as NumberType;
+ if (numberValue.kind === "number") {
+ return ts.factory.createNumericLiteral(numberValue.value);
+ }
- return ts.factory.createStringLiteral(
- (constant.value as unknown as String).value
- );
+ return ts.factory.createStringLiteral((constant.value as StringType).value);
};
export const createEnumType = (enumFilePath: string) => {
- const phpFile = fs.readFileSync(enumFilePath);
- const ast = parser.parseCode(
- phpFile.toString(),
- enumFilePath.split("/").at(-1)!
- );
+ const phpFile = fs.readFileSync(enumFilePath);
+ const ast = parser.parseCode(
+ phpFile.toString(),
+ enumFilePath.split("/").at(-1) ?? "",
+ );
- // find Namespace
- const namespace = ast.children.find(
- (child) => child.kind === "namespace"
- ) as Namespace;
+ // find Namespace
+ const namespace = ast.children.find(
+ (child) => child.kind === "namespace",
+ ) as Namespace;
- // find Class Block
- const classBlock = namespace.children.find(
- (child) => child.kind === "class"
- ) as Class;
+ // find Class Block
+ const classBlock = namespace.children.find(
+ (child) => child.kind === "class",
+ ) as Class;
- const className = (classBlock.name as Identifier).name;
+ const className = (classBlock.name as Identifier).name;
- // get Constants
- const constants = (
- classBlock.body.filter(
- (declaration) => declaration.kind === "classconstant"
- ) as unknown as ClassConstant[]
- ).map((declaration) => declaration.constants.at(0)) as Constant[];
+ // get Constants
+ const constants = (
+ classBlock.body.filter(
+ (declaration) => declaration.kind === "classconstant",
+ ) as unknown as ClassConstant[]
+ ).map((declaration) => declaration.constants.at(0)) as Constant[];
- return ts.factory.createEnumDeclaration(
- undefined,
- ts.factory.createIdentifier(className),
- constants.map((constant) =>
- ts.factory.createEnumMember(
- ts.factory.createIdentifier(
- (constant.name as unknown as Identifier).name
- ),
- getInitializer(constant)
- )
- )
- );
+ return ts.factory.createEnumDeclaration(
+ undefined,
+ ts.factory.createIdentifier(className),
+ constants.map((constant) =>
+ ts.factory.createEnumMember(
+ ts.factory.createIdentifier(
+ (constant.name as unknown as Identifier).name,
+ ),
+ getInitializer(constant),
+ ),
+ ),
+ );
};
export const createLaravelEnumTypes = (enumFilePaths: string[]) => {
- return enumFilePaths.map((path) => createEnumType(path));
+ return enumFilePaths.map((path) => createEnumType(path));
};
diff --git a/src/models/createSource.ts b/src/models/createSource.ts
index 0a53c78..d5f980c 100644
--- a/src/models/createSource.ts
+++ b/src/models/createSource.ts
@@ -1,46 +1,46 @@
import ts from "typescript";
-import { CLIOptions } from "../cli";
+import type { CLIOptions } from "../cli";
+import type { LaravelModelType } from "../types";
import { createEnumTypes } from "./createEnumTypes";
import { createLaravelEnumTypes } from "./createLarvelEnumTypes";
import { createTypes } from "./createTypes";
-import { LaravelModelType } from "../types";
const createSourceFile = (
- modelData: LaravelModelType[],
- enums: string[],
- options: CLIOptions
+ modelData: LaravelModelType[],
+ enums: string[],
+ options: CLIOptions,
) => {
- return ts.factory.createSourceFile(
- [
- ...createTypes(modelData),
- ...(options.laravelEnum
- ? createLaravelEnumTypes(enums)
- : createEnumTypes(enums)),
- ],
- ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
- ts.NodeFlags.None
- );
+ return ts.factory.createSourceFile(
+ [
+ ...createTypes(modelData),
+ ...(options.laravelEnum
+ ? createLaravelEnumTypes(enums)
+ : createEnumTypes(enums)),
+ ],
+ ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
+ ts.NodeFlags.None,
+ );
};
export const createSource = (
- filename: string,
- modelData: LaravelModelType[],
- enums: string[],
- options: CLIOptions
+ filename: string,
+ modelData: LaravelModelType[],
+ enums: string[],
+ options: CLIOptions,
) => {
- const resultFile = ts.createSourceFile(
- filename,
- "",
- ts.ScriptTarget.Latest,
- false,
- ts.ScriptKind.TS
- );
- const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
+ const resultFile = ts.createSourceFile(
+ filename,
+ "",
+ ts.ScriptTarget.Latest,
+ false,
+ ts.ScriptKind.TS,
+ );
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
- const result = printer.printNode(
- ts.EmitHint.Unspecified,
- createSourceFile(modelData, enums, options),
- resultFile
- );
+ const result = printer.printNode(
+ ts.EmitHint.Unspecified,
+ createSourceFile(modelData, enums, options),
+ resultFile,
+ );
- return result;
+ return result;
};
diff --git a/src/models/createTypes.ts b/src/models/createTypes.ts
index b0185b2..d7d84de 100644
--- a/src/models/createTypes.ts
+++ b/src/models/createTypes.ts
@@ -1,155 +1,160 @@
-import ts, { TypeNode } from "typescript";
+import ts, { type TypeNode } from "typescript";
+import type {
+ Attribute,
+ ColumnType,
+ LaravelModelType,
+ Relation,
+} from "../types";
import { convertCamelToSnake, isEnum } from "../utils";
-import { Attribute, ColumnType, LaravelModelType, Relation } from "../types";
type TSModelKeyword =
- | ts.SyntaxKind.NumberKeyword
- | ts.SyntaxKind.StringKeyword
- | ts.SyntaxKind.BooleanKeyword;
+ | ts.SyntaxKind.NumberKeyword
+ | ts.SyntaxKind.StringKeyword
+ | ts.SyntaxKind.BooleanKeyword;
const keywordTypeDictionary: Record = {
- "bigint unsigned": ts.SyntaxKind.NumberKeyword,
- "integer unsigned": ts.SyntaxKind.NumberKeyword,
- bigint: ts.SyntaxKind.NumberKeyword,
- integer: ts.SyntaxKind.NumberKeyword,
- smallint: ts.SyntaxKind.NumberKeyword,
- boolean: ts.SyntaxKind.BooleanKeyword,
- datetime: ts.SyntaxKind.StringKeyword,
- date: ts.SyntaxKind.StringKeyword,
- text: ts.SyntaxKind.StringKeyword,
- string: ts.SyntaxKind.StringKeyword,
- char: ts.SyntaxKind.StringKeyword,
- varchar: ts.SyntaxKind.StringKeyword,
+ "bigint unsigned": ts.SyntaxKind.NumberKeyword,
+ "integer unsigned": ts.SyntaxKind.NumberKeyword,
+ bigint: ts.SyntaxKind.NumberKeyword,
+ integer: ts.SyntaxKind.NumberKeyword,
+ smallint: ts.SyntaxKind.NumberKeyword,
+ boolean: ts.SyntaxKind.BooleanKeyword,
+ datetime: ts.SyntaxKind.StringKeyword,
+ date: ts.SyntaxKind.StringKeyword,
+ text: ts.SyntaxKind.StringKeyword,
+ string: ts.SyntaxKind.StringKeyword,
+ char: ts.SyntaxKind.StringKeyword,
+ varchar: ts.SyntaxKind.StringKeyword,
};
const getKeywordType = (columnType: ColumnType | null) => {
- if (!columnType) {
- return ts.SyntaxKind.AnyKeyword;
- }
- const keywordType = keywordTypeDictionary[columnType];
- if (keywordType) return keywordType;
+ if (!columnType) {
+ return ts.SyntaxKind.AnyKeyword;
+ }
+ const keywordType = keywordTypeDictionary[columnType];
+ if (keywordType) return keywordType;
- if (columnType.match(/string|text|char.*|varchar.*/)) {
- return ts.SyntaxKind.StringKeyword;
- }
+ if (columnType.match(/string|text|char.*|varchar.*/)) {
+ return ts.SyntaxKind.StringKeyword;
+ }
- return ts.SyntaxKind.AnyKeyword;
+ return ts.SyntaxKind.AnyKeyword;
};
const manyRelations: Relation["type"][] = [
- "HasMany",
- "MorphMany",
- "HasManyThrough",
- "BelongsToMany",
+ "HasMany",
+ "MorphMany",
+ "HasManyThrough",
+ "BelongsToMany",
];
const oneRlations: Relation["type"][] = [
- "HasOne",
- "MorphOne",
- "HasOneThrough",
- "BelongsTo",
+ "HasOne",
+ "MorphOne",
+ "HasOneThrough",
+ "BelongsTo",
];
const getRelationNode = (relation: Relation) => {
- // many relation
- if (manyRelations.includes(relation.type)) {
- return ts.factory.createArrayTypeNode(
- ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier(getClassName(relation.related)!),
- undefined
- )
- );
- }
-
- // one relation
- if (oneRlations.includes(relation.type)) {
- return ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier(getClassName(relation.related)!),
- undefined
- );
- }
-
- return ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
+ // many relation
+ if (manyRelations.includes(relation.type)) {
+ return ts.factory.createArrayTypeNode(
+ ts.factory.createTypeReferenceNode(
+ ts.factory.createIdentifier(getClassName(relation.related) ?? ""),
+ undefined,
+ ),
+ );
+ }
+
+ // one relation
+ if (oneRlations.includes(relation.type)) {
+ return ts.factory.createTypeReferenceNode(
+ ts.factory.createIdentifier(getClassName(relation.related) ?? ""),
+ undefined,
+ );
+ }
+
+ return ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
};
const getClassName = (namespace: string) => {
- return namespace.split("\\").at(-1);
+ return namespace.split("\\").at(-1);
};
const createAttributeType = (attribute: Attribute) => {
- let node: TypeNode = ts.factory.createKeywordTypeNode(
- getKeywordType(attribute.type)
- );
-
- // Blob type
- if (attribute.type === "blob") {
- node = ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier("Blob"),
- undefined
- );
- }
-
- // Date, DateTime cast
- if (attribute.cast && ["date", "datetime"].includes(attribute.cast)) {
- node = ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
- }
-
- // Json type
- if (attribute.type === "json") {
- if (attribute.cast === "array") {
- node = ts.factory.createArrayTypeNode(
- ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)
- );
- }
-
- if (attribute.cast === "object") {
- node = ts.factory.createTypeReferenceNode("Record", [
- ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
- ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
- ]);
- }
- }
-
- // Enum type
- if (attribute.cast && isEnum(attribute)) {
- // Create enum type node
- node = ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier(attribute.cast.split("\\").at(-1)!),
- undefined
- );
- }
-
- return ts.factory.createPropertySignature(
- undefined,
- ts.factory.createIdentifier(attribute.name),
- attribute.nullable
- ? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
- : undefined,
- node
- );
+ let node: TypeNode = ts.factory.createKeywordTypeNode(
+ getKeywordType(attribute.type),
+ );
+
+ // Blob type
+ if (attribute.type === "blob") {
+ node = ts.factory.createTypeReferenceNode(
+ ts.factory.createIdentifier("Blob"),
+ undefined,
+ );
+ }
+
+ // Date, DateTime cast
+ if (attribute.cast && ["date", "datetime"].includes(attribute.cast)) {
+ node = ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
+ }
+
+ // Json type
+ if (attribute.type === "json") {
+ if (attribute.cast === "array") {
+ node = ts.factory.createArrayTypeNode(
+ ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
+ );
+ }
+
+ if (attribute.cast === "object") {
+ node = ts.factory.createTypeReferenceNode("Record", [
+ ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
+ ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
+ ]);
+ }
+ }
+
+ // Enum type
+ if (attribute.cast && isEnum(attribute)) {
+ // Create enum type node
+ node = ts.factory.createTypeReferenceNode(
+ ts.factory.createIdentifier(attribute.cast.split("\\").at(-1) ?? ""),
+ undefined,
+ );
+ }
+
+ return ts.factory.createPropertySignature(
+ undefined,
+ ts.factory.createIdentifier(attribute.name),
+ attribute.nullable
+ ? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
+ : undefined,
+ node,
+ );
};
export const createTypes = (modelData: LaravelModelType[]) => {
- const modelNames = modelData.map((data) => data.class);
- return modelData.map((model) =>
- ts.factory.createTypeAliasDeclaration(
- [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
- ts.factory.createIdentifier(getClassName(model.class)!),
- undefined,
- ts.factory.createTypeLiteralNode([
- ...model.attributes
- .filter((attribute) => !attribute.hidden)
- .map((attribute) => createAttributeType(attribute)),
- ...model.relations
- .filter((relation) => modelNames.includes(relation.related))
- .map((relation) =>
- ts.factory.createPropertySignature(
- undefined,
- ts.factory.createIdentifier(convertCamelToSnake(relation.name)),
- ts.factory.createToken(ts.SyntaxKind.QuestionToken),
- getRelationNode(relation)
- )
- ),
- ])
- )
- );
+ const modelNames = modelData.map((data) => data.class);
+ return modelData.map((model) =>
+ ts.factory.createTypeAliasDeclaration(
+ [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
+ ts.factory.createIdentifier(getClassName(model.class) ?? ""),
+ undefined,
+ ts.factory.createTypeLiteralNode([
+ ...model.attributes
+ .filter((attribute) => !attribute.hidden)
+ .map((attribute) => createAttributeType(attribute)),
+ ...model.relations
+ .filter((relation) => modelNames.includes(relation.related))
+ .map((relation) =>
+ ts.factory.createPropertySignature(
+ undefined,
+ ts.factory.createIdentifier(convertCamelToSnake(relation.name)),
+ ts.factory.createToken(ts.SyntaxKind.QuestionToken),
+ getRelationNode(relation),
+ ),
+ ),
+ ]),
+ ),
+ );
};
diff --git a/src/print.ts b/src/print.ts
index 33e0db0..70232e3 100644
--- a/src/print.ts
+++ b/src/print.ts
@@ -1,14 +1,18 @@
-import fs from "fs";
-import path from "path";
+import fs from "node:fs";
+import path from "node:path";
-function printGeneratedTS(filename: string, result: string, outputPath: string) {
- fs.writeFileSync(path.join(outputPath, filename), result);
+function printGeneratedTS(
+ filename: string,
+ result: string,
+ outputPath: string,
+) {
+ fs.writeFileSync(path.join(outputPath, filename), result);
}
export function print(filename: string, result: string, outputPath: string) {
- if (!fs.existsSync(outputPath)) {
- fs.mkdirSync(outputPath, { recursive: true });
- }
+ if (!fs.existsSync(outputPath)) {
+ fs.mkdirSync(outputPath, { recursive: true });
+ }
- printGeneratedTS(filename, result, outputPath);
-}
\ No newline at end of file
+ printGeneratedTS(filename, result, outputPath);
+}
diff --git a/src/routes/createSource.ts b/src/routes/createSource.ts
index 6511047..67e52d5 100644
--- a/src/routes/createSource.ts
+++ b/src/routes/createSource.ts
@@ -1,41 +1,38 @@
import ts from "typescript";
-import { CLIOptions } from "../cli";
+import type { CLIOptions } from "../cli";
+import type { LaravelRouteListType } from "../types";
import { createTypes } from "./createTypes";
-import { LaravelRouteListType } from "../types";
const createSourceFile = (
- routeListData: LaravelRouteListType[],
- options: CLIOptions
+ routeListData: LaravelRouteListType[],
+ options: CLIOptions,
) => {
- return ts.factory.createSourceFile(
- [
- ...createTypes(routeListData),
- ],
- ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
- ts.NodeFlags.None
- );
+ return ts.factory.createSourceFile(
+ [...createTypes(routeListData)],
+ ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
+ ts.NodeFlags.None,
+ );
};
export const createRouteParamsSource = (
- filename: string,
- routeListData: LaravelRouteListType[],
- options: CLIOptions
+ filename: string,
+ routeListData: LaravelRouteListType[],
+ options: CLIOptions,
) => {
- const resultFile = ts.createSourceFile(
- filename,
- "",
- ts.ScriptTarget.Latest,
- false,
- ts.ScriptKind.TS
- );
- const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
+ const resultFile = ts.createSourceFile(
+ filename,
+ "",
+ ts.ScriptTarget.Latest,
+ false,
+ ts.ScriptKind.TS,
+ );
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
- const result = printer.printNode(
- ts.EmitHint.Unspecified,
- createSourceFile(routeListData, options),
- resultFile
- );
+ const result = printer.printNode(
+ ts.EmitHint.Unspecified,
+ createSourceFile(routeListData, options),
+ resultFile,
+ );
- return result;
+ return result;
};
-
diff --git a/src/routes/createTypes.ts b/src/routes/createTypes.ts
index 1130d55..9e00291 100644
--- a/src/routes/createTypes.ts
+++ b/src/routes/createTypes.ts
@@ -1,32 +1,39 @@
import ts from "typescript";
-import { LaravelRouteListType } from "../types";
+import type { LaravelRouteListType } from "../types";
export const createTypes = (routeListData: LaravelRouteListType[]) => {
- return [
- ts.factory.createTypeAliasDeclaration(
- [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
- ts.factory.createIdentifier("RouteParams"),
- undefined,
- ts.factory.createTypeLiteralNode(
- routeListData
- .filter((route) => route.name)
- .map((route) => {
- const params = Array.from(route.uri.matchAll(/\{(.*?)\}/g), param => param[1]);
- return ts.factory.createPropertySignature(
- undefined,
- ts.factory.createStringLiteral(route.name!),
- undefined,
- ts.factory.createTypeLiteralNode(params?.map((param) =>
- ts.factory.createPropertySignature(
- undefined,
- ts.factory.createIdentifier(param),
- undefined,
- ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
- ),
- ))
- );
- })
- )
- )
- ];
+ return [
+ ts.factory.createTypeAliasDeclaration(
+ [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
+ ts.factory.createIdentifier("RouteParams"),
+ undefined,
+ ts.factory.createTypeLiteralNode(
+ routeListData
+ .filter((route) => route.name)
+ .map((route) => {
+ const params = Array.from(
+ route.uri.matchAll(/\{(.*?)\}/g),
+ (param) => param[1],
+ );
+ return ts.factory.createPropertySignature(
+ undefined,
+ ts.factory.createStringLiteral(route.name ?? ""),
+ undefined,
+ ts.factory.createTypeLiteralNode(
+ params?.map((param) =>
+ ts.factory.createPropertySignature(
+ undefined,
+ ts.factory.createIdentifier(param),
+ undefined,
+ ts.factory.createKeywordTypeNode(
+ ts.SyntaxKind.StringKeyword,
+ ),
+ ),
+ ),
+ ),
+ );
+ }),
+ ),
+ ),
+ ];
};
diff --git a/src/types/index.ts b/src/types/index.ts
index 96dd7c7..b5baedf 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -1,74 +1,74 @@
export type Relation = {
- name: string;
- type:
- | "MorphMany"
- | "HasMany"
- | "BelongsToMany"
- | "HasManyThrough"
- | "BelongsTo"
- | "HasOne"
- | "MorphOne"
- | "HasOneThrough";
- related: string;
+ name: string;
+ type:
+ | "MorphMany"
+ | "HasMany"
+ | "BelongsToMany"
+ | "HasManyThrough"
+ | "BelongsTo"
+ | "HasOne"
+ | "MorphOne"
+ | "HasOneThrough";
+ related: string;
};
export type ColumnType =
- | "bigint unsigned"
- | "bigint"
- | "datetime"
- | "date"
- | string; // "string(n)"
+ | "bigint unsigned"
+ | "bigint"
+ | "datetime"
+ | "date"
+ | string; // "string(n)"
type CastType =
- | "array"
- | "AsStringable::class"
- | "boolean"
- | "collection"
- | "date"
- | "datetime"
- | "immutable_date"
- | "immutable_datetime"
- | "double"
- | "encrypted"
- | "encrypted:array"
- | "encrypted:collection"
- | "encrypted:object"
- | "float"
- | "integer"
- | "object"
- | "real"
- | "string"
- | "timestamp"
- | null
- | string; // "decimal:"
+ | "array"
+ | "AsStringable::class"
+ | "boolean"
+ | "collection"
+ | "date"
+ | "datetime"
+ | "immutable_date"
+ | "immutable_datetime"
+ | "double"
+ | "encrypted"
+ | "encrypted:array"
+ | "encrypted:collection"
+ | "encrypted:object"
+ | "float"
+ | "integer"
+ | "object"
+ | "real"
+ | "string"
+ | "timestamp"
+ | null
+ | string; // "decimal:"
export type Attribute = {
- name: string;
- type: ColumnType;
- increments: boolean;
- nullable: boolean;
- default: null;
- unique: boolean;
- fillable: boolean;
- hidden: boolean;
- appended: null;
- cast: CastType;
+ name: string;
+ type: ColumnType;
+ increments: boolean;
+ nullable: boolean;
+ default: null;
+ unique: boolean;
+ fillable: boolean;
+ hidden: boolean;
+ appended: null;
+ cast: CastType;
};
export type LaravelModelType = {
- class: string;
- database: "mysql" | "sqlite" | "pgsql";
- table: string;
- attributes: Attribute[];
- relations: Relation[];
- observers: any[];
+ class: string;
+ database: "mysql" | "sqlite" | "pgsql";
+ table: string;
+ attributes: Attribute[];
+ relations: Relation[];
+ observers: unknown[];
};
export type LaravelRouteListType = {
- "domain": string | null;
- "method": 'GET|HEAD' | 'POST' | 'PUT|PATCH' | 'DELETE';
- "uri": string,
- "name": string | null,
- "action": string,
- "middleware": string[]
-}
+ domain: string | null;
+ method: "GET|HEAD" | "POST" | "PUT|PATCH" | "DELETE";
+ uri: string;
+ name: string | null;
+ action: string;
+ middleware: string[];
+};
diff --git a/src/types/inertia.ts b/src/types/inertia.ts
index 99f2438..cbec567 100644
--- a/src/types/inertia.ts
+++ b/src/types/inertia.ts
@@ -1,21 +1,21 @@
export type PaginationLink = {
- active: boolean;
- label: string;
- url: string | null;
+ active: boolean;
+ label: string;
+ url: string | null;
};
export type Paginate = {
- current_page: number;
- data: T[];
- first_page_url: string;
- from: number;
- last_page: number;
- last_page_url: string;
- links: PaginationLink[];
- next_page_url: string | null;
- path: string;
- per_page: number;
- prev_page_url: string | null;
- to: number;
- total: number;
-}
+ current_page: number;
+ data: T[];
+ first_page_url: string;
+ from: number;
+ last_page: number;
+ last_page_url: string;
+ links: PaginationLink[];
+ next_page_url: string | null;
+ path: string;
+ per_page: number;
+ prev_page_url: string | null;
+ to: number;
+ total: number;
+};
diff --git a/src/utils.ts b/src/utils.ts
index 07d4baf..f7306ec 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,42 +1,42 @@
+import fs from "node:fs";
+import { Engine, type Namespace, type Program } from "php-parser";
import { defaultEnumPath } from "./constants";
-import { Attribute } from "./types";
-import { Engine, Namespace, Program } from "php-parser";
-import fs from "fs";
+import type { Attribute } from "./types";
// "app/Enums" -> "App-Enums"
const enumPath = defaultEnumPath
- .split("/")
- .map((name) => name.charAt(0).toUpperCase() + name.substring(1).toLowerCase())
- .join("-");
+ .split("/")
+ .map((name) => name.charAt(0).toUpperCase() + name.substring(1).toLowerCase())
+ .join("-");
export const isEnum = (attribute: Attribute, customEnumPath?: string) => {
- return attribute.cast
- ?.replaceAll("\\", "-")
- .match(new RegExp(customEnumPath ?? enumPath));
+ return attribute.cast
+ ?.replaceAll("\\", "-")
+ .match(new RegExp(customEnumPath ?? enumPath));
};
export const convertCamelToSnake = (camelCaseString: string): string => {
- return camelCaseString.replace(/[A-Z]/g, (str) => `_${str.toLowerCase()}`);
+ return camelCaseString.replace(/[A-Z]/g, (str) => `_${str.toLowerCase()}`);
};
export const formatNamespaceForCommand = (namespace: string): string => {
- return namespace.replaceAll(/\\/g, "\\\\");
+ return namespace.replaceAll(/\\/g, "\\\\");
};
export const getPhpAst = (phpFilePath: string): Program => {
- const parser = new Engine({});
+ const parser = new Engine({});
- const file = fs.readFileSync(phpFilePath);
- const fileName = phpFilePath.split("/").at(-1)!;
- const ast = parser.parseCode(file.toString(), fileName);
+ const file = fs.readFileSync(phpFilePath);
+ const fileName = phpFilePath.split("/").at(-1) ?? "";
+ const ast = parser.parseCode(file.toString(), fileName);
- return ast;
+ return ast;
};
export const getPhpNamespace = (phpAst: Program): Namespace => {
- const namespaceObj = phpAst.children.find(
- (child) => child.kind === "namespace"
- ) as Namespace;
+ const namespaceObj = phpAst.children.find(
+ (child) => child.kind === "namespace",
+ ) as Namespace;
- return namespaceObj;
+ return namespaceObj;
};
diff --git a/templates/route.d.ts b/templates/route.d.ts
index 782c000..b38817e 100644
--- a/templates/route.d.ts
+++ b/templates/route.d.ts
@@ -1,30 +1,30 @@
-import { Config, Router } from "ziggy-js";
-import { RouteParams } from "./param";
+import type { Config, Router } from "ziggy-js";
+import type { RouteParams } from "./param";
type CustomRouter = {
- get params(): RouteParams[T];
- current(): Extract | undefined;
- current(
- name: Extract,
- params?: RouteParams[T]
- ): boolean;
+ get params(): RouteParams[T];
+ current(): Extract | undefined;
+ current(
+ name: Extract,
+ params?: RouteParams[T],
+ ): boolean;
} & Router;
declare global {
- declare function route(): CustomRouter;
- declare function route(
- name: T,
- params?: RouteParams[T],
- absolute?: boolean,
- config?: Config
- ): string;
+ declare function route(): CustomRouter;
+ declare function route(
+ name: T,
+ params?: RouteParams[T],
+ absolute?: boolean,
+ config?: Config,
+ ): string;
}
declare module "vue" {
- interface ComponentCustomProperties {
- route: (() => CustomRouter) &
- ((
- name: T,
- params?: RouteParams[T],
- absolute?: boolean,
- config?: Config
- ) => string);
- }
+ interface ComponentCustomProperties {
+ route: (() => CustomRouter) &
+ ((
+ name: T,
+ params?: RouteParams[T],
+ absolute?: boolean,
+ config?: Config,
+ ) => string);
+ }
}
diff --git a/tsconfig.json b/tsconfig.json
index 1de2ccc..a58c0d8 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,19 +1,17 @@
{
- "compilerOptions": {
- "module": "commonjs",
- "moduleResolution": "node",
- "allowSyntheticDefaultImports": true,
- "strict": true,
- "esModuleInterop": true,
- "noImplicitAny": true,
- "downlevelIteration": true,
- "resolveJsonModule": true,
- "outDir": "dist",
- "lib": ["ESNext", "DOM"],
- "declaration": true,
- "target": "ESNext"
- },
- "include": [
- "src"
- ],
+ "compilerOptions": {
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "esModuleInterop": true,
+ "noImplicitAny": true,
+ "downlevelIteration": true,
+ "resolveJsonModule": true,
+ "outDir": "dist",
+ "lib": ["ESNext", "DOM"],
+ "declaration": true,
+ "target": "ESNext"
+ },
+ "include": ["src"]
}