@cdmbase/wiki-browser 12.0.18-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/lib/components/Logo.d.ts +4 -0
- package/lib/components/Logo.d.ts.map +1 -0
- package/lib/components/Logo.js +16 -0
- package/lib/components/Logo.js.map +1 -0
- package/lib/components/help/SidebarSearch.d.ts +8 -0
- package/lib/components/help/SidebarSearch.d.ts.map +1 -0
- package/lib/components/help/SidebarSearch.js +111 -0
- package/lib/components/help/SidebarSearch.js.map +1 -0
- package/lib/components/help/index.d.ts +2 -0
- package/lib/components/help/index.d.ts.map +1 -0
- package/lib/components/landing/FeatureCard.d.ts +13 -0
- package/lib/components/landing/FeatureCard.d.ts.map +1 -0
- package/lib/components/landing/FeatureCard.js +85 -0
- package/lib/components/landing/FeatureCard.js.map +1 -0
- package/lib/components/landing/QuickLinkCard.d.ts +8 -0
- package/lib/components/landing/QuickLinkCard.d.ts.map +1 -0
- package/lib/components/landing/QuickLinkCard.js +26 -0
- package/lib/components/landing/QuickLinkCard.js.map +1 -0
- package/lib/components/landing/SearchInput.d.ts +10 -0
- package/lib/components/landing/SearchInput.d.ts.map +1 -0
- package/lib/components/landing/SearchInput.js +223 -0
- package/lib/components/landing/SearchInput.js.map +1 -0
- package/lib/components/landing/index.d.ts +4 -0
- package/lib/components/landing/index.d.ts.map +1 -0
- package/lib/components/welcome.d.ts +3 -0
- package/lib/components/welcome.d.ts.map +1 -0
- package/lib/compute.d.ts +4 -0
- package/lib/compute.d.ts.map +1 -0
- package/lib/compute.js +96 -0
- package/lib/compute.js.map +1 -0
- package/lib/config/env-config.d.ts +4 -0
- package/lib/config/env-config.d.ts.map +1 -0
- package/lib/config/env-config.js +7 -0
- package/lib/config/env-config.js.map +1 -0
- package/lib/docs.config.d.ts +48 -0
- package/lib/docs.config.d.ts.map +1 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -0
- package/lib/loaders/search.d.ts +1 -0
- package/lib/loaders/search.d.ts.map +1 -0
- package/lib/module.d.ts +4 -0
- package/lib/module.d.ts.map +1 -0
- package/lib/module.js +11 -0
- package/lib/module.js.map +1 -0
- package/lib/pages/ArticlePage/ArticlePage.d.ts +4 -0
- package/lib/pages/ArticlePage/ArticlePage.d.ts.map +1 -0
- package/lib/pages/ArticlePage/ArticlePage.js +222 -0
- package/lib/pages/ArticlePage/ArticlePage.js.map +1 -0
- package/lib/pages/ArticlePage/index.d.ts +3 -0
- package/lib/pages/ArticlePage/index.d.ts.map +1 -0
- package/lib/pages/ArticlePage/index.js +3 -0
- package/lib/pages/ArticlePage/index.js.map +1 -0
- package/lib/pages/CategoryCollection/CategoryCollection.d.ts +4 -0
- package/lib/pages/CategoryCollection/CategoryCollection.d.ts.map +1 -0
- package/lib/pages/CategoryCollection/CategoryCollection.js +103 -0
- package/lib/pages/CategoryCollection/CategoryCollection.js.map +1 -0
- package/lib/pages/CategoryCollection/index.d.ts +3 -0
- package/lib/pages/CategoryCollection/index.d.ts.map +1 -0
- package/lib/pages/CategoryCollection/index.js +3 -0
- package/lib/pages/CategoryCollection/index.js.map +1 -0
- package/lib/pages/Help/HelpIndex.d.ts +4 -0
- package/lib/pages/Help/HelpIndex.d.ts.map +1 -0
- package/lib/pages/Help/HelpIndex.js +44 -0
- package/lib/pages/Help/HelpIndex.js.map +1 -0
- package/lib/pages/Help/index.d.ts +4 -0
- package/lib/pages/Help/index.d.ts.map +1 -0
- package/lib/pages/Help/index.js +226 -0
- package/lib/pages/Help/index.js.map +1 -0
- package/lib/pages/Landing/index.d.ts +3 -0
- package/lib/pages/Landing/index.d.ts.map +1 -0
- package/lib/pages/Landing/index.js +281 -0
- package/lib/pages/Landing/index.js.map +1 -0
- package/lib/routes.json +2533 -0
- package/lib/seo.d.ts +22 -0
- package/lib/seo.d.ts.map +1 -0
- package/lib/slot-fill/FooterFill.d.ts +3 -0
- package/lib/slot-fill/FooterFill.d.ts.map +1 -0
- package/lib/slot-fill/FooterFill.js +18 -0
- package/lib/slot-fill/FooterFill.js.map +1 -0
- package/lib/slot-fill/LogoFill.d.ts +5 -0
- package/lib/slot-fill/LogoFill.d.ts.map +1 -0
- package/lib/slot-fill/LogoFill.js +74 -0
- package/lib/slot-fill/LogoFill.js.map +1 -0
- package/lib/slot-fill/consts.d.ts +5 -0
- package/lib/slot-fill/consts.d.ts.map +1 -0
- package/lib/slot-fill/consts.js +1 -0
- package/lib/slot-fill/consts.js.map +1 -0
- package/lib/slot-fill/index.d.ts +4 -0
- package/lib/slot-fill/index.d.ts.map +1 -0
- package/lib/templates/assets/images/add-link-frontend.png +0 -0
- package/lib/templates/assets/images/add-package-backend.png +0 -0
- package/lib/templates/assets/images/add-to-backend-module.png +0 -0
- package/lib/templates/assets/images/add-upload-client-frontend.png +0 -0
- package/lib/templates/assets/images/additional-parameters.png +0 -0
- package/lib/templates/assets/images/aeh-implementation.png +0 -0
- package/lib/templates/assets/images/aeh-usage.png +0 -0
- package/lib/templates/assets/images/apollo-client/recommendation_cache_mgmt.png +0 -0
- package/lib/templates/assets/images/app-deploy-new-version/jenkins1.PNG +0 -0
- package/lib/templates/assets/images/app-deploy-new-version/jenkins2.PNG +0 -0
- package/lib/templates/assets/images/auth-wrapper-code.png +0 -0
- package/lib/templates/assets/images/cdebase.png +0 -0
- package/lib/templates/assets/images/cdm-locales-directory.png +0 -0
- package/lib/templates/assets/images/client-settings.png +0 -0
- package/lib/templates/assets/images/codegen_file_update.png +0 -0
- package/lib/templates/assets/images/configuration.png +0 -0
- package/lib/templates/assets/images/copy-plugin.png +0 -0
- package/lib/templates/assets/images/docusaurus.png +0 -0
- package/lib/templates/assets/images/error-link.png +0 -0
- package/lib/templates/assets/images/error-sample.png +0 -0
- package/lib/templates/assets/images/extension copy.png +0 -0
- package/lib/templates/assets/images/extension.png +0 -0
- package/lib/templates/assets/images/graphql/graphql-folder-backend.png +0 -0
- package/lib/templates/assets/images/graphql/graphql-folder-with-gql.png +0 -0
- package/lib/templates/assets/images/i18n-config.png +0 -0
- package/lib/templates/assets/images/image.png +0 -0
- package/lib/templates/assets/images/logo.svg +10 -0
- package/lib/templates/assets/images/logo1.svg +1 -0
- package/lib/templates/assets/images/modify-upload-false-server.png +0 -0
- package/lib/templates/assets/images/navigation-auth-enabled.png +0 -0
- package/lib/templates/assets/images/org-dashboard-navigation.png +0 -0
- package/lib/templates/assets/images/org-navigation.png +0 -0
- package/lib/templates/assets/images/preferences_graphql_type.png +0 -0
- package/lib/templates/assets/images/provider.png +0 -0
- package/lib/templates/assets/images/route-config.png +0 -0
- package/lib/templates/assets/images/service-accounts.png +0 -0
- package/lib/templates/assets/images/source-code/source-code-environments.png +0 -0
- package/lib/templates/assets/images/source-code/source-code-organization.png +0 -0
- package/lib/templates/assets/images/spin-clone-develop-deployment/jenkins-changes.png +0 -0
- package/lib/templates/assets/images/spin-clone-develop-deployment/lerna-changes.png +0 -0
- package/lib/templates/assets/images/spin-clone-develop-deployment/root-package-json-changes.png +0 -0
- package/lib/templates/assets/images/spin-clone-develop-deployment/values-dev-changes.png +0 -0
- package/lib/templates/assets/images/sso-mappers.png +0 -0
- package/lib/templates/assets/images/sso-picture-mapper.png +0 -0
- package/lib/templates/assets/images/sso-settings.png +0 -0
- package/lib/templates/assets/images/timesheet_apollo_cache.png +0 -0
- package/lib/templates/assets/images/timesheet_query.png +0 -0
- package/lib/templates/assets/images/tutorial/docsVersionDropdown.png +0 -0
- package/lib/templates/assets/images/tutorial/localeDropdown.png +0 -0
- package/lib/templates/assets/images/unauthenticated.png +0 -0
- package/lib/templates/assets/images/undraw_docusaurus_mountain.svg +170 -0
- package/lib/templates/assets/images/undraw_docusaurus_react.svg +169 -0
- package/lib/templates/assets/images/undraw_docusaurus_tree.svg +1 -0
- package/lib/templates/assets/images/vite-plugin-config.png +0 -0
- package/lib/templates/content/docs/Generators/Project/generate-fullproject.md +12 -0
- package/lib/templates/content/docs/LLM/Logger.llm.md +194 -0
- package/lib/templates/content/docs/LLM/backend-proxies-services-llm.md +2687 -0
- package/lib/templates/content/docs/LLM/backend-service-llm.md +3384 -0
- package/lib/templates/content/docs/LLM/db_migration_llm.md +954 -0
- package/lib/templates/content/docs/LLM/frontend/REMIX-15.3-upgrade-llm.md +1245 -0
- package/lib/templates/content/docs/LLM/inngest/INNGEST_FUNCTION_DEVELOPMENT_GUIDE_LLM.md +1241 -0
- package/lib/templates/content/docs/LLM/inngest/INNGEST_NAMESPACE_LLM.md +384 -0
- package/lib/templates/content/docs/LLM/llm_workflow_namespace.md +384 -0
- package/lib/templates/content/docs/LLM/organization-components-form-llm.md +1395 -0
- package/lib/templates/content/docs/LLM/page-component-llm.md +173 -0
- package/lib/templates/content/docs/LLM/preferences-settings-llm.md +2781 -0
- package/lib/templates/content/docs/LLM/tailwind-css-llm.md +502 -0
- package/lib/templates/content/docs/UI/SchemaBasedUI.md +334 -0
- package/lib/templates/content/docs/UI/SlotFillComponent.md +334 -0
- package/lib/templates/content/docs/adminide-modules/account/auth0-login.md +31 -0
- package/lib/templates/content/docs/adminide-modules/account/index.md +14 -0
- package/lib/templates/content/docs/adminide-modules/account/keycloak-remix-setup.md +86 -0
- package/lib/templates/content/docs/adminide-modules/account/remix-auth-setup.md +79 -0
- package/lib/templates/content/docs/adminide-modules/account/various-auth-qatest.md +157 -0
- package/lib/templates/content/docs/adminide-modules/api-builders/graphql.md +906 -0
- package/lib/templates/content/docs/adminide-modules/billing/payments/index.md +14 -0
- package/lib/templates/content/docs/adminide-modules/billing/payments/stripe/index.md +14 -0
- package/lib/templates/content/docs/adminide-modules/billing/payments/stripe/settingup-stripe-locally.md +25 -0
- package/lib/templates/content/docs/adminide-modules/billing/tier-config.md +293 -0
- package/lib/templates/content/docs/adminide-modules/connectors/Connector.md +207 -0
- package/lib/templates/content/docs/adminide-modules/file-upload/index.md +16 -0
- package/lib/templates/content/docs/adminide-modules/file-upload/setup.md +435 -0
- package/lib/templates/content/docs/adminide-modules/file-upload/upload-file-using-signed-url.md +161 -0
- package/lib/templates/content/docs/adminide-modules/preferences/AddAdditionalPermissions.md +151 -0
- package/lib/templates/content/docs/adminide-modules/preferences/Configuration.md +241 -0
- package/lib/templates/content/docs/adminide-modules/preferences/Policy-Configuration.md +61 -0
- package/lib/templates/content/docs/adminide-modules/preferences/UI-components/ResourceSettingsLoader.md +319 -0
- package/lib/templates/content/docs/adminide-modules/preferences/contribute_scope_target.md +280 -0
- package/lib/templates/content/docs/adminide-modules/preferences/generate-urii.md +94 -0
- package/lib/templates/content/docs/adminide-modules/preferences/index.md +28 -0
- package/lib/templates/content/docs/adminide-modules/preferences/machine-configuration.md +157 -0
- package/lib/templates/content/docs/adminide-modules/preferences/pageSettings/generateCdecodeUri.md +1289 -0
- package/lib/templates/content/docs/adminide-modules/preferences/pageSettings/migratingFromUseSettings.md +215 -0
- package/lib/templates/content/docs/adminide-modules/preferences/permissions/Roles-Permissions.md +72 -0
- package/lib/templates/content/docs/adminide-modules/preferences/permissions/settingUserPermissions.md +139 -0
- package/lib/templates/content/docs/adminide-modules/preferences/preference-dependency.md +138 -0
- package/lib/templates/content/docs/adminide-modules/preferences/route-based-configuration.md +41 -0
- package/lib/templates/content/docs/adminide-modules/preferences/schema-configuration.md +71 -0
- package/lib/templates/content/docs/adminide-modules/preferences/supported.md +24 -0
- package/lib/templates/content/docs/adminide-modules/preferences/useSettingsLoader.md +248 -0
- package/lib/templates/content/docs/adminide-modules/project-tools/auth-providers.md +1317 -0
- package/lib/templates/content/docs/adminide-modules/project-tools/keycloak-guide.md +543 -0
- package/lib/templates/content/docs/adminide-modules/project-tools/tenant-management/tenant-based-authentication.md +846 -0
- package/lib/templates/content/docs/adminide-modules/project-tools/tenant-management/tenant-management.md +708 -0
- package/lib/templates/content/docs/adminide-modules/project-tools/tenant-management/tenants.md +1117 -0
- package/lib/templates/content/docs/chrome-extension/index.md +14 -0
- package/lib/templates/content/docs/chrome-extension/setup.md +30 -0
- package/lib/templates/content/docs/contributing/adding-package.md +23 -0
- package/lib/templates/content/docs/contributing/adding_new_modules.md +99 -0
- package/lib/templates/content/docs/contributing/architecture-updates.md +19 -0
- package/lib/templates/content/docs/contributing/avoid-using-promises-ui.md +116 -0
- package/lib/templates/content/docs/contributing/coding-guidelines.md +111 -0
- package/lib/templates/content/docs/contributing/do-and-dont.md +42 -0
- package/lib/templates/content/docs/contributing/faq.md +22 -0
- package/lib/templates/content/docs/contributing/folder-setup/browser.md +12 -0
- package/lib/templates/content/docs/contributing/folder-setup/config.md +12 -0
- package/lib/templates/content/docs/contributing/folder-setup/containers-server.md +12 -0
- package/lib/templates/content/docs/contributing/folder-setup/core.md +12 -0
- package/lib/templates/content/docs/contributing/folder-setup/graphql.md +12 -0
- package/lib/templates/content/docs/contributing/folder-setup/index.md +30 -0
- package/lib/templates/content/docs/contributing/folder-setup/module.md +12 -0
- package/lib/templates/content/docs/contributing/folder-setup/server.md +12 -0
- package/lib/templates/content/docs/contributing/folder-setup/services.md +12 -0
- package/lib/templates/content/docs/contributing/folder-setup/store.md +12 -0
- package/lib/templates/content/docs/contributing/frontend-coding.md +30 -0
- package/lib/templates/content/docs/contributing/git-subtree-sharing.md +73 -0
- package/lib/templates/content/docs/contributing/graphql-subscriptions.md +69 -0
- package/lib/templates/content/docs/contributing/how-to-contribute.md +30 -0
- package/lib/templates/content/docs/contributing/how_to_check_pure_esm.md +29 -0
- package/lib/templates/content/docs/contributing/index.md +60 -0
- package/lib/templates/content/docs/contributing/installation-issues.md +23 -0
- package/lib/templates/content/docs/contributing/keyboard-shortcut.md +131 -0
- package/lib/templates/content/docs/contributing/language/locale-support.md +12 -0
- package/lib/templates/content/docs/contributing/lerna-build-tools.md +516 -0
- package/lib/templates/content/docs/contributing/lerna-yarn-workspaces.md +95 -0
- package/lib/templates/content/docs/contributing/lint-and-formatter.md +20 -0
- package/lib/templates/content/docs/contributing/mobile-setup.md +16 -0
- package/lib/templates/content/docs/contributing/project-setup.md +233 -0
- package/lib/templates/content/docs/contributing/react/index.md +14 -0
- package/lib/templates/content/docs/contributing/react/lazy-component.md +70 -0
- package/lib/templates/content/docs/contributing/run-various-options.md +124 -0
- package/lib/templates/content/docs/contributing/schema-first-graphql-types.md +37 -0
- package/lib/templates/content/docs/contributing/source-code-organization.md +57 -0
- package/lib/templates/content/docs/contributing/staging-docker.md +88 -0
- package/lib/templates/content/docs/contributing/third-party/apollo-client-v3-tutorials.md +28 -0
- package/lib/templates/content/docs/contributing/third-party/index.md +18 -0
- package/lib/templates/content/docs/contributing/typescript-contribution.md +16 -0
- package/lib/templates/content/docs/devops/app-deploy-new-version.md +30 -0
- package/lib/templates/content/docs/devops/index.md +14 -0
- package/lib/templates/content/docs/devops/mobile-jenkins-build.md +40 -0
- package/lib/templates/content/docs/devops/versioning-the-project.md +128 -0
- package/lib/templates/content/docs/error-handler/application-error-handler.md +40 -0
- package/lib/templates/content/docs/error-handler/error-handling.md +26 -0
- package/lib/templates/content/docs/error-handler/index.md +16 -0
- package/lib/templates/content/docs/error-handler/logging-errors.md +14 -0
- package/lib/templates/content/docs/feature-api/copy-operation.md +427 -0
- package/lib/templates/content/docs/feature-api/feature-browser/assets.md +46 -0
- package/lib/templates/content/docs/feature-api/feature-browser/auth-permissions.md +12 -0
- package/lib/templates/content/docs/feature-api/feature-browser/feature.md +131 -0
- package/lib/templates/content/docs/feature-api/feature-browser/index.md +22 -0
- package/lib/templates/content/docs/feature-api/feature-browser/routes-menu.md +110 -0
- package/lib/templates/content/docs/feature-api/feature-browser/routing-convention.md +124 -0
- package/lib/templates/content/docs/feature-api/feature-browser/routing.md +338 -0
- package/lib/templates/content/docs/feature-api/feature-mobile/auth-permissions.md +20 -0
- package/lib/templates/content/docs/feature-api/feature-mobile/feature.md +130 -0
- package/lib/templates/content/docs/feature-api/feature-mobile/index.md +18 -0
- package/lib/templates/content/docs/feature-api/feature-mobile/navigation.md +187 -0
- package/lib/templates/content/docs/feature-api/feature-server/Scheduling.md +44 -0
- package/lib/templates/content/docs/feature-api/feature-server/dataloader.md +320 -0
- package/lib/templates/content/docs/feature-api/feature-server/dependency-injection.md +81 -0
- package/lib/templates/content/docs/feature-api/feature-server/feature.md +65 -0
- package/lib/templates/content/docs/feature-api/feature-server/generic-dataloader.md +135 -0
- package/lib/templates/content/docs/feature-api/feature-server/index.md +40 -0
- package/lib/templates/content/docs/feature-api/feature-server/migration.md +127 -0
- package/lib/templates/content/docs/feature-api/feature-server/mongo-model.md +72 -0
- package/lib/templates/content/docs/feature-api/feature-server/permissions.md +12 -0
- package/lib/templates/content/docs/feature-api/feature-server/policies.md +57 -0
- package/lib/templates/content/docs/feature-api/feature-server/preferences.md +57 -0
- package/lib/templates/content/docs/feature-api/feature-server/repositories.md +114 -0
- package/lib/templates/content/docs/feature-api/feature-server/resolvers.md +126 -0
- package/lib/templates/content/docs/feature-api/feature-server/rules.md +132 -0
- package/lib/templates/content/docs/feature-api/feature-server/schema.md +12 -0
- package/lib/templates/content/docs/feature-api/feature-server/services.md +102 -0
- package/lib/templates/content/docs/feature-api/feature-server/setup-resource-crud.md +359 -0
- package/lib/templates/content/docs/feature-api/index.md +18 -0
- package/lib/templates/content/docs/graphql/apolloClient-mutation.md +94 -0
- package/lib/templates/content/docs/graphql/index.md +14 -0
- package/lib/templates/content/docs/graphql/scalars.md +15 -0
- package/lib/templates/content/docs/help/index.md +14 -0
- package/lib/templates/content/docs/help/intro.md +16 -0
- package/lib/templates/content/docs/intl/ant-design-menu-translation.md +74 -0
- package/lib/templates/content/docs/intl/intl-namespace.md +129 -0
- package/lib/templates/content/docs/intl/vite-plugin-intl.md +87 -0
- package/lib/templates/content/docs/intl/webpack-plugin-intl.md +12 -0
- package/lib/templates/content/docs/intro.md +18 -0
- package/lib/templates/content/docs/knowledge/basic-fullstack.md +238 -0
- package/lib/templates/content/docs/mailing/index.md +14 -0
- package/lib/templates/content/docs/mailing/mailing-template.md +148 -0
- package/lib/templates/content/docs/mobile/App-navigation-generator.md +410 -0
- package/lib/templates/content/docs/mobile/MobileTestCases.md +264 -0
- package/lib/templates/content/docs/mobile/eas-profile-build.md +107 -0
- package/lib/templates/content/docs/mobile/expo-push-notification-setup.md +216 -0
- package/lib/templates/content/docs/mobile/index.md +14 -0
- package/lib/templates/content/docs/mobile/routes.md +83 -0
- package/lib/templates/content/docs/organization/adding-account-context.md +116 -0
- package/lib/templates/content/docs/organization/adding-org-mobile-navigation.md +22 -0
- package/lib/templates/content/docs/organization/adding-org-web-navigation.md +12 -0
- package/lib/templates/content/docs/organization/index.md +20 -0
- package/lib/templates/content/docs/organization/initialization.md +20 -0
- package/lib/templates/content/docs/organization/organization-resource-vs-resource.md +112 -0
- package/lib/templates/content/docs/remix/configuration/component-structure-best-practices.md +152 -0
- package/lib/templates/content/docs/remix/configuration/configurations.md +218 -0
- package/lib/templates/content/docs/remix/configuration/css-import-and-stylesheets.md +142 -0
- package/lib/templates/content/docs/remix/configuration/dont-subcomponent-network.md +166 -0
- package/lib/templates/content/docs/remix/configuration/generated-data-loaders.md +122 -0
- package/lib/templates/content/docs/remix/configuration/generated-resource-loaders.md +257 -0
- package/lib/templates/content/docs/remix/configuration/query-params-generator.md +216 -0
- package/lib/templates/content/docs/remix/configuration/routes-extra-icons.md +103 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-advanced.md +86 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-auth.md +113 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-best-practices.md +55 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-fields.md +79 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-graphql.md +79 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-index.md +112 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-loaders.md +165 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-middleware.md +196 -0
- package/lib/templates/content/docs/remix/configuration/routes-json-overview.md +53 -0
- package/lib/templates/content/docs/remix/data-loaders.md +43 -0
- package/lib/templates/content/docs/remix/devtools/remix-devtools.md +58 -0
- package/lib/templates/content/docs/remix/examples/changes-using-servercode.md +79 -0
- package/lib/templates/content/docs/remix/extra-icons.md +62 -0
- package/lib/templates/content/docs/remix/extra-links.md +65 -0
- package/lib/templates/content/docs/remix/generated-data-loaders.md +114 -0
- package/lib/templates/content/docs/remix/queryParamsGenerator.md +89 -0
- package/lib/templates/content/docs/remix/resources.md +16 -0
- package/lib/templates/content/docs/remix/styles.md +132 -0
- package/lib/templates/content/docs/remix/wiki.md +12 -0
- package/lib/templates/content/docs/security/auth-wrapper/auth-wrapper.md +24 -0
- package/lib/templates/content/docs/security/index.md +18 -0
- package/lib/templates/content/docs/security/secure-button-mobilenative.md +88 -0
- package/lib/templates/content/docs/security/secure-button-web.md +89 -0
- package/lib/templates/content/docs/server-side/account-customization.md +82 -0
- package/lib/templates/content/docs/server-side/apollo/caching.md +164 -0
- package/lib/templates/content/docs/server-side/backend-architecture/FINAL-DECISION.md +209 -0
- package/lib/templates/content/docs/server-side/backend-architecture/TRUE-FINAL-ARCHITECTURE.md +603 -0
- package/lib/templates/content/docs/server-side/backend-architecture/index1.md +0 -0
- package/lib/templates/content/docs/server-side/backend-coding.md +839 -0
- package/lib/templates/content/docs/server-side/e2b/manageing-template.md +197 -0
- package/lib/templates/content/docs/server-side/index.md +14 -0
- package/lib/templates/content/docs/server-side/inngest-functions-module.md +309 -0
- package/lib/templates/content/docs/server-side/listen-stripe-events.md +43 -0
- package/lib/templates/content/docs/server-side/slug-service.md +323 -0
- package/lib/templates/content/docs/tests/index.md +18 -0
- package/lib/templates/content/docs/tests/jest-test-debug-vscode.md +40 -0
- package/lib/templates/content/docs/tests/known-errors.md +116 -0
- package/lib/templates/content/docs/tests/service-test-template.md +118 -0
- package/lib/templates/content/docs/tests/test-setup.md +93 -0
- package/lib/templates/content/docs/xstate.md +23 -0
- package/lib/types.d.ts +37 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/utils/docsNavigation.d.ts +9 -0
- package/lib/utils/docsNavigation.d.ts.map +1 -0
- package/lib/utils/docsNavigation.js +37 -0
- package/lib/utils/docsNavigation.js.map +1 -0
- package/lib/utils/helpCenterUtils.d.ts +26 -0
- package/lib/utils/helpCenterUtils.d.ts.map +1 -0
- package/lib/utils/index.d.ts +3 -0
- package/lib/utils/index.d.ts.map +1 -0
- package/lib/utils/index.js +3 -0
- package/lib/utils/index.js.map +1 -0
- package/lib/utils/markdownLoader.d.ts +36 -0
- package/lib/utils/markdownLoader.d.ts.map +1 -0
- package/lib/utils/markdownLoader.js +2242 -0
- package/lib/utils/markdownLoader.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,954 @@
|
|
|
1
|
+
# LLM Instructions: Database Migration Generator for AdminIDE Stack
|
|
2
|
+
|
|
3
|
+
You are an expert database migration generator for the AdminIDE Stack system. When asked to create a database migration, follow these instructions precisely.
|
|
4
|
+
|
|
5
|
+
## Core Requirements
|
|
6
|
+
|
|
7
|
+
### Interface Implementation
|
|
8
|
+
|
|
9
|
+
All migrations MUST implement the `IDatabaseMigration` interface:
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
export interface IDatabaseMigration {
|
|
13
|
+
id: string; // Unique identifier: ${ClassName}_YYYYMMDD
|
|
14
|
+
up?(): Promise<void>; // Execute migration
|
|
15
|
+
down?(): Promise<void>; // Rollback migration
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Required Imports
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { IDatabaseMigration } from '@adminide-stack/core';
|
|
23
|
+
import { inject, injectable } from 'inversify';
|
|
24
|
+
import { Connection } from 'mongoose';
|
|
25
|
+
import { CdmLogger } from '@cdm-logger/core';
|
|
26
|
+
import { DB_COLL_NAMES } from 'common/server';
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Class Structure Rules
|
|
30
|
+
|
|
31
|
+
1. MUST use `@injectable()` decorator
|
|
32
|
+
2. MUST inject `MongoDBConnection` and `Logger`
|
|
33
|
+
3. MUST initialize logger with `logger.child({ className: ClassName.name })`
|
|
34
|
+
4. Migration ID MUST follow format: `${ClassName}_YYYYMMDD`
|
|
35
|
+
5. ALWAYS use try-catch blocks in up() and down() methods
|
|
36
|
+
6. ALWAYS log operations using Pino format specifiers (%s, %d, %j)
|
|
37
|
+
7. Make migrations idempotent (safe to run multiple times)
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Migration Types
|
|
42
|
+
|
|
43
|
+
### Type 1: Standard Database Migration
|
|
44
|
+
|
|
45
|
+
**Use when:**
|
|
46
|
+
|
|
47
|
+
- Modifying schema (adding/removing fields)
|
|
48
|
+
- Updating data
|
|
49
|
+
- Managing indexes
|
|
50
|
+
- Field deprecation
|
|
51
|
+
- Data cleanup
|
|
52
|
+
|
|
53
|
+
**Template:**
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { IDatabaseMigration } from '@adminide-stack/core';
|
|
57
|
+
import { inject, injectable } from 'inversify';
|
|
58
|
+
import { Connection } from 'mongoose';
|
|
59
|
+
import { CdmLogger } from '@cdm-logger/core';
|
|
60
|
+
import { DB_COLL_NAMES } from 'common/server';
|
|
61
|
+
|
|
62
|
+
@injectable()
|
|
63
|
+
export class [MigrationName]Migration implements IDatabaseMigration {
|
|
64
|
+
id = `${[MigrationName]Migration.name}_[YYYYMMDD]`;
|
|
65
|
+
logger: CdmLogger.ILogger;
|
|
66
|
+
|
|
67
|
+
constructor(
|
|
68
|
+
@inject('MongoDBConnection') private readonly db: Connection,
|
|
69
|
+
@inject('Logger') logger: CdmLogger.ILogger,
|
|
70
|
+
) {
|
|
71
|
+
this.logger = logger.child({ className: [MigrationName]Migration.name });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async up?(): Promise<void> {
|
|
75
|
+
this.logger.info('Running migration up: [Description]');
|
|
76
|
+
try {
|
|
77
|
+
const collection = this.db.collection(DB_COLL_NAMES.[CollectionName]);
|
|
78
|
+
|
|
79
|
+
// Check if migration already applied (idempotency)
|
|
80
|
+
const existing = await collection.findOne({ [checkCriteria] });
|
|
81
|
+
if (existing) {
|
|
82
|
+
this.logger.info('Migration already applied, skipping');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Perform migration
|
|
87
|
+
const result = await collection.updateMany(
|
|
88
|
+
{ [filter] },
|
|
89
|
+
{
|
|
90
|
+
$set: { [fieldsToAdd] },
|
|
91
|
+
$unset: { [fieldsToRemove] }
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
this.logger.info('Modified %d documents', result.modifiedCount);
|
|
96
|
+
this.logger.info('Migration up completed successfully');
|
|
97
|
+
} catch (error) {
|
|
98
|
+
this.logger.error(error, 'Migration up failed: %s', error.message);
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async down?(): Promise<void> {
|
|
104
|
+
this.logger.info('Running migration down: Reverting changes');
|
|
105
|
+
try {
|
|
106
|
+
const collection = this.db.collection(DB_COLL_NAMES.[CollectionName]);
|
|
107
|
+
|
|
108
|
+
// Revert changes from up()
|
|
109
|
+
await collection.updateMany(
|
|
110
|
+
{ [filter] },
|
|
111
|
+
{ [revertOperations] }
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
this.logger.info('Migration down completed successfully');
|
|
115
|
+
} catch (error) {
|
|
116
|
+
this.logger.error(error, 'Migration down failed: %s', error.message);
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Type 2: Extension-Based Migration
|
|
124
|
+
|
|
125
|
+
**Use when:**
|
|
126
|
+
|
|
127
|
+
- Seeding marketplace extensions
|
|
128
|
+
- Creating form templates
|
|
129
|
+
- Registering extension registries with source documents
|
|
130
|
+
|
|
131
|
+
**Additional Required Imports:**
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import mongoose, { Connection } from 'mongoose';
|
|
135
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
136
|
+
import { readFileSync, existsSync } from 'fs';
|
|
137
|
+
import { resolve } from 'path';
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Helper Function:**
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
function getPathFromModule(relativePath: string) {
|
|
144
|
+
return resolve(__dirname, relativePath);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Template:**
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { IDatabaseMigration } from '@adminide-stack/core';
|
|
152
|
+
import { inject, injectable } from 'inversify';
|
|
153
|
+
import mongoose, { Connection } from 'mongoose';
|
|
154
|
+
import { CdmLogger } from '@cdm-logger/core';
|
|
155
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
156
|
+
import { DB_COLL_NAMES } from 'common/server';
|
|
157
|
+
import { readFileSync, existsSync } from 'fs';
|
|
158
|
+
import { resolve } from 'path';
|
|
159
|
+
|
|
160
|
+
function getPathFromModule(relativePath: string) {
|
|
161
|
+
return resolve(__dirname, relativePath);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@injectable()
|
|
165
|
+
export class Seed[ExtensionName]Migration implements IDatabaseMigration {
|
|
166
|
+
id = `${Seed[ExtensionName]Migration.name}_[YYYYMMDD]`;
|
|
167
|
+
logger: CdmLogger.ILogger;
|
|
168
|
+
private templateData: any;
|
|
169
|
+
|
|
170
|
+
constructor(
|
|
171
|
+
@inject('MongoDBConnection') private readonly db: Connection,
|
|
172
|
+
@inject('Logger') logger: CdmLogger.ILogger,
|
|
173
|
+
) {
|
|
174
|
+
this.logger = logger.child({ className: Seed[ExtensionName]Migration.name });
|
|
175
|
+
|
|
176
|
+
// Load template.json
|
|
177
|
+
try {
|
|
178
|
+
const templatePath = getPathFromModule('./template.json');
|
|
179
|
+
this.logger.info('Loading template from: %s', templatePath);
|
|
180
|
+
|
|
181
|
+
if (!existsSync(templatePath)) {
|
|
182
|
+
throw new Error(`template.json not found at: ${templatePath}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const templateContent = readFileSync(templatePath, 'utf-8');
|
|
186
|
+
const parsedData = JSON.parse(templateContent);
|
|
187
|
+
const templateArray = Array.isArray(parsedData) ? parsedData : [parsedData];
|
|
188
|
+
|
|
189
|
+
if (templateArray.length === 0) {
|
|
190
|
+
throw new Error('Template.json is empty');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
this.templateData = this.convertExtendedJson(templateArray[0]);
|
|
194
|
+
this.logger.info('Template loaded successfully');
|
|
195
|
+
} catch (error) {
|
|
196
|
+
this.logger.error(error, 'Failed to load template.json');
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Convert MongoDB Extended JSON to native types
|
|
202
|
+
private convertExtendedJson(obj: any): any {
|
|
203
|
+
if (obj === null || obj === undefined) return obj;
|
|
204
|
+
|
|
205
|
+
// Handle ObjectId: { "$oid": "507f1f77bcf86cd799439011" }
|
|
206
|
+
if (typeof obj === 'object' && obj.$oid) {
|
|
207
|
+
return new mongoose.Types.ObjectId(obj.$oid);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Handle Date: { "$date": "2024-01-01T00:00:00.000Z" }
|
|
211
|
+
if (typeof obj === 'object' && obj.$date) {
|
|
212
|
+
return new Date(obj.$date);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Handle arrays
|
|
216
|
+
if (Array.isArray(obj)) {
|
|
217
|
+
return obj.map(item => this.convertExtendedJson(item));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Handle objects recursively
|
|
221
|
+
if (typeof obj === 'object') {
|
|
222
|
+
const converted: any = {};
|
|
223
|
+
for (const key in obj) {
|
|
224
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
225
|
+
converted[key] = this.convertExtendedJson(obj[key]);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return converted;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return obj;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async up?(): Promise<void> {
|
|
235
|
+
this.logger.info('Running Up - Seeding [ExtensionName] Extension Data');
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
// Validate collection names exist
|
|
239
|
+
if (!DB_COLL_NAMES.[SourceCollection]) {
|
|
240
|
+
throw new Error('DB_COLL_NAMES.[SourceCollection] is undefined');
|
|
241
|
+
}
|
|
242
|
+
if (!DB_COLL_NAMES.ExtensionRegistries) {
|
|
243
|
+
throw new Error('DB_COLL_NAMES.ExtensionRegistries is undefined');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Step 1: Insert Source Document (Form/Configuration/Template)
|
|
247
|
+
const sourceDocumentId = new mongoose.Types.ObjectId('[your-source-doc-id]');
|
|
248
|
+
this.logger.info('Checking if source document exists with _id: %s', sourceDocumentId.toString());
|
|
249
|
+
|
|
250
|
+
const existingSourceDoc = await this.db
|
|
251
|
+
.collection(DB_COLL_NAMES.[SourceCollection])
|
|
252
|
+
.findOne({ _id: sourceDocumentId });
|
|
253
|
+
|
|
254
|
+
if (!existingSourceDoc) {
|
|
255
|
+
this.logger.info('Source document does not exist, inserting...');
|
|
256
|
+
try {
|
|
257
|
+
await this.db
|
|
258
|
+
.collection(DB_COLL_NAMES.[SourceCollection])
|
|
259
|
+
.insertOne(this.templateData);
|
|
260
|
+
this.logger.info('Source document seeded with ID: %s', sourceDocumentId.toString());
|
|
261
|
+
} catch (insertError) {
|
|
262
|
+
if (insertError.code === 11000) {
|
|
263
|
+
this.logger.info('Source document already exists (duplicate key)');
|
|
264
|
+
} else {
|
|
265
|
+
throw insertError;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
this.logger.info('Source document already exists with _id: %s', existingSourceDoc._id);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Step 2: Insert Extension Registry
|
|
273
|
+
const extensionSlug = '[org-id]/[extension-name]';
|
|
274
|
+
this.logger.info('Checking if extension exists with slug: %s', extensionSlug);
|
|
275
|
+
|
|
276
|
+
const existingExtension = await this.db
|
|
277
|
+
.collection(DB_COLL_NAMES.ExtensionRegistries)
|
|
278
|
+
.findOne({ extensionSlug });
|
|
279
|
+
|
|
280
|
+
if (!existingExtension) {
|
|
281
|
+
this.logger.info('Extension does not exist, inserting...');
|
|
282
|
+
|
|
283
|
+
const extensionDocument = {
|
|
284
|
+
extensionSlug,
|
|
285
|
+
name: '[Extension Display Name]',
|
|
286
|
+
sourceCollection: '[FORMS|CONFIGURATIONS|TEMPLATES]',
|
|
287
|
+
sourceDocumentId: sourceDocumentId.toString(),
|
|
288
|
+
releases: [{
|
|
289
|
+
extensionSlug,
|
|
290
|
+
sourceCollection: '[FORMS|CONFIGURATIONS|TEMPLATES]',
|
|
291
|
+
sourceDocumentId: sourceDocumentId.toString(),
|
|
292
|
+
version: '1.0.0',
|
|
293
|
+
manifest: JSON.stringify({
|
|
294
|
+
name: '[Extension Display Name]',
|
|
295
|
+
version: '1.0.0',
|
|
296
|
+
description: '[Extension description]',
|
|
297
|
+
publisher: '[org-id]',
|
|
298
|
+
main: 'index.js',
|
|
299
|
+
author: '[Author Name]',
|
|
300
|
+
keywords: ['keyword1', 'keyword2'],
|
|
301
|
+
license: 'MIT',
|
|
302
|
+
engines: { node: '>=14.0.0' },
|
|
303
|
+
displayName: '[Extension Display Name]',
|
|
304
|
+
category: 'utility',
|
|
305
|
+
tags: ['tag1', 'tag2'],
|
|
306
|
+
activationEvents: ['*'],
|
|
307
|
+
contributes: {
|
|
308
|
+
uiLayout: [{
|
|
309
|
+
title: '[Feature Title]',
|
|
310
|
+
properties: {
|
|
311
|
+
'[config.key]': {
|
|
312
|
+
type: 'string',
|
|
313
|
+
default: '[default-value]',
|
|
314
|
+
description: '[Configuration description]',
|
|
315
|
+
scope: 'window',
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
}],
|
|
319
|
+
},
|
|
320
|
+
}),
|
|
321
|
+
bundle: `
|
|
322
|
+
(function() {
|
|
323
|
+
'use strict';
|
|
324
|
+
var exports = {
|
|
325
|
+
activate: function() { /* Extension activation logic */ },
|
|
326
|
+
deactivate: function() { /* Extension cleanup */ }
|
|
327
|
+
};
|
|
328
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = exports;
|
|
329
|
+
if (typeof define === 'function' && define.amd) define([], function() { return exports; });
|
|
330
|
+
if (typeof window !== 'undefined') window.extensionExports = exports;
|
|
331
|
+
})();
|
|
332
|
+
`,
|
|
333
|
+
}],
|
|
334
|
+
uuid: uuidv4(),
|
|
335
|
+
createdAt: new Date(),
|
|
336
|
+
updatedAt: new Date(),
|
|
337
|
+
__v: 0,
|
|
338
|
+
version: '1.0.0',
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
const result = await this.db
|
|
343
|
+
.collection(DB_COLL_NAMES.ExtensionRegistries)
|
|
344
|
+
.insertOne(extensionDocument);
|
|
345
|
+
this.logger.info('Extension seeded with _id: %s', result.insertedId);
|
|
346
|
+
this.logger.info('Extension sourceCollection: %s', extensionDocument.sourceCollection);
|
|
347
|
+
this.logger.info('Extension sourceDocumentId: %s', extensionDocument.sourceDocumentId);
|
|
348
|
+
} catch (extensionError) {
|
|
349
|
+
this.logger.error('Failed to insert extension: %j', {
|
|
350
|
+
errorMessage: extensionError.message,
|
|
351
|
+
errorCode: extensionError.code,
|
|
352
|
+
extensionSlug,
|
|
353
|
+
});
|
|
354
|
+
throw extensionError;
|
|
355
|
+
}
|
|
356
|
+
} else {
|
|
357
|
+
this.logger.info('Extension already exists with _id: %s', existingExtension._id);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
this.logger.info('Migration up completed successfully');
|
|
361
|
+
} catch (error) {
|
|
362
|
+
this.logger.error('Migration up failed: %j', {
|
|
363
|
+
errorMessage: error.message,
|
|
364
|
+
errorStack: error.stack,
|
|
365
|
+
errorName: error.name,
|
|
366
|
+
});
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async down?(): Promise<void> {
|
|
372
|
+
this.logger.info('Running Down - Removing [ExtensionName] Extension Data');
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
// Remove Source Document
|
|
376
|
+
const sourceDocumentId = new mongoose.Types.ObjectId('[your-source-doc-id]');
|
|
377
|
+
await this.db.collection(DB_COLL_NAMES.[SourceCollection]).deleteOne({
|
|
378
|
+
_id: sourceDocumentId,
|
|
379
|
+
});
|
|
380
|
+
this.logger.info('Source document removed');
|
|
381
|
+
|
|
382
|
+
// Remove Extension Registry
|
|
383
|
+
await this.db.collection(DB_COLL_NAMES.ExtensionRegistries).deleteOne({
|
|
384
|
+
extensionSlug: '[org-id]/[extension-name]',
|
|
385
|
+
});
|
|
386
|
+
this.logger.info('Extension registry removed');
|
|
387
|
+
|
|
388
|
+
this.logger.info('Migration down completed successfully');
|
|
389
|
+
} catch (error) {
|
|
390
|
+
this.logger.error(error, 'Migration down failed: %s', error.message);
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## Common Standard Migration Patterns
|
|
400
|
+
|
|
401
|
+
### Add Field with Default Value
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
await collection.updateMany({ newField: { $exists: false } }, { $set: { newField: 'default-value' } });
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Remove Deprecated Field
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
await collection.updateMany({}, { $unset: { deprecatedField: '' } });
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Rename Field
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
await collection.updateMany({}, { $rename: { oldFieldName: 'newFieldName' } });
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Update Nested Fields
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
await collection.updateMany({}, { $set: { 'settings.roles': ['USER'] } });
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Drop Index Safely
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
try {
|
|
429
|
+
await collection.dropIndex('old_index_name');
|
|
430
|
+
this.logger.info('Dropped index: old_index_name');
|
|
431
|
+
} catch (error) {
|
|
432
|
+
this.logger.warn('Could not drop index: %s', error.message);
|
|
433
|
+
// Don't throw - index might not exist
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Generate Unique Slugs
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
const items = await collection.find({ slug: { $exists: false } }).toArray();
|
|
441
|
+
for (const item of items) {
|
|
442
|
+
let slug = item.name
|
|
443
|
+
.toLowerCase()
|
|
444
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
445
|
+
.replace(/(^-|-$)/g, '');
|
|
446
|
+
|
|
447
|
+
let uniqueSlug = slug;
|
|
448
|
+
let counter = 1;
|
|
449
|
+
while (await collection.findOne({ slug: uniqueSlug })) {
|
|
450
|
+
uniqueSlug = `${slug}-${counter}`;
|
|
451
|
+
counter++;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
await collection.updateOne({ _id: item._id }, { $set: { slug: uniqueSlug } });
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Array Operations
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
// Add to array
|
|
462
|
+
{
|
|
463
|
+
$push: {
|
|
464
|
+
arrayField: 'newValue';
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Remove from array
|
|
469
|
+
{
|
|
470
|
+
$pull: {
|
|
471
|
+
arrayField: 'valueToRemove';
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Add only if not exists
|
|
476
|
+
{
|
|
477
|
+
$addToSet: {
|
|
478
|
+
arrayField: 'uniqueValue';
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Set entire array
|
|
483
|
+
{
|
|
484
|
+
$set: {
|
|
485
|
+
arrayField: ['value1', 'value2'];
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
## Extension Source Collection Types
|
|
493
|
+
|
|
494
|
+
- `FORMS` - For FormBuilderExtension collection
|
|
495
|
+
- `CONFIGURATIONS` - For configuration-based extensions
|
|
496
|
+
- `TEMPLATES` - For template extensions
|
|
497
|
+
- Custom collection names as needed
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
501
|
+
## Logging Standards
|
|
502
|
+
|
|
503
|
+
MUST use Pino format specifiers:
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
// String values (including ObjectIds)
|
|
507
|
+
this.logger.info('Processing document: %s', documentId);
|
|
508
|
+
this.logger.info('Extension slug: %s', extensionSlug);
|
|
509
|
+
|
|
510
|
+
// Numeric values
|
|
511
|
+
this.logger.info('Modified %d documents', count);
|
|
512
|
+
this.logger.info('Found %d existing records', existingCount);
|
|
513
|
+
|
|
514
|
+
// Objects (automatic JSON serialization)
|
|
515
|
+
this.logger.info('Document data: %j', documentObject);
|
|
516
|
+
this.logger.error('Error details: %j', { code: error.code, message: error.message });
|
|
517
|
+
|
|
518
|
+
// Errors (error object FIRST, then message)
|
|
519
|
+
this.logger.error(error, 'Failed to insert document');
|
|
520
|
+
this.logger.error(error, 'Migration failed: %s', error.message);
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## Container Registration
|
|
526
|
+
|
|
527
|
+
After creating the migration file, it MUST be registered in the module's container configuration.
|
|
528
|
+
|
|
529
|
+
### File Structure
|
|
530
|
+
|
|
531
|
+
```
|
|
532
|
+
packages-modules/[module]/server/src/modules/[submodule]/
|
|
533
|
+
├── migrations/
|
|
534
|
+
│ ├── dbMigrations/
|
|
535
|
+
│ │ ├── index.ts # Export all migrations
|
|
536
|
+
│ │ ├── your-migration.migration.ts # Migration class
|
|
537
|
+
│ │ └── template.json # (For extensions only)
|
|
538
|
+
│ └── index.ts # Re-export dbMigrations
|
|
539
|
+
├── containers/
|
|
540
|
+
│ └── module.ts # Container bindings
|
|
541
|
+
└── store/
|
|
542
|
+
└── repositories/ # Required repositories
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Registration Pattern
|
|
546
|
+
|
|
547
|
+
**Step 1: Export from migrations/dbMigrations/index.ts**
|
|
548
|
+
|
|
549
|
+
```typescript
|
|
550
|
+
export * from './your-migration.migration';
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
**Step 2: Import and Bind in containers/module.ts**
|
|
554
|
+
|
|
555
|
+
```typescript
|
|
556
|
+
import { ContainerModule, interfaces } from 'inversify';
|
|
557
|
+
import { YourMigrationName } from '../migrations';
|
|
558
|
+
|
|
559
|
+
export const YourMigrationModule: (settings: any) => interfaces.ContainerModule = (setting) =>
|
|
560
|
+
new ContainerModule((bind: interfaces.Bind) => {
|
|
561
|
+
// Other bindings...
|
|
562
|
+
|
|
563
|
+
// Migration binding
|
|
564
|
+
bind('MongodbMigration').to(YourMigrationName).whenTargetNamed(YourMigrationName.name);
|
|
565
|
+
});
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### Multiple Migrations
|
|
569
|
+
|
|
570
|
+
```typescript
|
|
571
|
+
// Migrations execute in binding order
|
|
572
|
+
bind('MongodbMigration').to(FirstMigration).whenTargetNamed(FirstMigration.name);
|
|
573
|
+
bind('MongodbMigration').to(SecondMigration).whenTargetNamed(SecondMigration.name);
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
578
|
+
## Rollup Configuration for Migrations with JSON Files
|
|
579
|
+
|
|
580
|
+
When migrations require template JSON files (e.g., for extension-based migrations), the Rollup configuration MUST be updated to copy these files to the build output directory.
|
|
581
|
+
|
|
582
|
+
### File Location
|
|
583
|
+
|
|
584
|
+
Update the `rollup.config.mjs` file in the package's server directory:
|
|
585
|
+
|
|
586
|
+
```
|
|
587
|
+
packages/[module]/server/rollup.config.mjs
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Required Configuration
|
|
591
|
+
|
|
592
|
+
Add the following plugins and configuration to your Rollup config:
|
|
593
|
+
|
|
594
|
+
```javascript
|
|
595
|
+
import { createRollupConfig } from '../../../rollup.config.base.mjs';
|
|
596
|
+
import json from '@rollup/plugin-json';
|
|
597
|
+
import { copy } from '@web/rollup-plugin-copy';
|
|
598
|
+
|
|
599
|
+
// Define any additional plugins specific to this bundle
|
|
600
|
+
const additionalPlugins = [
|
|
601
|
+
json({
|
|
602
|
+
// Only process .json files, not generated .json files
|
|
603
|
+
compact: true,
|
|
604
|
+
preferConst: true,
|
|
605
|
+
}),
|
|
606
|
+
copy({
|
|
607
|
+
patterns: [
|
|
608
|
+
// Copy migration template JSON files
|
|
609
|
+
'migrations/vite-db/form-extension-template.json',
|
|
610
|
+
'migrations/vite-db/marketplace-extension.json',
|
|
611
|
+
// Add more patterns as needed for other migrations
|
|
612
|
+
],
|
|
613
|
+
rootDir: './src',
|
|
614
|
+
}),
|
|
615
|
+
];
|
|
616
|
+
|
|
617
|
+
// Use the createRollupConfig function to merge the base and specific configurations
|
|
618
|
+
export default (commandLineArgs) => {
|
|
619
|
+
const isWatchMode = commandLineArgs.watch;
|
|
620
|
+
return [
|
|
621
|
+
createRollupConfig(
|
|
622
|
+
{
|
|
623
|
+
input: ['src/index.ts', 'src/microservice-module.ts'],
|
|
624
|
+
plugins: [
|
|
625
|
+
// Spread in additional plugins specific to this config
|
|
626
|
+
...additionalPlugins,
|
|
627
|
+
],
|
|
628
|
+
output: [
|
|
629
|
+
{
|
|
630
|
+
dir: 'lib',
|
|
631
|
+
format: 'es',
|
|
632
|
+
name: 'PageCreator',
|
|
633
|
+
compact: true,
|
|
634
|
+
exports: 'named',
|
|
635
|
+
sourcemap: true,
|
|
636
|
+
preserveModules: true,
|
|
637
|
+
chunkFileNames: '[name]-[hash].[format].js',
|
|
638
|
+
},
|
|
639
|
+
],
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
isWatchMode,
|
|
643
|
+
generateRoutesJSON: false,
|
|
644
|
+
},
|
|
645
|
+
),
|
|
646
|
+
];
|
|
647
|
+
};
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
### Key Points:
|
|
651
|
+
|
|
652
|
+
1. **@rollup/plugin-json**: Enables importing and processing JSON files
|
|
653
|
+
- `compact: true` - Minifies the JSON
|
|
654
|
+
- `preferConst: true` - Uses const declarations
|
|
655
|
+
|
|
656
|
+
2. **@web/rollup-plugin-copy**: Copies files to build output
|
|
657
|
+
- `patterns`: Array of file paths relative to `rootDir`
|
|
658
|
+
- `rootDir`: Base directory (usually `'./src'`)
|
|
659
|
+
- Files are copied to `lib/` maintaining directory structure
|
|
660
|
+
|
|
661
|
+
3. **Pattern Examples**:
|
|
662
|
+
```javascript
|
|
663
|
+
patterns: [
|
|
664
|
+
'migrations/dbMigrations/template.json', // Single file
|
|
665
|
+
'migrations/vite-db/*.json', // All JSON in directory
|
|
666
|
+
'migrations/**/template.json', // Recursive pattern
|
|
667
|
+
];
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
### Build Output Structure
|
|
671
|
+
|
|
672
|
+
After build, JSON files will be available at:
|
|
673
|
+
|
|
674
|
+
```
|
|
675
|
+
lib/
|
|
676
|
+
└── migrations/
|
|
677
|
+
└── vite-db/
|
|
678
|
+
├── form-extension-template.json
|
|
679
|
+
└── seed-aieditor-vite-extension.migration.js
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
### Migration File Access
|
|
683
|
+
|
|
684
|
+
In the migration, the JSON file is accessed using:
|
|
685
|
+
|
|
686
|
+
```typescript
|
|
687
|
+
function getPathFromModule(relativePath: string) {
|
|
688
|
+
return resolve(__dirname, relativePath);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const templatePath = getPathFromModule('./template.json');
|
|
692
|
+
const templateContent = readFileSync(templatePath, 'utf-8');
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
### Verification
|
|
696
|
+
|
|
697
|
+
After building, verify the JSON files are copied:
|
|
698
|
+
|
|
699
|
+
```bash
|
|
700
|
+
yarn build
|
|
701
|
+
ls -la lib/migrations/vite-db/
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
You should see both `.js` migration files and `.json` template files.
|
|
705
|
+
|
|
706
|
+
---
|
|
707
|
+
|
|
708
|
+
## FormBuilder Extension Template.json
|
|
709
|
+
|
|
710
|
+
For FormBuilder extensions, the template.json file MUST be in the same directory as the migration:
|
|
711
|
+
|
|
712
|
+
```json
|
|
713
|
+
{
|
|
714
|
+
"_id": { "$oid": "68f5ac65b3c3d926d4eeaada" },
|
|
715
|
+
"name": "Your Template Name",
|
|
716
|
+
"metadataSchema": [
|
|
717
|
+
{
|
|
718
|
+
"schemaId": "stepper-form",
|
|
719
|
+
"formId": "your-form-id",
|
|
720
|
+
"type": "Multi Form",
|
|
721
|
+
"configurationNodes": [
|
|
722
|
+
{
|
|
723
|
+
"type": "form",
|
|
724
|
+
"properties": {
|
|
725
|
+
/* form schema */
|
|
726
|
+
}
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
"type": "formUI",
|
|
730
|
+
"properties": {
|
|
731
|
+
/* UI schema */
|
|
732
|
+
}
|
|
733
|
+
},
|
|
734
|
+
{ "type": "definitions", "properties": {} },
|
|
735
|
+
{
|
|
736
|
+
"type": "connections",
|
|
737
|
+
"properties": {
|
|
738
|
+
/* step connections */
|
|
739
|
+
}
|
|
740
|
+
},
|
|
741
|
+
{ "type": "configuration", "properties": {} },
|
|
742
|
+
{ "type": "customButtons", "properties": {} },
|
|
743
|
+
{
|
|
744
|
+
"type": "resources",
|
|
745
|
+
"properties": {
|
|
746
|
+
/* API resources */
|
|
747
|
+
}
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
"type": "inngest",
|
|
751
|
+
"properties": {
|
|
752
|
+
/* workflow config */
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
"type": "formData",
|
|
757
|
+
"properties": {
|
|
758
|
+
/* default form data */
|
|
759
|
+
}
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
"type": "form-metadata",
|
|
763
|
+
"properties": {
|
|
764
|
+
/* metadata */
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
]
|
|
768
|
+
}
|
|
769
|
+
],
|
|
770
|
+
"createdAt": { "$date": "2024-01-01T00:00:00.000Z" },
|
|
771
|
+
"updatedAt": { "$date": "2024-01-01T00:00:00.000Z" }
|
|
772
|
+
}
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
### Extension Manifest Configuration
|
|
776
|
+
|
|
777
|
+
```typescript
|
|
778
|
+
contributes: {
|
|
779
|
+
uiLayout: [{
|
|
780
|
+
title: 'Your Feature',
|
|
781
|
+
properties: {
|
|
782
|
+
'uilayout.your.feature.templateName': {
|
|
783
|
+
type: 'string',
|
|
784
|
+
default: 'Template Name',
|
|
785
|
+
description: 'Template description',
|
|
786
|
+
scope: 'window',
|
|
787
|
+
},
|
|
788
|
+
'uilayout.your.feature.enabled': {
|
|
789
|
+
type: 'boolean',
|
|
790
|
+
default: true,
|
|
791
|
+
description: 'Enable feature',
|
|
792
|
+
scope: 'window',
|
|
793
|
+
},
|
|
794
|
+
'uilayout.your.feature.formId': {
|
|
795
|
+
type: 'string',
|
|
796
|
+
default: 'your-form-id',
|
|
797
|
+
description: 'Form ID',
|
|
798
|
+
scope: 'window',
|
|
799
|
+
},
|
|
800
|
+
'uilayout.your.feature.formextensionId': {
|
|
801
|
+
type: 'string',
|
|
802
|
+
default: '68f5ac65b3c3d926d4eeaada',
|
|
803
|
+
description: 'Form extension ID',
|
|
804
|
+
scope: 'window',
|
|
805
|
+
},
|
|
806
|
+
},
|
|
807
|
+
}],
|
|
808
|
+
}
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
---
|
|
812
|
+
|
|
813
|
+
## Validation Checklist
|
|
814
|
+
|
|
815
|
+
Before generating migration code, verify:
|
|
816
|
+
|
|
817
|
+
- [ ] Migration ID uses format: `${ClassName}_YYYYMMDD`
|
|
818
|
+
- [ ] Class has `@injectable()` decorator
|
|
819
|
+
- [ ] Logger initialized with `logger.child({ className: ClassName.name })`
|
|
820
|
+
- [ ] All database operations wrapped in try-catch
|
|
821
|
+
- [ ] Logging uses format specifiers (%s, %d, %j)
|
|
822
|
+
- [ ] Migration is idempotent (checks if already applied)
|
|
823
|
+
- [ ] down() method properly reverts up() changes
|
|
824
|
+
- [ ] For extensions: template.json loading and convertExtendedJson included
|
|
825
|
+
- [ ] For extensions: sourceCollection and sourceDocumentId are set correctly
|
|
826
|
+
- [ ] Error handling re-throws errors after logging
|
|
827
|
+
- [ ] Container binding added to module.ts
|
|
828
|
+
- [ ] Export added to migrations index.ts
|
|
829
|
+
- [ ] **For migrations with JSON files: Rollup configuration updated with copy plugin**
|
|
830
|
+
|
|
831
|
+
---
|
|
832
|
+
|
|
833
|
+
## Common Errors and Solutions
|
|
834
|
+
|
|
835
|
+
### Error: Collection not found
|
|
836
|
+
|
|
837
|
+
```typescript
|
|
838
|
+
// Solution: Use DB_COLL_NAMES constants
|
|
839
|
+
import { DB_COLL_NAMES } from 'common/server';
|
|
840
|
+
const collection = this.db.collection(DB_COLL_NAMES.YourCollection);
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
### Error: Duplicate key error (code: 11000)
|
|
844
|
+
|
|
845
|
+
```typescript
|
|
846
|
+
// Solution: Check for existing documents first
|
|
847
|
+
const existing = await collection.findOne({ _id: documentId });
|
|
848
|
+
if (existing) {
|
|
849
|
+
this.logger.info('Document already exists, skipping');
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
### Error: Template file not found
|
|
855
|
+
|
|
856
|
+
```typescript
|
|
857
|
+
// Solution: Use getPathFromModule helper
|
|
858
|
+
function getPathFromModule(relativePath: string) {
|
|
859
|
+
return resolve(__dirname, relativePath);
|
|
860
|
+
}
|
|
861
|
+
const templatePath = getPathFromModule('./template.json');
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
### Error: Template JSON not copied to build output
|
|
865
|
+
|
|
866
|
+
```bash
|
|
867
|
+
# Solution: Update rollup.config.mjs with copy plugin
|
|
868
|
+
# See "Rollup Configuration for Migrations with JSON Files" section above
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
```javascript
|
|
872
|
+
import { copy } from '@web/rollup-plugin-copy';
|
|
873
|
+
|
|
874
|
+
const additionalPlugins = [
|
|
875
|
+
copy({
|
|
876
|
+
patterns: ['migrations/your-migration/template.json'],
|
|
877
|
+
rootDir: './src',
|
|
878
|
+
}),
|
|
879
|
+
];
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
---
|
|
883
|
+
|
|
884
|
+
## Response Format
|
|
885
|
+
|
|
886
|
+
When generating a migration, provide:
|
|
887
|
+
|
|
888
|
+
1. **Complete migration file code**
|
|
889
|
+
2. **File path**: `packages-modules/[module]/server/src/modules/[submodule]/migrations/dbMigrations/[migration-name].migration.ts`
|
|
890
|
+
3. **Export statement** for `migrations/dbMigrations/index.ts`:
|
|
891
|
+
```typescript
|
|
892
|
+
export * from './your-migration.migration';
|
|
893
|
+
```
|
|
894
|
+
4. **Container binding** for `containers/module.ts`:
|
|
895
|
+
|
|
896
|
+
```typescript
|
|
897
|
+
import { YourMigration } from '../migrations';
|
|
898
|
+
|
|
899
|
+
bind('MongodbMigration').to(YourMigration).whenTargetNamed(YourMigration.name);
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
5. **template.json** (if FormBuilder extension migration)
|
|
903
|
+
6. **Rollup configuration update** (if migration uses JSON template files):
|
|
904
|
+
```javascript
|
|
905
|
+
// Add to rollup.config.mjs additionalPlugins array
|
|
906
|
+
copy({
|
|
907
|
+
patterns: ['migrations/[subdir]/template.json'],
|
|
908
|
+
rootDir: './src',
|
|
909
|
+
}),
|
|
910
|
+
```
|
|
911
|
+
7. **Verification** that all checklist items are met
|
|
912
|
+
|
|
913
|
+
---
|
|
914
|
+
|
|
915
|
+
## Example Prompt
|
|
916
|
+
|
|
917
|
+
When requesting a migration, use this format:
|
|
918
|
+
|
|
919
|
+
```
|
|
920
|
+
Create a database migration for AdminIDE Stack:
|
|
921
|
+
|
|
922
|
+
Type: [Standard / Extension-based]
|
|
923
|
+
Purpose: [Brief description]
|
|
924
|
+
Collection(s): [Collection names]
|
|
925
|
+
Operations: [What needs to be done]
|
|
926
|
+
|
|
927
|
+
[For Extensions only:]
|
|
928
|
+
- Extension name: [name]
|
|
929
|
+
- Extension slug: [org-id/extension-name]
|
|
930
|
+
- Source collection: [FORMS/CONFIGURATIONS/TEMPLATES]
|
|
931
|
+
- Source document ID: [id if known]
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
**Example:**
|
|
935
|
+
|
|
936
|
+
```
|
|
937
|
+
Create a database migration for AdminIDE Stack:
|
|
938
|
+
|
|
939
|
+
Type: Extension-based
|
|
940
|
+
Purpose: Seed project template form with Auth0 and MongoDB configuration
|
|
941
|
+
Collection(s): FormBuilderExtension, ExtensionRegistries
|
|
942
|
+
Operations: Create form schema and register extension
|
|
943
|
+
|
|
944
|
+
Extension name: Backend Project Template
|
|
945
|
+
Extension slug: org-abc123/backend-template
|
|
946
|
+
Source collection: FORMS
|
|
947
|
+
Source document ID: 68f5ac65b3c3d926d4eeaada
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
---
|
|
951
|
+
|
|
952
|
+
**Version:** 2.0 (Merged)
|
|
953
|
+
**Last Updated:** November 9, 2025
|
|
954
|
+
**Maintained by:** AdminIDE Stack Team
|