fimo 0.7.0-experimental.2 → 0.7.0-experimental.4
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/README.md +1 -1
- package/assets/content-templates/hooks-template.eta +27 -73
- package/assets/content-templates/types-template.eta +1 -4
- package/assets/skills/fimo/SKILL.md +2 -2
- package/assets/skills/fimo/references/content.md +42 -46
- package/assets/skills/fimo/references/locales.md +0 -8
- package/assets/skills/fimo/references/setup-react-router.md +2 -2
- package/assets/skills/fimo/references/ui.md +1 -1
- package/assets/skills/fimo-cli/references/content.md +14 -17
- package/assets/skills/fimo-cli/references/domains.md +1 -1
- package/assets/skills/fimo-cli/references/locales.md +0 -6
- package/assets/skills/fimo-migration/SKILL.md +22 -2
- package/assets/skills/fimo-migration/references/content.md +8 -0
- package/assets/skills/fimo-migration/references/manifest.md +35 -0
- package/assets/skills/fimo-migration/references/qa.md +24 -2
- package/assets/skills/fimo-migration/references/workflow.md +12 -1
- package/assets/skills/fimo-migration/references/wrap.md +582 -0
- package/assets/skills/fimo-studio/references/custom-domains.md +1 -1
- package/assets/skills/fimo-studio/references/visual-editing.md +5 -3
- package/dist/build/vite/index.d.ts.map +1 -0
- package/dist/build/vite/plugins/content-sync.d.ts.map +1 -0
- package/dist/build/vite/plugins/content-sync.test.d.ts +2 -0
- package/dist/build/vite/plugins/content-sync.test.d.ts.map +1 -0
- package/dist/build/vite/plugins/content-sync.test.js +64 -0
- package/dist/build/vite/plugins/data-id.d.ts.map +1 -0
- package/dist/build/vite/plugins/fimo-config.d.ts.map +1 -0
- package/dist/build/vite/plugins/fimo-config.test.d.ts +2 -0
- package/dist/build/vite/plugins/fimo-config.test.d.ts.map +1 -0
- package/dist/build/vite/plugins/fimo-config.test.js +46 -0
- package/dist/build/vite/plugins/overlay.d.ts.map +1 -0
- package/dist/build/vite/plugins/robots.d.ts.map +1 -0
- package/dist/build/vite/plugins/seo.d.ts.map +1 -0
- package/dist/build/vite/plugins/server-config.d.ts.map +1 -0
- package/dist/build/vite/plugins/sitemap.d.ts.map +1 -0
- package/dist/build/vite/plugins/translations.d.ts +2 -8
- package/dist/build/vite/plugins/translations.d.ts.map +1 -0
- package/dist/build/vite/plugins/translations.js +3 -121
- package/dist/build/vite/plugins/translations.test.d.ts +2 -0
- package/dist/build/vite/plugins/translations.test.d.ts.map +1 -0
- package/dist/build/vite/plugins/translations.test.js +266 -0
- package/dist/cli/bundle.json +2 -3
- package/dist/cli/index.js +117939 -1186
- package/dist/runtime/app/FimoProviders.d.ts +1 -11
- package/dist/runtime/app/FimoProviders.d.ts.map +1 -0
- package/dist/runtime/app/FimoProviders.js +2 -23
- package/dist/runtime/app/FimoScripts.d.ts.map +1 -0
- package/dist/runtime/app/index.d.ts.map +1 -0
- package/dist/runtime/app/prefetch.d.ts.map +1 -0
- package/dist/runtime/astro/index.d.ts +2 -0
- package/dist/runtime/astro/index.d.ts.map +1 -0
- package/dist/runtime/astro/index.js +3 -0
- package/dist/runtime/astro/server.d.ts +3 -0
- package/dist/runtime/astro/server.d.ts.map +1 -0
- package/dist/runtime/astro/server.js +6 -0
- package/dist/runtime/content/DataSourcesProvider.d.ts.map +1 -0
- package/dist/runtime/content/RefreshContentProvider.d.ts.map +1 -0
- package/dist/runtime/content/RefreshContentProvider.test.d.ts +2 -0
- package/dist/runtime/content/RefreshContentProvider.test.d.ts.map +1 -0
- package/dist/runtime/content/RefreshContentProvider.test.js +33 -0
- package/dist/runtime/content/parseCollectionResponse.d.ts.map +1 -0
- package/dist/runtime/content/parseCollectionResponse.test.d.ts +2 -0
- package/dist/runtime/content/parseCollectionResponse.test.d.ts.map +1 -0
- package/dist/runtime/content/parseCollectionResponse.test.js +20 -0
- package/dist/runtime/content/utils/retrieve-data-sources.d.ts +0 -1
- package/dist/runtime/content/utils/retrieve-data-sources.d.ts.map +1 -0
- package/dist/runtime/content/utils/retrieve-data-sources.js +4 -27
- package/dist/runtime/core/AppErrorBoundary.d.ts.map +1 -0
- package/dist/runtime/core/ViteLifecycles.d.ts.map +1 -0
- package/dist/runtime/i18n/FimoLocaleContext.d.ts.map +1 -0
- package/dist/runtime/i18n/FimoLocaleContext.js +1 -0
- package/dist/runtime/i18n/locale.d.ts.map +1 -0
- package/dist/runtime/i18n/locale.test.d.ts +2 -0
- package/dist/runtime/i18n/locale.test.d.ts.map +1 -0
- package/dist/runtime/i18n/locale.test.js +58 -0
- package/dist/runtime/iframe/ConsoleErrorProvider.d.ts.map +1 -0
- package/dist/runtime/iframe/ScreenshotProvider.d.ts.map +1 -0
- package/dist/runtime/iframe/runtime/overlay-hmr.d.ts.map +1 -0
- package/dist/runtime/iframe/runtime/overlay-hmr.js +0 -3
- package/dist/runtime/iframe/runtime/overlay-runtime.d.ts.map +1 -0
- package/dist/runtime/index.d.ts +0 -3
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +0 -2
- package/dist/runtime/next/FimoNextProviders.d.ts +24 -0
- package/dist/runtime/next/FimoNextProviders.d.ts.map +1 -0
- package/dist/runtime/next/FimoNextProviders.js +20 -0
- package/dist/runtime/next/FimoNextScripts.d.ts +10 -0
- package/dist/runtime/next/FimoNextScripts.d.ts.map +1 -0
- package/dist/runtime/next/FimoNextScripts.js +19 -0
- package/dist/runtime/next/index.d.ts +14 -0
- package/dist/runtime/next/index.d.ts.map +1 -0
- package/dist/runtime/next/index.js +14 -0
- package/dist/runtime/next/server.d.ts +2 -0
- package/dist/runtime/next/server.d.ts.map +1 -0
- package/dist/runtime/next/server.js +4 -0
- package/dist/runtime/nuxt/composables.d.ts +9 -0
- package/dist/runtime/nuxt/composables.d.ts.map +1 -0
- package/dist/runtime/nuxt/composables.js +57 -0
- package/dist/runtime/nuxt/index.d.ts +3 -0
- package/dist/runtime/nuxt/index.d.ts.map +1 -0
- package/dist/runtime/nuxt/index.js +6 -0
- package/dist/runtime/nuxt/server.d.ts +3 -0
- package/dist/runtime/nuxt/server.d.ts.map +1 -0
- package/dist/runtime/nuxt/server.js +6 -0
- package/dist/runtime/paths/get-fimo-paths.d.ts.map +1 -0
- package/dist/runtime/paths/get-fimo-paths.js +5 -15
- package/dist/runtime/paths/index.d.ts.map +1 -0
- package/dist/runtime/paths/types.d.ts.map +1 -0
- package/dist/runtime/primitives/components/Boolean.d.ts.map +1 -0
- package/dist/runtime/primitives/components/Date.d.ts.map +1 -0
- package/dist/runtime/primitives/components/Image.d.ts.map +1 -0
- package/dist/runtime/primitives/components/RichText.d.ts.map +1 -0
- package/dist/runtime/primitives/components/StaticImage.d.ts.map +1 -0
- package/dist/runtime/primitives/components/Text.d.ts.map +1 -0
- package/dist/runtime/primitives/components/Video.d.ts.map +1 -0
- package/dist/runtime/primitives/components/index.d.ts.map +1 -0
- package/dist/runtime/primitives/index.d.ts.map +1 -0
- package/dist/runtime/primitives/labels-hook.d.ts +33 -0
- package/dist/runtime/primitives/labels-hook.d.ts.map +1 -0
- package/dist/runtime/primitives/labels-hook.js +41 -0
- package/dist/runtime/primitives/labels-store.d.ts +12 -0
- package/dist/runtime/primitives/labels-store.d.ts.map +1 -0
- package/dist/runtime/primitives/labels-store.js +60 -0
- package/dist/runtime/primitives/labels-store.test.d.ts +2 -0
- package/dist/runtime/primitives/labels-store.test.d.ts.map +1 -0
- package/dist/runtime/primitives/labels-store.test.js +66 -0
- package/dist/runtime/primitives/lib/image-optimization.d.ts.map +1 -0
- package/dist/runtime/primitives/lib/index.d.ts.map +1 -0
- package/dist/runtime/primitives/lib/primitives.d.ts +2 -2
- package/dist/runtime/primitives/lib/primitives.d.ts.map +1 -0
- package/dist/runtime/primitives/lib/query.d.ts +3 -5
- package/dist/runtime/primitives/lib/query.d.ts.map +1 -0
- package/dist/runtime/primitives/lib/template.d.ts.map +1 -0
- package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts +5 -3
- package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts.map +1 -0
- package/dist/runtime/primitives/lib/useFimoLiveValue.js +11 -4
- package/dist/runtime/primitives/public-surface.test.d.ts +2 -0
- package/dist/runtime/primitives/public-surface.test.d.ts.map +1 -0
- package/dist/runtime/primitives/public-surface.test.js +45 -0
- package/dist/runtime/primitives/translations.d.ts +1 -27
- package/dist/runtime/primitives/translations.d.ts.map +1 -0
- package/dist/runtime/primitives/translations.js +7 -85
- package/dist/runtime/react/FimoSpaProviders.d.ts +36 -0
- package/dist/runtime/react/FimoSpaProviders.d.ts.map +1 -0
- package/dist/runtime/react/FimoSpaProviders.js +63 -0
- package/dist/runtime/react/index.d.ts +33 -0
- package/dist/runtime/react/index.d.ts.map +1 -0
- package/dist/runtime/react/index.js +39 -0
- package/dist/runtime/react-router/index.d.ts.map +1 -0
- package/dist/runtime/react-router/navigation-utils.d.ts.map +1 -0
- package/dist/runtime/react-router/navigation.d.ts.map +1 -0
- package/dist/runtime/react-router/navigation.test.d.ts +2 -0
- package/dist/runtime/react-router/navigation.test.d.ts.map +1 -0
- package/dist/runtime/react-router/navigation.test.js +35 -0
- package/dist/runtime/react-router/routes.d.ts.map +1 -0
- package/dist/runtime/react-router/routes.test.d.ts +2 -0
- package/dist/runtime/react-router/routes.test.d.ts.map +1 -0
- package/dist/runtime/react-router/routes.test.js +67 -0
- package/dist/runtime/remix/FimoRemixProviders.d.ts +16 -0
- package/dist/runtime/remix/FimoRemixProviders.d.ts.map +1 -0
- package/dist/runtime/remix/FimoRemixProviders.js +12 -0
- package/dist/runtime/remix/FimoRemixScripts.d.ts +10 -0
- package/dist/runtime/remix/FimoRemixScripts.d.ts.map +1 -0
- package/dist/runtime/remix/FimoRemixScripts.js +19 -0
- package/dist/runtime/remix/index.d.ts +12 -0
- package/dist/runtime/remix/index.d.ts.map +1 -0
- package/dist/runtime/remix/index.js +11 -0
- package/dist/runtime/remix/server.d.ts +2 -0
- package/dist/runtime/remix/server.d.ts.map +1 -0
- package/dist/runtime/remix/server.js +2 -0
- package/dist/runtime/routing/AppResetScrollOnNavigate.d.ts.map +1 -0
- package/dist/runtime/routing/AppRoutesProvider.d.ts.map +1 -0
- package/dist/runtime/seo/buildSeoForPath.d.ts +3 -1
- package/dist/runtime/seo/buildSeoForPath.d.ts.map +1 -0
- package/dist/runtime/seo/buildSeoForPath.js +1 -4
- package/dist/runtime/seo/htmlProps.d.ts.map +1 -0
- package/dist/runtime/seo/index.d.ts.map +1 -0
- package/dist/runtime/seo/resolver.d.ts.map +1 -0
- package/dist/runtime/shared/display-routes.d.ts +14 -0
- package/dist/runtime/shared/display-routes.d.ts.map +1 -0
- package/dist/runtime/shared/display-routes.js +25 -0
- package/dist/runtime/shared/fimo-config.server.d.ts.map +1 -0
- package/dist/runtime/shared/fimo-config.server.js +1 -0
- package/dist/runtime/shared/fimo-config.types.d.ts +2 -0
- package/dist/runtime/shared/fimo-config.types.d.ts.map +1 -0
- package/dist/runtime/shared/i18n-messages.d.ts +29 -0
- package/dist/runtime/shared/i18n-messages.d.ts.map +1 -0
- package/dist/runtime/shared/i18n-messages.js +79 -0
- package/dist/runtime/shared/i18n-messages.test.d.ts +2 -0
- package/dist/runtime/shared/i18n-messages.test.d.ts.map +1 -0
- package/dist/runtime/shared/i18n-messages.test.js +39 -0
- package/dist/runtime/shared/labels-fetch.pure.d.ts +17 -0
- package/dist/runtime/shared/labels-fetch.pure.d.ts.map +1 -0
- package/dist/runtime/shared/labels-fetch.pure.js +71 -0
- package/dist/runtime/shared/labels-fetch.server.d.ts +7 -0
- package/dist/runtime/shared/labels-fetch.server.d.ts.map +1 -0
- package/dist/runtime/shared/labels-fetch.server.js +97 -0
- package/dist/runtime/shared/labels-t.pure.d.ts +2 -0
- package/dist/runtime/shared/labels-t.pure.d.ts.map +1 -0
- package/dist/runtime/shared/labels-t.pure.js +10 -0
- package/dist/runtime/svelte/index.d.ts +3 -0
- package/dist/runtime/svelte/index.d.ts.map +1 -0
- package/dist/runtime/svelte/index.js +7 -0
- package/dist/runtime/svelte/server.d.ts +3 -0
- package/dist/runtime/svelte/server.d.ts.map +1 -0
- package/dist/runtime/svelte/server.js +6 -0
- package/dist/runtime/t/OptionalTracking.d.ts.map +1 -0
- package/dist/runtime/t/OptionalTracking.test.d.ts +2 -0
- package/dist/runtime/t/OptionalTracking.test.d.ts.map +1 -0
- package/dist/runtime/t/OptionalTracking.test.js +11 -0
- package/dist/runtime/t/Pageview.d.ts.map +1 -0
- package/dist/runtime/templates.d.ts.map +1 -0
- package/dist/runtime/vue/index.d.ts +3 -0
- package/dist/runtime/vue/index.d.ts.map +1 -0
- package/dist/runtime/vue/index.js +5 -0
- package/dist/runtime/vue/server.d.ts +3 -0
- package/dist/runtime/vue/server.d.ts.map +1 -0
- package/dist/runtime/vue/server.js +6 -0
- package/dist/runtime/wrapped/FimoFileLink.d.ts +17 -0
- package/dist/runtime/wrapped/FimoFileLink.d.ts.map +1 -0
- package/dist/runtime/wrapped/FimoFileLink.js +19 -0
- package/dist/runtime/wrapped/FimoMediaImage.d.ts +14 -0
- package/dist/runtime/wrapped/FimoMediaImage.d.ts.map +1 -0
- package/dist/runtime/wrapped/FimoMediaImage.js +20 -0
- package/dist/runtime/wrapped/FimoMediaVideo.d.ts +14 -0
- package/dist/runtime/wrapped/FimoMediaVideo.d.ts.map +1 -0
- package/dist/runtime/wrapped/FimoMediaVideo.js +20 -0
- package/dist/runtime/wrapped/FimoRichTextBlock.d.ts +23 -0
- package/dist/runtime/wrapped/FimoRichTextBlock.d.ts.map +1 -0
- package/dist/runtime/wrapped/FimoRichTextBlock.js +22 -0
- package/dist/runtime/wrapped/FimoText.d.ts +22 -0
- package/dist/runtime/wrapped/FimoText.d.ts.map +1 -0
- package/dist/runtime/wrapped/FimoText.js +20 -0
- package/dist/runtime/wrapped/WrappedContentContext.d.ts +28 -0
- package/dist/runtime/wrapped/WrappedContentContext.d.ts.map +1 -0
- package/dist/runtime/wrapped/WrappedContentContext.js +30 -0
- package/dist/runtime/wrapped/WrappedProviders.d.ts +33 -0
- package/dist/runtime/wrapped/WrappedProviders.d.ts.map +1 -0
- package/dist/runtime/wrapped/WrappedProviders.js +101 -0
- package/dist/runtime/wrapped/dom.d.ts +4 -0
- package/dist/runtime/wrapped/dom.d.ts.map +1 -0
- package/dist/runtime/wrapped/dom.js +23 -0
- package/dist/runtime/wrapped/dom.test.d.ts +2 -0
- package/dist/runtime/wrapped/dom.test.d.ts.map +1 -0
- package/dist/runtime/wrapped/dom.test.js +16 -0
- package/dist/runtime/wrapped/server.d.ts +77 -0
- package/dist/runtime/wrapped/server.d.ts.map +1 -0
- package/dist/runtime/wrapped/server.js +237 -0
- package/dist/runtime/wrapped/server.test.d.ts +2 -0
- package/dist/runtime/wrapped/server.test.d.ts.map +1 -0
- package/dist/runtime/wrapped/server.test.js +91 -0
- package/dist/scripts/apply-wrap-templates.d.ts +61 -0
- package/dist/scripts/apply-wrap-templates.js +132 -0
- package/dist/scripts/apply-wrap-templates.test.js +199 -0
- package/dist/scripts/apply-wrap.d.ts +46 -0
- package/dist/scripts/apply-wrap.js +278 -0
- package/dist/scripts/apply-wrap.test.d.ts +1 -0
- package/dist/scripts/apply-wrap.test.js +226 -0
- package/dist/scripts/cms/discover.d.ts +2 -0
- package/dist/scripts/cms/discover.js +17 -0
- package/dist/scripts/cms/example-content.d.ts +32 -0
- package/dist/scripts/cms/example-content.js +87 -0
- package/dist/scripts/cms/example-content.test.d.ts +1 -0
- package/dist/scripts/cms/example-content.test.js +57 -0
- package/dist/scripts/cms/strapi/detect.d.ts +2 -0
- package/dist/scripts/cms/strapi/detect.js +185 -0
- package/dist/scripts/cms/strapi/detect.test.d.ts +1 -0
- package/dist/scripts/cms/strapi/detect.test.js +56 -0
- package/dist/scripts/cms/strapi/index.d.ts +3 -0
- package/dist/scripts/cms/strapi/index.js +15 -0
- package/dist/scripts/cms/strapi/list-source.d.ts +17 -0
- package/dist/scripts/cms/strapi/list-source.js +90 -0
- package/dist/scripts/cms/strapi/list-source.test.d.ts +1 -0
- package/dist/scripts/cms/strapi/list-source.test.js +33 -0
- package/dist/scripts/cms/strapi/schema-map.d.ts +9 -0
- package/dist/scripts/cms/strapi/schema-map.js +138 -0
- package/dist/scripts/cms/strapi/schema-map.test.d.ts +1 -0
- package/dist/scripts/cms/strapi/schema-map.test.js +87 -0
- package/dist/scripts/cms/types.d.ts +76 -0
- package/dist/scripts/cms/types.js +11 -0
- package/dist/scripts/detect-framework.d.ts +39 -0
- package/dist/scripts/detect-framework.js +133 -0
- package/dist/scripts/detect-framework.test.d.ts +1 -0
- package/dist/scripts/detect-framework.test.js +122 -0
- package/dist/scripts/draft-rows.d.ts +19 -0
- package/dist/scripts/draft-rows.js +135 -0
- package/dist/scripts/draft-rows.test.d.ts +1 -0
- package/dist/scripts/draft-rows.test.js +113 -0
- package/dist/scripts/draft-schemas.d.ts +27 -0
- package/dist/scripts/draft-schemas.js +188 -0
- package/dist/scripts/draft-schemas.test.d.ts +1 -0
- package/dist/scripts/draft-schemas.test.js +152 -0
- package/dist/scripts/frameworks/astro/routes.d.ts +3 -0
- package/dist/scripts/frameworks/astro/routes.js +55 -0
- package/dist/scripts/frameworks/astro/template-style.d.ts +7 -0
- package/dist/scripts/frameworks/astro/template-style.js +19 -0
- package/dist/scripts/frameworks/discover.d.ts +70 -0
- package/dist/scripts/frameworks/discover.js +171 -0
- package/dist/scripts/frameworks/next/rewire.d.ts +30 -0
- package/dist/scripts/frameworks/next/rewire.js +258 -0
- package/dist/scripts/frameworks/next/rewire.test.d.ts +1 -0
- package/dist/scripts/frameworks/next/rewire.test.js +134 -0
- package/dist/scripts/frameworks/next/routes.d.ts +17 -0
- package/dist/scripts/frameworks/next/routes.js +134 -0
- package/dist/scripts/frameworks/next/routes.test.d.ts +1 -0
- package/dist/scripts/frameworks/next/routes.test.js +64 -0
- package/dist/scripts/frameworks/next/wiring.d.ts +26 -0
- package/dist/scripts/frameworks/next/wiring.js +163 -0
- package/dist/scripts/frameworks/next/wiring.test.d.ts +1 -0
- package/dist/scripts/frameworks/next/wiring.test.js +146 -0
- package/dist/scripts/frameworks/nuxt/routes.d.ts +3 -0
- package/dist/scripts/frameworks/nuxt/routes.js +54 -0
- package/dist/scripts/frameworks/nuxt/template-style.d.ts +9 -0
- package/dist/scripts/frameworks/nuxt/template-style.js +27 -0
- package/dist/scripts/frameworks/remix/routes.d.ts +3 -0
- package/dist/scripts/frameworks/remix/routes.js +50 -0
- package/dist/scripts/frameworks/sveltekit/routes.d.ts +3 -0
- package/dist/scripts/frameworks/sveltekit/routes.js +50 -0
- package/dist/scripts/frameworks/sveltekit/template-style.d.ts +9 -0
- package/dist/scripts/frameworks/sveltekit/template-style.js +54 -0
- package/dist/scripts/frameworks/vite/routes.d.ts +15 -0
- package/dist/scripts/frameworks/vite/routes.js +93 -0
- package/dist/scripts/frameworks/vite/routes.test.d.ts +1 -0
- package/dist/scripts/frameworks/vite/routes.test.js +73 -0
- package/dist/scripts/frameworks/vite/wiring.d.ts +10 -0
- package/dist/scripts/frameworks/vite/wiring.js +99 -0
- package/dist/scripts/frameworks/vite/wiring.test.d.ts +1 -0
- package/dist/scripts/frameworks/vite/wiring.test.js +88 -0
- package/dist/scripts/generated-types.test.d.ts +1 -0
- package/dist/scripts/generated-types.test.js +212 -0
- package/dist/scripts/lib/validation-report.d.ts +1 -0
- package/dist/scripts/lib/validation-report.js +3 -0
- package/dist/scripts/lint-translation-keys.test.d.ts +1 -0
- package/dist/scripts/lint-translation-keys.test.js +16 -0
- package/dist/scripts/render-parity/capture.d.ts +56 -0
- package/dist/scripts/render-parity/capture.js +366 -0
- package/dist/scripts/render-parity/capture.test.d.ts +1 -0
- package/dist/scripts/render-parity/capture.test.js +306 -0
- package/dist/scripts/render-parity/compare.d.ts +59 -0
- package/dist/scripts/render-parity/compare.js +0 -0
- package/dist/scripts/render-parity/compare.test.d.ts +1 -0
- package/dist/scripts/render-parity/compare.test.js +98 -0
- package/dist/scripts/render-parity/normalize-html.d.ts +13 -0
- package/dist/scripts/render-parity/normalize-html.js +223 -0
- package/dist/scripts/render-parity/normalize-html.test.d.ts +1 -0
- package/dist/scripts/render-parity/normalize-html.test.js +58 -0
- package/dist/scripts/render-parity/types.d.ts +41 -0
- package/dist/scripts/render-parity/types.js +1 -0
- package/dist/scripts/resolve-routes.js +1 -1
- package/dist/scripts/scan-wrappable.d.ts +196 -0
- package/dist/scripts/scan-wrappable.js +1221 -0
- package/dist/scripts/scan-wrappable.test.d.ts +1 -0
- package/dist/scripts/scan-wrappable.test.js +502 -0
- package/dist/scripts/sync-schemas.test.d.ts +1 -0
- package/dist/scripts/sync-schemas.test.js +124 -0
- package/dist/scripts/validate-route-metadata.js +11 -0
- package/dist/scripts/validate-route-metadata.test.d.ts +1 -0
- package/dist/scripts/validate-route-metadata.test.js +116 -0
- package/dist/scripts/validate-schemas.test.d.ts +1 -0
- package/dist/scripts/validate-schemas.test.js +263 -0
- package/package.json +85 -20
- package/scripts/add-ext.mjs +47 -0
- package/scripts/bump-version.mjs +58 -0
- package/scripts/bundle-cli.mjs +123 -0
- package/scripts/install-cli.mjs +36 -0
- package/scripts/lib/channels.mjs +70 -0
- package/scripts/lib/dev-release-state.mjs +44 -0
- package/scripts/lib/dev-release-state.test.ts +36 -0
- package/scripts/lib/postinstall-onboarding.test.ts +82 -0
- package/scripts/lib/release-core.mjs +225 -0
- package/scripts/lib/release-core.test.ts +147 -0
- package/scripts/migration-qa-gate.test.ts +155 -0
- package/scripts/publish-npm.mjs +180 -0
- package/templates/react-router/package.json +7 -10
- package/templates/react-router/src/pages/Index.tsx +1 -1
- package/dist/runtime/content/client/createContentClient.d.ts +0 -5
- package/dist/runtime/content/client/createContentClient.js +0 -300
- package/dist/runtime/content/client/createSingletonClient.d.ts +0 -5
- package/dist/runtime/content/client/createSingletonClient.js +0 -108
- package/dist/runtime/content/client/internal.d.ts +0 -44
- package/dist/runtime/content/client/internal.js +0 -236
- package/dist/runtime/content/client/types.d.ts +0 -136
- package/dist/runtime/content/client/useQueryLocale.d.ts +0 -2
- package/dist/runtime/content/client/useQueryLocale.js +0 -6
- /package/dist/{runtime/content/client/types.js → scripts/apply-wrap-templates.test.d.ts} +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const titleCase = (seg) => seg
|
|
4
|
+
.replace(/[_\-]+/g, ' ')
|
|
5
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
6
|
+
.split(/\s+/)
|
|
7
|
+
.filter(Boolean)
|
|
8
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
|
|
9
|
+
.join(' ');
|
|
10
|
+
const ROUTE_SOURCE = /\.(tsx|jsx|ts|js)$/;
|
|
11
|
+
const SKIP_DIRS = new Set(['node_modules', 'dist', 'build', '.git']);
|
|
12
|
+
/**
|
|
13
|
+
* Route discovery for Vite React SPAs: there is no file convention, so the
|
|
14
|
+
* routes live in react-router code. v1 reads LITERAL, ABSOLUTE paths from the
|
|
15
|
+
* two mainstream declaration shapes —
|
|
16
|
+
*
|
|
17
|
+
* <Route path="/pricing" element={…} /> (JSX tree)
|
|
18
|
+
* createBrowserRouter([{ path: '/pricing', … }]) (object form)
|
|
19
|
+
*
|
|
20
|
+
* — in files that import react-router. Relative child paths and index routes
|
|
21
|
+
* need the router's nesting semantics to resolve; they (and `*` wildcards)
|
|
22
|
+
* stay out of v1: the fimo-config route registry accepts manual entries for
|
|
23
|
+
* anything the scan cannot see, and scanned routes only ever ADD to it.
|
|
24
|
+
*/
|
|
25
|
+
export function discoverReactRouterDomRoutes(rootDir) {
|
|
26
|
+
const srcDir = join(rootDir, 'src');
|
|
27
|
+
const files = [];
|
|
28
|
+
const walk = (dir) => {
|
|
29
|
+
let entries;
|
|
30
|
+
try {
|
|
31
|
+
entries = readdirSync(dir);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
if (SKIP_DIRS.has(entry)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const full = join(dir, entry);
|
|
41
|
+
let isDir = false;
|
|
42
|
+
try {
|
|
43
|
+
isDir = statSync(full).isDirectory();
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (isDir) {
|
|
49
|
+
walk(full);
|
|
50
|
+
}
|
|
51
|
+
else if (ROUTE_SOURCE.test(entry)) {
|
|
52
|
+
files.push(full);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
walk(srcDir);
|
|
57
|
+
const routes = new Map();
|
|
58
|
+
for (const file of files) {
|
|
59
|
+
let code;
|
|
60
|
+
try {
|
|
61
|
+
code = readFileSync(file, 'utf8');
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (!/from\s+['"]react-router(?:-dom)?['"]/.test(code)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const paths = [
|
|
70
|
+
...[...code.matchAll(/<Route\b[^>]*\bpath\s*=\s*(?:"([^"]+)"|'([^']+)'|\{\s*(?:"([^"]+)"|'([^']+)')\s*\})/g)],
|
|
71
|
+
...[...code.matchAll(/\bpath\s*:\s*(?:"([^"]+)"|'([^']+)')/g)],
|
|
72
|
+
]
|
|
73
|
+
.map((match) => match.slice(1).find(Boolean))
|
|
74
|
+
.filter((path) => typeof path === 'string');
|
|
75
|
+
for (const raw of paths) {
|
|
76
|
+
if (!raw.startsWith('/') || raw.includes('*')) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const path = raw !== '/' && raw.endsWith('/') ? raw.slice(0, -1) : raw;
|
|
80
|
+
if (routes.has(path)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const parts = path.split('/').filter(Boolean);
|
|
84
|
+
const lastStatic = [...parts].reverse().find((part) => !part.startsWith(':'));
|
|
85
|
+
routes.set(path, {
|
|
86
|
+
id: path === '/' ? 'home' : parts.map((part) => part.replace(/^:/, '')).join('-'),
|
|
87
|
+
path,
|
|
88
|
+
label: path === '/' ? 'Home' : titleCase(lastStatic ?? parts[parts.length - 1] ?? path),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return [...routes.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
93
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { discoverReactRouterDomRoutes } from './routes.js';
|
|
6
|
+
let dir;
|
|
7
|
+
const project = (files) => {
|
|
8
|
+
dir = mkdtempSync(join(tmpdir(), 'vite-routes-'));
|
|
9
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
10
|
+
const full = join(dir, rel);
|
|
11
|
+
mkdirSync(join(full, '..'), { recursive: true });
|
|
12
|
+
writeFileSync(full, content);
|
|
13
|
+
}
|
|
14
|
+
return dir;
|
|
15
|
+
};
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
if (dir) {
|
|
18
|
+
rmSync(dir, { recursive: true, force: true });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
describe('discoverReactRouterDomRoutes', () => {
|
|
22
|
+
it('reads literal absolute paths from JSX Route trees', () => {
|
|
23
|
+
const root = project({
|
|
24
|
+
'src/App.tsx': `import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
|
25
|
+
export function App() {
|
|
26
|
+
return (
|
|
27
|
+
<BrowserRouter>
|
|
28
|
+
<Routes>
|
|
29
|
+
<Route path="/" element={<Home />} />
|
|
30
|
+
<Route path="/about" element={<About />} />
|
|
31
|
+
<Route path="/features/pricing" element={<Pricing />} />
|
|
32
|
+
<Route path={'/contact'} element={<Contact />} />
|
|
33
|
+
</Routes>
|
|
34
|
+
</BrowserRouter>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
`,
|
|
38
|
+
});
|
|
39
|
+
const routes = discoverReactRouterDomRoutes(root);
|
|
40
|
+
expect(routes.map((route) => route.path)).toEqual(['/', '/about', '/contact', '/features/pricing']);
|
|
41
|
+
expect(routes[0]).toEqual({ id: 'home', path: '/', label: 'Home' });
|
|
42
|
+
expect(routes.find((route) => route.path === '/features/pricing')).toEqual({
|
|
43
|
+
id: 'features-pricing',
|
|
44
|
+
path: '/features/pricing',
|
|
45
|
+
label: 'Pricing',
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
it('reads object-form routers and keeps :params, skipping wildcards and relative paths', () => {
|
|
49
|
+
const root = project({
|
|
50
|
+
'src/router.ts': `import { createBrowserRouter } from 'react-router-dom';
|
|
51
|
+
export const router = createBrowserRouter([
|
|
52
|
+
{ path: '/', element: null },
|
|
53
|
+
{ path: '/posts/:slug', element: null },
|
|
54
|
+
{ path: '*', element: null },
|
|
55
|
+
{ path: 'relative-child', element: null },
|
|
56
|
+
]);
|
|
57
|
+
`,
|
|
58
|
+
});
|
|
59
|
+
const routes = discoverReactRouterDomRoutes(root);
|
|
60
|
+
expect(routes.map((route) => route.path)).toEqual(['/', '/posts/:slug']);
|
|
61
|
+
expect(routes.find((route) => route.path === '/posts/:slug')).toEqual({
|
|
62
|
+
id: 'posts-slug',
|
|
63
|
+
path: '/posts/:slug',
|
|
64
|
+
label: 'Posts',
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
it('ignores path-shaped literals in files that do not import react-router', () => {
|
|
68
|
+
const root = project({
|
|
69
|
+
'src/config.ts': `export const nav = [{ path: '/dashboard', label: 'Dashboard' }];\n`,
|
|
70
|
+
});
|
|
71
|
+
expect(discoverReactRouterDomRoutes(root)).toEqual([]);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RootWiringResult } from '../next/wiring.js';
|
|
2
|
+
/**
|
|
3
|
+
* Vite React SPA root wiring: wrap the root render argument with
|
|
4
|
+
* `FimoSpaProviders` (client-side label fetch; inline fallbacks until it
|
|
5
|
+
* resolves) and import the project's fimo-config.json. Only the mainstream
|
|
6
|
+
* entry shape is touched — a `createRoot(…).render(<JSX/>)` (or legacy
|
|
7
|
+
* `ReactDOM.render(<JSX/>, …)`) call whose first argument is a JSX element.
|
|
8
|
+
* Anything else bails with the reason and stays agent work via the recipe.
|
|
9
|
+
*/
|
|
10
|
+
export declare function ensureSpaEntryWiring(cwd: string): RootWiringResult;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parseSync } from 'oxc-parser';
|
|
4
|
+
/**
|
|
5
|
+
* Vite React SPA root wiring: wrap the root render argument with
|
|
6
|
+
* `FimoSpaProviders` (client-side label fetch; inline fallbacks until it
|
|
7
|
+
* resolves) and import the project's fimo-config.json. Only the mainstream
|
|
8
|
+
* entry shape is touched — a `createRoot(…).render(<JSX/>)` (or legacy
|
|
9
|
+
* `ReactDOM.render(<JSX/>, …)`) call whose first argument is a JSX element.
|
|
10
|
+
* Anything else bails with the reason and stays agent work via the recipe.
|
|
11
|
+
*/
|
|
12
|
+
export function ensureSpaEntryWiring(cwd) {
|
|
13
|
+
const entryFile = ['src/main.tsx', 'src/main.jsx', 'src/index.tsx', 'src/index.jsx']
|
|
14
|
+
.map((candidate) => path.join(cwd, candidate))
|
|
15
|
+
.find((candidate) => fs.existsSync(candidate));
|
|
16
|
+
const bail = (reason, rel = 'src/main.tsx') => ({
|
|
17
|
+
changes: [],
|
|
18
|
+
warnings: [`Root wiring not applied: ${reason} — follow the SPA recipe in the agent prompt (${rel}).`],
|
|
19
|
+
wired: false,
|
|
20
|
+
});
|
|
21
|
+
if (!entryFile) {
|
|
22
|
+
return bail('no src/main or src/index entry file found');
|
|
23
|
+
}
|
|
24
|
+
const rel = path.relative(cwd, entryFile);
|
|
25
|
+
const code = fs.readFileSync(entryFile, 'utf8');
|
|
26
|
+
if (code.includes('FimoSpaProviders')) {
|
|
27
|
+
return { changes: [], warnings: [], wired: true };
|
|
28
|
+
}
|
|
29
|
+
let program;
|
|
30
|
+
try {
|
|
31
|
+
program = parseSync(entryFile, code).program;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return bail('entry file could not be parsed', rel);
|
|
35
|
+
}
|
|
36
|
+
// The JSX argument of the root render call, found structurally: a call
|
|
37
|
+
// whose callee property is `render` with a JSX element first argument
|
|
38
|
+
// (covers `createRoot(…).render(<X/>)` and `ReactDOM.render(<X/>, node)`).
|
|
39
|
+
const renderArg = (function find(node) {
|
|
40
|
+
if (!node || typeof node !== 'object') {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const candidate = node;
|
|
44
|
+
if (candidate.type === 'CallExpression' &&
|
|
45
|
+
candidate.callee?.type === 'MemberExpression' &&
|
|
46
|
+
candidate.callee?.property?.name === 'render' &&
|
|
47
|
+
(candidate.arguments?.[0]?.type === 'JSXElement' || candidate.arguments?.[0]?.type === 'JSXFragment')) {
|
|
48
|
+
return candidate.arguments[0];
|
|
49
|
+
}
|
|
50
|
+
for (const value of Object.values(candidate)) {
|
|
51
|
+
if (Array.isArray(value)) {
|
|
52
|
+
for (const entry of value) {
|
|
53
|
+
const found = find(entry);
|
|
54
|
+
if (found) {
|
|
55
|
+
return found;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else if (value && typeof value === 'object') {
|
|
60
|
+
const found = find(value);
|
|
61
|
+
if (found) {
|
|
62
|
+
return found;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
})(program);
|
|
68
|
+
if (!renderArg) {
|
|
69
|
+
return bail('no root render call with a JSX argument found', rel);
|
|
70
|
+
}
|
|
71
|
+
const configImportPath = path
|
|
72
|
+
.relative(path.dirname(entryFile), path.join(cwd, 'fimo-config.json'))
|
|
73
|
+
.split(path.sep)
|
|
74
|
+
.join('/');
|
|
75
|
+
const jsx = code.slice(renderArg.start, renderArg.end);
|
|
76
|
+
const open = '<FimoSpaProviders\n' +
|
|
77
|
+
' config={fimoConfig}\n' +
|
|
78
|
+
' apiUrl={import.meta.env.VITE_API_URL}\n' +
|
|
79
|
+
' previewScriptUrl={import.meta.env.DEV ? import.meta.env.VITE_FIMO_PREVIEW_SCRIPT_URL : undefined}\n' +
|
|
80
|
+
' >';
|
|
81
|
+
let next = code.slice(0, renderArg.start) + `${open}\n ${jsx}\n </FimoSpaProviders>` + code.slice(renderArg.end);
|
|
82
|
+
// AST-positioned like ensureFimoImport: a multiline import's first line is
|
|
83
|
+
// not the end of the imports.
|
|
84
|
+
const importBlock = `import { FimoSpaProviders } from 'fimo/react';\nimport fimoConfig from '${configImportPath.startsWith('.') ? configImportPath : `./${configImportPath}`}';`;
|
|
85
|
+
let lastImportEnd = -1;
|
|
86
|
+
for (const node of program.body ?? []) {
|
|
87
|
+
if (node.type === 'ImportDeclaration') {
|
|
88
|
+
lastImportEnd = node.end;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// The wrap edit above only touched positions at/after renderArg.start,
|
|
92
|
+
// which is always beyond the import block, so import spans still hold.
|
|
93
|
+
next =
|
|
94
|
+
lastImportEnd >= 0
|
|
95
|
+
? `${next.slice(0, lastImportEnd)}\n${importBlock}${next.slice(lastImportEnd)}`
|
|
96
|
+
: `${importBlock}\n${next}`;
|
|
97
|
+
fs.writeFileSync(entryFile, next);
|
|
98
|
+
return { changes: [`wired FimoSpaProviders into ${rel}`], warnings: [], wired: true };
|
|
99
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { ensureSpaEntryWiring } from './wiring.js';
|
|
6
|
+
let dir;
|
|
7
|
+
const project = (files) => {
|
|
8
|
+
dir = mkdtempSync(join(tmpdir(), 'vite-wiring-'));
|
|
9
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
10
|
+
const full = join(dir, rel);
|
|
11
|
+
mkdirSync(join(full, '..'), { recursive: true });
|
|
12
|
+
writeFileSync(full, content);
|
|
13
|
+
}
|
|
14
|
+
return dir;
|
|
15
|
+
};
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
if (dir) {
|
|
18
|
+
rmSync(dir, { recursive: true, force: true });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
describe('ensureSpaEntryWiring', () => {
|
|
22
|
+
it('wraps the StrictMode root render and imports the config', () => {
|
|
23
|
+
const root = project({
|
|
24
|
+
'src/main.tsx': `import { StrictMode } from 'react';
|
|
25
|
+
import { createRoot } from 'react-dom/client';
|
|
26
|
+
import { App } from './App';
|
|
27
|
+
|
|
28
|
+
createRoot(document.getElementById('root')!).render(
|
|
29
|
+
<StrictMode>
|
|
30
|
+
<App />
|
|
31
|
+
</StrictMode>
|
|
32
|
+
);
|
|
33
|
+
`,
|
|
34
|
+
});
|
|
35
|
+
const result = ensureSpaEntryWiring(root);
|
|
36
|
+
const code = readFileSync(join(root, 'src/main.tsx'), 'utf8');
|
|
37
|
+
expect(result.wired).toBe(true);
|
|
38
|
+
expect(result.changes).toEqual(['wired FimoSpaProviders into src/main.tsx']);
|
|
39
|
+
expect(code).toContain("import { FimoSpaProviders } from 'fimo/react';");
|
|
40
|
+
expect(code).toContain("import fimoConfig from '../fimo-config.json';");
|
|
41
|
+
expect(code).toContain('<FimoSpaProviders');
|
|
42
|
+
expect(code).toContain('config={fimoConfig}');
|
|
43
|
+
expect(code).toContain('apiUrl={import.meta.env.VITE_API_URL}');
|
|
44
|
+
expect(code).toContain('previewScriptUrl={import.meta.env.DEV ? import.meta.env.VITE_FIMO_PREVIEW_SCRIPT_URL : undefined}');
|
|
45
|
+
// The original tree stays inside the provider.
|
|
46
|
+
expect(code.indexOf('<StrictMode>')).toBeGreaterThan(code.indexOf('<FimoSpaProviders'));
|
|
47
|
+
expect(code.indexOf('</StrictMode>')).toBeLessThan(code.indexOf('</FimoSpaProviders>'));
|
|
48
|
+
// The config import lands AFTER the last original import, not inside one.
|
|
49
|
+
expect(code.indexOf("from './App'")).toBeLessThan(code.indexOf('fimo-config.json'));
|
|
50
|
+
});
|
|
51
|
+
it('handles a bare render argument and src/index fallback', () => {
|
|
52
|
+
const root = project({
|
|
53
|
+
'src/index.jsx': `import ReactDOM from 'react-dom/client';
|
|
54
|
+
import App from './App';
|
|
55
|
+
|
|
56
|
+
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
|
|
57
|
+
`,
|
|
58
|
+
});
|
|
59
|
+
const result = ensureSpaEntryWiring(root);
|
|
60
|
+
const code = readFileSync(join(root, 'src/index.jsx'), 'utf8');
|
|
61
|
+
expect(result.wired).toBe(true);
|
|
62
|
+
expect(code).toContain('<FimoSpaProviders');
|
|
63
|
+
expect(code).toContain('<App />');
|
|
64
|
+
});
|
|
65
|
+
it('is a no-op when the entry is already wired', () => {
|
|
66
|
+
const wired = `import { FimoSpaProviders } from 'fimo/react';
|
|
67
|
+
import { createRoot } from 'react-dom/client';
|
|
68
|
+
|
|
69
|
+
createRoot(document.getElementById('root')).render(<FimoSpaProviders config={{}}><p>hi</p></FimoSpaProviders>);
|
|
70
|
+
`;
|
|
71
|
+
const root = project({ 'src/main.tsx': wired });
|
|
72
|
+
const result = ensureSpaEntryWiring(root);
|
|
73
|
+
expect(result.wired).toBe(true);
|
|
74
|
+
expect(result.changes).toEqual([]);
|
|
75
|
+
expect(readFileSync(join(root, 'src/main.tsx'), 'utf8')).toBe(wired);
|
|
76
|
+
});
|
|
77
|
+
it('bails when there is no entry file or no JSX render call', () => {
|
|
78
|
+
const empty = project({ 'src/app.ts': 'export {};\n' });
|
|
79
|
+
expect(ensureSpaEntryWiring(empty).wired).toBe(false);
|
|
80
|
+
rmSync(empty, { recursive: true, force: true });
|
|
81
|
+
const noJsx = project({
|
|
82
|
+
'src/main.tsx': `import { mount } from './mount';\nmount(document.getElementById('root'));\n`,
|
|
83
|
+
});
|
|
84
|
+
const result = ensureSpaEntryWiring(noJsx);
|
|
85
|
+
expect(result.wired).toBe(false);
|
|
86
|
+
expect(result.warnings[0]).toContain('no root render call');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { dirname, join, resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { afterAll, describe, expect, it } from 'vitest';
|
|
8
|
+
import { syncSchemas } from './sync-schemas.js';
|
|
9
|
+
/**
|
|
10
|
+
* The generated-types gate: generate hooks + types from fixture schemas that
|
|
11
|
+
* cover every field type and reference shape, add usage code written the way
|
|
12
|
+
* an agent writes site code, and run the real TypeScript compiler over the
|
|
13
|
+
* output. A template regression fails this test — never a user project.
|
|
14
|
+
*/
|
|
15
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
17
|
+
const runtimeBarrel = resolve(here, '../runtime/index.ts');
|
|
18
|
+
const tscBin = requireFromHere.resolve('typescript/bin/tsc');
|
|
19
|
+
const fixtureRoot = mkdtempSync(join(tmpdir(), 'fimo-generated-types-'));
|
|
20
|
+
const schemasDir = join(fixtureRoot, 'src', 'schemas');
|
|
21
|
+
const SCHEMAS = {
|
|
22
|
+
Author: {
|
|
23
|
+
uid: 'Author',
|
|
24
|
+
name: 'Author',
|
|
25
|
+
i18n: { enabled: true },
|
|
26
|
+
fields: {
|
|
27
|
+
name: { type: 'string', required: true },
|
|
28
|
+
bio: { type: 'richtext' },
|
|
29
|
+
avatar: { type: 'media' },
|
|
30
|
+
active: { type: 'boolean' },
|
|
31
|
+
bornAt: { type: 'date' },
|
|
32
|
+
meta: { type: 'json' },
|
|
33
|
+
rating: { type: 'number' },
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
Video: {
|
|
37
|
+
uid: 'Video',
|
|
38
|
+
name: 'Video',
|
|
39
|
+
i18n: { enabled: false },
|
|
40
|
+
fields: {
|
|
41
|
+
title: { type: 'string', required: true },
|
|
42
|
+
clips: { type: 'media', multiple: true },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
Post: {
|
|
46
|
+
uid: 'Post',
|
|
47
|
+
name: 'Post',
|
|
48
|
+
i18n: { enabled: true },
|
|
49
|
+
fields: {
|
|
50
|
+
title: { type: 'string', required: true },
|
|
51
|
+
author: { type: 'reference', target: 'Author' },
|
|
52
|
+
contributors: { type: 'reference', target: 'Author', many: true },
|
|
53
|
+
related: { type: 'reference', target: 'Post', many: true },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
Page: {
|
|
57
|
+
uid: 'Page',
|
|
58
|
+
name: 'Page',
|
|
59
|
+
i18n: { enabled: true },
|
|
60
|
+
fields: {
|
|
61
|
+
title: { type: 'string' },
|
|
62
|
+
featured: { type: 'reference', targets: ['Post', 'Video'], many: true },
|
|
63
|
+
hero: { type: 'reference', targets: ['Author', 'Video'] },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
SiteSettings: {
|
|
67
|
+
uid: 'SiteSettings',
|
|
68
|
+
name: 'SiteSettings',
|
|
69
|
+
isSingleton: true,
|
|
70
|
+
i18n: { enabled: false },
|
|
71
|
+
fields: {
|
|
72
|
+
siteName: { type: 'string', required: true },
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
const USAGE = `// Agent-style usage of the generated types — compile-time contract.
|
|
77
|
+
import type { PageResult, PageWhere } from "./Page";
|
|
78
|
+
import Post from "./Post";
|
|
79
|
+
import type { PostResult } from "./Post";
|
|
80
|
+
|
|
81
|
+
export function renderFeatured(page: PageResult<undefined, readonly ["featured", "hero"]>): string[] {
|
|
82
|
+
const out: string[] = [];
|
|
83
|
+
for (const item of page.featured) {
|
|
84
|
+
switch (item.$type) {
|
|
85
|
+
case "Post":
|
|
86
|
+
out.push(\`post \${String(item.title)} by \${item.author?.documentId ?? "nobody"}\`);
|
|
87
|
+
break;
|
|
88
|
+
case "Video":
|
|
89
|
+
out.push(\`video \${String(item.title)} with \${item.clips.length} clip(s)\`);
|
|
90
|
+
break;
|
|
91
|
+
default: {
|
|
92
|
+
const exhaustive: never = item;
|
|
93
|
+
return exhaustive;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const hero = page.hero;
|
|
98
|
+
if (hero && hero.$type === "Author") {
|
|
99
|
+
out.push(\`hero \${String(hero.name)}\`);
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function renderPost(post: PostResult<undefined, readonly ["author", "contributors"]>): string {
|
|
105
|
+
const contributors = post.contributors.map((author) => String(author.name)).join(", ");
|
|
106
|
+
return \`\${String(post.title)} — \${post.author ? String(post.author.name) : "anonymous"} (\${contributors})\`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function whereContract(): PageWhere {
|
|
110
|
+
return {
|
|
111
|
+
title: "Home",
|
|
112
|
+
// @ts-expect-error filtering on polymorphic reference fields is not supported
|
|
113
|
+
featured: "some-document-id",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function paginatedPostContract(): void {
|
|
118
|
+
const legacy = Post.useGet({ limit: 3 });
|
|
119
|
+
const page = Post.useList({
|
|
120
|
+
fields: ["title", "author"] as const,
|
|
121
|
+
populate: ["author"] as const,
|
|
122
|
+
limit: 12,
|
|
123
|
+
offset: 24,
|
|
124
|
+
});
|
|
125
|
+
const infinite = Post.useInfiniteList({
|
|
126
|
+
fields: ["title", "author"] as const,
|
|
127
|
+
populate: ["author"] as const,
|
|
128
|
+
limit: 12,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const legacyPosts: PostResult[] = legacy.data ?? [];
|
|
132
|
+
const pagePosts: PostResult<readonly ["title", "author"], readonly ["author"]>[] = page.data?.items ?? [];
|
|
133
|
+
const total: number = page.data?.pageInfo.total ?? 0;
|
|
134
|
+
const nextOffset: number | null = page.data?.pageInfo.nextOffset ?? null;
|
|
135
|
+
const infinitePosts: PostResult<readonly ["title", "author"], readonly ["author"]>[] = infinite.items;
|
|
136
|
+
const firstAuthorName: string = infinitePosts[0]?.author ? String(infinitePosts[0].author.name) : "";
|
|
137
|
+
const hasMore: boolean = infinite.hasMore;
|
|
138
|
+
const isLoadingMore: boolean = infinite.isLoadingMore;
|
|
139
|
+
|
|
140
|
+
void infinite.loadMore();
|
|
141
|
+
// @ts-expect-error infinite lists own offset progression
|
|
142
|
+
Post.useInfiniteList({ offset: 12 });
|
|
143
|
+
|
|
144
|
+
void [legacyPosts, pagePosts, total, nextOffset, infinitePosts, firstAuthorName, hasMore, isLoadingMore];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function paginatedPostFetchContract(): Promise<void> {
|
|
148
|
+
const page = await Post.list({ limit: 12, offset: 24 });
|
|
149
|
+
const legacyPosts: PostResult[] = await Post.get({ limit: 3 });
|
|
150
|
+
const pagePosts: PostResult[] = page.items;
|
|
151
|
+
const hasMore: boolean = page.pageInfo.hasMore;
|
|
152
|
+
|
|
153
|
+
void [legacyPosts, pagePosts, hasMore];
|
|
154
|
+
}
|
|
155
|
+
`;
|
|
156
|
+
const SHIMS = `declare module "virtual:fimo-config" {
|
|
157
|
+
const config: Record<string, unknown>;
|
|
158
|
+
export default config;
|
|
159
|
+
}
|
|
160
|
+
declare module "virtual:translations" {
|
|
161
|
+
export const dictionaries: Record<string, Record<string, string>>;
|
|
162
|
+
export const defaultLocale: string;
|
|
163
|
+
export const signature: string;
|
|
164
|
+
export const updatedAt: string;
|
|
165
|
+
}
|
|
166
|
+
declare module "*.css" {}
|
|
167
|
+
`;
|
|
168
|
+
afterAll(() => {
|
|
169
|
+
rmSync(fixtureRoot, { recursive: true, force: true });
|
|
170
|
+
});
|
|
171
|
+
describe('generated types gate', () => {
|
|
172
|
+
it('generates hooks + types for every field shape and the output typechecks with agent-style usage', () => {
|
|
173
|
+
mkdirSync(schemasDir, { recursive: true });
|
|
174
|
+
// Real react-query types: the fixture resolves them like a real project.
|
|
175
|
+
const reactQueryDir = dirname(requireFromHere.resolve('@tanstack/react-query/package.json'));
|
|
176
|
+
mkdirSync(join(fixtureRoot, 'node_modules', '@tanstack'), { recursive: true });
|
|
177
|
+
symlinkSync(reactQueryDir, join(fixtureRoot, 'node_modules', '@tanstack', 'react-query'));
|
|
178
|
+
for (const [uid, schema] of Object.entries(SCHEMAS)) {
|
|
179
|
+
writeFileSync(join(schemasDir, `${uid}.json`), JSON.stringify(schema, null, 2));
|
|
180
|
+
}
|
|
181
|
+
const result = syncSchemas({ cwd: fixtureRoot, verbose: false });
|
|
182
|
+
expect(result.created).toBeGreaterThanOrEqual(Object.keys(SCHEMAS).length * 2);
|
|
183
|
+
writeFileSync(join(schemasDir, 'usage.ts'), USAGE);
|
|
184
|
+
writeFileSync(join(fixtureRoot, 'shims.d.ts'), SHIMS);
|
|
185
|
+
writeFileSync(join(fixtureRoot, 'tsconfig.json'), JSON.stringify({
|
|
186
|
+
compilerOptions: {
|
|
187
|
+
strict: true,
|
|
188
|
+
noEmit: true,
|
|
189
|
+
skipLibCheck: true,
|
|
190
|
+
target: 'ES2022',
|
|
191
|
+
module: 'ESNext',
|
|
192
|
+
moduleResolution: 'bundler',
|
|
193
|
+
jsx: 'react-jsx',
|
|
194
|
+
allowImportingTsExtensions: true,
|
|
195
|
+
baseUrl: '.',
|
|
196
|
+
paths: {
|
|
197
|
+
'fimo/ui': [runtimeBarrel],
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
include: ['src/**/*.ts', 'shims.d.ts'],
|
|
201
|
+
}, null, 2));
|
|
202
|
+
let output = '';
|
|
203
|
+
try {
|
|
204
|
+
output = execFileSync(process.execPath, [tscBin, '-p', fixtureRoot], { encoding: 'utf8' });
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
const failure = error;
|
|
208
|
+
throw new Error(`generated types failed to typecheck:\n${failure.stdout ?? ''}${failure.stderr ?? ''}`);
|
|
209
|
+
}
|
|
210
|
+
expect(output.trim()).toBe('');
|
|
211
|
+
}, 120_000);
|
|
212
|
+
});
|
|
@@ -76,6 +76,9 @@ function formatPhaseHuman(phase) {
|
|
|
76
76
|
out.push(` ${parts.join(', ')}`);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
+
for (const warning of phase.warnings ?? []) {
|
|
80
|
+
out.push(` ⚠ ${warning}`);
|
|
81
|
+
}
|
|
79
82
|
for (const err of phase.errors) {
|
|
80
83
|
const loc = err.file ? ` (${err.file}${err.line ? `:${err.line}` : ''}${err.column ? `:${err.column}` : ''})` : '';
|
|
81
84
|
out.push(` [${err.kind}] ${err.rule}: ${err.message}${loc}`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { lintTranslationKeys } from './lint-translation-keys';
|
|
3
|
+
describe('lintTranslationKeys', () => {
|
|
4
|
+
it('allows literal DB-owned label keys', () => {
|
|
5
|
+
const errors = lintTranslationKeys('src/pages/Index.tsx', "const title = t('hero.title');");
|
|
6
|
+
expect(errors).toEqual([]);
|
|
7
|
+
});
|
|
8
|
+
it('rejects inline fallback values', () => {
|
|
9
|
+
const errors = lintTranslationKeys('src/pages/Index.tsx', "const title = t('hero.title', 'Welcome');");
|
|
10
|
+
expect(errors).toEqual([expect.objectContaining({ rule: 'labels/no-inline-default' })]);
|
|
11
|
+
});
|
|
12
|
+
it('rejects dynamic keys', () => {
|
|
13
|
+
const errors = lintTranslationKeys('src/pages/Index.tsx', 'const title = t(heroKey);');
|
|
14
|
+
expect(errors).toEqual([expect.objectContaining({ rule: 'translation-keys' })]);
|
|
15
|
+
});
|
|
16
|
+
});
|