diff --git a/.changeset/four-comics-grab.md b/.changeset/four-comics-grab.md new file mode 100644 index 000000000..e087ca8e0 --- /dev/null +++ b/.changeset/four-comics-grab.md @@ -0,0 +1,5 @@ +--- +'@baseplate-dev/utils': patch +--- + +Add option to ignore filenames when cleaning empty directories diff --git a/.changeset/large-olives-cheer.md b/.changeset/large-olives-cheer.md new file mode 100644 index 000000000..b38c27c34 --- /dev/null +++ b/.changeset/large-olives-cheer.md @@ -0,0 +1,5 @@ +--- +'@baseplate-dev/sync': patch +--- + +Ignore .template-metadata.json files when cleaning empty directories diff --git a/.changeset/react-generators-tanstack-router.md b/.changeset/react-generators-tanstack-router.md new file mode 100644 index 000000000..e8d78758c --- /dev/null +++ b/.changeset/react-generators-tanstack-router.md @@ -0,0 +1,5 @@ +--- +'@baseplate-dev/react-generators': patch +--- + +Migrate React generators to use Tanstack Router instead of React Router for improved type safety and developer experience diff --git a/packages/core-generators/src/renderers/extractor/utils/deduplicate-templates.ts b/packages/core-generators/src/renderers/extractor/utils/deduplicate-templates.ts index d648290d3..6b54d4055 100644 --- a/packages/core-generators/src/renderers/extractor/utils/deduplicate-templates.ts +++ b/packages/core-generators/src/renderers/extractor/utils/deduplicate-templates.ts @@ -16,7 +16,7 @@ export function deduplicateTemplateFileExtractorSourceFiles< // Sort by modified time to ensure the latest version is used for (const file of files.toSorted( - (a, b) => a.modifiedTime.getTime() - b.modifiedTime.getTime(), + (a, b) => b.modifiedTime.getTime() - a.modifiedTime.getTime(), )) { const fileKey = `${file.metadata.generator}__${file.metadata.name}`; if (addedTemplates.has(fileKey)) { diff --git a/packages/project-builder-server/src/compiler/admin/crud/index.ts b/packages/project-builder-server/src/compiler/admin/crud/index.ts index efea137e3..48aba614f 100644 --- a/packages/project-builder-server/src/compiler/admin/crud/index.ts +++ b/packages/project-builder-server/src/compiler/admin/crud/index.ts @@ -107,7 +107,6 @@ export function compileAdminCrudSection( const modelName = builder.nameFromId(crudSection.modelRef); const { disableCreate } = crudSection; return reactRoutesGenerator({ - id: crudSection.id, name: sectionName, children: { section: adminCrudSectionGenerator({ diff --git a/packages/project-builder-server/src/compiler/admin/index.ts b/packages/project-builder-server/src/compiler/admin/index.ts index 248fdc1f5..8f9bc11e7 100644 --- a/packages/project-builder-server/src/compiler/admin/index.ts +++ b/packages/project-builder-server/src/compiler/admin/index.ts @@ -23,8 +23,8 @@ import { composeReactGenerators, reactApolloGenerator, reactComponentsGenerator, - reactNotFoundHandlerGenerator, reactRouterGenerator, + reactRoutesGenerator, reactSentryGenerator, reactTailwindGenerator, } from '@baseplate-dev/react-generators'; @@ -77,16 +77,41 @@ function buildAdmin(builder: AdminAppEntryBuilder): GeneratorBundle { reactRouter: reactRouterGenerator({ children: safeMerge( { - reactNotFoundHandler: reactNotFoundHandlerGenerator({}), - admin: adminHomeGenerator({}), - adminRoutes: backendApp.enableBullQueue - ? adminBullBoardGenerator({ - bullBoardUrl: `http://localhost:${ - generalSettings.portOffset + 1 - }`, - }) - : undefined, - routes: compileAdminFeatures(builder), + adminRoute: reactRoutesGenerator({ + name: '_admin', + children: { + adminLayout: adminLayoutGenerator({ + links: [ + { + type: 'link', + label: 'Home', + icon: 'MdHome', + path: '/', + }, + ...buildNavigationLinks(builder), + ...(backendApp.enableBullQueue + ? [ + { + type: 'link' as const, + label: 'Queues', + icon: 'AiOutlineOrderedList', + path: '/bull-board', + }, + ] + : []), + ], + }), + admin: adminHomeGenerator({}), + adminRoutes: backendApp.enableBullQueue + ? adminBullBoardGenerator({ + bullBoardUrl: `http://localhost:${ + generalSettings.portOffset + 1 + }`, + }) + : undefined, + routes: compileAdminFeatures(builder), + }, + }), }, rootFeatures, ), @@ -103,22 +128,6 @@ function buildAdmin(builder: AdminAppEntryBuilder): GeneratorBundle { }, }), apolloError: apolloErrorGenerator({}), - adminLayout: adminLayoutGenerator({ - links: [ - { type: 'link', label: 'Home', icon: 'MdHome', path: '/' }, - ...buildNavigationLinks(builder), - ...(backendApp.enableBullQueue - ? [ - { - type: 'link' as const, - label: 'Queues', - icon: 'AiOutlineOrderedList', - path: '/bull-board', - }, - ] - : []), - ], - }), adminComponents: adminComponentsGenerator({}), }, }, diff --git a/packages/project-builder-server/src/compiler/admin/sections.ts b/packages/project-builder-server/src/compiler/admin/sections.ts index 20daaa0e5..f926335fe 100644 --- a/packages/project-builder-server/src/compiler/admin/sections.ts +++ b/packages/project-builder-server/src/compiler/admin/sections.ts @@ -72,10 +72,7 @@ function compileAdminFeatureRecursive( builder.appCompiler.getChildrenForFeature(featureId); return reactRoutesGenerator({ - id: featureId, name: featureName, - // add admin layout to any root features - layoutKey: feature.parentRef ? undefined : 'admin', children: { $sections: sectionDescriptors, $childRoutes: subDescriptors, diff --git a/packages/project-builder-server/src/compiler/web/features.ts b/packages/project-builder-server/src/compiler/web/features.ts index 1db520283..91e848088 100644 --- a/packages/project-builder-server/src/compiler/web/features.ts +++ b/packages/project-builder-server/src/compiler/web/features.ts @@ -36,7 +36,6 @@ function compileWebFeatureRecursive( builder.appCompiler.getChildrenForFeature(featureId); return reactRoutesGenerator({ - id: featureId, name: featureName, children: safeMerge({ $childRoutes: subDescriptors }, generatorsForFeature), }); diff --git a/packages/project-builder-server/src/compiler/web/index.ts b/packages/project-builder-server/src/compiler/web/index.ts index c965d0002..f38ce149a 100644 --- a/packages/project-builder-server/src/compiler/web/index.ts +++ b/packages/project-builder-server/src/compiler/web/index.ts @@ -13,7 +13,6 @@ import { apolloSentryGenerator, composeReactGenerators, reactApolloGenerator, - reactNotFoundHandlerGenerator, reactRouterGenerator, reactSentryGenerator, reactTailwindGenerator, @@ -42,11 +41,11 @@ function buildReact(builder: AppEntryBuilder): GeneratorBundle { reactRouter: reactRouterGenerator({ children: safeMerge( { - reactNotFoundHandler: reactNotFoundHandlerGenerator({}), features: compileWebFeatures(builder), }, rootFeatures, ), + renderPlaceholderIndex: true, }), reactTailwind: reactTailwindGenerator({}), reactSentry: reactSentryGenerator({}), diff --git a/packages/react-generators/src/constants/react-packages.ts b/packages/react-generators/src/constants/react-packages.ts index f5537e4e7..e3d46962f 100644 --- a/packages/react-generators/src/constants/react-packages.ts +++ b/packages/react-generators/src/constants/react-packages.ts @@ -2,7 +2,8 @@ export const REACT_PACKAGES = { // React react: '19.1.0', 'react-dom': '19.1.0', - 'react-router-dom': '6.30.0', + '@tanstack/react-router': '1.124.0', + '@tanstack/router-plugin': '1.124.0', '@types/node': `^22.0.0`, '@types/react': '19.1.3', '@types/react-dom': '19.1.3', diff --git a/packages/react-generators/src/generators/admin/admin-bull-board/admin-bull-board.generator.ts b/packages/react-generators/src/generators/admin/admin-bull-board/admin-bull-board.generator.ts index 521ded20b..333e35a72 100644 --- a/packages/react-generators/src/generators/admin/admin-bull-board/admin-bull-board.generator.ts +++ b/packages/react-generators/src/generators/admin/admin-bull-board/admin-bull-board.generator.ts @@ -1,7 +1,5 @@ import { renderTextTemplateFileAction, - tsCodeFragment, - tsImportBuilder, typescriptFileProvider, } from '@baseplate-dev/core-generators'; import { @@ -21,7 +19,6 @@ import { reactConfigProvider, } from '#src/generators/core/react-config/index.js'; import { reactErrorImportsProvider } from '#src/generators/core/react-error/index.js'; -import { reactRoutesProvider } from '#src/providers/routes.js'; import { ADMIN_ADMIN_BULL_BOARD_GENERATED } from './generated/index.js'; @@ -49,7 +46,6 @@ export const adminBullBoardGenerator = createGenerator({ reactConfigImports: reactConfigImportsProvider, reactErrorImports: reactErrorImportsProvider, reactApollo: reactApolloProvider, - reactRoutes: reactRoutesProvider, generatedGraphqlImports: generatedGraphqlImportsProvider, paths: ADMIN_ADMIN_BULL_BOARD_GENERATED.paths.provider, }, @@ -59,7 +55,6 @@ export const adminBullBoardGenerator = createGenerator({ reactConfigImports, reactErrorImports, reactApollo, - reactRoutes, generatedGraphqlImports, paths, }) { @@ -67,16 +62,6 @@ export const adminBullBoardGenerator = createGenerator({ build: async (builder) => { reactApollo.registerGqlFile(paths.bullBoard); - reactRoutes.registerRoute({ - path: 'bull-board', - element: tsCodeFragment( - '', - tsImportBuilder() - .default('BullBoardPage') - .from(paths.bullBoardPage), - ), - }); - await builder.apply( typescriptFile.renderTemplateFile({ template: diff --git a/packages/react-generators/src/generators/admin/admin-bull-board/extractor.json b/packages/react-generators/src/generators/admin/admin-bull-board/extractor.json index 70581558c..abdba4138 100644 --- a/packages/react-generators/src/generators/admin/admin-bull-board/extractor.json +++ b/packages/react-generators/src/generators/admin/admin-bull-board/extractor.json @@ -1,14 +1,14 @@ { "name": "admin/admin-bull-board", "templates": { - "src/pages/bull-board/bull-board.gql": { + "routes/bull-board/bull-board.gql": { "name": "bull-board", "type": "text", "fileOptions": { "kind": "singleton" }, - "pathRootRelativePath": "{src-root}/pages/bull-board/bull-board.gql", + "pathRootRelativePath": "{routes-root}/bull-board/bull-board.gql", "variables": {} }, - "src/pages/bull-board/index.tsx": { + "routes/bull-board/index.tsx": { "name": "bull-board-page", "type": "ts", "fileOptions": { "kind": "singleton" }, @@ -31,7 +31,7 @@ "packagePathSpecifier": "@baseplate-dev/react-generators:src/generators/core/react-error/generated/ts-import-providers.ts" } }, - "pathRootRelativePath": "{src-root}/pages/bull-board/index.tsx", + "pathRootRelativePath": "{routes-root}/bull-board/index.tsx", "variables": {} } } diff --git a/packages/react-generators/src/generators/admin/admin-bull-board/generated/template-paths.ts b/packages/react-generators/src/generators/admin/admin-bull-board/generated/template-paths.ts index 26cc05597..98e2c88f4 100644 --- a/packages/react-generators/src/generators/admin/admin-bull-board/generated/template-paths.ts +++ b/packages/react-generators/src/generators/admin/admin-bull-board/generated/template-paths.ts @@ -1,6 +1,7 @@ -import { packageInfoProvider } from '@baseplate-dev/core-generators'; import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; +import { reactRoutesProvider } from '#src/providers/routes.js'; + export interface AdminAdminBullBoardPaths { bullBoard: string; bullBoardPage: string; @@ -11,16 +12,16 @@ const adminAdminBullBoardPaths = createProviderType( ); const adminAdminBullBoardPathsTask = createGeneratorTask({ - dependencies: { packageInfo: packageInfoProvider }, + dependencies: { reactRoutes: reactRoutesProvider }, exports: { adminAdminBullBoardPaths: adminAdminBullBoardPaths.export() }, - run({ packageInfo }) { - const srcRoot = packageInfo.getPackageSrcPath(); + run({ reactRoutes }) { + const routesRoot = reactRoutes.getDirectoryBase(); return { providers: { adminAdminBullBoardPaths: { - bullBoard: `${srcRoot}/pages/bull-board/bull-board.gql`, - bullBoardPage: `${srcRoot}/pages/bull-board/index.tsx`, + bullBoard: `${routesRoot}/bull-board/bull-board.gql`, + bullBoardPage: `${routesRoot}/bull-board/index.tsx`, }, }, }; diff --git a/packages/react-generators/src/generators/admin/admin-bull-board/generated/typed-templates.ts b/packages/react-generators/src/generators/admin/admin-bull-board/generated/typed-templates.ts index 5de8ba3c2..5874094c5 100644 --- a/packages/react-generators/src/generators/admin/admin-bull-board/generated/typed-templates.ts +++ b/packages/react-generators/src/generators/admin/admin-bull-board/generated/typed-templates.ts @@ -15,7 +15,7 @@ const bullBoard = createTextTemplateFile({ source: { path: path.join( import.meta.dirname, - '../templates/src/pages/bull-board/bull-board.gql', + '../templates/routes/bull-board/bull-board.gql', ), }, variables: {}, @@ -33,7 +33,7 @@ const bullBoardPage = createTsTemplateFile({ source: { path: path.join( import.meta.dirname, - '../templates/src/pages/bull-board/index.tsx', + '../templates/routes/bull-board/index.tsx', ), }, variables: {}, diff --git a/packages/react-generators/src/generators/admin/admin-bull-board/templates/src/pages/bull-board/bull-board.gql b/packages/react-generators/src/generators/admin/admin-bull-board/templates/routes/bull-board/bull-board.gql similarity index 100% rename from packages/react-generators/src/generators/admin/admin-bull-board/templates/src/pages/bull-board/bull-board.gql rename to packages/react-generators/src/generators/admin/admin-bull-board/templates/routes/bull-board/bull-board.gql diff --git a/packages/react-generators/src/generators/admin/admin-bull-board/templates/src/pages/bull-board/index.tsx b/packages/react-generators/src/generators/admin/admin-bull-board/templates/routes/bull-board/index.tsx similarity index 90% rename from packages/react-generators/src/generators/admin/admin-bull-board/templates/src/pages/bull-board/index.tsx rename to packages/react-generators/src/generators/admin/admin-bull-board/templates/routes/bull-board/index.tsx index d53f3b0a6..9cd405dc9 100644 --- a/packages/react-generators/src/generators/admin/admin-bull-board/templates/src/pages/bull-board/index.tsx +++ b/packages/react-generators/src/generators/admin/admin-bull-board/templates/routes/bull-board/index.tsx @@ -6,8 +6,13 @@ import { useCreateBullBoardAuthCodeMutation } from '%generatedGraphqlImports'; import { ErrorableLoader } from '%reactComponentsImports'; import { config } from '%reactConfigImports'; import { logAndFormatError } from '%reactErrorImports'; +import { createFileRoute } from '@tanstack/react-router'; import { useEffect, useState } from 'react'; +export const Route = createFileRoute('/bull-board/')({ + component: BullBoardPage, +}); + function BullBoardPage(): ReactElement { const [createBullBoardAuthCode] = useCreateBullBoardAuthCodeMutation(); const [error, setError] = useState(null); @@ -43,5 +48,3 @@ function BullBoardPage(): ReactElement { return ; } - -export default BullBoardPage; diff --git a/packages/react-generators/src/generators/admin/admin-crud-edit/admin-crud-edit.generator.ts b/packages/react-generators/src/generators/admin/admin-crud-edit/admin-crud-edit.generator.ts index d369680e3..c8501c947 100644 --- a/packages/react-generators/src/generators/admin/admin-crud-edit/admin-crud-edit.generator.ts +++ b/packages/react-generators/src/generators/admin/admin-crud-edit/admin-crud-edit.generator.ts @@ -11,8 +11,8 @@ import { createGeneratorTask, createProviderType, } from '@baseplate-dev/sync'; -import { notEmpty } from '@baseplate-dev/utils'; -import { sortBy } from 'es-toolkit'; +import { notEmpty, quot } from '@baseplate-dev/utils'; +import { kebabCase, sortBy } from 'es-toolkit'; import { dasherize, underscore } from 'inflection'; import { z } from 'zod'; @@ -20,7 +20,6 @@ import { reactComponentsImportsProvider } from '#src/generators/core/react-compo import { reactErrorImportsProvider } from '#src/generators/core/react-error/index.js'; import { reactRoutesProvider } from '#src/providers/routes.js'; import { lowerCaseFirst, titleizeCamel } from '#src/utils/case.js'; -import { createRouteElement } from '#src/utils/routes.js'; import { mergeGraphQLFields } from '#src/writers/graphql/index.js'; import type { AdminCrudInput } from '../_providers/admin-crud-input-container.js'; @@ -76,7 +75,8 @@ export const adminCrudEditGenerator = createGenerator({ reactComponentsImports, reactErrorImports, }) { - const editSchemaPath = `${reactRoutes.getDirectoryBase()}/edit/${lowerCaseFirst( + const routePrefix = reactRoutes.getRoutePrefix(); + const editSchemaPath = `${reactRoutes.getDirectoryBase()}/-schemas/${lowerCaseFirst( dasherize(underscore(modelName)), )}-schema.ts`; @@ -92,21 +92,17 @@ export const adminCrudEditGenerator = createGenerator({ editSchemaPath, ); - const editFormComponentPath = `${reactRoutes.getDirectoryBase()}/edit/${modelName}EditForm.tsx`; + const editFormComponentPath = `${reactRoutes.getDirectoryBase()}/-components/${kebabCase(modelName)}-edit-form.tsx`; const editFormComponentName = `${modelName}EditForm`; - const editFormComponentExpression = TsCodeUtils.defaultImportFragment( + const editFormComponentExpression = TsCodeUtils.importFragment( editFormComponentName, editFormComponentPath, ); - const editPagePath = `${reactRoutes.getDirectoryBase()}/edit/edit.page.tsx`; + const editPagePath = `${reactRoutes.getDirectoryBase()}/$id.tsx`; const editPageName = `${modelName}EditPage`; - reactRoutes.registerRoute({ - path: ':id/edit', - element: createRouteElement(editPageName, editPagePath), - }); - const createPagePath = `${reactRoutes.getDirectoryBase()}/edit/create.page.tsx`; + const createPagePath = `${reactRoutes.getDirectoryBase()}/new.tsx`; const createPageName = `${modelName}CreatePage`; const editQueryInfo = adminCrudQueries.getEditQueryHookInfo(); @@ -117,7 +113,7 @@ export const adminCrudEditGenerator = createGenerator({ return { providers: { adminCrudEdit: { - getDirectoryBase: () => `${reactRoutes.getDirectoryBase()}/edit`, + getDirectoryBase: () => reactRoutes.getDirectoryBase(), getSchemaPath: () => editSchemaPath, getSchemaImport: () => editSchemaPath, }, @@ -238,7 +234,11 @@ export const adminCrudEditGenerator = createGenerator({ template: ADMIN_ADMIN_CRUD_EDIT_GENERATED.templates.createPage, destination: createPagePath, + importMapProviders: { + reactErrorImports, + }, variables: { + TPL_ROUTE_VALUE: quot(`${routePrefix}/new`), TPL_COMPONENT_NAME: createPageName, TPL_EDIT_FORM: tsTemplate`<${editFormComponentExpression} submitData={submitData} ${inputLoaderExtraProps} />`, TPL_CREATE_MUTATION: createInfo.hookExpression, @@ -252,11 +252,6 @@ export const adminCrudEditGenerator = createGenerator({ }, }), ); - - reactRoutes.registerRoute({ - path: 'new', - element: createRouteElement(createPageName, createPagePath), - }); } const editPageLoader: DataLoader = { @@ -292,7 +287,11 @@ export const adminCrudEditGenerator = createGenerator({ id: `edit-${modelId}`, template: ADMIN_ADMIN_CRUD_EDIT_GENERATED.templates.editPage, destination: editPagePath, + importMapProviders: { + reactErrorImports, + }, variables: { + TPL_ROUTE_VALUE: quot(`${routePrefix}/$id`), TPL_COMPONENT_NAME: editPageName, TPL_EDIT_FORM: tsTemplate`<${editFormComponentExpression} submitData={submitData} initialData={initialData} ${inputLoaderExtraProps} />`, TPL_UPDATE_MUTATION: updateInfo.hookExpression, diff --git a/packages/react-generators/src/generators/admin/admin-crud-edit/extractor.json b/packages/react-generators/src/generators/admin/admin-crud-edit/extractor.json index 116694521..acbec2077 100644 --- a/packages/react-generators/src/generators/admin/admin-crud-edit/extractor.json +++ b/packages/react-generators/src/generators/admin/admin-crud-edit/extractor.json @@ -1,52 +1,38 @@ { "name": "admin/admin-crud-edit", "templates": { - "create.page.tsx": { + "create.tsx": { "name": "create-page", "type": "ts", "fileOptions": { - "generatorTemplatePath": "create.page.tsx", + "generatorTemplatePath": "create.tsx", "kind": "instance" }, "generator": "@baseplate-dev/react-generators#admin/admin-crud-edit", - "importMapProviders": {}, - "variables": { - "TPL_COMPONENT_NAME": {}, - "TPL_CREATE_MUTATION": {}, - "TPL_DATA_GATE": {}, - "TPL_DATA_LOADER": {}, - "TPL_EDIT_FORM": {}, - "TPL_FORM_DATA_NAME": {}, - "TPL_MODEL_NAME": {}, - "TPL_MUTATION_NAME": {}, - "TPL_REFETCH_DOCUMENT": {} - } - }, - "edit.page.tsx": { - "name": "edit-page", - "type": "ts", - "fileOptions": { - "generatorTemplatePath": "edit.page.tsx", - "kind": "instance" + "importMapProviders": { + "reactErrorImportsProvider": { + "importName": "reactErrorImportsProvider", + "packagePathSpecifier": "@baseplate-dev/react-generators:src/generators/core/react-error/generated/ts-import-providers.ts" + } }, - "generator": "@baseplate-dev/react-generators#admin/admin-crud-edit", - "importMapProviders": {}, "variables": { "TPL_COMPONENT_NAME": {}, + "TPL_CREATE_MUTATION": {}, "TPL_DATA_GATE": {}, "TPL_DATA_LOADER": {}, "TPL_EDIT_FORM": {}, "TPL_FORM_DATA_NAME": {}, "TPL_MODEL_NAME": {}, "TPL_MUTATION_NAME": {}, - "TPL_UPDATE_MUTATION": {} + "TPL_REFETCH_DOCUMENT": {}, + "TPL_ROUTE_VALUE": {} } }, - "EditForm.tsx": { + "edit-form.tsx": { "name": "edit-form", "type": "ts", "fileOptions": { - "generatorTemplatePath": "EditForm.tsx", + "generatorTemplatePath": "edit-form.tsx", "kind": "instance" }, "generator": "@baseplate-dev/react-generators#admin/admin-crud-edit", @@ -70,6 +56,32 @@ "TPL_INPUTS": {} } }, + "edit.tsx": { + "name": "edit-page", + "type": "ts", + "fileOptions": { + "generatorTemplatePath": "edit.tsx", + "kind": "instance" + }, + "generator": "@baseplate-dev/react-generators#admin/admin-crud-edit", + "importMapProviders": { + "reactErrorImportsProvider": { + "importName": "reactErrorImportsProvider", + "packagePathSpecifier": "@baseplate-dev/react-generators:src/generators/core/react-error/generated/ts-import-providers.ts" + } + }, + "variables": { + "TPL_COMPONENT_NAME": {}, + "TPL_DATA_GATE": {}, + "TPL_DATA_LOADER": {}, + "TPL_EDIT_FORM": {}, + "TPL_FORM_DATA_NAME": {}, + "TPL_MODEL_NAME": {}, + "TPL_MUTATION_NAME": {}, + "TPL_ROUTE_VALUE": {}, + "TPL_UPDATE_MUTATION": {} + } + }, "schema.ts": { "name": "schema", "type": "ts", diff --git a/packages/react-generators/src/generators/admin/admin-crud-edit/generated/template-renderers.ts b/packages/react-generators/src/generators/admin/admin-crud-edit/generated/template-renderers.ts index 36bd2f27b..cf39eaae6 100644 --- a/packages/react-generators/src/generators/admin/admin-crud-edit/generated/template-renderers.ts +++ b/packages/react-generators/src/generators/admin/admin-crud-edit/generated/template-renderers.ts @@ -74,6 +74,9 @@ const adminAdminCrudEditRenderersTask = createGeneratorTask({ render: (options) => typescriptFile.renderTemplateFile({ template: ADMIN_ADMIN_CRUD_EDIT_TEMPLATES.createPage, + importMapProviders: { + reactErrorImports, + }, ...options, }), }, @@ -92,6 +95,9 @@ const adminAdminCrudEditRenderersTask = createGeneratorTask({ render: (options) => typescriptFile.renderTemplateFile({ template: ADMIN_ADMIN_CRUD_EDIT_TEMPLATES.editPage, + importMapProviders: { + reactErrorImports, + }, ...options, }), }, diff --git a/packages/react-generators/src/generators/admin/admin-crud-edit/generated/typed-templates.ts b/packages/react-generators/src/generators/admin/admin-crud-edit/generated/typed-templates.ts index 82bb6d2ff..d434321d8 100644 --- a/packages/react-generators/src/generators/admin/admin-crud-edit/generated/typed-templates.ts +++ b/packages/react-generators/src/generators/admin/admin-crud-edit/generated/typed-templates.ts @@ -5,11 +5,11 @@ import { reactComponentsImportsProvider } from '#src/generators/core/react-compo import { reactErrorImportsProvider } from '#src/generators/core/react-error/generated/ts-import-providers.js'; const createPage = createTsTemplateFile({ - fileOptions: { generatorTemplatePath: 'create.page.tsx', kind: 'instance' }, - importMapProviders: {}, + fileOptions: { generatorTemplatePath: 'create.tsx', kind: 'instance' }, + importMapProviders: { reactErrorImports: reactErrorImportsProvider }, name: 'create-page', source: { - path: path.join(import.meta.dirname, '../templates/create.page.tsx'), + path: path.join(import.meta.dirname, '../templates/create.tsx'), }, variables: { TPL_COMPONENT_NAME: {}, @@ -21,18 +21,19 @@ const createPage = createTsTemplateFile({ TPL_MODEL_NAME: {}, TPL_MUTATION_NAME: {}, TPL_REFETCH_DOCUMENT: {}, + TPL_ROUTE_VALUE: {}, }, }); const editForm = createTsTemplateFile({ - fileOptions: { generatorTemplatePath: 'EditForm.tsx', kind: 'instance' }, + fileOptions: { generatorTemplatePath: 'edit-form.tsx', kind: 'instance' }, importMapProviders: { reactComponentsImports: reactComponentsImportsProvider, reactErrorImports: reactErrorImportsProvider, }, name: 'edit-form', source: { - path: path.join(import.meta.dirname, '../templates/EditForm.tsx'), + path: path.join(import.meta.dirname, '../templates/edit-form.tsx'), }, variables: { TPL_COMPONENT_NAME: {}, @@ -46,11 +47,11 @@ const editForm = createTsTemplateFile({ }); const editPage = createTsTemplateFile({ - fileOptions: { generatorTemplatePath: 'edit.page.tsx', kind: 'instance' }, - importMapProviders: {}, + fileOptions: { generatorTemplatePath: 'edit.tsx', kind: 'instance' }, + importMapProviders: { reactErrorImports: reactErrorImportsProvider }, name: 'edit-page', source: { - path: path.join(import.meta.dirname, '../templates/edit.page.tsx'), + path: path.join(import.meta.dirname, '../templates/edit.tsx'), }, variables: { TPL_COMPONENT_NAME: {}, @@ -60,6 +61,7 @@ const editPage = createTsTemplateFile({ TPL_FORM_DATA_NAME: {}, TPL_MODEL_NAME: {}, TPL_MUTATION_NAME: {}, + TPL_ROUTE_VALUE: {}, TPL_UPDATE_MUTATION: {}, }, }); @@ -80,7 +82,7 @@ const schema = createTsTemplateFile({ export const ADMIN_ADMIN_CRUD_EDIT_TEMPLATES = { createPage, - editPage, editForm, + editPage, schema, }; diff --git a/packages/react-generators/src/generators/admin/admin-crud-edit/templates/create.page.tsx b/packages/react-generators/src/generators/admin/admin-crud-edit/templates/create.tsx similarity index 75% rename from packages/react-generators/src/generators/admin/admin-crud-edit/templates/create.page.tsx rename to packages/react-generators/src/generators/admin/admin-crud-edit/templates/create.tsx index 4391517ed..5d91f5a79 100644 --- a/packages/react-generators/src/generators/admin/admin-crud-edit/templates/create.page.tsx +++ b/packages/react-generators/src/generators/admin/admin-crud-edit/templates/create.tsx @@ -2,9 +2,14 @@ import type { ReactElement } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { logError } from '%reactErrorImports'; +import { createFileRoute, useNavigate } from '@tanstack/react-router'; import { toast } from 'sonner'; +export const Route = createFileRoute(TPL_ROUTE_VALUE)({ + component: TPL_COMPONENT_NAME, +}); + function TPL_COMPONENT_NAME(): ReactElement { TPL_DATA_LOADER; @@ -23,7 +28,7 @@ function TPL_COMPONENT_NAME(): ReactElement { variables: { input: { data: formData } }, }); toast.success('Successfully created item!'); - navigate('..'); + navigate({ to: '..' }).catch(logError); }; TPL_DATA_GATE; @@ -39,5 +44,3 @@ function TPL_COMPONENT_NAME(): ReactElement { ); } - -export default TPL_COMPONENT_NAME; diff --git a/packages/react-generators/src/generators/admin/admin-crud-edit/templates/EditForm.tsx b/packages/react-generators/src/generators/admin/admin-crud-edit/templates/edit-form.tsx similarity index 92% rename from packages/react-generators/src/generators/admin/admin-crud-edit/templates/EditForm.tsx rename to packages/react-generators/src/generators/admin/admin-crud-edit/templates/edit-form.tsx index 91b53fe72..636e9b343 100644 --- a/packages/react-generators/src/generators/admin/admin-crud-edit/templates/EditForm.tsx +++ b/packages/react-generators/src/generators/admin/admin-crud-edit/templates/edit-form.tsx @@ -15,7 +15,9 @@ interface Props { TPL_EXTRA_PROPS; } -function TPL_COMPONENT_NAME(TPL_DESTRUCTURED_PROPS: Props): ReactElement { +export function TPL_COMPONENT_NAME( + TPL_DESTRUCTURED_PROPS: Props, +): ReactElement { const { handleSubmit, control } = useForm({ resolver: zodResolver(TPL_EDIT_SCHEMA), defaultValues: initialData, @@ -48,5 +50,3 @@ function TPL_COMPONENT_NAME(TPL_DESTRUCTURED_PROPS: Props): ReactElement { ); } - -export default TPL_COMPONENT_NAME; diff --git a/packages/react-generators/src/generators/admin/admin-crud-edit/templates/edit.page.tsx b/packages/react-generators/src/generators/admin/admin-crud-edit/templates/edit.tsx similarity index 70% rename from packages/react-generators/src/generators/admin/admin-crud-edit/templates/edit.page.tsx rename to packages/react-generators/src/generators/admin/admin-crud-edit/templates/edit.tsx index 6b487751f..453d51847 100644 --- a/packages/react-generators/src/generators/admin/admin-crud-edit/templates/edit.page.tsx +++ b/packages/react-generators/src/generators/admin/admin-crud-edit/templates/edit.tsx @@ -2,11 +2,16 @@ import type { ReactElement } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; +import { logError } from '%reactErrorImports'; +import { createFileRoute, useNavigate } from '@tanstack/react-router'; import { toast } from 'sonner'; +export const Route = createFileRoute(TPL_ROUTE_VALUE)({ + component: TPL_COMPONENT_NAME, +}); + function TPL_COMPONENT_NAME(): ReactElement { - const { id } = useParams() as { id: string }; + const { id } = Route.useParams(); TPL_DATA_LOADER; @@ -20,7 +25,7 @@ function TPL_COMPONENT_NAME(): ReactElement { variables: { input: { id, data: formData } }, }); toast.success('Successfully updated item!'); - navigate('..'); + navigate({ to: '..' }).catch(logError); }; return ( @@ -34,5 +39,3 @@ function TPL_COMPONENT_NAME(): ReactElement { ); } - -export default TPL_COMPONENT_NAME; diff --git a/packages/react-generators/src/generators/admin/admin-crud-embedded-form/admin-crud-embedded-form.generator.ts b/packages/react-generators/src/generators/admin/admin-crud-embedded-form/admin-crud-embedded-form.generator.ts index b8db3b500..f3515ff3c 100644 --- a/packages/react-generators/src/generators/admin/admin-crud-embedded-form/admin-crud-embedded-form.generator.ts +++ b/packages/react-generators/src/generators/admin/admin-crud-embedded-form/admin-crud-embedded-form.generator.ts @@ -16,7 +16,7 @@ import { } from '@baseplate-dev/sync'; import { notEmpty } from '@baseplate-dev/utils'; import { posixJoin } from '@baseplate-dev/utils/node'; -import { sortBy } from 'es-toolkit'; +import { kebabCase, sortBy } from 'es-toolkit'; import { z } from 'zod'; import type { GraphQLField } from '#src/writers/graphql/index.js'; @@ -223,7 +223,7 @@ export const adminCrudEmbeddedFormGenerator = createGenerator({ const formPath = posixJoin( adminCrudEdit.getDirectoryBase(), - `${formName}.tsx`, + `-components/${kebabCase(formName)}.tsx`, ); const inputDataDependencies = inputFields.flatMap( diff --git a/packages/react-generators/src/generators/admin/admin-crud-list/admin-crud-list.generator.ts b/packages/react-generators/src/generators/admin/admin-crud-list/admin-crud-list.generator.ts index 242132864..88c2aff5c 100644 --- a/packages/react-generators/src/generators/admin/admin-crud-list/admin-crud-list.generator.ts +++ b/packages/react-generators/src/generators/admin/admin-crud-list/admin-crud-list.generator.ts @@ -9,7 +9,8 @@ import { typescriptFileProvider, } from '@baseplate-dev/core-generators'; import { createGenerator, createGeneratorTask } from '@baseplate-dev/sync'; -import { notEmpty } from '@baseplate-dev/utils'; +import { notEmpty, quot } from '@baseplate-dev/utils'; +import { kebabCase } from 'es-toolkit'; import { pluralize } from 'inflection'; import { z } from 'zod'; @@ -17,7 +18,6 @@ import { reactComponentsImportsProvider } from '#src/generators/core/react-compo import { reactErrorImportsProvider } from '#src/generators/core/react-error/index.js'; import { reactRoutesProvider } from '#src/providers/routes.js'; import { titleizeCamel } from '#src/utils/case.js'; -import { createRouteElement } from '#src/utils/routes.js'; import { mergeGraphQLFields } from '#src/writers/graphql/index.js'; import type { AdminCrudColumn } from '../_providers/admin-crud-column-container.js'; @@ -59,9 +59,10 @@ export const adminCrudListGenerator = createGenerator({ reactComponentsImports, reactErrorImports, }) { + const routePrefix = reactRoutes.getRoutePrefix(); const columns: AdminCrudColumn[] = []; - const listPagePath = `${reactRoutes.getDirectoryBase()}/list/index.page.tsx`; - const tableComponentPath = `${reactRoutes.getDirectoryBase()}/list/${modelName}Table.tsx`; + const listPagePath = `${reactRoutes.getDirectoryBase()}/index.tsx`; + const tableComponentPath = `${reactRoutes.getDirectoryBase()}/-components/${kebabCase(modelName)}-table.tsx`; const tableComponentName = `${modelName}Table`; const listInfo = adminCrudQueries.getListQueryHookInfo(); @@ -128,6 +129,7 @@ export const adminCrudListGenerator = createGenerator({ template: ADMIN_ADMIN_CRUD_LIST_GENERATED.templates.listPage, destination: listPagePath, variables: { + TPL_ROUTE_VALUE: quot(`${routePrefix}/`), TPL_PAGE_NAME: listPageComponentName, TPL_DELETE_FUNCTION: deleteInfo.fieldName, TPL_DELETE_MUTATION: deleteInfo.hookExpression, @@ -136,8 +138,7 @@ export const adminCrudListGenerator = createGenerator({ TPL_PLURAL_MODEL: titleizeCamel(pluralize(modelName)), TPL_TABLE_COMPONENT: tsCodeFragment( `<${tableComponentName} deleteItem={handleDeleteItem} items={data.${listInfo.fieldName}} ${tableLoaderExtraProps} />`, - TsCodeUtils.defaultImport( - tableComponentName, + TsCodeUtils.importBuilder([tableComponentName]).from( tableComponentPath, ), ), @@ -148,12 +149,14 @@ export const adminCrudListGenerator = createGenerator({ : tsCodeFragment( `
- +
`, [ - tsImportBuilder(['Link']).from('react-router-dom'), + tsImportBuilder(['Link']).from( + '@tanstack/react-router', + ), reactComponentsImports.Button.declaration(), reactComponentsImports.ErrorableLoader.declaration(), ], @@ -168,11 +171,6 @@ export const adminCrudListGenerator = createGenerator({ }), ); - reactRoutes.registerRoute({ - index: true, - element: createRouteElement(listPageComponentName, listPagePath), - }); - const headers = sortedColumns.map( (column) => tsTemplateWithImports( @@ -212,6 +210,7 @@ export const adminCrudListGenerator = createGenerator({ deleteItem, ${dataDependencies.map((d) => d.propName).join(',\n')} }`, + TPL_EDIT_ROUTE: quot(`${routePrefix}/$id`), }, importMapProviders: { reactComponentsImports, diff --git a/packages/react-generators/src/generators/admin/admin-crud-list/extractor.json b/packages/react-generators/src/generators/admin/admin-crud-list/extractor.json index 7fa5fa563..b503385d9 100644 --- a/packages/react-generators/src/generators/admin/admin-crud-list/extractor.json +++ b/packages/react-generators/src/generators/admin/admin-crud-list/extractor.json @@ -1,11 +1,11 @@ { "name": "admin/admin-crud-list", "templates": { - "index.page.tsx": { + "index.tsx": { "name": "list-page", "type": "ts", "fileOptions": { - "generatorTemplatePath": "index.page.tsx", + "generatorTemplatePath": "index.tsx", "kind": "instance" }, "generator": "@baseplate-dev/react-generators#admin/admin-crud-list", @@ -25,6 +25,7 @@ "TPL_PAGE_NAME": {}, "TPL_PLURAL_MODEL": {}, "TPL_REFETCH_DOCUMENT": {}, + "TPL_ROUTE_VALUE": {}, "TPL_ROW_FRAGMENT_NAME": {}, "TPL_TABLE_COMPONENT": {} } @@ -51,6 +52,7 @@ "TPL_CELLS": {}, "TPL_COMPONENT_NAME": {}, "TPL_DESTRUCTURED_PROPS": {}, + "TPL_EDIT_ROUTE": {}, "TPL_EXTRA_PROPS": {}, "TPL_HEADERS": {}, "TPL_PLURAL_MODEL": {}, diff --git a/packages/react-generators/src/generators/admin/admin-crud-list/generated/typed-templates.ts b/packages/react-generators/src/generators/admin/admin-crud-list/generated/typed-templates.ts index 4a763b9e1..e664437c4 100644 --- a/packages/react-generators/src/generators/admin/admin-crud-list/generated/typed-templates.ts +++ b/packages/react-generators/src/generators/admin/admin-crud-list/generated/typed-templates.ts @@ -5,13 +5,13 @@ import { reactComponentsImportsProvider } from '#src/generators/core/react-compo import { reactErrorImportsProvider } from '#src/generators/core/react-error/generated/ts-import-providers.js'; const listPage = createTsTemplateFile({ - fileOptions: { generatorTemplatePath: 'index.page.tsx', kind: 'instance' }, + fileOptions: { generatorTemplatePath: 'index.tsx', kind: 'instance' }, importMapProviders: { reactComponentsImports: reactComponentsImportsProvider, }, name: 'list-page', source: { - path: path.join(import.meta.dirname, '../templates/index.page.tsx'), + path: path.join(import.meta.dirname, '../templates/index.tsx'), }, variables: { TPL_CREATE_BUTTON: {}, @@ -23,6 +23,7 @@ const listPage = createTsTemplateFile({ TPL_PAGE_NAME: {}, TPL_PLURAL_MODEL: {}, TPL_REFETCH_DOCUMENT: {}, + TPL_ROUTE_VALUE: {}, TPL_ROW_FRAGMENT_NAME: {}, TPL_TABLE_COMPONENT: {}, }, @@ -42,6 +43,7 @@ const table = createTsTemplateFile({ TPL_CELLS: {}, TPL_COMPONENT_NAME: {}, TPL_DESTRUCTURED_PROPS: {}, + TPL_EDIT_ROUTE: {}, TPL_EXTRA_PROPS: {}, TPL_HEADERS: {}, TPL_PLURAL_MODEL: {}, diff --git a/packages/react-generators/src/generators/admin/admin-crud-list/templates/Table.tsx b/packages/react-generators/src/generators/admin/admin-crud-list/templates/Table.tsx index c3e84a09b..8644df318 100644 --- a/packages/react-generators/src/generators/admin/admin-crud-list/templates/Table.tsx +++ b/packages/react-generators/src/generators/admin/admin-crud-list/templates/Table.tsx @@ -13,7 +13,7 @@ import { useConfirmDialog, } from '%reactComponentsImports'; import { logAndFormatError } from '%reactErrorImports'; -import { Link } from 'react-router-dom'; +import { Link } from '@tanstack/react-router'; import { toast } from 'sonner'; interface Props { @@ -22,7 +22,9 @@ interface Props { TPL_EXTRA_PROPS; } -function TPL_COMPONENT_NAME(TPL_DESTRUCTURED_PROPS: Props): ReactElement { +export function TPL_COMPONENT_NAME( + TPL_DESTRUCTURED_PROPS: Props, +): ReactElement { const { requestConfirm } = useConfirmDialog(); function handleDelete(item: TPL_ROW_FRAGMENT): void { requestConfirm({ @@ -63,7 +65,7 @@ function TPL_COMPONENT_NAME(TPL_DESTRUCTURED_PROPS: Props): ReactElement { - + @@ -84,5 +86,3 @@ function TPL_COMPONENT_NAME(TPL_DESTRUCTURED_PROPS: Props): ReactElement { ); } - -export default TPL_COMPONENT_NAME; diff --git a/packages/react-generators/src/generators/admin/admin-crud-list/templates/index.page.tsx b/packages/react-generators/src/generators/admin/admin-crud-list/templates/index.tsx similarity index 84% rename from packages/react-generators/src/generators/admin/admin-crud-list/templates/index.page.tsx rename to packages/react-generators/src/generators/admin/admin-crud-list/templates/index.tsx index efdce58d9..20c190aac 100644 --- a/packages/react-generators/src/generators/admin/admin-crud-list/templates/index.page.tsx +++ b/packages/react-generators/src/generators/admin/admin-crud-list/templates/index.tsx @@ -3,6 +3,11 @@ import type { ReactElement } from 'react'; import { ErrorableLoader } from '%reactComponentsImports'; +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute(TPL_ROUTE_VALUE)({ + component: TPL_PAGE_NAME, +}); function TPL_PAGE_NAME(): ReactElement { TPL_DATA_LOADER; @@ -36,5 +41,3 @@ function TPL_PAGE_NAME(): ReactElement { ); } - -export default TPL_PAGE_NAME; diff --git a/packages/react-generators/src/generators/admin/admin-home/admin-home.generator.ts b/packages/react-generators/src/generators/admin/admin-home/admin-home.generator.ts index 44f31a035..aab896fcc 100644 --- a/packages/react-generators/src/generators/admin/admin-home/admin-home.generator.ts +++ b/packages/react-generators/src/generators/admin/admin-home/admin-home.generator.ts @@ -4,8 +4,6 @@ import { z } from 'zod'; import { authHooksImportsProvider } from '#src/generators/auth/_providers/auth-hooks.js'; import { reactComponentsImportsProvider } from '#src/generators/core/react-components/index.js'; -import { reactRoutesProvider } from '#src/providers/routes.js'; -import { createRouteElement } from '#src/utils/routes.js'; import { ADMIN_ADMIN_HOME_GENERATED } from './generated/index.js'; @@ -22,22 +20,9 @@ export const adminHomeGenerator = createGenerator({ reactComponentsImports: reactComponentsImportsProvider, authHooksImports: authHooksImportsProvider, typescriptFile: typescriptFileProvider, - reactRoutes: reactRoutesProvider, paths: ADMIN_ADMIN_HOME_GENERATED.paths.provider, }, - run({ - authHooksImports, - reactComponentsImports, - reactRoutes, - typescriptFile, - paths, - }) { - reactRoutes.registerRoute({ - index: true, - element: createRouteElement('Home', paths.home), - layoutKey: 'admin', - }); - + run({ authHooksImports, reactComponentsImports, typescriptFile, paths }) { return { build: async (builder) => { await builder.apply( diff --git a/packages/react-generators/src/generators/admin/admin-home/extractor.json b/packages/react-generators/src/generators/admin/admin-home/extractor.json index 68e08b8ae..381187ca5 100644 --- a/packages/react-generators/src/generators/admin/admin-home/extractor.json +++ b/packages/react-generators/src/generators/admin/admin-home/extractor.json @@ -1,7 +1,7 @@ { "name": "admin/admin-home", "templates": { - "src/pages/Home/index.tsx": { + "routes/index.tsx": { "name": "home", "type": "ts", "fileOptions": { "kind": "singleton" }, @@ -16,7 +16,7 @@ "packagePathSpecifier": "@baseplate-dev/react-generators:src/generators/core/react-components/generated/ts-import-providers.ts" } }, - "pathRootRelativePath": "{src-root}/pages/Home/index.tsx", + "pathRootRelativePath": "{routes-root}/index.tsx", "variables": {} } } diff --git a/packages/react-generators/src/generators/admin/admin-home/generated/template-paths.ts b/packages/react-generators/src/generators/admin/admin-home/generated/template-paths.ts index 2f730604c..03cce96ba 100644 --- a/packages/react-generators/src/generators/admin/admin-home/generated/template-paths.ts +++ b/packages/react-generators/src/generators/admin/admin-home/generated/template-paths.ts @@ -1,6 +1,7 @@ -import { packageInfoProvider } from '@baseplate-dev/core-generators'; import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; +import { reactRoutesProvider } from '#src/providers/routes.js'; + export interface AdminAdminHomePaths { home: string; } @@ -10,14 +11,14 @@ const adminAdminHomePaths = createProviderType( ); const adminAdminHomePathsTask = createGeneratorTask({ - dependencies: { packageInfo: packageInfoProvider }, + dependencies: { reactRoutes: reactRoutesProvider }, exports: { adminAdminHomePaths: adminAdminHomePaths.export() }, - run({ packageInfo }) { - const srcRoot = packageInfo.getPackageSrcPath(); + run({ reactRoutes }) { + const routesRoot = reactRoutes.getDirectoryBase(); return { providers: { - adminAdminHomePaths: { home: `${srcRoot}/pages/Home/index.tsx` }, + adminAdminHomePaths: { home: `${routesRoot}/index.tsx` }, }, }; }, diff --git a/packages/react-generators/src/generators/admin/admin-home/generated/typed-templates.ts b/packages/react-generators/src/generators/admin/admin-home/generated/typed-templates.ts index a70af46c6..82efc79db 100644 --- a/packages/react-generators/src/generators/admin/admin-home/generated/typed-templates.ts +++ b/packages/react-generators/src/generators/admin/admin-home/generated/typed-templates.ts @@ -12,10 +12,7 @@ const home = createTsTemplateFile({ }, name: 'home', source: { - path: path.join( - import.meta.dirname, - '../templates/src/pages/Home/index.tsx', - ), + path: path.join(import.meta.dirname, '../templates/routes/index.tsx'), }, variables: {}, }); diff --git a/packages/react-generators/src/generators/admin/admin-home/templates/src/pages/Home/index.tsx b/packages/react-generators/src/generators/admin/admin-home/templates/routes/index.tsx similarity index 77% rename from packages/react-generators/src/generators/admin/admin-home/templates/src/pages/Home/index.tsx rename to packages/react-generators/src/generators/admin/admin-home/templates/routes/index.tsx index bc81a4e68..8ae29a801 100644 --- a/packages/react-generators/src/generators/admin/admin-home/templates/src/pages/Home/index.tsx +++ b/packages/react-generators/src/generators/admin/admin-home/templates/routes/index.tsx @@ -4,6 +4,11 @@ import type { ReactElement } from 'react'; import { useCurrentUser } from '%authHooksImports'; import { ErrorableLoader } from '%reactComponentsImports'; +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/')({ + component: HomePage, +}); function HomePage(): ReactElement { const { user, error } = useCurrentUser(); @@ -19,5 +24,3 @@ function HomePage(): ReactElement { ); } - -export default HomePage; diff --git a/packages/react-generators/src/generators/admin/admin-layout/admin-layout.generator.ts b/packages/react-generators/src/generators/admin/admin-layout/admin-layout.generator.ts index fa5693784..ce6b31aa8 100644 --- a/packages/react-generators/src/generators/admin/admin-layout/admin-layout.generator.ts +++ b/packages/react-generators/src/generators/admin/admin-layout/admin-layout.generator.ts @@ -1,16 +1,8 @@ -import { - tsCodeFragment, - TsCodeUtils, - tsImportBuilder, - typescriptFileProvider, -} from '@baseplate-dev/core-generators'; +import { TsCodeUtils, tsImportBuilder } from '@baseplate-dev/core-generators'; import { createGenerator, createGeneratorTask } from '@baseplate-dev/sync'; import { z } from 'zod'; -import { authComponentsImportsProvider } from '#src/generators/auth/_providers/auth-components.js'; -import { authHooksImportsProvider } from '#src/generators/auth/_providers/auth-hooks.js'; import { reactComponentsImportsProvider } from '#src/generators/core/react-components/index.js'; -import { reactRoutesProvider } from '#src/providers/routes.js'; import { ADMIN_ADMIN_LAYOUT_GENERATED } from './generated/index.js'; @@ -43,34 +35,25 @@ export const adminLayoutGenerator = createGenerator({ descriptorSchema, buildTasks: ({ links = [] }) => ({ paths: ADMIN_ADMIN_LAYOUT_GENERATED.paths.task, - main: createGeneratorTask({ + renderers: ADMIN_ADMIN_LAYOUT_GENERATED.renderers.task, + route: createGeneratorTask({ + dependencies: { + renderers: ADMIN_ADMIN_LAYOUT_GENERATED.renderers.provider, + }, + run({ renderers }) { + return { + build: async (builder) => { + await builder.apply(renderers.adminRoute.render({})); + }, + }; + }, + }), + layout: createGeneratorTask({ dependencies: { reactComponentsImports: reactComponentsImportsProvider, - reactRoutes: reactRoutesProvider, - authComponentsImports: authComponentsImportsProvider, - typescriptFile: typescriptFileProvider, - authHooksImports: authHooksImportsProvider, - paths: ADMIN_ADMIN_LAYOUT_GENERATED.paths.provider, + renderers: ADMIN_ADMIN_LAYOUT_GENERATED.renderers.provider, }, - run({ - reactComponentsImports, - reactRoutes, - authComponentsImports, - typescriptFile, - authHooksImports, - paths, - }) { - reactRoutes.registerLayout({ - key: 'admin', - element: tsCodeFragment( - ``, - [ - tsImportBuilder(['AdminLayout']).from(paths.adminLayout), - authComponentsImports.RequireAuth.declaration(), - ], - ), - }); - + run({ reactComponentsImports, renderers }) { return { build: async (builder) => { const navEntries = Object.fromEntries( @@ -78,29 +61,23 @@ export const adminLayoutGenerator = createGenerator({ link.path, TsCodeUtils.templateWithImports([ reactComponentsImports.NavigationMenuItemWithLink.declaration(), - tsImportBuilder(['NavLink']).from('react-router-dom'), + tsImportBuilder(['Link']).from('@tanstack/react-router'), ])` - + <${TsCodeUtils.importFragment(link.icon, getIconImport(link.icon))} /> ${link.label} - + `, ]), ); await builder.apply( - typescriptFile.renderTemplateFile({ - template: ADMIN_ADMIN_LAYOUT_GENERATED.templates.adminLayout, - destination: paths.adminLayout, + renderers.adminLayout.render({ variables: { TPL_SIDEBAR_LINKS: TsCodeUtils.mergeFragments(navEntries), }, - importMapProviders: { - authHooksImports, - reactComponentsImports, - }, }), ); }, diff --git a/packages/react-generators/src/generators/admin/admin-layout/extractor.json b/packages/react-generators/src/generators/admin/admin-layout/extractor.json index 936300d2f..9e79f013d 100644 --- a/packages/react-generators/src/generators/admin/admin-layout/extractor.json +++ b/packages/react-generators/src/generators/admin/admin-layout/extractor.json @@ -1,6 +1,21 @@ { "name": "admin/admin-layout", "templates": { + "routes/route.tsx": { + "name": "admin-route", + "type": "ts", + "fileOptions": { "kind": "singleton" }, + "generator": "@baseplate-dev/react-generators#admin/admin-layout", + "importMapProviders": { + "authComponentsImportsProvider": { + "importName": "authComponentsImportsProvider", + "packagePathSpecifier": "@baseplate-dev/react-generators:src/generators/auth/_providers/auth-components.ts" + } + }, + "pathRootRelativePath": "{routes-root}/route.tsx", + "projectExports": { "Route": {} }, + "variables": {} + }, "src/components/admin-layout/admin-layout.tsx": { "name": "admin-layout", "type": "ts", diff --git a/packages/react-generators/src/generators/admin/admin-layout/generated/index.ts b/packages/react-generators/src/generators/admin/admin-layout/generated/index.ts index 9753dad47..7624423eb 100644 --- a/packages/react-generators/src/generators/admin/admin-layout/generated/index.ts +++ b/packages/react-generators/src/generators/admin/admin-layout/generated/index.ts @@ -1,8 +1,10 @@ import { ADMIN_ADMIN_LAYOUT_PATHS } from './template-paths.js'; import { ADMIN_ADMIN_LAYOUT_RENDERERS } from './template-renderers.js'; +import { ADMIN_ADMIN_LAYOUT_IMPORTS } from './ts-import-providers.js'; import { ADMIN_ADMIN_LAYOUT_TEMPLATES } from './typed-templates.js'; export const ADMIN_ADMIN_LAYOUT_GENERATED = { + imports: ADMIN_ADMIN_LAYOUT_IMPORTS, paths: ADMIN_ADMIN_LAYOUT_PATHS, renderers: ADMIN_ADMIN_LAYOUT_RENDERERS, templates: ADMIN_ADMIN_LAYOUT_TEMPLATES, diff --git a/packages/react-generators/src/generators/admin/admin-layout/generated/template-paths.ts b/packages/react-generators/src/generators/admin/admin-layout/generated/template-paths.ts index 86156d604..26cf187ec 100644 --- a/packages/react-generators/src/generators/admin/admin-layout/generated/template-paths.ts +++ b/packages/react-generators/src/generators/admin/admin-layout/generated/template-paths.ts @@ -1,7 +1,10 @@ import { packageInfoProvider } from '@baseplate-dev/core-generators'; import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; +import { reactRoutesProvider } from '#src/providers/routes.js'; + export interface AdminAdminLayoutPaths { + adminRoute: string; adminLayout: string; } @@ -10,15 +13,20 @@ const adminAdminLayoutPaths = createProviderType( ); const adminAdminLayoutPathsTask = createGeneratorTask({ - dependencies: { packageInfo: packageInfoProvider }, + dependencies: { + packageInfo: packageInfoProvider, + reactRoutes: reactRoutesProvider, + }, exports: { adminAdminLayoutPaths: adminAdminLayoutPaths.export() }, - run({ packageInfo }) { + run({ packageInfo, reactRoutes }) { + const routesRoot = reactRoutes.getDirectoryBase(); const srcRoot = packageInfo.getPackageSrcPath(); return { providers: { adminAdminLayoutPaths: { adminLayout: `${srcRoot}/components/admin-layout/admin-layout.tsx`, + adminRoute: `${routesRoot}/route.tsx`, }, }, }; diff --git a/packages/react-generators/src/generators/admin/admin-layout/generated/template-renderers.ts b/packages/react-generators/src/generators/admin/admin-layout/generated/template-renderers.ts index 827f5e3d2..fe9f50f52 100644 --- a/packages/react-generators/src/generators/admin/admin-layout/generated/template-renderers.ts +++ b/packages/react-generators/src/generators/admin/admin-layout/generated/template-renderers.ts @@ -4,6 +4,7 @@ import type { BuilderAction } from '@baseplate-dev/sync'; import { typescriptFileProvider } from '@baseplate-dev/core-generators'; import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; +import { authComponentsImportsProvider } from '#src/generators/auth/_providers/auth-components.js'; import { authHooksImportsProvider } from '#src/generators/auth/_providers/auth-hooks.js'; import { reactComponentsImportsProvider } from '#src/generators/core/react-components/generated/ts-import-providers.js'; @@ -21,6 +22,16 @@ export interface AdminAdminLayoutRenderers { >, ) => BuilderAction; }; + adminRoute: { + render: ( + options: Omit< + RenderTsTemplateFileActionInput< + typeof ADMIN_ADMIN_LAYOUT_TEMPLATES.adminRoute + >, + 'destination' | 'importMapProviders' | 'template' + >, + ) => BuilderAction; + }; } const adminAdminLayoutRenderers = createProviderType( @@ -29,13 +40,20 @@ const adminAdminLayoutRenderers = createProviderType( const adminAdminLayoutRenderersTask = createGeneratorTask({ dependencies: { + authComponentsImports: authComponentsImportsProvider, authHooksImports: authHooksImportsProvider, paths: ADMIN_ADMIN_LAYOUT_PATHS.provider, reactComponentsImports: reactComponentsImportsProvider, typescriptFile: typescriptFileProvider, }, exports: { adminAdminLayoutRenderers: adminAdminLayoutRenderers.export() }, - run({ authHooksImports, paths, reactComponentsImports, typescriptFile }) { + run({ + authComponentsImports, + authHooksImports, + paths, + reactComponentsImports, + typescriptFile, + }) { return { providers: { adminAdminLayoutRenderers: { @@ -51,6 +69,17 @@ const adminAdminLayoutRenderersTask = createGeneratorTask({ ...options, }), }, + adminRoute: { + render: (options) => + typescriptFile.renderTemplateFile({ + template: ADMIN_ADMIN_LAYOUT_TEMPLATES.adminRoute, + destination: paths.adminRoute, + importMapProviders: { + authComponentsImports, + }, + ...options, + }), + }, }, }, }; diff --git a/packages/react-generators/src/generators/admin/admin-layout/generated/ts-import-providers.ts b/packages/react-generators/src/generators/admin/admin-layout/generated/ts-import-providers.ts new file mode 100644 index 000000000..ed57a2fc3 --- /dev/null +++ b/packages/react-generators/src/generators/admin/admin-layout/generated/ts-import-providers.ts @@ -0,0 +1,46 @@ +import type { TsImportMapProviderFromSchema } from '@baseplate-dev/core-generators'; + +import { + createTsImportMap, + createTsImportMapSchema, + packageScope, +} from '@baseplate-dev/core-generators'; +import { + createGeneratorTask, + createReadOnlyProviderType, +} from '@baseplate-dev/sync'; + +import { ADMIN_ADMIN_LAYOUT_PATHS } from './template-paths.js'; + +const adminLayoutImportsSchema = createTsImportMapSchema({ Route: {} }); + +export type AdminLayoutImportsProvider = TsImportMapProviderFromSchema< + typeof adminLayoutImportsSchema +>; + +export const adminLayoutImportsProvider = + createReadOnlyProviderType( + 'admin-layout-imports', + ); + +const adminAdminLayoutImportsTask = createGeneratorTask({ + dependencies: { + paths: ADMIN_ADMIN_LAYOUT_PATHS.provider, + }, + exports: { + adminLayoutImports: adminLayoutImportsProvider.export(packageScope), + }, + run({ paths }) { + return { + providers: { + adminLayoutImports: createTsImportMap(adminLayoutImportsSchema, { + Route: paths.adminRoute, + }), + }, + }; + }, +}); + +export const ADMIN_ADMIN_LAYOUT_IMPORTS = { + task: adminAdminLayoutImportsTask, +}; diff --git a/packages/react-generators/src/generators/admin/admin-layout/generated/typed-templates.ts b/packages/react-generators/src/generators/admin/admin-layout/generated/typed-templates.ts index e2302764e..ac63cfcbc 100644 --- a/packages/react-generators/src/generators/admin/admin-layout/generated/typed-templates.ts +++ b/packages/react-generators/src/generators/admin/admin-layout/generated/typed-templates.ts @@ -1,6 +1,7 @@ import { createTsTemplateFile } from '@baseplate-dev/core-generators'; import path from 'node:path'; +import { authComponentsImportsProvider } from '#src/generators/auth/_providers/auth-components.js'; import { authHooksImportsProvider } from '#src/generators/auth/_providers/auth-hooks.js'; import { reactComponentsImportsProvider } from '#src/generators/core/react-components/generated/ts-import-providers.js'; @@ -20,4 +21,15 @@ const adminLayout = createTsTemplateFile({ variables: { TPL_SIDEBAR_LINKS: {} }, }); -export const ADMIN_ADMIN_LAYOUT_TEMPLATES = { adminLayout }; +const adminRoute = createTsTemplateFile({ + fileOptions: { kind: 'singleton' }, + importMapProviders: { authComponentsImports: authComponentsImportsProvider }, + name: 'admin-route', + projectExports: { Route: {} }, + source: { + path: path.join(import.meta.dirname, '../templates/routes/route.tsx'), + }, + variables: {}, +}); + +export const ADMIN_ADMIN_LAYOUT_TEMPLATES = { adminRoute, adminLayout }; diff --git a/packages/react-generators/src/generators/admin/admin-layout/index.ts b/packages/react-generators/src/generators/admin/admin-layout/index.ts index f6bfe2ad0..12e223293 100644 --- a/packages/react-generators/src/generators/admin/admin-layout/index.ts +++ b/packages/react-generators/src/generators/admin/admin-layout/index.ts @@ -1 +1,3 @@ export * from './admin-layout.generator.js'; +export type { AdminLayoutImportsProvider } from './generated/ts-import-providers.js'; +export { adminLayoutImportsProvider } from './generated/ts-import-providers.js'; diff --git a/packages/react-generators/src/generators/admin/admin-layout/templates/routes/route.tsx b/packages/react-generators/src/generators/admin/admin-layout/templates/routes/route.tsx new file mode 100644 index 000000000..5c4dd3d1c --- /dev/null +++ b/packages/react-generators/src/generators/admin/admin-layout/templates/routes/route.tsx @@ -0,0 +1,20 @@ +// @ts-nocheck + +import type React from 'react'; + +import { RequireAuth } from '%authComponentsImports'; +import { createFileRoute } from '@tanstack/react-router'; + +import { AdminLayout } from '../../components/admin-layout/admin-layout.js'; + +export const Route = createFileRoute('/_admin')({ + component: AuthenticatedAdminLayout, +}); + +function AuthenticatedAdminLayout(): React.ReactElement { + return ( + + + + ); +} diff --git a/packages/react-generators/src/generators/admin/admin-layout/templates/src/components/admin-layout/admin-layout.tsx b/packages/react-generators/src/generators/admin/admin-layout/templates/src/components/admin-layout/admin-layout.tsx index 384efa487..f16654ad6 100644 --- a/packages/react-generators/src/generators/admin/admin-layout/templates/src/components/admin-layout/admin-layout.tsx +++ b/packages/react-generators/src/generators/admin/admin-layout/templates/src/components/admin-layout/admin-layout.tsx @@ -11,7 +11,7 @@ import { SidebarLayoutContent, SidebarLayoutSidebar, } from '%reactComponentsImports'; -import { Outlet } from 'react-router-dom'; +import { Outlet } from '@tanstack/react-router'; interface Props { className?: string; diff --git a/packages/react-generators/src/generators/core/_utils/index.ts b/packages/react-generators/src/generators/core/_utils/index.ts deleted file mode 100644 index 2904ea930..000000000 --- a/packages/react-generators/src/generators/core/_utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './render-routes.js'; diff --git a/packages/react-generators/src/generators/core/_utils/render-routes.ts b/packages/react-generators/src/generators/core/_utils/render-routes.ts deleted file mode 100644 index 7ceb69e6f..000000000 --- a/packages/react-generators/src/generators/core/_utils/render-routes.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type { TsCodeFragment } from '@baseplate-dev/core-generators'; - -import { TsCodeUtils, tsImportBuilder } from '@baseplate-dev/core-generators'; -import { groupBy, sortBy } from 'es-toolkit'; - -import type { ReactRoute, ReactRouteLayout } from '#src/providers/routes.js'; - -export function renderRoutes( - routes: ReactRoute[], - layouts: ReactRouteLayout[], -): TsCodeFragment { - // group routes by layout key - const routesByLayoutKey = groupBy(routes, (route) => route.layoutKey ?? ''); - - // Sort layout keys to ensure empty string (no layout) is at the bottom - const sortedLayoutKeys = Object.keys(routesByLayoutKey).sort((a, b) => { - if (a === '') return 1; - if (b === '') return -1; - return a.localeCompare(b); - }); - - const renderedRoutes = sortedLayoutKeys.flatMap((layoutKey) => { - const isNoLayout = layoutKey === ''; - const layout = isNoLayout ? null : layouts.find((l) => layoutKey === l.key); - - if (!isNoLayout && !layout) { - throw new Error(`Layout with key ${layoutKey} not found`); - } - - const routesGroup = routesByLayoutKey[layoutKey]; - const sortedRoutesGroup = sortBy(routesGroup, [ - (route) => (route.index ? 0 : 1), - (route) => (route.path === '*' ? 1 : 0), - (route) => route.path, - ]); - const routeExpressions = TsCodeUtils.mergeFragmentsPresorted( - sortedRoutesGroup.map((route) => - TsCodeUtils.mergeFragmentsAsJsxElement( - 'Route', - { - path: route.path, - index: route.index, - element: route.element, - children: route.children, - }, - [tsImportBuilder(['Route']).from('react-router-dom')], - ), - ), - ); - if (layout) { - return TsCodeUtils.mergeFragmentsAsJsxElement( - 'Route', - { - element: layout.element, - children: routeExpressions, - }, - [tsImportBuilder(['Route']).from('react-router-dom')], - ); - } - return routeExpressions; - }); - - return TsCodeUtils.mergeFragmentsPresorted(renderedRoutes, '\n\n'); -} diff --git a/packages/react-generators/src/generators/core/index.ts b/packages/react-generators/src/generators/core/index.ts index b02c82b8b..494d7b51a 100644 --- a/packages/react-generators/src/generators/core/index.ts +++ b/packages/react-generators/src/generators/core/index.ts @@ -1,12 +1,10 @@ export * from './_composers/index.js'; -export * from './_utils/index.js'; export * from './react-app/index.js'; export * from './react-components/index.js'; export * from './react-config/index.js'; export * from './react-error-boundary/index.js'; export * from './react-error/index.js'; export * from './react-logger/index.js'; -export * from './react-not-found-handler/index.js'; export * from './react-proxy/index.js'; export * from './react-router/index.js'; export * from './react-routes/index.js'; diff --git a/packages/react-generators/src/generators/core/react-app/extractor.json b/packages/react-generators/src/generators/core/react-app/extractor.json index 65396d070..ccb11a2a4 100644 --- a/packages/react-generators/src/generators/core/react-app/extractor.json +++ b/packages/react-generators/src/generators/core/react-app/extractor.json @@ -1,13 +1,13 @@ { "name": "core/react-app", "templates": { - "src/app/App.tsx": { + "src/app/app.tsx": { "name": "app", "type": "ts", "fileOptions": { "kind": "singleton" }, "generator": "@baseplate-dev/react-generators#core/react-app", "importMapProviders": {}, - "pathRootRelativePath": "{src-root}/app/App.tsx", + "pathRootRelativePath": "{src-root}/app/app.tsx", "variables": { "TPL_RENDER_ROOT": {} } } } diff --git a/packages/react-generators/src/generators/core/react-app/generated/template-paths.ts b/packages/react-generators/src/generators/core/react-app/generated/template-paths.ts index e0ad5806e..0c65535b0 100644 --- a/packages/react-generators/src/generators/core/react-app/generated/template-paths.ts +++ b/packages/react-generators/src/generators/core/react-app/generated/template-paths.ts @@ -17,7 +17,7 @@ const coreReactAppPathsTask = createGeneratorTask({ return { providers: { - coreReactAppPaths: { app: `${srcRoot}/app/App.tsx` }, + coreReactAppPaths: { app: `${srcRoot}/app/app.tsx` }, }, }; }, diff --git a/packages/react-generators/src/generators/core/react-app/generated/typed-templates.ts b/packages/react-generators/src/generators/core/react-app/generated/typed-templates.ts index 3dbc9c127..a38629a45 100644 --- a/packages/react-generators/src/generators/core/react-app/generated/typed-templates.ts +++ b/packages/react-generators/src/generators/core/react-app/generated/typed-templates.ts @@ -6,7 +6,7 @@ const app = createTsTemplateFile({ importMapProviders: {}, name: 'app', source: { - path: path.join(import.meta.dirname, '../templates/src/app/App.tsx'), + path: path.join(import.meta.dirname, '../templates/src/app/app.tsx'), }, variables: { TPL_RENDER_ROOT: {} }, }); diff --git a/packages/react-generators/src/generators/core/react-app/react-app.generator.ts b/packages/react-generators/src/generators/core/react-app/react-app.generator.ts index 75b3e39d9..f9f42442d 100644 --- a/packages/react-generators/src/generators/core/react-app/react-app.generator.ts +++ b/packages/react-generators/src/generators/core/react-app/react-app.generator.ts @@ -71,7 +71,7 @@ export const reactAppGenerator = createGenerator({ reactBaseConfig.appFragment.set( tsCodeFragment( '', - tsImportBuilder().default('App').from('@/src/app/App'), + tsImportBuilder(['App']).from('@/src/app/app'), ), ); }, diff --git a/packages/react-generators/src/generators/core/react-app/templates/src/app/App.tsx b/packages/react-generators/src/generators/core/react-app/templates/src/app/app.tsx similarity index 62% rename from packages/react-generators/src/generators/core/react-app/templates/src/app/App.tsx rename to packages/react-generators/src/generators/core/react-app/templates/src/app/app.tsx index d56bdafcd..6811c5a2b 100644 --- a/packages/react-generators/src/generators/core/react-app/templates/src/app/App.tsx +++ b/packages/react-generators/src/generators/core/react-app/templates/src/app/app.tsx @@ -2,8 +2,6 @@ import type { ReactElement } from 'react'; -function App(): ReactElement { +export function App(): ReactElement { return TPL_RENDER_ROOT; } - -export default App; diff --git a/packages/react-generators/src/generators/core/react-components/templates/src/components/not-found-card/not-found-card.tsx b/packages/react-generators/src/generators/core/react-components/templates/src/components/not-found-card/not-found-card.tsx index f8357ed99..a7145dadf 100644 --- a/packages/react-generators/src/generators/core/react-components/templates/src/components/not-found-card/not-found-card.tsx +++ b/packages/react-generators/src/generators/core/react-components/templates/src/components/not-found-card/not-found-card.tsx @@ -2,26 +2,20 @@ import type React from 'react'; -import { useNavigate } from 'react-router-dom'; +import { Link } from '@tanstack/react-router'; -import { Button } from '../button/button.js'; -import { ErrorDisplay } from '../error-display/error-display.js'; +import { Button, ErrorDisplay } from '../index.js'; export function NotFoundCard(): React.JSX.Element { - const navigate = useNavigate(); return ( { - navigate('/'); - }} - > - Back to Home - + + + } /> ); diff --git a/packages/react-generators/src/generators/core/react-not-found-handler/extractor.json b/packages/react-generators/src/generators/core/react-not-found-handler/extractor.json deleted file mode 100644 index b19662da7..000000000 --- a/packages/react-generators/src/generators/core/react-not-found-handler/extractor.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "core/react-not-found-handler", - "templates": { - "src/pages/NotFound.page.tsx": { - "name": "not-found-page", - "type": "ts", - "fileOptions": { "kind": "singleton" }, - "generator": "@baseplate-dev/react-generators#core/react-not-found-handler", - "importMapProviders": { - "reactComponentsImportsProvider": { - "importName": "reactComponentsImportsProvider", - "packagePathSpecifier": "@baseplate-dev/react-generators:src/generators/core/react-components/generated/ts-import-providers.ts" - } - }, - "pathRootRelativePath": "{src-root}/pages/NotFound.page.tsx", - "variables": {} - } - } -} diff --git a/packages/react-generators/src/generators/core/react-not-found-handler/generated/index.ts b/packages/react-generators/src/generators/core/react-not-found-handler/generated/index.ts deleted file mode 100644 index beaabb38e..000000000 --- a/packages/react-generators/src/generators/core/react-not-found-handler/generated/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { CORE_REACT_NOT_FOUND_HANDLER_PATHS } from './template-paths.js'; -import { CORE_REACT_NOT_FOUND_HANDLER_RENDERERS } from './template-renderers.js'; -import { CORE_REACT_NOT_FOUND_HANDLER_TEMPLATES } from './typed-templates.js'; - -export const CORE_REACT_NOT_FOUND_HANDLER_GENERATED = { - paths: CORE_REACT_NOT_FOUND_HANDLER_PATHS, - renderers: CORE_REACT_NOT_FOUND_HANDLER_RENDERERS, - templates: CORE_REACT_NOT_FOUND_HANDLER_TEMPLATES, -}; diff --git a/packages/react-generators/src/generators/core/react-not-found-handler/generated/template-paths.ts b/packages/react-generators/src/generators/core/react-not-found-handler/generated/template-paths.ts deleted file mode 100644 index 9255010b0..000000000 --- a/packages/react-generators/src/generators/core/react-not-found-handler/generated/template-paths.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { packageInfoProvider } from '@baseplate-dev/core-generators'; -import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; - -export interface CoreReactNotFoundHandlerPaths { - notFoundPage: string; -} - -const coreReactNotFoundHandlerPaths = - createProviderType( - 'core-react-not-found-handler-paths', - ); - -const coreReactNotFoundHandlerPathsTask = createGeneratorTask({ - dependencies: { packageInfo: packageInfoProvider }, - exports: { - coreReactNotFoundHandlerPaths: coreReactNotFoundHandlerPaths.export(), - }, - run({ packageInfo }) { - const srcRoot = packageInfo.getPackageSrcPath(); - - return { - providers: { - coreReactNotFoundHandlerPaths: { - notFoundPage: `${srcRoot}/pages/NotFound.page.tsx`, - }, - }, - }; - }, -}); - -export const CORE_REACT_NOT_FOUND_HANDLER_PATHS = { - provider: coreReactNotFoundHandlerPaths, - task: coreReactNotFoundHandlerPathsTask, -}; diff --git a/packages/react-generators/src/generators/core/react-not-found-handler/generated/template-renderers.ts b/packages/react-generators/src/generators/core/react-not-found-handler/generated/template-renderers.ts deleted file mode 100644 index 8edad31db..000000000 --- a/packages/react-generators/src/generators/core/react-not-found-handler/generated/template-renderers.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type { RenderTsTemplateFileActionInput } from '@baseplate-dev/core-generators'; -import type { BuilderAction } from '@baseplate-dev/sync'; - -import { typescriptFileProvider } from '@baseplate-dev/core-generators'; -import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; - -import { reactComponentsImportsProvider } from '#src/generators/core/react-components/generated/ts-import-providers.js'; - -import { CORE_REACT_NOT_FOUND_HANDLER_PATHS } from './template-paths.js'; -import { CORE_REACT_NOT_FOUND_HANDLER_TEMPLATES } from './typed-templates.js'; - -export interface CoreReactNotFoundHandlerRenderers { - notFoundPage: { - render: ( - options: Omit< - RenderTsTemplateFileActionInput< - typeof CORE_REACT_NOT_FOUND_HANDLER_TEMPLATES.notFoundPage - >, - 'destination' | 'importMapProviders' | 'template' - >, - ) => BuilderAction; - }; -} - -const coreReactNotFoundHandlerRenderers = - createProviderType( - 'core-react-not-found-handler-renderers', - ); - -const coreReactNotFoundHandlerRenderersTask = createGeneratorTask({ - dependencies: { - paths: CORE_REACT_NOT_FOUND_HANDLER_PATHS.provider, - reactComponentsImports: reactComponentsImportsProvider, - typescriptFile: typescriptFileProvider, - }, - exports: { - coreReactNotFoundHandlerRenderers: - coreReactNotFoundHandlerRenderers.export(), - }, - run({ paths, reactComponentsImports, typescriptFile }) { - return { - providers: { - coreReactNotFoundHandlerRenderers: { - notFoundPage: { - render: (options) => - typescriptFile.renderTemplateFile({ - template: CORE_REACT_NOT_FOUND_HANDLER_TEMPLATES.notFoundPage, - destination: paths.notFoundPage, - importMapProviders: { - reactComponentsImports, - }, - ...options, - }), - }, - }, - }, - }; - }, -}); - -export const CORE_REACT_NOT_FOUND_HANDLER_RENDERERS = { - provider: coreReactNotFoundHandlerRenderers, - task: coreReactNotFoundHandlerRenderersTask, -}; diff --git a/packages/react-generators/src/generators/core/react-not-found-handler/generated/typed-templates.ts b/packages/react-generators/src/generators/core/react-not-found-handler/generated/typed-templates.ts deleted file mode 100644 index 714e3ad4d..000000000 --- a/packages/react-generators/src/generators/core/react-not-found-handler/generated/typed-templates.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { createTsTemplateFile } from '@baseplate-dev/core-generators'; -import path from 'node:path'; - -import { reactComponentsImportsProvider } from '#src/generators/core/react-components/generated/ts-import-providers.js'; - -const notFoundPage = createTsTemplateFile({ - fileOptions: { kind: 'singleton' }, - importMapProviders: { - reactComponentsImports: reactComponentsImportsProvider, - }, - name: 'not-found-page', - source: { - path: path.join( - import.meta.dirname, - '../templates/src/pages/NotFound.page.tsx', - ), - }, - variables: {}, -}); - -export const CORE_REACT_NOT_FOUND_HANDLER_TEMPLATES = { notFoundPage }; diff --git a/packages/react-generators/src/generators/core/react-not-found-handler/index.ts b/packages/react-generators/src/generators/core/react-not-found-handler/index.ts deleted file mode 100644 index 9ffb976ab..000000000 --- a/packages/react-generators/src/generators/core/react-not-found-handler/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './react-not-found-handler.generator.js'; diff --git a/packages/react-generators/src/generators/core/react-not-found-handler/react-not-found-handler.generator.ts b/packages/react-generators/src/generators/core/react-not-found-handler/react-not-found-handler.generator.ts deleted file mode 100644 index 2a878743b..000000000 --- a/packages/react-generators/src/generators/core/react-not-found-handler/react-not-found-handler.generator.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { - packageScope, - tsCodeFragment, - tsImportBuilder, - typescriptFileProvider, -} from '@baseplate-dev/core-generators'; -import { - createGenerator, - createGeneratorTask, - createProviderType, -} from '@baseplate-dev/sync'; -import { z } from 'zod'; - -import type { ReactRoute } from '#src/providers/routes.js'; - -import { reactRoutesProvider } from '#src/providers/routes.js'; - -import { reactComponentsImportsProvider } from '../react-components/index.js'; -import { CORE_REACT_NOT_FOUND_HANDLER_GENERATED } from './generated/index.js'; - -const descriptorSchema = z.object({ - layoutKey: z.string().optional(), -}); - -export interface ReactNotFoundProvider { - getNotFoundRoute(): ReactRoute; -} - -export const reactNotFoundProvider = - createProviderType('react-not-found'); - -export const reactNotFoundHandlerGenerator = createGenerator({ - name: 'core/react-not-found-handler', - generatorFileUrl: import.meta.url, - descriptorSchema, - buildTasks: ({ layoutKey }) => ({ - paths: CORE_REACT_NOT_FOUND_HANDLER_GENERATED.paths.task, - main: createGeneratorTask({ - dependencies: { - reactRoutes: reactRoutesProvider, - reactComponentsImports: reactComponentsImportsProvider, - typescriptFile: typescriptFileProvider, - paths: CORE_REACT_NOT_FOUND_HANDLER_GENERATED.paths.provider, - }, - exports: { - reactNotFound: reactNotFoundProvider.export(packageScope), - }, - run({ reactRoutes, reactComponentsImports, typescriptFile, paths }) { - const notFoundRoute = { - path: '*', - element: tsCodeFragment( - ``, - tsImportBuilder().default('NotFoundPage').from(paths.notFoundPage), - ), - }; - - reactRoutes.registerRoute({ - ...notFoundRoute, - layoutKey, - }); - return { - providers: { - reactNotFound: { - getNotFoundRoute: () => notFoundRoute, - }, - }, - build: async (builder) => { - await builder.apply( - typescriptFile.renderTemplateFile({ - template: - CORE_REACT_NOT_FOUND_HANDLER_GENERATED.templates.notFoundPage, - destination: paths.notFoundPage, - importMapProviders: { - reactComponentsImports, - }, - }), - ); - }, - }; - }, - }), - }), -}); diff --git a/packages/react-generators/src/generators/core/react-not-found-handler/templates/src/pages/NotFound.page.tsx b/packages/react-generators/src/generators/core/react-not-found-handler/templates/src/pages/NotFound.page.tsx deleted file mode 100644 index 759abb4e9..000000000 --- a/packages/react-generators/src/generators/core/react-not-found-handler/templates/src/pages/NotFound.page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// @ts-nocheck - -import type { ReactElement } from 'react'; - -import { NotFoundCard } from '%reactComponentsImports'; - -function NotFoundPage(): ReactElement { - return ; -} - -export default NotFoundPage; diff --git a/packages/react-generators/src/generators/core/react-router/extractor.json b/packages/react-generators/src/generators/core/react-router/extractor.json index 35255176e..80f47a71e 100644 --- a/packages/react-generators/src/generators/core/react-router/extractor.json +++ b/packages/react-generators/src/generators/core/react-router/extractor.json @@ -1,14 +1,47 @@ { "name": "core/react-router", "templates": { - "src/pages/index.tsx": { - "name": "index", + "routes/__root.tsx": { + "name": "root-route", "type": "ts", "fileOptions": { "kind": "singleton" }, "generator": "@baseplate-dev/react-generators#core/react-router", "importMapProviders": {}, - "pathRootRelativePath": "{src-root}/pages/index.tsx", - "variables": { "TPL_RENDER_HEADER": {}, "TPL_ROUTES": {} } + "pathRootRelativePath": "{routes-root}/__root.tsx", + "projectExports": { "Route": {} }, + "variables": { "TPL_ROOT_ROUTE_OPTIONS": {} } + }, + "routes/index.tsx": { + "name": "placeholder-index", + "type": "ts", + "fileOptions": { "kind": "singleton" }, + "generator": "@baseplate-dev/react-generators#core/react-router", + "importMapProviders": {}, + "pathRootRelativePath": "{routes-root}/index.tsx", + "variables": {} + }, + "src/app/app-routes.tsx": { + "name": "app-routes", + "type": "ts", + "fileOptions": { "kind": "singleton" }, + "generator": "@baseplate-dev/react-generators#core/react-router", + "importMapProviders": { + "reactComponentsImportsProvider": { + "importName": "reactComponentsImportsProvider", + "packagePathSpecifier": "@baseplate-dev/react-generators:src/generators/core/react-components/generated/ts-import-providers.ts" + } + }, + "pathRootRelativePath": "{src-root}/app/app-routes.tsx", + "projectExports": { "AppRoutes": {}, "router": {} }, + "variables": { "TPL_RENDER_HEADER": {} } + }, + "src/route-tree.gen.ts": { + "name": "route-tree", + "type": "ts", + "fileOptions": { "kind": "singleton" }, + "generator": "@baseplate-dev/react-generators#core/react-router", + "pathRootRelativePath": "{src-root}/route-tree.gen.ts", + "projectExportsOnly": true } } } diff --git a/packages/react-generators/src/generators/core/react-router/generated/index.ts b/packages/react-generators/src/generators/core/react-router/generated/index.ts index 53e2e1ff6..0f08917f2 100644 --- a/packages/react-generators/src/generators/core/react-router/generated/index.ts +++ b/packages/react-generators/src/generators/core/react-router/generated/index.ts @@ -1,8 +1,10 @@ import { CORE_REACT_ROUTER_PATHS } from './template-paths.js'; import { CORE_REACT_ROUTER_RENDERERS } from './template-renderers.js'; +import { CORE_REACT_ROUTER_IMPORTS } from './ts-import-providers.js'; import { CORE_REACT_ROUTER_TEMPLATES } from './typed-templates.js'; export const CORE_REACT_ROUTER_GENERATED = { + imports: CORE_REACT_ROUTER_IMPORTS, paths: CORE_REACT_ROUTER_PATHS, renderers: CORE_REACT_ROUTER_RENDERERS, templates: CORE_REACT_ROUTER_TEMPLATES, diff --git a/packages/react-generators/src/generators/core/react-router/generated/template-paths.ts b/packages/react-generators/src/generators/core/react-router/generated/template-paths.ts index 8af734224..c373ac1c9 100644 --- a/packages/react-generators/src/generators/core/react-router/generated/template-paths.ts +++ b/packages/react-generators/src/generators/core/react-router/generated/template-paths.ts @@ -1,8 +1,13 @@ import { packageInfoProvider } from '@baseplate-dev/core-generators'; import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; +import { reactRoutesProvider } from '#src/providers/routes.js'; + export interface CoreReactRouterPaths { - index: string; + rootRoute: string; + placeholderIndex: string; + appRoutes: string; + routeTree: string; } const coreReactRouterPaths = createProviderType( @@ -10,14 +15,23 @@ const coreReactRouterPaths = createProviderType( ); const coreReactRouterPathsTask = createGeneratorTask({ - dependencies: { packageInfo: packageInfoProvider }, + dependencies: { + packageInfo: packageInfoProvider, + reactRoutes: reactRoutesProvider, + }, exports: { coreReactRouterPaths: coreReactRouterPaths.export() }, - run({ packageInfo }) { + run({ packageInfo, reactRoutes }) { + const routesRoot = reactRoutes.getDirectoryBase(); const srcRoot = packageInfo.getPackageSrcPath(); return { providers: { - coreReactRouterPaths: { index: `${srcRoot}/pages/index.tsx` }, + coreReactRouterPaths: { + appRoutes: `${srcRoot}/app/app-routes.tsx`, + placeholderIndex: `${routesRoot}/index.tsx`, + rootRoute: `${routesRoot}/__root.tsx`, + routeTree: `${srcRoot}/route-tree.gen.ts`, + }, }, }; }, diff --git a/packages/react-generators/src/generators/core/react-router/generated/template-renderers.ts b/packages/react-generators/src/generators/core/react-router/generated/template-renderers.ts index 363d84e78..8a3cba82e 100644 --- a/packages/react-generators/src/generators/core/react-router/generated/template-renderers.ts +++ b/packages/react-generators/src/generators/core/react-router/generated/template-renderers.ts @@ -4,15 +4,47 @@ import type { BuilderAction } from '@baseplate-dev/sync'; import { typescriptFileProvider } from '@baseplate-dev/core-generators'; import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; +import { reactComponentsImportsProvider } from '#src/generators/core/react-components/generated/ts-import-providers.js'; + import { CORE_REACT_ROUTER_PATHS } from './template-paths.js'; import { CORE_REACT_ROUTER_TEMPLATES } from './typed-templates.js'; export interface CoreReactRouterRenderers { - index: { + appRoutes: { + render: ( + options: Omit< + RenderTsTemplateFileActionInput< + typeof CORE_REACT_ROUTER_TEMPLATES.appRoutes + >, + 'destination' | 'importMapProviders' | 'template' + >, + ) => BuilderAction; + }; + placeholderIndex: { + render: ( + options: Omit< + RenderTsTemplateFileActionInput< + typeof CORE_REACT_ROUTER_TEMPLATES.placeholderIndex + >, + 'destination' | 'importMapProviders' | 'template' + >, + ) => BuilderAction; + }; + rootRoute: { + render: ( + options: Omit< + RenderTsTemplateFileActionInput< + typeof CORE_REACT_ROUTER_TEMPLATES.rootRoute + >, + 'destination' | 'importMapProviders' | 'template' + >, + ) => BuilderAction; + }; + routeTree: { render: ( options: Omit< RenderTsTemplateFileActionInput< - typeof CORE_REACT_ROUTER_TEMPLATES.index + typeof CORE_REACT_ROUTER_TEMPLATES.routeTree >, 'destination' | 'importMapProviders' | 'template' >, @@ -27,18 +59,46 @@ const coreReactRouterRenderers = createProviderType( const coreReactRouterRenderersTask = createGeneratorTask({ dependencies: { paths: CORE_REACT_ROUTER_PATHS.provider, + reactComponentsImports: reactComponentsImportsProvider, typescriptFile: typescriptFileProvider, }, exports: { coreReactRouterRenderers: coreReactRouterRenderers.export() }, - run({ paths, typescriptFile }) { + run({ paths, reactComponentsImports, typescriptFile }) { return { providers: { coreReactRouterRenderers: { - index: { + appRoutes: { + render: (options) => + typescriptFile.renderTemplateFile({ + template: CORE_REACT_ROUTER_TEMPLATES.appRoutes, + destination: paths.appRoutes, + importMapProviders: { + reactComponentsImports, + }, + ...options, + }), + }, + placeholderIndex: { + render: (options) => + typescriptFile.renderTemplateFile({ + template: CORE_REACT_ROUTER_TEMPLATES.placeholderIndex, + destination: paths.placeholderIndex, + ...options, + }), + }, + rootRoute: { + render: (options) => + typescriptFile.renderTemplateFile({ + template: CORE_REACT_ROUTER_TEMPLATES.rootRoute, + destination: paths.rootRoute, + ...options, + }), + }, + routeTree: { render: (options) => typescriptFile.renderTemplateFile({ - template: CORE_REACT_ROUTER_TEMPLATES.index, - destination: paths.index, + template: CORE_REACT_ROUTER_TEMPLATES.routeTree, + destination: paths.routeTree, ...options, }), }, diff --git a/packages/react-generators/src/generators/core/react-router/generated/ts-import-providers.ts b/packages/react-generators/src/generators/core/react-router/generated/ts-import-providers.ts new file mode 100644 index 000000000..c8aed3b95 --- /dev/null +++ b/packages/react-generators/src/generators/core/react-router/generated/ts-import-providers.ts @@ -0,0 +1,52 @@ +import type { TsImportMapProviderFromSchema } from '@baseplate-dev/core-generators'; + +import { + createTsImportMap, + createTsImportMapSchema, + packageScope, +} from '@baseplate-dev/core-generators'; +import { + createGeneratorTask, + createReadOnlyProviderType, +} from '@baseplate-dev/sync'; + +import { CORE_REACT_ROUTER_PATHS } from './template-paths.js'; + +const reactRouterImportsSchema = createTsImportMapSchema({ + AppRoutes: {}, + Route: {}, + router: {}, +}); + +export type ReactRouterImportsProvider = TsImportMapProviderFromSchema< + typeof reactRouterImportsSchema +>; + +export const reactRouterImportsProvider = + createReadOnlyProviderType( + 'react-router-imports', + ); + +const coreReactRouterImportsTask = createGeneratorTask({ + dependencies: { + paths: CORE_REACT_ROUTER_PATHS.provider, + }, + exports: { + reactRouterImports: reactRouterImportsProvider.export(packageScope), + }, + run({ paths }) { + return { + providers: { + reactRouterImports: createTsImportMap(reactRouterImportsSchema, { + AppRoutes: paths.appRoutes, + Route: paths.rootRoute, + router: paths.appRoutes, + }), + }, + }; + }, +}); + +export const CORE_REACT_ROUTER_IMPORTS = { + task: coreReactRouterImportsTask, +}; diff --git a/packages/react-generators/src/generators/core/react-router/generated/typed-templates.ts b/packages/react-generators/src/generators/core/react-router/generated/typed-templates.ts index d626ca31a..5727bb344 100644 --- a/packages/react-generators/src/generators/core/react-router/generated/typed-templates.ts +++ b/packages/react-generators/src/generators/core/react-router/generated/typed-templates.ts @@ -1,14 +1,54 @@ import { createTsTemplateFile } from '@baseplate-dev/core-generators'; import path from 'node:path'; -const index = createTsTemplateFile({ +import { reactComponentsImportsProvider } from '#src/generators/core/react-components/generated/ts-import-providers.js'; + +const appRoutes = createTsTemplateFile({ + fileOptions: { kind: 'singleton' }, + importMapProviders: { + reactComponentsImports: reactComponentsImportsProvider, + }, + name: 'app-routes', + projectExports: { AppRoutes: {}, router: {} }, + source: { + path: path.join(import.meta.dirname, '../templates/src/app/app-routes.tsx'), + }, + variables: { TPL_RENDER_HEADER: {} }, +}); + +const placeholderIndex = createTsTemplateFile({ + fileOptions: { kind: 'singleton' }, + importMapProviders: {}, + name: 'placeholder-index', + source: { + path: path.join(import.meta.dirname, '../templates/routes/index.tsx'), + }, + variables: {}, +}); + +const rootRoute = createTsTemplateFile({ fileOptions: { kind: 'singleton' }, importMapProviders: {}, - name: 'index', + name: 'root-route', + projectExports: { Route: {} }, + source: { + path: path.join(import.meta.dirname, '../templates/routes/__root.tsx'), + }, + variables: { TPL_ROOT_ROUTE_OPTIONS: {} }, +}); + +const routeTree = createTsTemplateFile({ + fileOptions: { kind: 'singleton' }, + name: 'route-tree', source: { - path: path.join(import.meta.dirname, '../templates/src/pages/index.tsx'), + path: path.join(import.meta.dirname, '../templates/src/route-tree.gen.ts'), }, - variables: { TPL_RENDER_HEADER: {}, TPL_ROUTES: {} }, + variables: {}, }); -export const CORE_REACT_ROUTER_TEMPLATES = { index }; +export const CORE_REACT_ROUTER_TEMPLATES = { + rootRoute, + placeholderIndex, + appRoutes, + routeTree, +}; diff --git a/packages/react-generators/src/generators/core/react-router/index.ts b/packages/react-generators/src/generators/core/react-router/index.ts index cfa042779..3fc39b07c 100644 --- a/packages/react-generators/src/generators/core/react-router/index.ts +++ b/packages/react-generators/src/generators/core/react-router/index.ts @@ -1 +1,3 @@ +export type { ReactRouterImportsProvider } from './generated/ts-import-providers.js'; +export { reactRouterImportsProvider } from './generated/ts-import-providers.js'; export * from './react-router.generator.js'; diff --git a/packages/react-generators/src/generators/core/react-router/react-router.generator.ts b/packages/react-generators/src/generators/core/react-router/react-router.generator.ts index 1c67e8d06..e37c1764e 100644 --- a/packages/react-generators/src/generators/core/react-router/react-router.generator.ts +++ b/packages/react-generators/src/generators/core/react-router/react-router.generator.ts @@ -1,41 +1,44 @@ -import type { TsCodeFragment } from '@baseplate-dev/core-generators'; +import type { + TsCodeFragment, + TsTemplateOutputTemplateMetadata, +} from '@baseplate-dev/core-generators'; import { createNodePackagesTask, + eslintConfigProvider, extractPackageVersions, packageScope, + pathRootsProvider, + prettierProvider, tsCodeFragment, TsCodeUtils, tsImportBuilder, - typescriptFileProvider, + tsTemplate, } from '@baseplate-dev/core-generators'; import { createConfigProviderTask, createGenerator, createGeneratorTask, - createReadOnlyProviderType, + createProviderTask, } from '@baseplate-dev/sync'; import { z } from 'zod'; -import type { ReactRoute, ReactRouteLayout } from '#src/providers/routes.js'; - import { REACT_PACKAGES } from '#src/constants/react-packages.js'; -import { - reactRoutesProvider, - reactRoutesReadOnlyProvider, -} from '#src/providers/routes.js'; +import { reactRoutesProvider } from '#src/providers/routes.js'; -import { renderRoutes } from '../_utils/render-routes.js'; import { reactAppConfigProvider } from '../react-app/index.js'; +import { reactBaseConfigProvider } from '../react/react.generator.js'; import { CORE_REACT_ROUTER_GENERATED } from './generated/index.js'; -const descriptorSchema = z.object({}); +const descriptorSchema = z.object({ + renderPlaceholderIndex: z.boolean().default(false), +}); const [setupTask, reactRouterConfigProvider, reactRouterConfigValuesProvider] = createConfigProviderTask( (t) => ({ renderHeaders: t.map(), - routesComponent: t.scalar(), + rootLayoutComponent: t.scalar(), }), { prefix: 'react-router', @@ -45,117 +48,123 @@ const [setupTask, reactRouterConfigProvider, reactRouterConfigValuesProvider] = export { reactRouterConfigProvider }; -const reactRouteValuesProvider = createReadOnlyProviderType<{ - routes: ReactRoute[]; - layouts: ReactRouteLayout[]; -}>('react-route-values'); - export const reactRouterGenerator = createGenerator({ name: 'core/react-router', generatorFileUrl: import.meta.url, descriptorSchema, - buildTasks: () => ({ + buildTasks: ({ renderPlaceholderIndex }) => ({ setup: setupTask, nodePackages: createNodePackagesTask({ - prod: extractPackageVersions(REACT_PACKAGES, ['react-router-dom']), + prod: extractPackageVersions(REACT_PACKAGES, ['@tanstack/react-router']), + dev: extractPackageVersions(REACT_PACKAGES, ['@tanstack/router-plugin']), }), paths: CORE_REACT_ROUTER_GENERATED.paths.task, + imports: CORE_REACT_ROUTER_GENERATED.imports.task, + renderers: CORE_REACT_ROUTER_GENERATED.renderers.task, + vite: createProviderTask(reactBaseConfigProvider, (reactBaseConfig) => { + reactBaseConfig.vitePlugins.set( + '@tanstack/router-plugin', + tsTemplate`${TsCodeUtils.importFragment( + 'tanstackRouter', + '@tanstack/router-plugin/vite', + )}({ + target: 'react', + autoCodeSplitting: true, + generatedRouteTree: './src/route-tree.gen.ts', + quoteStyle: 'single', + })`, + ); + }), reactAppConfig: createGeneratorTask({ dependencies: { reactAppConfig: reactAppConfigProvider, paths: CORE_REACT_ROUTER_GENERATED.paths.provider, }, run({ reactAppConfig, paths }) { - reactAppConfig.renderWrappers.set('react-router', { - wrap: (contents) => - TsCodeUtils.templateWithImports( - tsImportBuilder(['BrowserRouter']).from('react-router-dom'), - )`${contents}`, - type: 'router', - }); - reactAppConfig.renderRoot.set( tsCodeFragment( - '', - tsImportBuilder().default('PagesRoot').from(paths.index), + '', + tsImportBuilder(['AppRoutes']).from(paths.appRoutes), ), ); }, }), + prettier: createProviderTask(prettierProvider, (prettier) => { + prettier.addPrettierIgnore('/src/route-tree.gen.ts'); + }), + eslint: createProviderTask(eslintConfigProvider, (eslint) => { + eslint.eslintIgnore.push('src/route-tree.gen.ts'); + }), routes: createGeneratorTask({ + dependencies: { + pathRoots: pathRootsProvider, + }, exports: { reactRoutes: reactRoutesProvider.export(packageScope), - reactRoutesReadOnly: reactRoutesReadOnlyProvider.export(packageScope), }, - outputs: { - reactRouteValuesProvider: reactRouteValuesProvider.export(), - }, - run() { - const routes: ReactRoute[] = []; - const layouts: ReactRouteLayout[] = []; + run({ pathRoots }) { + const directoryBase = `@/src/routes`; + + pathRoots.registerPathRoot('routes-root', directoryBase); return { providers: { reactRoutes: { - registerRoute(route) { - routes.push(route); - }, - registerLayout(layout) { - layouts.push(layout); - }, - getDirectoryBase: () => `@/src/pages`, - getRoutePrefix: () => ``, - }, - reactRoutesReadOnly: { - getDirectoryBase: () => `@/src/pages`, + getDirectoryBase: () => directoryBase, getRoutePrefix: () => ``, }, }, - build: () => ({ - reactRouteValuesProvider: { - routes, - layouts, - }, - }), }; }, }), main: createGeneratorTask({ dependencies: { reactRouterConfigValues: reactRouterConfigValuesProvider, - reactRouteValues: reactRouteValuesProvider, - typescriptFile: typescriptFileProvider, - paths: CORE_REACT_ROUTER_GENERATED.paths.provider, + renderers: CORE_REACT_ROUTER_GENERATED.renderers.provider, }, run({ - reactRouterConfigValues: { - routesComponent = tsCodeFragment( - 'Routes', - tsImportBuilder(['Routes']).from('react-router-dom'), - ), - renderHeaders, - }, - reactRouteValues: { routes, layouts }, - typescriptFile, - paths, + reactRouterConfigValues: { renderHeaders, rootLayoutComponent }, + renderers, }) { return { build: async (builder) => { - // TODO: Make sure we don't have more than one layout key - - // group routes by layout key - const renderedRoutes = renderRoutes(routes, layouts); - await builder.apply( - typescriptFile.renderTemplateFile({ - template: CORE_REACT_ROUTER_GENERATED.templates.index, - destination: paths.index, + renderers.appRoutes.render({ variables: { TPL_RENDER_HEADER: TsCodeUtils.mergeFragments(renderHeaders), - TPL_ROUTES: TsCodeUtils.template`<${routesComponent}>${renderedRoutes}`, }, }), ); + + await builder.apply( + renderers.rootRoute.render({ + variables: { + TPL_ROOT_ROUTE_OPTIONS: TsCodeUtils.mergeFragmentsAsObject({ + component: rootLayoutComponent, + }), + }, + }), + ); + + if (renderPlaceholderIndex) { + await builder.apply(renderers.placeholderIndex.render({})); + } + + // Write a pseudo-file so that the template extractor can infer metadata for the + // generated route tree file + builder.writeFile({ + id: 'route-tree', + destination: '@/src/route-tree.gen.ts', + contents: '', + options: { skipWriting: true }, + templateMetadata: { + generator: builder.generatorInfo.name, + name: 'route-tree', + projectExportsOnly: true, + type: 'ts', + fileOptions: { kind: 'singleton' }, + } satisfies TsTemplateOutputTemplateMetadata, + }); }, }; }, diff --git a/packages/react-generators/src/generators/core/react-router/templates/routes/__root.tsx b/packages/react-generators/src/generators/core/react-router/templates/routes/__root.tsx new file mode 100644 index 000000000..c8ec1ff4a --- /dev/null +++ b/packages/react-generators/src/generators/core/react-router/templates/routes/__root.tsx @@ -0,0 +1,5 @@ +// @ts-nocheck + +import { createRootRoute } from '@tanstack/react-router'; + +export const Route = createRootRoute(TPL_ROOT_ROUTE_OPTIONS); diff --git a/packages/react-generators/src/generators/core/react-router/templates/routes/index.tsx b/packages/react-generators/src/generators/core/react-router/templates/routes/index.tsx new file mode 100644 index 000000000..4677fb837 --- /dev/null +++ b/packages/react-generators/src/generators/core/react-router/templates/routes/index.tsx @@ -0,0 +1,18 @@ +// @ts-nocheck + +import type { ReactElement } from 'react'; + +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/')({ + component: HomePage, +}); + +function HomePage(): ReactElement { + return ( +
+

Hello World

+

This is the home page of a generated app.

+
+ ); +} diff --git a/packages/react-generators/src/generators/core/react-router/templates/src/app/app-routes.tsx b/packages/react-generators/src/generators/core/react-router/templates/src/app/app-routes.tsx new file mode 100644 index 000000000..7aeeb499a --- /dev/null +++ b/packages/react-generators/src/generators/core/react-router/templates/src/app/app-routes.tsx @@ -0,0 +1,37 @@ +// @ts-nocheck + +import type { ErrorRouteComponent } from '@tanstack/react-router'; + +import { Button, ErrorDisplay, NotFoundCard } from '%reactComponentsImports'; +import { createRouter, RouterProvider } from '@tanstack/react-router'; + +import { routeTree } from '../route-tree.gen.js'; + +const ErrorComponent: ErrorRouteComponent = ({ + error, + reset, +}: React.ComponentProps) => ( + Reset} + /> +); + +export const router = createRouter({ + routeTree, + defaultNotFoundComponent: NotFoundCard, + defaultErrorComponent: ErrorComponent, +}); + +// Register the router instance for type safety +declare module '@tanstack/react-router' { + interface Register { + router: typeof router; + } +} + +export function AppRoutes(): React.ReactElement { + TPL_RENDER_HEADER; + + return ; +} diff --git a/packages/react-generators/src/generators/core/react-router/templates/src/pages/index.tsx b/packages/react-generators/src/generators/core/react-router/templates/src/pages/index.tsx deleted file mode 100644 index c1167f1ee..000000000 --- a/packages/react-generators/src/generators/core/react-router/templates/src/pages/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// @ts-nocheck - -import type { ReactElement } from 'react'; - -function PagesRoot(): ReactElement { - TPL_RENDER_HEADER; - - return TPL_ROUTES; -} - -export default PagesRoot; diff --git a/packages/react-generators/src/generators/core/react-routes/extractor.json b/packages/react-generators/src/generators/core/react-routes/extractor.json deleted file mode 100644 index af931723f..000000000 --- a/packages/react-generators/src/generators/core/react-routes/extractor.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "core/react-routes", - "templates": { - "routes.tsx": { - "name": "index", - "type": "ts", - "fileOptions": { - "generatorTemplatePath": "routes.tsx", - "kind": "instance" - }, - "generator": "@baseplate-dev/react-generators#core/react-routes", - "importMapProviders": {}, - "variables": { - "TPL_ROUTE_HEADER": {}, - "TPL_ROUTES": {}, - "TPL_ROUTES_NAME": {} - } - } - } -} diff --git a/packages/react-generators/src/generators/core/react-routes/generated/index.ts b/packages/react-generators/src/generators/core/react-routes/generated/index.ts deleted file mode 100644 index 8c0910b58..000000000 --- a/packages/react-generators/src/generators/core/react-routes/generated/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { CORE_REACT_ROUTES_RENDERERS } from './template-renderers.js'; -import { CORE_REACT_ROUTES_TEMPLATES } from './typed-templates.js'; - -export const CORE_REACT_ROUTES_GENERATED = { - renderers: CORE_REACT_ROUTES_RENDERERS, - templates: CORE_REACT_ROUTES_TEMPLATES, -}; diff --git a/packages/react-generators/src/generators/core/react-routes/generated/template-renderers.ts b/packages/react-generators/src/generators/core/react-routes/generated/template-renderers.ts deleted file mode 100644 index 13eae89bf..000000000 --- a/packages/react-generators/src/generators/core/react-routes/generated/template-renderers.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { RenderTsTemplateFileActionInput } from '@baseplate-dev/core-generators'; -import type { BuilderAction } from '@baseplate-dev/sync'; - -import { typescriptFileProvider } from '@baseplate-dev/core-generators'; -import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; - -import { CORE_REACT_ROUTES_TEMPLATES } from './typed-templates.js'; - -export interface CoreReactRoutesRenderers { - index: { - render: ( - options: Omit< - RenderTsTemplateFileActionInput< - typeof CORE_REACT_ROUTES_TEMPLATES.index - >, - 'importMapProviders' | 'template' - >, - ) => BuilderAction; - }; -} - -const coreReactRoutesRenderers = createProviderType( - 'core-react-routes-renderers', -); - -const coreReactRoutesRenderersTask = createGeneratorTask({ - dependencies: { typescriptFile: typescriptFileProvider }, - exports: { coreReactRoutesRenderers: coreReactRoutesRenderers.export() }, - run({ typescriptFile }) { - return { - providers: { - coreReactRoutesRenderers: { - index: { - render: (options) => - typescriptFile.renderTemplateFile({ - template: CORE_REACT_ROUTES_TEMPLATES.index, - ...options, - }), - }, - }, - }, - }; - }, -}); - -export const CORE_REACT_ROUTES_RENDERERS = { - provider: coreReactRoutesRenderers, - task: coreReactRoutesRenderersTask, -}; diff --git a/packages/react-generators/src/generators/core/react-routes/generated/typed-templates.ts b/packages/react-generators/src/generators/core/react-routes/generated/typed-templates.ts deleted file mode 100644 index 26254ab33..000000000 --- a/packages/react-generators/src/generators/core/react-routes/generated/typed-templates.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { createTsTemplateFile } from '@baseplate-dev/core-generators'; -import path from 'node:path'; - -const index = createTsTemplateFile({ - fileOptions: { generatorTemplatePath: 'routes.tsx', kind: 'instance' }, - importMapProviders: {}, - name: 'index', - source: { - path: path.join(import.meta.dirname, '../templates/routes.tsx'), - }, - variables: { TPL_ROUTE_HEADER: {}, TPL_ROUTES: {}, TPL_ROUTES_NAME: {} }, -}); - -export const CORE_REACT_ROUTES_TEMPLATES = { index }; diff --git a/packages/react-generators/src/generators/core/react-routes/react-routes.generator.ts b/packages/react-generators/src/generators/core/react-routes/react-routes.generator.ts index 2b52e7d2f..cf03e23df 100644 --- a/packages/react-generators/src/generators/core/react-routes/react-routes.generator.ts +++ b/packages/react-generators/src/generators/core/react-routes/react-routes.generator.ts @@ -1,32 +1,13 @@ -import type { TsCodeFragment } from '@baseplate-dev/core-generators'; - -import { - pathRootsProvider, - TsCodeUtils, - typescriptFileProvider, -} from '@baseplate-dev/core-generators'; +import { pathRootsProvider } from '@baseplate-dev/core-generators'; import { createGenerator, createGeneratorTask } from '@baseplate-dev/sync'; +import { convertCaseWithPrefix } from '@baseplate-dev/utils'; +import { kebabCase } from 'es-toolkit'; import { z } from 'zod'; -import type { ReactRoute, ReactRouteLayout } from '#src/providers/routes.js'; - -import { - reactRoutesProvider, - reactRoutesReadOnlyProvider, -} from '#src/providers/routes.js'; -import { dasherizeCamel, upperCaseFirst } from '#src/utils/case.js'; -import { createRouteElement } from '#src/utils/routes.js'; - -import { renderRoutes } from '../_utils/render-routes.js'; -import { reactNotFoundProvider } from '../react-not-found-handler/index.js'; -import { CORE_REACT_ROUTES_GENERATED } from './generated/index.js'; +import { reactRoutesProvider } from '#src/providers/routes.js'; const descriptorSchema = z.object({ - id: z.string().min(1), name: z.string().min(1), - layoutKey: z.string().optional(), - // whether to pass the routes through to the parent routes container - isPassthrough: z.boolean().optional(), }); export const reactRoutesGenerator = createGenerator({ @@ -34,23 +15,17 @@ export const reactRoutesGenerator = createGenerator({ generatorFileUrl: import.meta.url, descriptorSchema, getInstanceName: (descriptor) => descriptor.name, - buildTasks: ({ id, name, layoutKey, isPassthrough }) => ({ + buildTasks: ({ name }) => ({ main: createGeneratorTask({ dependencies: { reactRoutes: reactRoutesProvider.dependency().parentScopeOnly(), - typescriptFile: typescriptFileProvider, - reactNotFound: reactNotFoundProvider.dependency().optional(), pathRoots: pathRootsProvider.dependency(), }, exports: { reactRoutes: reactRoutesProvider.export(), - reactRoutesReadOnly: reactRoutesReadOnlyProvider.export(), }, - run({ reactRoutes, typescriptFile, reactNotFound, pathRoots }) { - const routes: ReactRoute[] = []; - const layouts: ReactRouteLayout[] = []; - - const pathName = dasherizeCamel(name); + run({ reactRoutes, pathRoots }) { + const pathName = convertCaseWithPrefix(name, kebabCase); const directoryBase = `${reactRoutes.getDirectoryBase()}/${pathName}`; @@ -60,83 +35,13 @@ export const reactRoutesGenerator = createGenerator({ return { providers: { reactRoutes: { - registerRoute(route) { - routes.push(route); - }, - registerLayout(layout) { - layouts.push(layout); - }, - getDirectoryBase: () => directoryBase, - getRoutePrefix: () => - `${reactRoutes.getRoutePrefix()}/${pathName}`, - }, - reactRoutesReadOnly: { getDirectoryBase: () => directoryBase, getRoutePrefix: () => - `${reactRoutes.getRoutePrefix()}/${pathName}`, + pathName.startsWith('_') + ? reactRoutes.getRoutePrefix() + : `${reactRoutes.getRoutePrefix()}/${pathName}`, }, }, - build: async (builder) => { - if (routes.length === 0) { - return; - } - - if (isPassthrough) { - const renderedRoutes = renderRoutes(routes, layouts); - - reactRoutes.registerRoute({ - path: pathName, - layoutKey, - children: renderedRoutes, - }); - for (const route of routes) - reactRoutes.registerRoute({ - ...route, - path: - route.path && - `${reactRoutes.getRoutePrefix()}/${pathName}/${route.path}`, - }); - for (const layout of layouts) reactRoutes.registerLayout(layout); - } else { - // if we have an optional notFoundHandler, we need to register it as a route - if (reactNotFound) { - routes.push(reactNotFound.getNotFoundRoute()); - } - - const renderedRoutes = renderRoutes(routes, layouts); - - const componentName = `${upperCaseFirst(name)}Routes`; - - await builder.apply( - typescriptFile.renderTemplateFile({ - id: `route-${id}`, - template: CORE_REACT_ROUTES_GENERATED.templates.index, - destination: `${directoryBase}/index.tsx`, - variables: { - TPL_ROUTE_HEADER: TsCodeUtils.mergeFragments( - new Map( - layouts.map( - (layout) => - [layout.key, layout.header] as [ - string, - TsCodeFragment | undefined, - ], - ), - ), - ), - TPL_ROUTES: renderedRoutes, - TPL_ROUTES_NAME: `${upperCaseFirst(name)}FeatureRoutes`, - }, - }), - ); - - reactRoutes.registerRoute({ - path: `${pathName}/*`, - layoutKey, - element: createRouteElement(componentName, directoryBase), - }); - } - }, }; }, }), diff --git a/packages/react-generators/src/generators/core/react-routes/templates/routes.tsx b/packages/react-generators/src/generators/core/react-routes/templates/routes.tsx deleted file mode 100644 index ab6ba20e2..000000000 --- a/packages/react-generators/src/generators/core/react-routes/templates/routes.tsx +++ /dev/null @@ -1,16 +0,0 @@ -// @ts-nocheck - -import type { ReactElement } from 'react'; - -import { Routes } from 'react-router-dom'; - -function TPL_ROUTES_NAME(): ReactElement { - TPL_ROUTE_HEADER; - return ( - - - - ); -} - -export default TPL_ROUTES_NAME; diff --git a/packages/react-generators/src/generators/core/react-sentry/extractor.json b/packages/react-generators/src/generators/core/react-sentry/extractor.json index 5f5b16837..cbe168a1c 100644 --- a/packages/react-generators/src/generators/core/react-sentry/extractor.json +++ b/packages/react-generators/src/generators/core/react-sentry/extractor.json @@ -10,6 +10,10 @@ "reactConfigImportsProvider": { "importName": "reactConfigImportsProvider", "packagePathSpecifier": "@baseplate-dev/react-generators:src/generators/core/react-config/generated/ts-import-providers.ts" + }, + "reactRouterImportsProvider": { + "importName": "reactRouterImportsProvider", + "packagePathSpecifier": "@baseplate-dev/react-generators:src/generators/core/react-router/generated/ts-import-providers.ts" } }, "pathRootRelativePath": "{src-root}/services/sentry.ts", diff --git a/packages/react-generators/src/generators/core/react-sentry/generated/template-renderers.ts b/packages/react-generators/src/generators/core/react-sentry/generated/template-renderers.ts index 59337dff6..1617659c3 100644 --- a/packages/react-generators/src/generators/core/react-sentry/generated/template-renderers.ts +++ b/packages/react-generators/src/generators/core/react-sentry/generated/template-renderers.ts @@ -5,6 +5,7 @@ import { typescriptFileProvider } from '@baseplate-dev/core-generators'; import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync'; import { reactConfigImportsProvider } from '#src/generators/core/react-config/generated/ts-import-providers.js'; +import { reactRouterImportsProvider } from '#src/generators/core/react-router/generated/ts-import-providers.js'; import { CORE_REACT_SENTRY_PATHS } from './template-paths.js'; import { CORE_REACT_SENTRY_TEMPLATES } from './typed-templates.js'; @@ -30,10 +31,11 @@ const coreReactSentryRenderersTask = createGeneratorTask({ dependencies: { paths: CORE_REACT_SENTRY_PATHS.provider, reactConfigImports: reactConfigImportsProvider, + reactRouterImports: reactRouterImportsProvider, typescriptFile: typescriptFileProvider, }, exports: { coreReactSentryRenderers: coreReactSentryRenderers.export() }, - run({ paths, reactConfigImports, typescriptFile }) { + run({ paths, reactConfigImports, reactRouterImports, typescriptFile }) { return { providers: { coreReactSentryRenderers: { @@ -44,6 +46,7 @@ const coreReactSentryRenderersTask = createGeneratorTask({ destination: paths.sentry, importMapProviders: { reactConfigImports, + reactRouterImports, }, ...options, }), diff --git a/packages/react-generators/src/generators/core/react-sentry/generated/typed-templates.ts b/packages/react-generators/src/generators/core/react-sentry/generated/typed-templates.ts index 801094d7a..0221e3fa3 100644 --- a/packages/react-generators/src/generators/core/react-sentry/generated/typed-templates.ts +++ b/packages/react-generators/src/generators/core/react-sentry/generated/typed-templates.ts @@ -2,10 +2,14 @@ import { createTsTemplateFile } from '@baseplate-dev/core-generators'; import path from 'node:path'; import { reactConfigImportsProvider } from '#src/generators/core/react-config/generated/ts-import-providers.js'; +import { reactRouterImportsProvider } from '#src/generators/core/react-router/generated/ts-import-providers.js'; const sentry = createTsTemplateFile({ fileOptions: { kind: 'singleton' }, - importMapProviders: { reactConfigImports: reactConfigImportsProvider }, + importMapProviders: { + reactConfigImports: reactConfigImportsProvider, + reactRouterImports: reactRouterImportsProvider, + }, name: 'sentry', projectExports: { logBreadcrumbToSentry: {}, logErrorToSentry: {} }, source: { diff --git a/packages/react-generators/src/generators/core/react-sentry/react-sentry.generator.ts b/packages/react-generators/src/generators/core/react-sentry/react-sentry.generator.ts index 6f4d71635..17d0ce331 100644 --- a/packages/react-generators/src/generators/core/react-sentry/react-sentry.generator.ts +++ b/packages/react-generators/src/generators/core/react-sentry/react-sentry.generator.ts @@ -6,9 +6,7 @@ import { packageScope, tsCodeFragment, TsCodeUtils, - tsHoistedFragment, tsImportBuilder, - typescriptFileProvider, } from '@baseplate-dev/core-generators'; import { createConfigProviderTask, @@ -21,12 +19,8 @@ import { z } from 'zod'; import { REACT_PACKAGES } from '#src/constants/react-packages.js'; import { authIdentifyProvider } from '#src/generators/auth/auth-identify/index.js'; -import { - reactConfigImportsProvider, - reactConfigProvider, -} from '../react-config/index.js'; +import { reactConfigProvider } from '../react-config/index.js'; import { reactErrorConfigProvider } from '../react-error/index.js'; -import { reactRouterConfigProvider } from '../react-router/index.js'; import { CORE_REACT_SENTRY_GENERATED } from './generated/index.js'; const descriptorSchema = z.object({}); @@ -55,6 +49,7 @@ export const reactSentryGenerator = createGenerator({ }), paths: CORE_REACT_SENTRY_GENERATED.paths.task, imports: CORE_REACT_SENTRY_GENERATED.imports.task, + renderers: CORE_REACT_SENTRY_GENERATED.renderers.task, reactError: createProviderTask( reactErrorConfigProvider, (reactErrorConfig) => { @@ -97,26 +92,14 @@ export const reactSentryGenerator = createGenerator({ }), main: createGeneratorTask({ dependencies: { - typescriptFile: typescriptFileProvider, - reactConfigImports: reactConfigImportsProvider, reactSentryConfigValues: reactSentryConfigValuesProvider, - paths: CORE_REACT_SENTRY_GENERATED.paths.provider, + renderers: CORE_REACT_SENTRY_GENERATED.renderers.provider, }, - run({ - typescriptFile, - reactConfigImports, - reactSentryConfigValues: { sentryScopeActions }, - paths, - }) { + run({ reactSentryConfigValues: { sentryScopeActions }, renderers }) { return { build: async (builder) => { await builder.apply( - typescriptFile.renderTemplateFile({ - template: CORE_REACT_SENTRY_GENERATED.templates.sentry, - destination: paths.sentry, - importMapProviders: { - reactConfigImports, - }, + renderers.sentry.render({ variables: { TPL_SENTRY_SCOPE_ACTIONS: TsCodeUtils.mergeFragments(sentryScopeActions), @@ -127,27 +110,5 @@ export const reactSentryGenerator = createGenerator({ }; }, }), - addRouterDomIntegration: createGeneratorTask({ - dependencies: { - reactRouterConfig: reactRouterConfigProvider, - }, - run({ reactRouterConfig }) { - reactRouterConfig.routesComponent.set( - tsCodeFragment('SentryRoutes', undefined, { - hoistedFragments: [ - tsHoistedFragment( - 'sentry-routes', - `const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes);`, - [ - tsImportBuilder().namespace('Sentry').from('@sentry/react'), - tsImportBuilder(['Routes']).from('react-router-dom'), - ], - ), - ], - }), - ); - return {}; - }, - }), }), }); diff --git a/packages/react-generators/src/generators/core/react-sentry/templates/src/services/sentry.ts b/packages/react-generators/src/generators/core/react-sentry/templates/src/services/sentry.ts index ff367b75a..8b0eaf10b 100644 --- a/packages/react-generators/src/generators/core/react-sentry/templates/src/services/sentry.ts +++ b/packages/react-generators/src/generators/core/react-sentry/templates/src/services/sentry.ts @@ -1,14 +1,8 @@ // @ts-nocheck import { config } from '%reactConfigImports'; +import { router } from '%reactRouterImports'; import * as Sentry from '@sentry/react'; -import React from 'react'; -import { - createRoutesFromChildren, - matchRoutes, - useLocation, - useNavigationType, -} from 'react-router-dom'; const SENTRY_ENABLED = !!config.VITE_SENTRY_DSN; const TRACE_SAMPLE_RATE = 1; @@ -17,15 +11,7 @@ if (SENTRY_ENABLED) { Sentry.init({ dsn: config.VITE_SENTRY_DSN, environment: config.VITE_ENVIRONMENT, - integrations: [ - Sentry.reactRouterV6BrowserTracingIntegration({ - useEffect: React.useEffect, - useLocation, - useNavigationType, - createRoutesFromChildren, - matchRoutes, - }), - ], + integrations: [Sentry.tanstackRouterBrowserTracingIntegration(router)], tracesSampleRate: TRACE_SAMPLE_RATE, }); } diff --git a/packages/react-generators/src/generators/core/react/extractor.json b/packages/react-generators/src/generators/core/react/extractor.json index 6287eb6bf..4f982acfb 100644 --- a/packages/react-generators/src/generators/core/react/extractor.json +++ b/packages/react-generators/src/generators/core/react/extractor.json @@ -37,13 +37,13 @@ "pathRootRelativePath": "{package-root}/vite.config.ts", "variables": { "TPL_CONFIG": {} } }, - "src/index.tsx": { - "name": "index", + "src/main.tsx": { + "name": "main", "type": "ts", "fileOptions": { "kind": "singleton" }, "generator": "@baseplate-dev/react-generators#core/react", "importMapProviders": {}, - "pathRootRelativePath": "{src-root}/index.tsx", + "pathRootRelativePath": "{src-root}/main.tsx", "variables": { "TPL_APP": {}, "TPL_HEADER": {} } }, "src/vite-env.d.ts": { diff --git a/packages/react-generators/src/generators/core/react/generated/template-paths.ts b/packages/react-generators/src/generators/core/react/generated/template-paths.ts index c2f551cff..4d1d1efab 100644 --- a/packages/react-generators/src/generators/core/react/generated/template-paths.ts +++ b/packages/react-generators/src/generators/core/react/generated/template-paths.ts @@ -6,7 +6,7 @@ export interface CoreReactPaths { readme: string; viteEnv: string; viteConfig: string; - index: string; + main: string; favicon: string; } @@ -23,8 +23,8 @@ const coreReactPathsTask = createGeneratorTask({ providers: { coreReactPaths: { favicon: `${packageRoot}/public/favicon.ico`, - index: `${srcRoot}/index.tsx`, indexHtml: `${packageRoot}/index.html`, + main: `${srcRoot}/main.tsx`, readme: `${packageRoot}/README.md`, viteConfig: `${packageRoot}/vite.config.ts`, viteEnv: `${srcRoot}/vite-env.d.ts`, diff --git a/packages/react-generators/src/generators/core/react/generated/template-renderers.ts b/packages/react-generators/src/generators/core/react/generated/template-renderers.ts index 1126eea45..0d95a6693 100644 --- a/packages/react-generators/src/generators/core/react/generated/template-renderers.ts +++ b/packages/react-generators/src/generators/core/react/generated/template-renderers.ts @@ -8,10 +8,10 @@ import { CORE_REACT_PATHS } from './template-paths.js'; import { CORE_REACT_TEMPLATES } from './typed-templates.js'; export interface CoreReactRenderers { - index: { + main: { render: ( options: Omit< - RenderTsTemplateFileActionInput, + RenderTsTemplateFileActionInput, 'destination' | 'importMapProviders' | 'template' >, ) => BuilderAction; @@ -40,11 +40,11 @@ const coreReactRenderersTask = createGeneratorTask({ return { providers: { coreReactRenderers: { - index: { + main: { render: (options) => typescriptFile.renderTemplateFile({ - template: CORE_REACT_TEMPLATES.index, - destination: paths.index, + template: CORE_REACT_TEMPLATES.main, + destination: paths.main, ...options, }), }, diff --git a/packages/react-generators/src/generators/core/react/generated/typed-templates.ts b/packages/react-generators/src/generators/core/react/generated/typed-templates.ts index fabe77aa3..1e9e9d699 100644 --- a/packages/react-generators/src/generators/core/react/generated/typed-templates.ts +++ b/packages/react-generators/src/generators/core/react/generated/typed-templates.ts @@ -16,12 +16,12 @@ const favicon = createRawTemplateFile({ fileOptions: { kind: 'singleton' }, }); -const index = createTsTemplateFile({ +const main = createTsTemplateFile({ fileOptions: { kind: 'singleton' }, importMapProviders: {}, - name: 'index', + name: 'main', source: { - path: path.join(import.meta.dirname, '../templates/src/index.tsx'), + path: path.join(import.meta.dirname, '../templates/src/main.tsx'), }, variables: { TPL_APP: {}, TPL_HEADER: {} }, }); @@ -71,4 +71,4 @@ const viteConfig = createTsTemplateFile({ variables: { TPL_CONFIG: {} }, }); -export const CORE_REACT_TEMPLATES = { staticGroup, viteConfig, index, favicon }; +export const CORE_REACT_TEMPLATES = { staticGroup, viteConfig, main, favicon }; diff --git a/packages/react-generators/src/generators/core/react/react.generator.ts b/packages/react-generators/src/generators/core/react/react.generator.ts index 434bb28a0..a4bb07e41 100644 --- a/packages/react-generators/src/generators/core/react/react.generator.ts +++ b/packages/react-generators/src/generators/core/react/react.generator.ts @@ -153,8 +153,8 @@ export const reactGenerator = createGenerator({ await builder.apply( typescriptFile.renderTemplateFile({ - template: CORE_REACT_GENERATED.templates.index, - destination: paths.index, + template: CORE_REACT_GENERATED.templates.main, + destination: paths.main, variables: { TPL_APP: appFragment ?? '
', TPL_HEADER: TsCodeUtils.mergeFragments( diff --git a/packages/react-generators/src/generators/core/react/templates/package/index.html b/packages/react-generators/src/generators/core/react/templates/package/index.html index 959e67cee..12f660a97 100644 --- a/packages/react-generators/src/generators/core/react/templates/package/index.html +++ b/packages/react-generators/src/generators/core/react/templates/package/index.html @@ -10,6 +10,6 @@
- + diff --git a/packages/react-generators/src/generators/core/react/templates/src/index.tsx b/packages/react-generators/src/generators/core/react/templates/src/main.tsx similarity index 100% rename from packages/react-generators/src/generators/core/react/templates/src/index.tsx rename to packages/react-generators/src/generators/core/react/templates/src/main.tsx diff --git a/packages/react-generators/src/providers/routes.ts b/packages/react-generators/src/providers/routes.ts index db3a131d5..91c08194d 100644 --- a/packages/react-generators/src/providers/routes.ts +++ b/packages/react-generators/src/providers/routes.ts @@ -17,21 +17,9 @@ export interface ReactRoute { } export interface ReactRoutesProvider { - registerLayout(layout: ReactRouteLayout): void; - registerRoute(route: ReactRoute): void; getRoutePrefix(): string; getDirectoryBase(): string; } export const reactRoutesProvider = createProviderType('react-routes'); - -export interface ReactRoutesReadOnlyProvider { - getRoutePrefix(): string; - getDirectoryBase(): string; -} - -export const reactRoutesReadOnlyProvider = - createProviderType('react-routes-read-only', { - isReadOnly: true, - }); diff --git a/packages/react-generators/src/utils/case.ts b/packages/react-generators/src/utils/case.ts index 658b1577e..9aecbfbff 100644 --- a/packages/react-generators/src/utils/case.ts +++ b/packages/react-generators/src/utils/case.ts @@ -1,4 +1,4 @@ -import { dasherize, titleize, underscore } from 'inflection'; +import { titleize, underscore } from 'inflection'; export function lowerCaseFirst(str: string): string { if (str.length === 0) { @@ -14,10 +14,6 @@ export function upperCaseFirst(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1); } -export function dasherizeCamel(str: string): string { - return dasherize(underscore(lowerCaseFirst(str))); -} - export function titleizeCamel(str: string): string { return titleize(underscore(str)); } diff --git a/packages/sync/src/output/clean-deleted-files.ts b/packages/sync/src/output/clean-deleted-files.ts index cd26613b6..074d60cae 100644 --- a/packages/sync/src/output/clean-deleted-files.ts +++ b/packages/sync/src/output/clean-deleted-files.ts @@ -95,6 +95,9 @@ export async function cleanDeletedFiles({ path.join(outputDirectory, relativePath), ), outputDirectory, + { + ignoreFiles: ['.template-metadata.json'], + }, ); return { diff --git a/packages/sync/src/utils/directories.ts b/packages/sync/src/utils/directories.ts index 3184c3b9a..92cf6ca96 100644 --- a/packages/sync/src/utils/directories.ts +++ b/packages/sync/src/utils/directories.ts @@ -1,18 +1,68 @@ import { promises as fs } from 'node:fs'; import path from 'node:path'; -export async function isDirectoryEmpty(dirPath: string): Promise { +interface DirectoryOptions { + ignoreFiles?: string[]; +} + +/** + * Checks if a directory is empty, optionally ignoring specified files. + * + * @param dirPath - Path to the directory to check + * @param options - Options including ignoreFiles array + * @param options.ignoreFiles - Array of file names to ignore when determining if directory is empty + * @returns Promise that resolves to true if directory is empty (or only contains ignored files), false otherwise + * + * @example + * // Check if directory is empty, ignoring .gitkeep files + * const isEmpty = await isDirectoryEmpty('/path/to/dir', { ignoreFiles: ['.gitkeep'] }); + */ +export async function isDirectoryEmpty( + dirPath: string, + options: DirectoryOptions = {}, +): Promise { try { const files = await fs.readdir(dirPath); - return files.length === 0; + const { ignoreFiles = [] } = options; + + // Filter out ignored files + const nonIgnoredFiles = files.filter((file) => !ignoreFiles.includes(file)); + return nonIgnoredFiles.length === 0; } catch { return false; } } +/** + * Removes empty ancestor directories starting from the parent directories of the given file paths. + * + * A directory is considered "empty" if it contains no files, or only contains files that are + * specified in the ignoreFiles option. Directories that only contain ignored files will be + * removed along with their ignored files. + * + * The function traverses up the directory tree from each file path's parent directory, + * removing empty directories until it encounters: + * - A directory that contains non-ignored files + * - The specified stop directory + * - The filesystem root + * + * @param filePaths - Array of file paths whose parent directories should be checked for removal + * @param stopAt - Directory path to stop at (this directory will not be removed) + * @param options - Options including ignoreFiles array + * @param options.ignoreFiles - Array of file names to ignore when determining if directory is empty + * + * @example + * // Remove empty directories after deleting files, ignoring .gitkeep files + * await removeEmptyAncestorDirectories( + * ['/project/src/components/Button.tsx'], + * '/project', + * { ignoreFiles: ['.gitkeep'] } + * ); + */ export async function removeEmptyAncestorDirectories( filePaths: string[], stopAt: string, + options: DirectoryOptions = {}, ): Promise { // Get unique parent directories from the file paths const uniqueParentDirs = [ @@ -32,11 +82,13 @@ export async function removeEmptyAncestorDirectories( ) { parsedDirs.add(currentDir); try { - if (await isDirectoryEmpty(currentDir)) { - await fs.rmdir(currentDir); + if (await isDirectoryEmpty(currentDir, options)) { + // Directory is empty (or only contains ignored files), remove it + await fs.rm(currentDir, { recursive: true, force: true }); currentDir = path.dirname(currentDir); } else { - break; // Stop if directory is not empty + // Directory contains non-ignored files, stop here + break; } } catch { break; // Stop on any error (e.g., permissions, non-existent directory) diff --git a/packages/sync/src/utils/directories.unit.test.ts b/packages/sync/src/utils/directories.unit.test.ts index d43364200..c8d71ab29 100644 --- a/packages/sync/src/utils/directories.unit.test.ts +++ b/packages/sync/src/utils/directories.unit.test.ts @@ -1,5 +1,4 @@ import { vol } from 'memfs'; -import fs from 'node:fs/promises'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { @@ -7,150 +6,195 @@ import { removeEmptyAncestorDirectories, } from './directories.js'; -// Mock the fs module vi.mock('node:fs'); vi.mock('node:fs/promises'); -beforeEach(() => { - vol.reset(); -}); +describe('directories', () => { + beforeEach(() => { + vol.reset(); + }); -describe('directory cleanup functions', () => { describe('isDirectoryEmpty', () => { it('should return true for empty directory', async () => { - // Setup - vol.mkdirSync('/empty'); - - // Test - const result = await isDirectoryEmpty('/empty'); + vol.mkdirSync('/empty-dir'); - // Assert + const result = await isDirectoryEmpty('/empty-dir'); expect(result).toBe(true); }); - it('should return false for non-empty directory', async () => { - // Setup - vol.mkdirSync('/nonempty'); - vol.writeFileSync('/nonempty/file.txt', 'content'); - - // Test - const result = await isDirectoryEmpty('/nonempty'); + it('should return false for directory with files', async () => { + vol.fromJSON({ + '/non-empty-dir/file.txt': 'content', + }); - // Assert + const result = await isDirectoryEmpty('/non-empty-dir'); expect(result).toBe(false); }); - it('should return false for non-existent directory', async () => { - const result = await isDirectoryEmpty('/nonexistent'); + it('should return false when directory does not exist', async () => { + const result = await isDirectoryEmpty('/non-existent-dir'); expect(result).toBe(false); }); - }); - describe('removeEmptyAncestorDirectories', () => { - it('should remove empty parent directories', async () => { - // Setup - const filePath = '/root/empty1/empty2/file.txt'; + it('should return true when directory only contains ignored files', async () => { vol.fromJSON({ - [filePath]: 'content', + '/dir-with-ignored/.gitkeep': '', + '/dir-with-ignored/.DS_Store': '', }); - await fs.unlink(filePath); - // Test - await removeEmptyAncestorDirectories([filePath], '/root'); + const result = await isDirectoryEmpty('/dir-with-ignored', { + ignoreFiles: ['.gitkeep', '.DS_Store'], + }); + expect(result).toBe(true); + }); - // Assert - expect(vol.existsSync('/root/empty1/empty2')).toBe(false); - expect(vol.existsSync('/root/empty1')).toBe(false); - expect(vol.existsSync('/root')).toBe(true); + it('should return false when directory contains both ignored and non-ignored files', async () => { + vol.fromJSON({ + '/mixed-dir/.gitkeep': '', + '/mixed-dir/important.txt': 'content', + }); + + const result = await isDirectoryEmpty('/mixed-dir', { + ignoreFiles: ['.gitkeep'], + }); + expect(result).toBe(false); }); - it('should stop at non-empty directories', async () => { - // Setup - const filePath = '/root/dir1/empty/file1.txt'; + it('should handle empty ignoreFiles array', async () => { vol.fromJSON({ - [filePath]: 'content', - '/root/dir1/other-file.txt': 'content', + '/test-dir/file.txt': 'content', }); - await fs.unlink(filePath); + const result = await isDirectoryEmpty('/test-dir', { ignoreFiles: [] }); + expect(result).toBe(false); + }); + }); - // Test - await removeEmptyAncestorDirectories([filePath], '/root'); + describe('removeEmptyAncestorDirectories', () => { + it('should remove empty ancestor directories', async () => { + // Setup: Create file structure + vol.fromJSON({ + '/root/level1/level2/level3/file.txt': 'content', + }); - // Assert - expect(vol.existsSync('/root/dir1/empty')).toBe(false); - expect(vol.existsSync('/root/dir1')).toBe(true); + // Simulate file deletion using memfs directly + vol.unlinkSync('/root/level1/level2/level3/file.txt'); + + // Test: Remove empty directories + await removeEmptyAncestorDirectories( + ['/root/level1/level2/level3/file.txt'], + '/root', + ); + + // Assert: Directories should be removed + const files = vol.toJSON(); + expect(files['/root/level1/level2/level3/file.txt']).toBeUndefined(); + expect(files['/root/level1/level2/level3/']).toBeUndefined(); + expect(files['/root/level1/level2/']).toBeUndefined(); + expect(files['/root/level1/']).toBeUndefined(); expect(vol.existsSync('/root')).toBe(true); }); - it('should stop at specified stopAt directory', async () => { - // Setup - const filePath = '/root/dir1/empty/file1.txt'; + it('should stop at specified directory', async () => { + // Setup: Create file structure + vol.fromJSON({ + '/root/level1/level2/level3/file.txt': 'content', + }); + + // Simulate file deletion using memfs directly + vol.unlinkSync('/root/level1/level2/level3/file.txt'); + + // Test: Remove empty directories with stop point + await removeEmptyAncestorDirectories( + ['/root/level1/level2/level3/file.txt'], + '/root/level1', + ); + + // Assert: Should stop at level1 + const files = vol.toJSON(); + expect(files['/root/level1/level2/level3/file.txt']).toBeUndefined(); + expect(vol.existsSync('/root/level1/level2/level3/')).toBe(false); + expect(vol.existsSync('/root/level1/level2/')).toBe(false); + expect(vol.existsSync('/root/level1/')).toBe(true); // Should not be removed + }); + + it('should not remove directories that are not empty', async () => { + // Setup: Create file structure with multiple files vol.fromJSON({ - [filePath]: 'content', + '/root/level1/level2/level3/file1.txt': 'content1', + '/root/level1/level2/level3/file2.txt': 'content2', }); - await fs.unlink(filePath); - // Test - await removeEmptyAncestorDirectories([filePath], '/root/dir1'); + // Simulate deletion of only one file using memfs directly + vol.unlinkSync('/root/level1/level2/level3/file1.txt'); + + // Test: Remove empty directories + await removeEmptyAncestorDirectories( + ['/root/level1/level2/level3/file1.txt'], + '/root', + ); - // Assert - expect(vol.existsSync('/root/dir1/empty')).toBe(false); - expect(vol.existsSync('/root/dir1')).toBe(true); + // Assert: Directory should not be removed because it still has file2.txt + const files = vol.toJSON(); + expect(files['/root/level1/level2/level3/file1.txt']).toBeUndefined(); + expect(files['/root/level1/level2/level3/file2.txt']).toBe('content2'); + expect(vol.existsSync('/root/level1/level2/level3/')).toBe(true); // Should not be removed }); it('should handle multiple file paths', async () => { - // Setup + // Setup: Create file structure vol.fromJSON({ - '/root/dir1/empty1/file1.txt': 'content', - '/root/dir2/empty2/file2.txt': 'content', - '/root/dir3/empty3/file3.txt': 'content', - '/root/dir3/other-file.txt': 'content', + '/root/level1/level2/file1.txt': 'content1', + '/root/level1/level2/file2.txt': 'content2', }); - const filePaths = [ - '/root/dir1/empty1/file1.txt', - '/root/dir2/empty2/file2.txt', - '/root/dir3/empty3/file3.txt', - ]; - - for (const filePath of filePaths) { - await fs.unlink(filePath); - } - - // Test - await removeEmptyAncestorDirectories(filePaths, '/root'); - - // Assert - expect(vol.existsSync('/root/dir1/empty1')).toBe(false); - expect(vol.existsSync('/root/dir2/empty2')).toBe(false); - expect(vol.existsSync('/root/dir3/empty3')).toBe(false); - expect(vol.existsSync('/root/dir1')).toBe(false); - expect(vol.existsSync('/root/dir2')).toBe(false); - expect(vol.existsSync('/root/dir3')).toBe(true); - expect(vol.existsSync('/root')).toBe(true); + // Simulate deletion of both files using memfs directly + vol.unlinkSync('/root/level1/level2/file1.txt'); + vol.unlinkSync('/root/level1/level2/file2.txt'); + + // Test: Remove empty directories + await removeEmptyAncestorDirectories( + ['/root/level1/level2/file1.txt', '/root/level1/level2/file2.txt'], + '/root', + ); + + // Assert: Directories should be removed + const files = vol.toJSON(); + expect(files['/root/level1/level2/file1.txt']).toBeUndefined(); + expect(files['/root/level1/level2/file2.txt']).toBeUndefined(); + expect(files['/root/level1/level2/']).toBeUndefined(); + expect(files['/root/level1/']).toBeUndefined(); }); - it('should handle errors gracefully', async () => { - // Setup + it('should remove directories that only contain ignored files', async () => { + // Setup: Create file structure with ignored files vol.fromJSON({ - '/root/dir1/empty/file1.txt': 'content', + '/root/level1/level2/level3/file.txt': 'content', + '/root/level1/level2/.gitkeep': '', }); - // Mock fs.rmdir to throw an error - const mockReaddir = vi.spyOn(fs, 'readdir'); - mockReaddir.mockRejectedValueOnce(new Error('Permission denied')); - - const filePath = '/root/dir1/empty/file1.txt'; - await fs.unlink(filePath); + // Simulate file deletion using memfs directly + vol.unlinkSync('/root/level1/level2/level3/file.txt'); + + // Test: Remove empty directories with ignoreFiles + await removeEmptyAncestorDirectories( + ['/root/level1/level2/level3/file.txt'], + '/root', + { ignoreFiles: ['.gitkeep'] }, + ); + + // Assert: level3 should be removed, and level2 should also be removed (including .gitkeep) + const files = vol.toJSON(); + expect(files['/root/level1/level2/level3/file.txt']).toBeUndefined(); + expect(files['/root/level1/level2/.gitkeep']).toBeUndefined(); // Should be removed with directory + expect(vol.existsSync('/root/level1/level2/level3/')).toBe(false); // Should be removed + expect(vol.existsSync('/root/level1/level2/')).toBe(false); // Should be removed (only contained ignored files) + }); - // Test + it('should handle non-existent directories gracefully', async () => { await expect( - removeEmptyAncestorDirectories([filePath], '/root'), + removeEmptyAncestorDirectories(['/non/existent/file.txt'], '/root'), ).resolves.not.toThrow(); - - // Assert - expect(mockReaddir).toHaveBeenCalled(); }); }); }); diff --git a/packages/tools/eslint-configs/rules/no-unused-generator-dependencies.js b/packages/tools/eslint-configs/rules/no-unused-generator-dependencies.js index 2573c548b..69e4e302c 100644 --- a/packages/tools/eslint-configs/rules/no-unused-generator-dependencies.js +++ b/packages/tools/eslint-configs/rules/no-unused-generator-dependencies.js @@ -108,6 +108,22 @@ export default { fix(fixer) { // Remove the entire property from the dependencies object // This handles commas and whitespace correctly for object properties. + const sourceCode = context.sourceCode; + const nextToken = sourceCode.getTokenAfter(depProp); + + // If there's a comma after this property, remove it too + if ( + nextToken && + nextToken.type === 'Punctuator' && + nextToken.value === ',' && + depProp.range + ) { + return fixer.removeRange([ + depProp.range[0], + nextToken.range[1], + ]); + } + return fixer.remove(depProp); }, }); diff --git a/packages/utils/src/string/convert-case-with-prefix.test.ts b/packages/utils/src/string/convert-case-with-prefix.test.ts new file mode 100644 index 000000000..b373367ad --- /dev/null +++ b/packages/utils/src/string/convert-case-with-prefix.test.ts @@ -0,0 +1,291 @@ +import { describe, expect, it } from 'vitest'; + +import { convertCaseWithPrefix } from './convert-case-with-prefix.js'; + +// Simple case conversion functions for testing +const kebabCase = (str: string): string => + str.replaceAll(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); +const camelCase = (str: string): string => + str.replaceAll(/[-_](.)/g, (_, char: string) => char.toUpperCase()); +const snakeCase = (str: string): string => + str.replaceAll(/([a-z])([A-Z])/g, '$1_$2').toLowerCase(); +const pascalCase = (str: string): string => { + const camel = camelCase(str); + return camel.charAt(0).toUpperCase() + camel.slice(1); +}; + +describe('convertCaseWithPrefix', () => { + describe('with kebabCase converter', () => { + it('converts normal strings to kebab-case', () => { + // Arrange + const input = 'layoutTest'; + + // Act + const result = convertCaseWithPrefix(input, kebabCase); + + // Assert + expect(result).toBe('layout-test'); + }); + + it('preserves underscore prefix and converts middle part', () => { + // Arrange + const input = '_layoutTest'; + + // Act + const result = convertCaseWithPrefix(input, kebabCase); + + // Assert + expect(result).toBe('_layout-test'); + }); + + it('preserves double underscore prefix and converts middle part', () => { + // Arrange + const input = '__privateHelper'; + + // Act + const result = convertCaseWithPrefix(input, kebabCase); + + // Assert + expect(result).toBe('__private-helper'); + }); + + it('preserves suffix and converts middle part', () => { + // Arrange + const input = 'layoutTest_'; + + // Act + const result = convertCaseWithPrefix(input, kebabCase); + + // Assert + expect(result).toBe('layout-test_'); + }); + + it('preserves both prefix and suffix', () => { + // Arrange + const input = '_layoutTest_'; + + // Act + const result = convertCaseWithPrefix(input, kebabCase); + + // Assert + expect(result).toBe('_layout-test_'); + }); + + it('handles multiple non-alphanumeric characters in prefix', () => { + // Arrange + const input = '___layoutTest'; + + // Act + const result = convertCaseWithPrefix(input, kebabCase); + + // Assert + expect(result).toBe('___layout-test'); + }); + + it('handles multiple non-alphanumeric characters in suffix', () => { + // Arrange + const input = 'layoutTest___'; + + // Act + const result = convertCaseWithPrefix(input, kebabCase); + + // Assert + expect(result).toBe('layout-test___'); + }); + + it('handles mixed non-alphanumeric characters in prefix', () => { + // Arrange + const input = '_-$layoutTest'; + + // Act + const result = convertCaseWithPrefix(input, kebabCase); + + // Assert + expect(result).toBe('_-$layout-test'); + }); + + it('handles mixed non-alphanumeric characters in suffix', () => { + // Arrange + const input = 'layoutTest_-$'; + + // Act + const result = convertCaseWithPrefix(input, kebabCase); + + // Assert + expect(result).toBe('layout-test_-$'); + }); + }); + + describe('with camelCase converter', () => { + it('converts normal strings to camelCase', () => { + // Arrange + const input = 'layout_test'; + + // Act + const result = convertCaseWithPrefix(input, camelCase); + + // Assert + expect(result).toBe('layoutTest'); + }); + + it('preserves prefix and converts middle part', () => { + // Arrange + const input = '_layout_test'; + + // Act + const result = convertCaseWithPrefix(input, camelCase); + + // Assert + expect(result).toBe('_layoutTest'); + }); + + it('preserves suffix and converts middle part', () => { + // Arrange + const input = 'layout_test_'; + + // Act + const result = convertCaseWithPrefix(input, camelCase); + + // Assert + expect(result).toBe('layoutTest_'); + }); + }); + + describe('with snakeCase converter', () => { + it('converts normal strings to snake_case', () => { + // Arrange + const input = 'layoutTest'; + + // Act + const result = convertCaseWithPrefix(input, snakeCase); + + // Assert + expect(result).toBe('layout_test'); + }); + + it('preserves prefix and converts middle part', () => { + // Arrange + const input = '_layoutTest'; + + // Act + const result = convertCaseWithPrefix(input, snakeCase); + + // Assert + expect(result).toBe('_layout_test'); + }); + }); + + describe('with pascalCase converter', () => { + it('converts normal strings to PascalCase', () => { + // Arrange + const input = 'layout_test'; + + // Act + const result = convertCaseWithPrefix(input, pascalCase); + + // Assert + expect(result).toBe('LayoutTest'); + }); + + it('preserves prefix and converts middle part', () => { + // Arrange + const input = '_layout_test'; + + // Act + const result = convertCaseWithPrefix(input, pascalCase); + + // Assert + expect(result).toBe('_LayoutTest'); + }); + }); + + describe('edge cases', () => { + it('returns input unchanged when entire string is non-alphanumeric', () => { + // Arrange + const input = '[id]'; + + // Act + const result = convertCaseWithPrefix(input, kebabCase); + + // Assert + expect(result).toBe('[id]'); + }); + + it('returns input unchanged when entire string is non-alphanumeric with multiple characters', () => { + // Arrange + const input = '_-$@#'; + + // Act + const result = convertCaseWithPrefix(input, kebabCase); + + // Assert + expect(result).toBe('_-$@#'); + }); + + it('handles empty string', () => { + // Arrange + const input = ''; + + // Act + const result = convertCaseWithPrefix(input, kebabCase); + + // Assert + expect(result).toBe(''); + }); + + it('handles string with only alphanumeric characters', () => { + // Arrange + const input = 'testString'; + + // Act + const result = convertCaseWithPrefix(input, kebabCase); + + // Assert + expect(result).toBe('test-string'); + }); + + it('handles string with only non-alphanumeric characters in middle', () => { + // Arrange + const input = 'test___string'; + + // Act + const result = convertCaseWithPrefix(input, kebabCase); + + // Assert + expect(result).toBe('test___string'); + }); + + it('handles complex mixed case scenarios', () => { + // Arrange + const input = '___MyComponentName___'; + + // Act + const result = convertCaseWithPrefix(input, kebabCase); + + // Assert + expect(result).toBe('___my-component-name___'); + }); + + it('handles numbers in the string', () => { + // Arrange + const input = '_test123String'; + + // Act + const result = convertCaseWithPrefix(input, kebabCase); + + // Assert + expect(result).toBe('_test123string'); + }); + + it('handles special characters like brackets', () => { + // Arrange + const input = '[testString]'; + + // Act + const result = convertCaseWithPrefix(input, kebabCase); + + // Assert + expect(result).toBe('[test-string]'); + }); + }); +}); diff --git a/packages/utils/src/string/convert-case-with-prefix.ts b/packages/utils/src/string/convert-case-with-prefix.ts new file mode 100644 index 000000000..d2e72135c --- /dev/null +++ b/packages/utils/src/string/convert-case-with-prefix.ts @@ -0,0 +1,49 @@ +/** + * Converts a string to the specified case while preserving non-alphanumeric prefixes and suffixes. + * + * @param input - The string to convert + * @param caseConverter - The case conversion function to apply + * @returns The converted string with preserved prefix and suffix + * + * @example + * ```typescript + * convertCaseWithPrefix('_layoutTest', kebabCase) + * // Returns: '_layout-test' + * + * convertCaseWithPrefix('__privateHelper', kebabCase) + * // Returns: '__private-helper' + * + * convertCaseWithPrefix('[id]', kebabCase) + * // Returns: '[id]' + * ``` + */ +export function convertCaseWithPrefix( + input: string, + caseConverter: (str: string) => string, +): string { + // Handle special pattern where the entire string is non-alphanumeric (like "[id]") + if (!/[a-zA-Z0-9]/.test(input)) { + return input; + } + + // Extract non-alphanumeric prefix + const prefixMatch = /^([^a-zA-Z0-9]*)/.exec(input); + const prefix = prefixMatch?.[1] ?? ''; + + // Extract non-alphanumeric suffix + const suffixMatch = /([^a-zA-Z0-9]*)$/.exec(input); + const suffix = suffixMatch?.[1] ?? ''; + + // Get the middle part (everything between prefix and suffix) + const startIndex = prefix.length; + const endIndex = input.length - suffix.length; + const middlePart = input.slice(startIndex, endIndex); + + // Only apply case conversion to the middle part if it contains alphanumeric characters + const convertedMiddlePart = + middlePart && /[a-zA-Z0-9]/.test(middlePart) + ? caseConverter(middlePart) + : middlePart; + + return prefix + convertedMiddlePart + suffix; +} diff --git a/packages/utils/src/string/index.ts b/packages/utils/src/string/index.ts index 07a45ad5b..5bcf64bb1 100644 --- a/packages/utils/src/string/index.ts +++ b/packages/utils/src/string/index.ts @@ -1,2 +1,3 @@ +export * from './convert-case-with-prefix.js'; export * from './quot.js'; export * from './random-key.js'; diff --git a/packages/utils/src/toposort/toposort.ts b/packages/utils/src/toposort/toposort.ts index cb748692e..7f064190d 100644 --- a/packages/utils/src/toposort/toposort.ts +++ b/packages/utils/src/toposort/toposort.ts @@ -89,18 +89,23 @@ function detectCycle( return false; } - // For cycle detection, we need to find nodes that weren't visited - const unvistedNodeIdx = nodes.findIndex((node, idx) => !visited.has(idx)); - - if (unvistedNodeIdx === -1) { - return []; + // For cycle detection, we need to try all unvisited nodes + for (let i = 0; i < nodes.length; i++) { + if (!visited.has(i)) { + // Reset path and visitSet for each starting node + path.length = 0; + visitSet.clear(); + + const cycleFound = dfs(i); + + if (cycleFound) { + // Convert path indices to actual nodes + return path.map((idx) => nodes[idx]); + } + } } - // Start DFS from any unvisited node - dfs(unvistedNodeIdx); - - // Convert path indices to actual nodes - return path.map((idx) => nodes[idx]); + return []; } /** diff --git a/packages/utils/src/toposort/toposort.unit.test.ts b/packages/utils/src/toposort/toposort.unit.test.ts index 3a4cbbd0b..3aaf0ac1a 100644 --- a/packages/utils/src/toposort/toposort.unit.test.ts +++ b/packages/utils/src/toposort/toposort.unit.test.ts @@ -252,4 +252,62 @@ describe('toposort', () => { expect(sortedReverse).toEqual(['a', 'c', 'b']); expectOrder(sorted, edges); }); + + it('should throw ToposortCyclicalDependencyError for complex graph with cycle', () => { + const nodes = ['a', 'b', 'c', 'd', 'e', 'f', 'g']; + const edges: [string, string][] = [ + // Non-cyclical dependencies + ['a', 'b'], + ['a', 'c'], + ['b', 'd'], + ['c', 'e'], + ['d', 'f'], + ['e', 'f'], + // Cyclical dependencies + ['f', 'g'], + ['g', 'c'], // Creates cycle: c -> e -> f -> g -> c + ]; + try { + toposort(nodes, edges); + } catch (e) { + expect(e).toBeInstanceOf(ToposortCyclicalDependencyError); + const { cyclePath } = e as ToposortCyclicalDependencyError; + // The cycle should contain: c -> e -> f -> g -> c + expect(cyclePath).toContain('c'); + expect(cyclePath).toContain('e'); + expect(cyclePath).toContain('f'); + expect(cyclePath).toContain('g'); + expect(cyclePath[0]).toBe(cyclePath.at(-1)); // Should start and end with same node + expect(cyclePath.length).toBeGreaterThan(3); // Should have at least 4 nodes in the cycle + } + }); + + it('should detect cycle in disconnected components where first unvisited node has no cycle', () => { + // This test case reproduces the bug where cycle detection would fail + // if the first unvisited node doesn't lead to a cycle, but other unvisited nodes do + const nodes = ['1', 'a', 'b', 'c', 'd', 'z']; + const edges: [string, string][] = [ + // Create a scenario where some nodes are processed by topological sort + // but 'isolated' will be only depended on by the cycle + ['b', '1'], + ['b', 'z'], + ['b', 'c'], + ['c', 'd'], + ['d', 'b'], // Creates cycle: b -> c -> d -> b + ]; + + try { + toposort(nodes, edges); + throw new Error('Expected ToposortCyclicalDependencyError'); + } catch (e) { + expect(e).toBeInstanceOf(ToposortCyclicalDependencyError); + const { cyclePath } = e as ToposortCyclicalDependencyError; + // Should find the cycle despite 'b' being the first unvisited node + expect(cyclePath.length).toBeGreaterThan(0); + expect(cyclePath).toContain('b'); + expect(cyclePath).toContain('c'); + expect(cyclePath).toContain('d'); + expect(cyclePath[0]).toBe(cyclePath.at(-1)); // Should start and end with same node + } + }); }); diff --git a/plugins/plugin-auth/src/auth0/core/node.ts b/plugins/plugin-auth/src/auth0/core/node.ts index 11681176f..f356e3d11 100644 --- a/plugins/plugin-auth/src/auth0/core/node.ts +++ b/plugins/plugin-auth/src/auth0/core/node.ts @@ -82,7 +82,6 @@ export default createPlatformPluginExport({ auth0Apollo: auth0ApolloGenerator({}), auth0Components: auth0ComponentsGenerator({}), auth0Callback: reactRoutesGenerator({ - id: 'auth', name: 'auth', children: { auth: auth0CallbackGenerator({}), @@ -104,7 +103,6 @@ export default createPlatformPluginExport({ auth0Apollo: auth0ApolloGenerator({}), auth0Components: auth0ComponentsGenerator({}), auth0Callback: reactRoutesGenerator({ - id: 'auth', name: 'auth', children: { auth: auth0CallbackGenerator({}), diff --git a/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/auth0-callback.generator.ts b/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/auth0-callback.generator.ts index 07710cd0f..ec37c0a9a 100644 --- a/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/auth0-callback.generator.ts +++ b/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/auth0-callback.generator.ts @@ -1,10 +1,8 @@ import { typescriptFileProvider } from '@baseplate-dev/core-generators'; import { authHooksImportsProvider, - createRouteElement, reactComponentsImportsProvider, reactErrorImportsProvider, - reactRoutesProvider, } from '@baseplate-dev/react-generators'; import { createGenerator, createGeneratorTask } from '@baseplate-dev/sync'; import { z } from 'zod'; @@ -25,7 +23,6 @@ export const auth0CallbackGenerator = createGenerator({ reactComponentsImports: reactComponentsImportsProvider, authHooksImports: authHooksImportsProvider, reactErrorImports: reactErrorImportsProvider, - reactRoutes: reactRoutesProvider, paths: AUTH0_AUTH0_CALLBACK_GENERATED.paths.provider, }, run({ @@ -33,20 +30,10 @@ export const auth0CallbackGenerator = createGenerator({ reactComponentsImports, authHooksImports, reactErrorImports, - reactRoutes, paths, }) { return { build: async (builder) => { - // Callback page - reactRoutes.registerRoute({ - path: 'auth0-callback', - element: createRouteElement( - 'Auth0CallbackPage', - paths.auth0CallbackPage, - ), - }); - await builder.apply( typescriptFile.renderTemplateFile({ template: @@ -61,11 +48,6 @@ export const auth0CallbackGenerator = createGenerator({ ); // Signup page - reactRoutes.registerRoute({ - path: 'signup', - element: createRouteElement('SignupPage', paths.signupPage), - }); - await builder.apply( typescriptFile.renderTemplateFile({ template: AUTH0_AUTH0_CALLBACK_GENERATED.templates.signupPage, diff --git a/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/extractor.json b/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/extractor.json index 56ca67c62..87e4f8c9a 100644 --- a/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/extractor.json +++ b/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/extractor.json @@ -1,7 +1,7 @@ { "name": "auth0/auth0-callback", "templates": { - "routes/auth0-callback.page.tsx": { + "routes/auth0-callback.tsx": { "name": "auth0-callback-page", "type": "ts", "fileOptions": { "kind": "singleton" }, @@ -20,10 +20,10 @@ "packagePathSpecifier": "@baseplate-dev/react-generators:src/generators/core/react-error/generated/ts-import-providers.ts" } }, - "pathRootRelativePath": "{routes-root}/auth0-callback.page.tsx", + "pathRootRelativePath": "{routes-root}/auth0-callback.tsx", "variables": {} }, - "routes/signup.page.tsx": { + "routes/signup.tsx": { "name": "signup-page", "type": "ts", "fileOptions": { "kind": "singleton" }, @@ -38,7 +38,7 @@ "packagePathSpecifier": "@baseplate-dev/react-generators:src/generators/core/react-error/generated/ts-import-providers.ts" } }, - "pathRootRelativePath": "{routes-root}/signup.page.tsx", + "pathRootRelativePath": "{routes-root}/signup.tsx", "variables": {} } } diff --git a/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/generated/template-paths.ts b/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/generated/template-paths.ts index 03c4900f6..a2a684f70 100644 --- a/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/generated/template-paths.ts +++ b/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/generated/template-paths.ts @@ -19,8 +19,8 @@ const auth0Auth0CallbackPathsTask = createGeneratorTask({ return { providers: { auth0Auth0CallbackPaths: { - auth0CallbackPage: `${routesRoot}/auth0-callback.page.tsx`, - signupPage: `${routesRoot}/signup.page.tsx`, + auth0CallbackPage: `${routesRoot}/auth0-callback.tsx`, + signupPage: `${routesRoot}/signup.tsx`, }, }, }; diff --git a/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/generated/typed-templates.ts b/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/generated/typed-templates.ts index cde04b1b7..a2ff1435b 100644 --- a/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/generated/typed-templates.ts +++ b/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/generated/typed-templates.ts @@ -17,7 +17,7 @@ const auth0CallbackPage = createTsTemplateFile({ source: { path: path.join( import.meta.dirname, - '../templates/routes/auth0-callback.page.tsx', + '../templates/routes/auth0-callback.tsx', ), }, variables: {}, @@ -31,7 +31,7 @@ const signupPage = createTsTemplateFile({ }, name: 'signup-page', source: { - path: path.join(import.meta.dirname, '../templates/routes/signup.page.tsx'), + path: path.join(import.meta.dirname, '../templates/routes/signup.tsx'), }, variables: {}, }); diff --git a/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/templates/routes/auth0-callback.page.tsx b/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/templates/routes/auth0-callback.tsx similarity index 87% rename from plugins/plugin-auth/src/auth0/generators/react/auth0-callback/templates/routes/auth0-callback.page.tsx rename to plugins/plugin-auth/src/auth0/generators/react/auth0-callback/templates/routes/auth0-callback.tsx index 3462f26ea..9ed109ef3 100644 --- a/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/templates/routes/auth0-callback.page.tsx +++ b/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/templates/routes/auth0-callback.tsx @@ -6,8 +6,8 @@ import { useLogOut } from '%authHooksImports'; import { Alert, Button, Card, Loader } from '%reactComponentsImports'; import { logError } from '%reactErrorImports'; import { OAuthError, useAuth0 } from '@auth0/auth0-react'; +import { createFileRoute, useNavigate } from '@tanstack/react-router'; import { useEffect, useRef, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; function formatAndReportAuthError(error: unknown): string { if ( @@ -21,6 +21,10 @@ function formatAndReportAuthError(error: unknown): string { return 'Sorry, we could not log you in. Please try again.'; } +export const Route = createFileRoute('/auth/auth0-callback')({ + component: Auth0CallbackPage, +}); + function Auth0CallbackPage(): ReactElement { const logOut = useLogOut(); const navigate = useNavigate(); @@ -37,7 +41,7 @@ function Auth0CallbackPage(): ReactElement { didHandleRedirect.current = true; handleRedirectCallback() .then(({ appState }: { appState?: { returnTo?: string } }) => { - navigate(appState?.returnTo ?? '/', { replace: true }); + navigate({ to: appState?.returnTo ?? '/' }).catch(logError); }) .catch((err: unknown) => { setError(formatAndReportAuthError(err)); @@ -57,5 +61,3 @@ function Auth0CallbackPage(): ReactElement {
); } - -export default Auth0CallbackPage; diff --git a/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/templates/routes/signup.page.tsx b/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/templates/routes/signup.tsx similarity index 74% rename from plugins/plugin-auth/src/auth0/generators/react/auth0-callback/templates/routes/signup.page.tsx rename to plugins/plugin-auth/src/auth0/generators/react/auth0-callback/templates/routes/signup.tsx index caab861a2..35c10a05e 100644 --- a/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/templates/routes/signup.page.tsx +++ b/plugins/plugin-auth/src/auth0/generators/react/auth0-callback/templates/routes/signup.tsx @@ -3,14 +3,19 @@ import type { ReactElement } from 'react'; import { Alert, Button, Card, Loader } from '%reactComponentsImports'; -import { logAndFormatError } from '%reactErrorImports'; +import { logAndFormatError, logError } from '%reactErrorImports'; import { useAuth0 } from '@auth0/auth0-react'; +import { createFileRoute, useNavigate } from '@tanstack/react-router'; import { useCallback, useEffect, useState } from 'react'; -import { Navigate } from 'react-router-dom'; + +export const Route = createFileRoute('/auth/signup')({ + component: SignupPage, +}); function SignupPage(): ReactElement { const { loginWithRedirect, isAuthenticated } = useAuth0(); const [error, setError] = useState(null); + const navigate = useNavigate(); const redirectToSignup = useCallback((): void => { loginWithRedirect({ authorizationParams: { screen_hint: 'signup' } }).catch( @@ -23,13 +28,10 @@ function SignupPage(): ReactElement { useEffect(() => { if (!isAuthenticated) { redirectToSignup(); + } else { + navigate({ to: '/' }).catch(logError); } - }, [isAuthenticated, redirectToSignup]); - - // if they are already authenticated, redirect them to home - if (isAuthenticated) { - return ; - } + }, [isAuthenticated, redirectToSignup, navigate]); return (
@@ -44,5 +46,3 @@ function SignupPage(): ReactElement {
); } - -export default SignupPage; diff --git a/tests/simple/baseplate/project-definition.json b/tests/simple/baseplate/project-definition.json index 968af7c56..58b49434a 100644 --- a/tests/simple/baseplate/project-definition.json +++ b/tests/simple/baseplate/project-definition.json @@ -15,9 +15,7 @@ } ], "cliVersion": "0.7.8", - "features": [ - { "id": "feature:EHdFHsD17AHI", "name": "blog", "parentRef": null } - ], + "features": [{ "id": "feature:EHdFHsD17AHI", "name": "blog" }], "isInitialized": true, "models": [ { @@ -74,7 +72,7 @@ "name": "BlogPost" } ], - "schemaVersion": 14, + "schemaVersion": 15, "settings": { "general": { "name": "simple", "portOffset": 3000 } }, "version": "0.1.0" } diff --git a/tests/simple/packages/web/.prettierignore b/tests/simple/packages/web/.prettierignore index 8d753b85c..bdafb9d8d 100644 --- a/tests/simple/packages/web/.prettierignore +++ b/tests/simple/packages/web/.prettierignore @@ -4,5 +4,6 @@ /dist /lib /node_modules +/src/route-tree.gen.ts pnpm-lock.yaml src/generated/graphql.tsx diff --git a/tests/simple/packages/web/baseplate/file-id-map.json b/tests/simple/packages/web/baseplate/file-id-map.json index 48c214f0c..0048f7bda 100644 --- a/tests/simple/packages/web/baseplate/file-id-map.json +++ b/tests/simple/packages/web/baseplate/file-id-map.json @@ -12,7 +12,7 @@ "@baseplate-dev/react-generators#apollo/react-apollo:codegen-yml": "codegen.yml", "@baseplate-dev/react-generators#apollo/react-apollo:graphql": "src/generated/graphql.tsx", "@baseplate-dev/react-generators#apollo/react-apollo:service": "src/services/apollo/index.ts", - "@baseplate-dev/react-generators#core/react-app:app": "src/app/App.tsx", + "@baseplate-dev/react-generators#core/react-app:app": "src/app/app.tsx", "@baseplate-dev/react-generators#core/react-components:alert": "src/components/alert/alert.tsx", "@baseplate-dev/react-generators#core/react-components:button": "src/components/button/button.tsx", "@baseplate-dev/react-generators#core/react-components:calendar": "src/components/calendar/calendar.tsx", @@ -65,15 +65,17 @@ "@baseplate-dev/react-generators#core/react-error:error-formatter": "src/services/error-formatter.ts", "@baseplate-dev/react-generators#core/react-error:error-logger": "src/services/error-logger.ts", "@baseplate-dev/react-generators#core/react-logger:logger": "src/services/logger.ts", - "@baseplate-dev/react-generators#core/react-not-found-handler:not-found-page": "src/pages/NotFound.page.tsx", - "@baseplate-dev/react-generators#core/react-router:index": "src/pages/index.tsx", + "@baseplate-dev/react-generators#core/react-router:app-routes": "src/app/app-routes.tsx", + "@baseplate-dev/react-generators#core/react-router:placeholder-index": "src/routes/index.tsx", + "@baseplate-dev/react-generators#core/react-router:root-route": "src/routes/__root.tsx", + "@baseplate-dev/react-generators#core/react-router:route-tree": "src/route-tree.gen.ts", "@baseplate-dev/react-generators#core/react-sentry:sentry": "src/services/sentry.ts", "@baseplate-dev/react-generators#core/react-tailwind:styles-css": "src/styles.css", "@baseplate-dev/react-generators#core/react-typescript:tsconfig-node": "tsconfig.node.json", "@baseplate-dev/react-generators#core/react-typescript:tsconfig-root": "tsconfig.json", "@baseplate-dev/react-generators#core/react:favicon": "public/favicon.ico", - "@baseplate-dev/react-generators#core/react:index": "src/index.tsx", "@baseplate-dev/react-generators#core/react:index-html": "index.html", + "@baseplate-dev/react-generators#core/react:main": "src/main.tsx", "@baseplate-dev/react-generators#core/react:readme": "README.md", "@baseplate-dev/react-generators#core/react:vite-config": "vite.config.ts", "@baseplate-dev/react-generators#core/react:vite-env": "src/vite-env.d.ts" diff --git a/tests/simple/packages/web/baseplate/generated/.prettierignore b/tests/simple/packages/web/baseplate/generated/.prettierignore index 8d753b85c..bdafb9d8d 100644 --- a/tests/simple/packages/web/baseplate/generated/.prettierignore +++ b/tests/simple/packages/web/baseplate/generated/.prettierignore @@ -4,5 +4,6 @@ /dist /lib /node_modules +/src/route-tree.gen.ts pnpm-lock.yaml src/generated/graphql.tsx diff --git a/tests/simple/packages/web/baseplate/generated/eslint.config.js b/tests/simple/packages/web/baseplate/generated/eslint.config.js index 2440c99aa..d91e2e700 100644 --- a/tests/simple/packages/web/baseplate/generated/eslint.config.js +++ b/tests/simple/packages/web/baseplate/generated/eslint.config.js @@ -31,6 +31,7 @@ const IGNORE_FILES = [ 'node_modules', 'src/generated/graphql.tsx', 'src/generated/graphql.tsx', + 'src/route-tree.gen.ts', ]; // Specifies which files should use the default tsconfig.json project diff --git a/tests/simple/packages/web/baseplate/generated/index.html b/tests/simple/packages/web/baseplate/generated/index.html index f53f77eef..2fa0bd604 100644 --- a/tests/simple/packages/web/baseplate/generated/index.html +++ b/tests/simple/packages/web/baseplate/generated/index.html @@ -10,6 +10,6 @@
- + diff --git a/tests/simple/packages/web/baseplate/generated/package.json b/tests/simple/packages/web/baseplate/generated/package.json index d93558917..be49fa2cd 100644 --- a/tests/simple/packages/web/baseplate/generated/package.json +++ b/tests/simple/packages/web/baseplate/generated/package.json @@ -34,6 +34,7 @@ "@headlessui/react": "2.2.2", "@hookform/resolvers": "5.0.1", "@sentry/react": "9.17.0", + "@tanstack/react-router": "1.124.0", "class-variance-authority": "0.7.1", "clsx": "2.1.1", "cmdk": "1.1.1", @@ -47,7 +48,6 @@ "react-error-boundary": "6.0.0", "react-hook-form": "7.56.3", "react-icons": "5.5.0", - "react-router-dom": "6.30.0", "sonner": "2.0.3", "zod": "3.24.1", "zustand": "5.0.3" @@ -60,6 +60,7 @@ "@graphql-codegen/typescript-react-apollo": "4.3.0", "@parcel/watcher": "2.4.1", "@tailwindcss/vite": "4.1.6", + "@tanstack/router-plugin": "1.124.0", "@types/node": "^22.0.0", "@types/react": "19.1.3", "@types/react-dom": "19.1.3", diff --git a/tests/simple/packages/web/baseplate/generated/src/app/app-routes.tsx b/tests/simple/packages/web/baseplate/generated/src/app/app-routes.tsx new file mode 100644 index 000000000..981d50573 --- /dev/null +++ b/tests/simple/packages/web/baseplate/generated/src/app/app-routes.tsx @@ -0,0 +1,33 @@ +import type { ErrorRouteComponent } from '@tanstack/react-router'; + +import { createRouter, RouterProvider } from '@tanstack/react-router'; + +import { Button, ErrorDisplay, NotFoundCard } from '../components'; +import { routeTree } from '../route-tree.gen'; + +const ErrorComponent: ErrorRouteComponent = ({ + error, + reset, +}: React.ComponentProps) => ( + Reset} + /> +); + +export const router = createRouter({ + routeTree, + defaultNotFoundComponent: NotFoundCard, + defaultErrorComponent: ErrorComponent, +}); + +// Register the router instance for type safety +declare module '@tanstack/react-router' { + interface Register { + router: typeof router; + } +} + +export function AppRoutes(): React.ReactElement { + return ; +} diff --git a/tests/simple/packages/web/src/app/App.tsx b/tests/simple/packages/web/baseplate/generated/src/app/app.tsx similarity index 56% rename from tests/simple/packages/web/src/app/App.tsx rename to tests/simple/packages/web/baseplate/generated/src/app/app.tsx index 127195344..c5f337a14 100644 --- a/tests/simple/packages/web/src/app/App.tsx +++ b/tests/simple/packages/web/baseplate/generated/src/app/app.tsx @@ -1,24 +1,18 @@ import type { ReactElement } from 'react'; -import { BrowserRouter } from 'react-router-dom'; - import { ConfirmDialog, Toaster } from '../components'; import { ErrorBoundary } from '../components/error-boundary/error-boundary'; -import PagesRoot from '../pages'; +import { AppRoutes } from './app-routes'; import AppApolloProvider from './AppApolloProvider'; -function App(): ReactElement { +export function App(): ReactElement { return ( - - - - - + + + ); } - -export default App; diff --git a/tests/simple/packages/web/baseplate/generated/src/components/not-found-card/not-found-card.tsx b/tests/simple/packages/web/baseplate/generated/src/components/not-found-card/not-found-card.tsx index aceee68ef..a96c3caa9 100644 --- a/tests/simple/packages/web/baseplate/generated/src/components/not-found-card/not-found-card.tsx +++ b/tests/simple/packages/web/baseplate/generated/src/components/not-found-card/not-found-card.tsx @@ -1,25 +1,19 @@ import type React from 'react'; -import { useNavigate } from 'react-router-dom'; +import { Link } from '@tanstack/react-router'; -import { Button } from '../button/button'; -import { ErrorDisplay } from '../error-display/error-display'; +import { Button, ErrorDisplay } from '..'; export function NotFoundCard(): React.JSX.Element { - const navigate = useNavigate(); return ( { - navigate('/'); - }} - > - Back to Home - + + + } /> ); diff --git a/tests/simple/packages/web/src/index.tsx b/tests/simple/packages/web/baseplate/generated/src/main.tsx similarity index 91% rename from tests/simple/packages/web/src/index.tsx rename to tests/simple/packages/web/baseplate/generated/src/main.tsx index 35a253ea9..c18aa926a 100644 --- a/tests/simple/packages/web/src/index.tsx +++ b/tests/simple/packages/web/baseplate/generated/src/main.tsx @@ -1,7 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import App from './app/App'; +import { App } from './app/app'; import './styles.css'; diff --git a/tests/simple/packages/web/baseplate/generated/src/pages/NotFound.page.tsx b/tests/simple/packages/web/baseplate/generated/src/pages/NotFound.page.tsx deleted file mode 100644 index f94b55fab..000000000 --- a/tests/simple/packages/web/baseplate/generated/src/pages/NotFound.page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import type { ReactElement } from 'react'; - -import { NotFoundCard } from '../components'; - -function NotFoundPage(): ReactElement { - return ; -} - -export default NotFoundPage; diff --git a/tests/simple/packages/web/baseplate/generated/src/pages/index.tsx b/tests/simple/packages/web/baseplate/generated/src/pages/index.tsx deleted file mode 100644 index 475f92ef7..000000000 --- a/tests/simple/packages/web/baseplate/generated/src/pages/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { ReactElement } from 'react'; - -import * as Sentry from '@sentry/react'; -import { Route, Routes } from 'react-router-dom'; - -import NotFoundPage from './NotFound.page'; - -const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes); - -function PagesRoot(): ReactElement { - return ( - - } /> - - ); -} - -export default PagesRoot; diff --git a/tests/simple/packages/web/baseplate/generated/src/route-tree.gen.ts b/tests/simple/packages/web/baseplate/generated/src/route-tree.gen.ts new file mode 100644 index 000000000..e69de29bb diff --git a/tests/simple/packages/web/baseplate/generated/src/routes/__root.tsx b/tests/simple/packages/web/baseplate/generated/src/routes/__root.tsx new file mode 100644 index 000000000..1a805781f --- /dev/null +++ b/tests/simple/packages/web/baseplate/generated/src/routes/__root.tsx @@ -0,0 +1,3 @@ +import { createRootRoute } from '@tanstack/react-router'; + +export const Route = createRootRoute({}); diff --git a/tests/simple/packages/web/baseplate/generated/src/routes/index.tsx b/tests/simple/packages/web/baseplate/generated/src/routes/index.tsx new file mode 100644 index 000000000..9e3e0c6d8 --- /dev/null +++ b/tests/simple/packages/web/baseplate/generated/src/routes/index.tsx @@ -0,0 +1,16 @@ +import type { ReactElement } from 'react'; + +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/')({ + component: HomePage, +}); + +function HomePage(): ReactElement { + return ( +
+

Hello World

+

This is the home page of a generated app.

+
+ ); +} diff --git a/tests/simple/packages/web/baseplate/generated/src/services/sentry.ts b/tests/simple/packages/web/baseplate/generated/src/services/sentry.ts index 7f06f4a5c..e917de36d 100644 --- a/tests/simple/packages/web/baseplate/generated/src/services/sentry.ts +++ b/tests/simple/packages/web/baseplate/generated/src/services/sentry.ts @@ -3,14 +3,8 @@ import type { GraphQLFormattedError } from 'graphql'; import { ApolloError } from '@apollo/client'; import * as Sentry from '@sentry/react'; import { GraphQLError } from 'graphql'; -import React from 'react'; -import { - createRoutesFromChildren, - matchRoutes, - useLocation, - useNavigationType, -} from 'react-router-dom'; +import { router } from '../app/app-routes'; import { config } from './config'; function configureSentryScopeForGraphqlError( @@ -37,15 +31,7 @@ if (SENTRY_ENABLED) { Sentry.init({ dsn: config.VITE_SENTRY_DSN, environment: config.VITE_ENVIRONMENT, - integrations: [ - Sentry.reactRouterV6BrowserTracingIntegration({ - useEffect: React.useEffect, - useLocation, - useNavigationType, - createRoutesFromChildren, - matchRoutes, - }), - ], + integrations: [Sentry.tanstackRouterBrowserTracingIntegration(router)], tracesSampleRate: TRACE_SAMPLE_RATE, }); } diff --git a/tests/simple/packages/web/baseplate/generated/vite.config.ts b/tests/simple/packages/web/baseplate/generated/vite.config.ts index 426159174..2e52fe860 100644 --- a/tests/simple/packages/web/baseplate/generated/vite.config.ts +++ b/tests/simple/packages/web/baseplate/generated/vite.config.ts @@ -1,4 +1,5 @@ import tailwindcss from '@tailwindcss/vite'; +import { tanstackRouter } from '@tanstack/router-plugin/vite'; import react from '@vitejs/plugin-react'; import { defineConfig, loadEnv } from 'vite'; import svgrPlugin from 'vite-plugin-svgr'; @@ -10,7 +11,18 @@ export default defineConfig(({ mode }) => { return { build: { outDir: 'build' }, - plugins: [tailwindcss(), react(), svgrPlugin(), viteTsconfigPaths()], + plugins: [ + tailwindcss(), + tanstackRouter({ + target: 'react', + autoCodeSplitting: true, + generatedRouteTree: './src/route-tree.gen.ts', + quoteStyle: 'single', + }), + react(), + svgrPlugin(), + viteTsconfigPaths(), + ], server: { port: envVars.PORT ? Number.parseInt(envVars.PORT, 10) : 3000, proxy: envVars.DEV_BACKEND_HOST diff --git a/tests/simple/packages/web/eslint.config.js b/tests/simple/packages/web/eslint.config.js index 2440c99aa..d91e2e700 100644 --- a/tests/simple/packages/web/eslint.config.js +++ b/tests/simple/packages/web/eslint.config.js @@ -31,6 +31,7 @@ const IGNORE_FILES = [ 'node_modules', 'src/generated/graphql.tsx', 'src/generated/graphql.tsx', + 'src/route-tree.gen.ts', ]; // Specifies which files should use the default tsconfig.json project diff --git a/tests/simple/packages/web/index.html b/tests/simple/packages/web/index.html index f53f77eef..2fa0bd604 100644 --- a/tests/simple/packages/web/index.html +++ b/tests/simple/packages/web/index.html @@ -10,6 +10,6 @@
- + diff --git a/tests/simple/packages/web/package.json b/tests/simple/packages/web/package.json index d93558917..be49fa2cd 100644 --- a/tests/simple/packages/web/package.json +++ b/tests/simple/packages/web/package.json @@ -34,6 +34,7 @@ "@headlessui/react": "2.2.2", "@hookform/resolvers": "5.0.1", "@sentry/react": "9.17.0", + "@tanstack/react-router": "1.124.0", "class-variance-authority": "0.7.1", "clsx": "2.1.1", "cmdk": "1.1.1", @@ -47,7 +48,6 @@ "react-error-boundary": "6.0.0", "react-hook-form": "7.56.3", "react-icons": "5.5.0", - "react-router-dom": "6.30.0", "sonner": "2.0.3", "zod": "3.24.1", "zustand": "5.0.3" @@ -60,6 +60,7 @@ "@graphql-codegen/typescript-react-apollo": "4.3.0", "@parcel/watcher": "2.4.1", "@tailwindcss/vite": "4.1.6", + "@tanstack/router-plugin": "1.124.0", "@types/node": "^22.0.0", "@types/react": "19.1.3", "@types/react-dom": "19.1.3", diff --git a/tests/simple/packages/web/src/app/app-routes.tsx b/tests/simple/packages/web/src/app/app-routes.tsx new file mode 100644 index 000000000..981d50573 --- /dev/null +++ b/tests/simple/packages/web/src/app/app-routes.tsx @@ -0,0 +1,33 @@ +import type { ErrorRouteComponent } from '@tanstack/react-router'; + +import { createRouter, RouterProvider } from '@tanstack/react-router'; + +import { Button, ErrorDisplay, NotFoundCard } from '../components'; +import { routeTree } from '../route-tree.gen'; + +const ErrorComponent: ErrorRouteComponent = ({ + error, + reset, +}: React.ComponentProps) => ( + Reset} + /> +); + +export const router = createRouter({ + routeTree, + defaultNotFoundComponent: NotFoundCard, + defaultErrorComponent: ErrorComponent, +}); + +// Register the router instance for type safety +declare module '@tanstack/react-router' { + interface Register { + router: typeof router; + } +} + +export function AppRoutes(): React.ReactElement { + return ; +} diff --git a/tests/simple/packages/web/baseplate/generated/src/app/App.tsx b/tests/simple/packages/web/src/app/app.tsx similarity index 56% rename from tests/simple/packages/web/baseplate/generated/src/app/App.tsx rename to tests/simple/packages/web/src/app/app.tsx index 127195344..c5f337a14 100644 --- a/tests/simple/packages/web/baseplate/generated/src/app/App.tsx +++ b/tests/simple/packages/web/src/app/app.tsx @@ -1,24 +1,18 @@ import type { ReactElement } from 'react'; -import { BrowserRouter } from 'react-router-dom'; - import { ConfirmDialog, Toaster } from '../components'; import { ErrorBoundary } from '../components/error-boundary/error-boundary'; -import PagesRoot from '../pages'; +import { AppRoutes } from './app-routes'; import AppApolloProvider from './AppApolloProvider'; -function App(): ReactElement { +export function App(): ReactElement { return ( - - - - - + + + ); } - -export default App; diff --git a/tests/simple/packages/web/src/components/not-found-card/not-found-card.tsx b/tests/simple/packages/web/src/components/not-found-card/not-found-card.tsx index aceee68ef..a96c3caa9 100644 --- a/tests/simple/packages/web/src/components/not-found-card/not-found-card.tsx +++ b/tests/simple/packages/web/src/components/not-found-card/not-found-card.tsx @@ -1,25 +1,19 @@ import type React from 'react'; -import { useNavigate } from 'react-router-dom'; +import { Link } from '@tanstack/react-router'; -import { Button } from '../button/button'; -import { ErrorDisplay } from '../error-display/error-display'; +import { Button, ErrorDisplay } from '..'; export function NotFoundCard(): React.JSX.Element { - const navigate = useNavigate(); return ( { - navigate('/'); - }} - > - Back to Home - + + + } /> ); diff --git a/tests/simple/packages/web/baseplate/generated/src/index.tsx b/tests/simple/packages/web/src/main.tsx similarity index 91% rename from tests/simple/packages/web/baseplate/generated/src/index.tsx rename to tests/simple/packages/web/src/main.tsx index 35a253ea9..c18aa926a 100644 --- a/tests/simple/packages/web/baseplate/generated/src/index.tsx +++ b/tests/simple/packages/web/src/main.tsx @@ -1,7 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import App from './app/App'; +import { App } from './app/app'; import './styles.css'; diff --git a/tests/simple/packages/web/src/pages/NotFound.page.tsx b/tests/simple/packages/web/src/pages/NotFound.page.tsx deleted file mode 100644 index f94b55fab..000000000 --- a/tests/simple/packages/web/src/pages/NotFound.page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import type { ReactElement } from 'react'; - -import { NotFoundCard } from '../components'; - -function NotFoundPage(): ReactElement { - return ; -} - -export default NotFoundPage; diff --git a/tests/simple/packages/web/src/pages/index.tsx b/tests/simple/packages/web/src/pages/index.tsx deleted file mode 100644 index a3d719a00..000000000 --- a/tests/simple/packages/web/src/pages/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import type { ReactElement } from 'react'; - -import * as Sentry from '@sentry/react'; -import { Route, Routes } from 'react-router-dom'; - -import { HomePage } from './home.page'; -import NotFoundPage from './NotFound.page'; - -const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes); - -function PagesRoot(): ReactElement { - return ( - - } /> - } /> - - ); -} - -export default PagesRoot; diff --git a/tests/simple/packages/web/src/route-tree.gen.ts b/tests/simple/packages/web/src/route-tree.gen.ts new file mode 100644 index 000000000..d204c269b --- /dev/null +++ b/tests/simple/packages/web/src/route-tree.gen.ts @@ -0,0 +1,59 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' + fileRoutesByTo: FileRoutesByTo + to: '/' + id: '__root__' | '/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/tests/simple/packages/web/src/routes/__root.tsx b/tests/simple/packages/web/src/routes/__root.tsx new file mode 100644 index 000000000..1a805781f --- /dev/null +++ b/tests/simple/packages/web/src/routes/__root.tsx @@ -0,0 +1,3 @@ +import { createRootRoute } from '@tanstack/react-router'; + +export const Route = createRootRoute({}); diff --git a/tests/simple/packages/web/src/pages/home.gql b/tests/simple/packages/web/src/routes/home.gql similarity index 100% rename from tests/simple/packages/web/src/pages/home.gql rename to tests/simple/packages/web/src/routes/home.gql diff --git a/tests/simple/packages/web/src/pages/home.page.tsx b/tests/simple/packages/web/src/routes/index.tsx similarity index 78% rename from tests/simple/packages/web/src/pages/home.page.tsx rename to tests/simple/packages/web/src/routes/index.tsx index 094354d13..bbf0a411c 100644 --- a/tests/simple/packages/web/src/pages/home.page.tsx +++ b/tests/simple/packages/web/src/routes/index.tsx @@ -1,9 +1,15 @@ import type { ReactElement } from 'react'; +import { createFileRoute } from '@tanstack/react-router'; + import { ErrorableLoader } from '@src/components'; import { useGetBlogPostsQuery } from '@src/generated/graphql'; -export function HomePage(): ReactElement { +export const Route = createFileRoute('/')({ + component: HomePage, +}); + +function HomePage(): ReactElement { const { data, error } = useGetBlogPostsQuery(); if (!data) { diff --git a/tests/simple/packages/web/src/services/sentry.ts b/tests/simple/packages/web/src/services/sentry.ts index 7f06f4a5c..e917de36d 100644 --- a/tests/simple/packages/web/src/services/sentry.ts +++ b/tests/simple/packages/web/src/services/sentry.ts @@ -3,14 +3,8 @@ import type { GraphQLFormattedError } from 'graphql'; import { ApolloError } from '@apollo/client'; import * as Sentry from '@sentry/react'; import { GraphQLError } from 'graphql'; -import React from 'react'; -import { - createRoutesFromChildren, - matchRoutes, - useLocation, - useNavigationType, -} from 'react-router-dom'; +import { router } from '../app/app-routes'; import { config } from './config'; function configureSentryScopeForGraphqlError( @@ -37,15 +31,7 @@ if (SENTRY_ENABLED) { Sentry.init({ dsn: config.VITE_SENTRY_DSN, environment: config.VITE_ENVIRONMENT, - integrations: [ - Sentry.reactRouterV6BrowserTracingIntegration({ - useEffect: React.useEffect, - useLocation, - useNavigationType, - createRoutesFromChildren, - matchRoutes, - }), - ], + integrations: [Sentry.tanstackRouterBrowserTracingIntegration(router)], tracesSampleRate: TRACE_SAMPLE_RATE, }); } diff --git a/tests/simple/packages/web/vite.config.ts b/tests/simple/packages/web/vite.config.ts index 426159174..2e52fe860 100644 --- a/tests/simple/packages/web/vite.config.ts +++ b/tests/simple/packages/web/vite.config.ts @@ -1,4 +1,5 @@ import tailwindcss from '@tailwindcss/vite'; +import { tanstackRouter } from '@tanstack/router-plugin/vite'; import react from '@vitejs/plugin-react'; import { defineConfig, loadEnv } from 'vite'; import svgrPlugin from 'vite-plugin-svgr'; @@ -10,7 +11,18 @@ export default defineConfig(({ mode }) => { return { build: { outDir: 'build' }, - plugins: [tailwindcss(), react(), svgrPlugin(), viteTsconfigPaths()], + plugins: [ + tailwindcss(), + tanstackRouter({ + target: 'react', + autoCodeSplitting: true, + generatedRouteTree: './src/route-tree.gen.ts', + quoteStyle: 'single', + }), + react(), + svgrPlugin(), + viteTsconfigPaths(), + ], server: { port: envVars.PORT ? Number.parseInt(envVars.PORT, 10) : 3000, proxy: envVars.DEV_BACKEND_HOST diff --git a/tests/simple/pnpm-lock.yaml b/tests/simple/pnpm-lock.yaml index fef653eb8..e260040a3 100644 --- a/tests/simple/pnpm-lock.yaml +++ b/tests/simple/pnpm-lock.yaml @@ -198,6 +198,9 @@ importers: '@sentry/react': specifier: 9.17.0 version: 9.17.0(react@19.1.0) + '@tanstack/react-router': + specifier: 1.124.0 + version: 1.124.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) class-variance-authority: specifier: 0.7.1 version: 0.7.1 @@ -237,9 +240,6 @@ importers: react-icons: specifier: 5.5.0 version: 5.5.0(react@19.1.0) - react-router-dom: - specifier: 6.30.0 - version: 6.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) sonner: specifier: 2.0.3 version: 2.0.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -271,6 +271,9 @@ importers: '@tailwindcss/vite': specifier: 4.1.6 version: 4.1.6(vite@6.3.5(@types/node@22.13.11)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.5.1)) + '@tanstack/router-plugin': + specifier: 1.124.0 + version: 1.124.0(@tanstack/react-router@1.124.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.3.5(@types/node@22.13.11)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.5.1)) '@types/node': specifier: ^22.0.0 version: 22.13.11 @@ -379,60 +382,122 @@ packages: resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.26.5': resolution: {integrity: sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.28.0': + resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} + engines: {node: '>=6.9.0'} + '@babel/core@7.26.10': resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} engines: {node: '>=6.9.0'} + '@babel/core@7.28.0': + resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.26.10': resolution: {integrity: sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==} engines: {node: '>=6.9.0'} + '@babel/generator@7.28.0': + resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.24.7': resolution: {integrity: sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==} engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.26.5': resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-create-class-features-plugin@7.25.0': resolution: {integrity: sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-create-class-features-plugin@7.27.1': + resolution: {integrity: sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + '@babel/helper-member-expression-to-functions@7.24.8': resolution: {integrity: sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==} engines: {node: '>=6.9.0'} + '@babel/helper-member-expression-to-functions@7.27.1': + resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.25.9': resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-transforms@7.26.0': resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.27.3': + resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@7.24.7': resolution: {integrity: sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==} engines: {node: '>=6.9.0'} + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.26.5': resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + '@babel/helper-replace-supers@7.25.0': resolution: {integrity: sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-replace-supers@7.27.1': + resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-simple-access@7.24.7': resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} engines: {node: '>=6.9.0'} @@ -441,27 +506,52 @@ packages: resolution: {integrity: sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==} engines: {node: '>=6.9.0'} + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.25.9': resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.25.9': resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.25.9': resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + '@babel/helpers@7.26.10': resolution: {integrity: sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.27.6': + resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.26.10': resolution: {integrity: sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.28.0': + resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-proposal-class-properties@7.18.6': resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} engines: {node: '>=6.9.0'} @@ -499,11 +589,23 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-object-rest-spread@7.8.3': resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-arrow-functions@7.24.7': resolution: {integrity: sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==} engines: {node: '>=6.9.0'} @@ -576,6 +678,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-modules-commonjs@7.27.1': + resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-object-super@7.24.7': resolution: {integrity: sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==} engines: {node: '>=6.9.0'} @@ -636,6 +744,18 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-typescript@7.28.0': + resolution: {integrity: sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.27.1': + resolution: {integrity: sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/runtime@7.26.10': resolution: {integrity: sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==} engines: {node: '>=6.9.0'} @@ -644,14 +764,26 @@ packages: resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==} engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.26.10': resolution: {integrity: sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.28.0': + resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} + engines: {node: '>=6.9.0'} + '@babel/types@7.26.10': resolution: {integrity: sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==} engines: {node: '>=6.9.0'} + '@babel/types@7.28.0': + resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==} + engines: {node: '>=6.9.0'} + '@date-fns/tz@1.2.0': resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==} @@ -1232,6 +1364,9 @@ packages: resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} + '@jridgewell/gen-mapping@0.3.12': + resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} + '@jridgewell/gen-mapping@0.3.8': resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} @@ -1250,6 +1385,9 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.29': + resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} + '@kamilkisiela/fast-url-parser@1.1.4': resolution: {integrity: sha512-gbkePEBupNydxCelHCESvFSFM8XPh1Zs/OAVRW/rKpEqPAl5PbOM90Si8mv9bvnR53uPD2s/FiRxdvSejpRJew==} @@ -2384,10 +2522,6 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@remix-run/router@1.23.0': - resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==} - engines: {node: '>=14.0.0'} - '@repeaterjs/repeater@3.0.6': resolution: {integrity: sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA==} @@ -2718,15 +2852,72 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 + '@tanstack/history@1.121.34': + resolution: {integrity: sha512-YL8dGi5ZU+xvtav2boRlw4zrRghkY6hvdcmHhA0RGSJ/CBgzv+cbADW9eYJLx74XMZvIQ1pp6VMbrpXnnM5gHA==} + engines: {node: '>=12'} + + '@tanstack/react-router@1.124.0': + resolution: {integrity: sha512-jJxuLbPP/Cxirnft3CoiGWyH0aj94VTmLNcYauvjTGRNbUitK4udvGaHXVEP8bcifYvpko7ptsqqBlisaosugA==} + engines: {node: '>=12'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-store@0.7.1': + resolution: {integrity: sha512-qUTEKdId6QPWGiWyKAPf/gkN29scEsz6EUSJ0C3HgLMgaqTAyBsQ2sMCfGVcqb+kkhEXAdjleCgH6LAPD6f2sA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@tanstack/react-virtual@3.13.8': resolution: {integrity: sha512-meS2AanUg50f3FBSNoAdBSRAh8uS0ue01qm7zrw65KGJtiXB9QXfybqZwkh4uFpRv2iX/eu5tjcH5wqUpwYLPg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@tanstack/router-core@1.124.0': + resolution: {integrity: sha512-mU2KA2v+ZFWC3NIjY2y+pPCx1sZDXPsUkzPjPPZxRgonE11nIu9MB89WuukqYuPbxoSWeodKNXsLe4KksGFCKA==} + engines: {node: '>=12'} + + '@tanstack/router-generator@1.124.0': + resolution: {integrity: sha512-fatjfBvgLh7i2xcLKO3QaM5egHAhMy57B7DfE44sYx1D7/xxLOubSEjSnVSLE3dWBrstZ3aqyuYYhw7NuoXB7g==} + engines: {node: '>=12'} + + '@tanstack/router-plugin@1.124.0': + resolution: {integrity: sha512-CqV3PCVoMrHw0HyTioIGHTTjaMRgfwbW4ax2Pule++smyetn+3KPLV6C3VWc0vdukZMQz13JvLORSSeM0B2cYQ==} + engines: {node: '>=12'} + peerDependencies: + '@rsbuild/core': '>=1.0.2' + '@tanstack/react-router': ^1.124.0 + vite: '>=5.0.0 || >=6.0.0' + vite-plugin-solid: ^2.11.2 + webpack: '>=5.92.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@tanstack/react-router': + optional: true + vite: + optional: true + vite-plugin-solid: + optional: true + webpack: + optional: true + + '@tanstack/router-utils@1.121.21': + resolution: {integrity: sha512-u7ubq1xPBtNiU7Fm+EOWlVWdgFLzuKOa1thhqdscVn8R4dNMUd1VoOjZ6AKmLw201VaUhFtlX+u0pjzI6szX7A==} + engines: {node: '>=12'} + + '@tanstack/store@0.7.1': + resolution: {integrity: sha512-PjUQKXEXhLYj2X5/6c1Xn/0/qKY0IVFxTJweopRfF26xfjVyb14yALydJrHupDh3/d+1WKmfEgZPBVCmDkzzwg==} + '@tanstack/virtual-core@3.13.8': resolution: {integrity: sha512-BT6w89Hqy7YKaWewYzmecXQzcJh6HTBbKYJIIkMaNU49DZ06LoTV3z32DWWEdUsgW6n1xTmwTLs4GtWrZC261w==} + '@tanstack/virtual-file-routes@1.121.21': + resolution: {integrity: sha512-3nuYsTyaq6ZN7jRZ9z6Gj3GXZqBOqOT0yzd/WZ33ZFfv4yVNIvsa5Lw+M1j3sgyEAxKMqGu/FaNi7FCjr3yOdw==} + engines: {node: '>=12'} + '@tybys/wasm-util@0.9.0': resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} @@ -3071,6 +3262,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@7.1.1: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} @@ -3126,6 +3322,10 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + ansis@4.1.0: + resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==} + engines: {node: '>=14'} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -3187,6 +3387,10 @@ packages: ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} @@ -3214,6 +3418,9 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} + babel-dead-code-elimination@1.0.10: + resolution: {integrity: sha512-DV5bdJZTzZ0zn0DC24v3jD7Mnidh6xhKa4GfKCbq3sfW8kaWhDdZjP3i81geA8T33tdYqWKw4D3fVv0CwEgKVA==} + babel-plugin-syntax-trailing-function-commas@7.0.0-beta.0: resolution: {integrity: sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==} @@ -3439,6 +3646,9 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-es@1.2.2: + resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + cookie-signature@1.2.2: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} @@ -3587,6 +3797,10 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -3802,6 +4016,11 @@ packages: resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} @@ -4396,6 +4615,10 @@ packages: isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + isbot@5.1.28: + resolution: {integrity: sha512-qrOp4g3xj8YNse4biorv6O5ZShwsJM0trsoda4y7j/Su7ZtTTfVXFzbKkpgcSoDrHS8FcTuUwcU04YimZlZOxw==} + engines: {node: '>=18'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -5228,19 +5451,6 @@ packages: '@types/react': optional: true - react-router-dom@6.30.0: - resolution: {integrity: sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - - react-router@6.30.0: - resolution: {integrity: sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -5267,6 +5477,10 @@ packages: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} + recast@0.23.11: + resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} + engines: {node: '>= 4'} + reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -5536,6 +5750,14 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} @@ -5655,6 +5877,12 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -5855,6 +6083,10 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + unplugin@2.3.5: + resolution: {integrity: sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw==} + engines: {node: '>=18.12.0'} + unrs-resolver@1.7.2: resolution: {integrity: sha512-BBKpaylOW8KbHsu378Zky/dGh4ckT/4NW/0SHRABdqRLcQJ2dAOjDo9g97p04sWflm0kqPqpUatxReNV/dqI5A==} @@ -6024,6 +6256,9 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -6234,8 +6469,16 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/compat-data@7.26.5': {} + '@babel/compat-data@7.28.0': {} + '@babel/core@7.26.10': dependencies: '@ampproject/remapping': 2.3.0 @@ -6256,6 +6499,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/core@7.28.0': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) + '@babel/helpers': 7.27.6 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.0 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/generator@7.26.10': dependencies: '@babel/parser': 7.26.10 @@ -6264,10 +6527,22 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.1.0 + '@babel/generator@7.28.0': + dependencies: + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 + jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.24.7': dependencies: '@babel/types': 7.26.10 + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.28.0 + '@babel/helper-compilation-targets@7.26.5': dependencies: '@babel/compat-data': 7.26.5 @@ -6276,6 +6551,14 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.24.4 + lru-cache: 5.1.1 + semver: 6.3.1 + '@babel/helper-create-class-features-plugin@7.25.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -6289,6 +6572,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.28.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + '@babel/helper-member-expression-to-functions@7.24.8': dependencies: '@babel/traverse': 7.26.10 @@ -6296,6 +6594,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-member-expression-to-functions@7.27.1': + dependencies: + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.0 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-imports@7.25.9': dependencies: '@babel/traverse': 7.26.10 @@ -6303,6 +6608,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.0 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -6312,12 +6624,27 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.0 + transitivePeerDependencies: + - supports-color + '@babel/helper-optimise-call-expression@7.24.7': dependencies: '@babel/types': 7.26.10 + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.28.0 + '@babel/helper-plugin-utils@7.26.5': {} + '@babel/helper-plugin-utils@7.27.1': {} + '@babel/helper-replace-supers@7.25.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -6327,6 +6654,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.28.0 + transitivePeerDependencies: + - supports-color + '@babel/helper-simple-access@7.24.7': dependencies: '@babel/traverse': 7.26.10 @@ -6341,21 +6677,43 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.0 + transitivePeerDependencies: + - supports-color + '@babel/helper-string-parser@7.25.9': {} + '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-validator-identifier@7.25.9': {} + '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-option@7.25.9': {} + '@babel/helper-validator-option@7.27.1': {} + '@babel/helpers@7.26.10': dependencies: '@babel/template': 7.26.9 '@babel/types': 7.26.10 + '@babel/helpers@7.27.6': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.0 + '@babel/parser@7.26.10': dependencies: '@babel/types': 7.26.10 + '@babel/parser@7.28.0': + dependencies: + '@babel/types': 7.28.0 + '@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -6393,11 +6751,21 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-transform-arrow-functions@7.24.7(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -6478,6 +6846,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-object-super@7.24.7(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -6540,6 +6916,28 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.0) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.0) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.0) + '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.0) + transitivePeerDependencies: + - supports-color + '@babel/runtime@7.26.10': dependencies: regenerator-runtime: 0.14.1 @@ -6550,6 +6948,12 @@ snapshots: '@babel/parser': 7.26.10 '@babel/types': 7.26.10 + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 + '@babel/traverse@7.26.10': dependencies: '@babel/code-frame': 7.26.2 @@ -6562,11 +6966,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.28.0': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/types': 7.28.0 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + '@babel/types@7.26.10': dependencies: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 + '@babel/types@7.28.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@date-fns/tz@1.2.0': {} '@emnapi/core@1.4.3': @@ -7370,6 +7791,11 @@ snapshots: dependencies: minipass: 7.1.2 + '@jridgewell/gen-mapping@0.3.12': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.29 + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 @@ -7387,6 +7813,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping@0.3.29': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + '@kamilkisiela/fast-url-parser@1.1.4': {} '@lukeed/ms@2.0.2': {} @@ -8643,8 +9074,6 @@ snapshots: dependencies: react: 19.1.0 - '@remix-run/router@1.23.0': {} - '@repeaterjs/repeater@3.0.6': {} '@rollup/pluginutils@5.1.4(rollup@4.40.0)': @@ -8959,14 +9388,94 @@ snapshots: tailwindcss: 4.1.6 vite: 6.3.5(@types/node@22.13.11)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.5.1) + '@tanstack/history@1.121.34': {} + + '@tanstack/react-router@1.124.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@tanstack/history': 1.121.34 + '@tanstack/react-store': 0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tanstack/router-core': 1.124.0 + isbot: 5.1.28 + jsesc: 3.1.0 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + + '@tanstack/react-store@0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@tanstack/store': 0.7.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + use-sync-external-store: 1.5.0(react@19.1.0) + '@tanstack/react-virtual@3.13.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@tanstack/virtual-core': 3.13.8 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + '@tanstack/router-core@1.124.0': + dependencies: + '@tanstack/history': 1.121.34 + '@tanstack/store': 0.7.1 + cookie-es: 1.2.2 + jsesc: 3.1.0 + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + + '@tanstack/router-generator@1.124.0': + dependencies: + '@tanstack/router-core': 1.124.0 + '@tanstack/router-utils': 1.121.21 + '@tanstack/virtual-file-routes': 1.121.21 + prettier: 3.5.3 + recast: 0.23.11 + source-map: 0.7.4 + tsx: 4.19.3 + zod: 3.24.4 + transitivePeerDependencies: + - supports-color + + '@tanstack/router-plugin@1.124.0(@tanstack/react-router@1.124.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.3.5(@types/node@22.13.11)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.5.1))': + dependencies: + '@babel/core': 7.28.0 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.0) + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.0 + '@tanstack/router-core': 1.124.0 + '@tanstack/router-generator': 1.124.0 + '@tanstack/router-utils': 1.121.21 + '@tanstack/virtual-file-routes': 1.121.21 + babel-dead-code-elimination: 1.0.10 + chokidar: 3.6.0 + unplugin: 2.3.5 + zod: 3.24.4 + optionalDependencies: + '@tanstack/react-router': 1.124.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + vite: 6.3.5(@types/node@22.13.11)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)(yaml@2.5.1) + transitivePeerDependencies: + - supports-color + + '@tanstack/router-utils@1.121.21': + dependencies: + '@babel/core': 7.28.0 + '@babel/generator': 7.28.0 + '@babel/parser': 7.28.0 + '@babel/preset-typescript': 7.27.1(@babel/core@7.28.0) + ansis: 4.1.0 + diff: 8.0.2 + transitivePeerDependencies: + - supports-color + + '@tanstack/store@0.7.1': {} + '@tanstack/virtual-core@3.13.8': {} + '@tanstack/virtual-file-routes@1.121.21': {} + '@tybys/wasm-util@0.9.0': dependencies: tslib: 2.8.1 @@ -9362,6 +9871,8 @@ snapshots: acorn@8.14.0: {} + acorn@8.15.0: {} + agent-base@7.1.1: dependencies: debug: 4.4.1 @@ -9417,6 +9928,8 @@ snapshots: ansi-styles@6.2.1: {} + ansis@4.1.0: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -9499,6 +10012,10 @@ snapshots: ast-types-flow@0.0.8: {} + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + astral-regex@2.0.0: {} atomic-sleep@1.0.0: {} @@ -9518,6 +10035,15 @@ snapshots: axobject-query@4.1.0: {} + babel-dead-code-elimination@1.0.10: + dependencies: + '@babel/core': 7.28.0 + '@babel/parser': 7.26.10 + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.0 + transitivePeerDependencies: + - supports-color + babel-plugin-syntax-trailing-function-commas@7.0.0-beta.0: {} babel-preset-fbjs@3.4.0(@babel/core@7.26.10): @@ -9815,6 +10341,8 @@ snapshots: convert-source-map@2.0.0: {} + cookie-es@1.2.2: {} + cookie-signature@1.2.2: {} cookie@0.7.2: {} @@ -9937,6 +10465,8 @@ snapshots: detect-node-es@1.1.0: {} + diff@8.0.2: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -10324,6 +10854,8 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.14.0) eslint-visitor-keys: 4.2.0 + esprima@4.0.1: {} + esquery@1.6.0: dependencies: estraverse: 5.3.0 @@ -10992,6 +11524,8 @@ snapshots: isarray@2.0.5: {} + isbot@5.1.28: {} + isexe@2.0.0: {} isomorphic-ws@5.0.0(ws@8.18.0): @@ -11746,18 +12280,6 @@ snapshots: optionalDependencies: '@types/react': 19.1.3 - react-router-dom@6.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): - dependencies: - '@remix-run/router': 1.23.0 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - react-router: 6.30.0(react@19.1.0) - - react-router@6.30.0(react@19.1.0): - dependencies: - '@remix-run/router': 1.23.0 - react: 19.1.0 - react-style-singleton@2.2.3(@types/react@19.1.3)(react@19.1.0): dependencies: get-nonce: 1.0.1 @@ -11780,6 +12302,14 @@ snapshots: real-require@0.2.0: {} + recast@0.23.11: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -12106,6 +12636,10 @@ snapshots: source-map-js@1.2.1: {} + source-map@0.6.1: {} + + source-map@0.7.4: {} + split2@4.2.0: {} sponge-case@1.0.1: @@ -12244,6 +12778,10 @@ snapshots: through@2.3.8: {} + tiny-invariant@1.3.3: {} + + tiny-warning@1.0.3: {} + tinybench@2.9.0: {} tinyexec@0.3.2: {} @@ -12426,6 +12964,12 @@ snapshots: unpipe@1.0.0: {} + unplugin@2.3.5: + dependencies: + acorn: 8.15.0 + picomatch: 4.0.2 + webpack-virtual-modules: 0.6.2 + unrs-resolver@1.7.2: dependencies: napi-postinstall: 0.2.3 @@ -12616,6 +13160,8 @@ snapshots: webidl-conversions@3.0.1: {} + webpack-virtual-modules@0.6.2: {} + whatwg-url@5.0.0: dependencies: tr46: 0.0.3