Kaynağa Gözat

feat(dashboard): Experimental schema introspection vite plugin

Michael Bromley 11 ay önce
ebeveyn
işleme
2041ccfb98

+ 4 - 0
packages/dashboard/src/main.tsx

@@ -5,6 +5,10 @@ import React, { useEffect } from 'react';
 import ReactDOM from 'react-dom/client';
 import { RouterProvider, createRouter } from '@tanstack/react-router';
 
+import { schemaInfo } from 'virtual:admin-api-schema';
+
+console.log(`schemaInfo:`, schemaInfo);
+import '@/framework/defaults.js';
 import { routeTree } from './routeTree.gen';
 import './styles.css';
 

+ 0 - 26
packages/dashboard/vite.config.js

@@ -1,26 +0,0 @@
-import { defineConfig } from 'vite';
-import path from 'path';
-import react from '@vitejs/plugin-react';
-import babel from 'vite-plugin-babel';
-import { lingui } from '@lingui/vite-plugin';
-import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
-import tailwindcss from '@tailwindcss/vite';
-
-// https://vite.dev/config/
-export default defineConfig({
-    plugins: [
-        TanStackRouterVite({ autoCodeSplitting: true }),
-        react({
-            babel: {
-                plugins: ['@lingui/babel-plugin-lingui-macro'],
-            },
-        }),
-        lingui(),
-        tailwindcss(),
-    ],
-    resolve: {
-        alias: {
-            '@': path.resolve(__dirname, './src'),
-        },
-    },
-});

+ 31 - 0
packages/dashboard/vite.config.ts

@@ -0,0 +1,31 @@
+import { lingui } from '@lingui/vite-plugin';
+import tailwindcss from '@tailwindcss/vite';
+import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
+import react from '@vitejs/plugin-react';
+import path from 'path';
+import { defineConfig } from 'vite';
+
+import { adminApiSchemaPlugin } from './vite/api-schema/vite-plugin-admin-api-schema.js';
+
+// https://vite.dev/config/
+export default defineConfig(async () => {
+    const vendureConfig = await import('../dev-server/dev-config').then(m => m.devConfig);
+    return {
+        plugins: [
+            TanStackRouterVite({ autoCodeSplitting: true }),
+            react({
+                babel: {
+                    plugins: ['@lingui/babel-plugin-lingui-macro'],
+                },
+            }),
+            lingui(),
+            tailwindcss(),
+            adminApiSchemaPlugin({ config: vendureConfig }),
+        ],
+        resolve: {
+            alias: {
+                '@': path.resolve(__dirname, './src'),
+            },
+        },
+    };
+});

+ 103 - 0
packages/dashboard/vite/api-schema/vite-plugin-admin-api-schema.ts

@@ -0,0 +1,103 @@
+import { GraphQLTypesLoader } from '@nestjs/graphql';
+import {
+    getConfig,
+    getFinalVendureSchema,
+    resetConfig,
+    runPluginConfigurations,
+    setConfig,
+    VENDURE_ADMIN_API_TYPE_PATHS,
+    VendureConfig,
+} from '@vendure/core';
+import {
+    buildSchema,
+    GraphQLList,
+    GraphQLNonNull,
+    GraphQLSchema,
+    GraphQLType,
+    isInputObjectType,
+    isObjectType,
+} from 'graphql';
+import { Plugin } from 'vite';
+
+interface SchemaInfo {
+    types: {
+        [typename: string]: {
+            [fieldname: string]: readonly [type: string, nullable: boolean, list: boolean];
+        };
+    };
+}
+
+function getTypeInfo(type: GraphQLType) {
+    let nullable = true;
+    let list = false;
+
+    // Unwrap NonNull
+    if (type instanceof GraphQLNonNull) {
+        nullable = false;
+        type = type.ofType;
+    }
+
+    // Unwrap List
+    if (type instanceof GraphQLList) {
+        list = true;
+        type = type.ofType;
+    }
+
+    return [type.toString().replace(/!$/, ''), nullable, list] as const;
+}
+
+function generateSchemaInfo(schema: GraphQLSchema): SchemaInfo {
+    const types = schema.getTypeMap();
+    const result: SchemaInfo = { types: {} };
+
+    Object.values(types).forEach(type => {
+        if (isObjectType(type) || isInputObjectType(type)) {
+            const fields = type.getFields();
+            result.types[type.name] = {};
+
+            Object.entries(fields).forEach(([fieldName, field]) => {
+                result.types[type.name][fieldName] = getTypeInfo(field.type);
+            });
+        }
+    });
+
+    return result;
+}
+
+const virtualModuleId = 'virtual:admin-api-schema';
+
+let defaultSchema: GraphQLSchema;
+let schemaInfo: SchemaInfo;
+
+export async function adminApiSchemaPlugin(options: { config: VendureConfig }): Promise<Plugin> {
+    resetConfig();
+    await setConfig(options.config ?? {});
+
+    const runtimeConfig = await runPluginConfigurations(getConfig() as any);
+    const typesLoader = new GraphQLTypesLoader();
+    const finalSchema = await getFinalVendureSchema({
+        config: runtimeConfig,
+        typePaths: VENDURE_ADMIN_API_TYPE_PATHS,
+        typesLoader,
+        apiType: 'admin',
+        output: 'sdl',
+    });
+    const safeSchema = buildSchema(finalSchema);
+    schemaInfo = generateSchemaInfo(safeSchema);
+
+    return {
+        name: 'vendure-admin-api-schema',
+        resolveId(id, importer) {
+            if (id === virtualModuleId) {
+                return id;
+            }
+        },
+        load(id) {
+            if (id === virtualModuleId) {
+                return `
+                    export const schemaInfo = ${JSON.stringify(schemaInfo)};
+                `;
+            }
+        },
+    };
+}