fimo 0.7.0-experimental.1 → 0.7.0-experimental.11
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/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/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 +617 -0
- package/assets/skills/fimo-studio/references/visual-editing.md +5 -3
- package/dist/build/vite/plugins/translations.d.ts +2 -8
- package/dist/build/vite/plugins/translations.d.ts.map +1 -1
- package/dist/build/vite/plugins/translations.js +3 -121
- package/dist/cli/bundle.json +1 -1
- package/dist/cli/index.js +20644 -12459
- package/dist/runtime/app/FimoProviders.d.ts +1 -11
- package/dist/runtime/app/FimoProviders.d.ts.map +1 -1
- package/dist/runtime/app/FimoProviders.js +2 -23
- 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/utils/retrieve-data-sources.d.ts +0 -1
- package/dist/runtime/content/utils/retrieve-data-sources.d.ts.map +1 -1
- package/dist/runtime/content/utils/retrieve-data-sources.js +4 -27
- package/dist/runtime/i18n/FimoLocaleContext.d.ts.map +1 -1
- package/dist/runtime/i18n/FimoLocaleContext.js +1 -0
- 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 -1
- package/dist/runtime/paths/get-fimo-paths.js +5 -15
- 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/primitives.d.ts +2 -2
- package/dist/runtime/primitives/lib/primitives.d.ts.map +1 -1
- package/dist/runtime/primitives/lib/query.d.ts +3 -5
- package/dist/runtime/primitives/lib/query.d.ts.map +1 -1
- package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts +5 -3
- package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts.map +1 -1
- package/dist/runtime/primitives/lib/useFimoLiveValue.js +11 -4
- package/dist/runtime/primitives/public-surface.test.js +5 -9
- package/dist/runtime/primitives/translations.d.ts +1 -27
- package/dist/runtime/primitives/translations.d.ts.map +1 -1
- 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/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/seo/buildSeoForPath.d.ts +3 -1
- package/dist/runtime/seo/buildSeoForPath.d.ts.map +1 -1
- package/dist/runtime/seo/buildSeoForPath.js +1 -4
- 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 -1
- 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 -1
- 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/strapi/client.d.ts +26 -0
- package/dist/runtime/strapi/client.d.ts.map +1 -0
- package/dist/runtime/strapi/client.js +88 -0
- package/dist/runtime/strapi/client.test.d.ts +2 -0
- package/dist/runtime/strapi/client.test.d.ts.map +1 -0
- package/dist/runtime/strapi/client.test.js +64 -0
- package/dist/runtime/strapi/index.d.ts +5 -0
- package/dist/runtime/strapi/index.d.ts.map +1 -0
- package/dist/runtime/strapi/index.js +6 -0
- package/dist/runtime/strapi/reshape.d.ts +28 -0
- package/dist/runtime/strapi/reshape.d.ts.map +1 -0
- package/dist/runtime/strapi/reshape.js +79 -0
- package/dist/runtime/strapi/reshape.test.d.ts +2 -0
- package/dist/runtime/strapi/reshape.test.d.ts.map +1 -0
- package/dist/runtime/strapi/reshape.test.js +33 -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/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.d.ts +1 -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 +108 -0
- package/dist/scripts/cms/example-content.test.d.ts +1 -0
- package/dist/scripts/cms/example-content.test.js +66 -0
- package/dist/scripts/cms/strapi/detect.d.ts +2 -0
- package/dist/scripts/cms/strapi/detect.js +186 -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 +25 -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 +149 -0
- package/dist/scripts/cms/strapi/schema-map.test.d.ts +1 -0
- package/dist/scripts/cms/strapi/schema-map.test.js +91 -0
- package/dist/scripts/cms/strapi/touchpoints.d.ts +31 -0
- package/dist/scripts/cms/strapi/touchpoints.js +145 -0
- package/dist/scripts/cms/strapi/touchpoints.test.d.ts +1 -0
- package/dist/scripts/cms/strapi/touchpoints.test.js +62 -0
- package/dist/scripts/cms/strapi/wire-client.d.ts +15 -0
- package/dist/scripts/cms/strapi/wire-client.js +107 -0
- package/dist/scripts/cms/strapi/wire-client.test.d.ts +1 -0
- package/dist/scripts/cms/strapi/wire-client.test.js +51 -0
- package/dist/scripts/cms/strapi/wiring.d.ts +17 -0
- package/dist/scripts/cms/strapi/wiring.js +36 -0
- package/dist/scripts/cms/strapi/wiring.test.d.ts +1 -0
- package/dist/scripts/cms/strapi/wiring.test.js +27 -0
- package/dist/scripts/cms/types.d.ts +117 -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/extract-translations.js +16 -11
- package/dist/scripts/extract-translations.test.d.ts +1 -0
- package/dist/scripts/extract-translations.test.js +104 -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 +178 -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 +33 -0
- package/dist/scripts/frameworks/next/wiring.js +186 -0
- package/dist/scripts/frameworks/next/wiring.test.d.ts +1 -0
- package/dist/scripts/frameworks/next/wiring.test.js +174 -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/support.d.ts +75 -0
- package/dist/scripts/frameworks/support.js +165 -0
- package/dist/scripts/frameworks/support.test.d.ts +1 -0
- package/dist/scripts/frameworks/support.test.js +87 -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.js +1 -1
- package/dist/scripts/i18n-namespace.d.ts +27 -0
- package/dist/scripts/i18n-namespace.js +155 -0
- package/dist/scripts/lib/validation-report.d.ts +1 -0
- package/dist/scripts/lib/validation-report.js +3 -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 +1232 -0
- package/dist/scripts/scan-wrappable.test.d.ts +1 -0
- package/dist/scripts/scan-wrappable.test.js +521 -0
- package/dist/scripts/sync-schemas.test.js +4 -24
- package/dist/scripts/validate-route-metadata.js +11 -0
- package/dist/scripts/validate-schemas.test.js +2 -2
- package/package.json +92 -5
- package/templates/react-router/package.json +7 -10
- package/dist/runtime/paths/get-fimo-paths.test.d.ts +0 -2
- package/dist/runtime/paths/get-fimo-paths.test.d.ts.map +0 -1
- package/dist/runtime/paths/get-fimo-paths.test.js +0 -69
|
@@ -0,0 +1,91 @@
|
|
|
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 { readStrapiSchemas } from './schema-map.js';
|
|
6
|
+
let dir;
|
|
7
|
+
function writeSchema(apiName, ctName, schema) {
|
|
8
|
+
const ctDir = join(dir, 'src', 'api', apiName, 'content-types', ctName);
|
|
9
|
+
mkdirSync(ctDir, { recursive: true });
|
|
10
|
+
writeFileSync(join(ctDir, 'schema.json'), JSON.stringify(schema));
|
|
11
|
+
}
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
if (dir) {
|
|
14
|
+
rmSync(dir, { recursive: true, force: true });
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
describe('readStrapiSchemas', () => {
|
|
18
|
+
it('maps Strapi attribute types to Fimo fields and resolves relations', () => {
|
|
19
|
+
dir = mkdtempSync(join(tmpdir(), 'strapi-schema-'));
|
|
20
|
+
writeSchema('blog-post', 'blog-post', {
|
|
21
|
+
kind: 'collectionType',
|
|
22
|
+
info: { singularName: 'blog-post', pluralName: 'blog-posts', displayName: 'Blog Post' },
|
|
23
|
+
pluginOptions: {},
|
|
24
|
+
attributes: {
|
|
25
|
+
title: { type: 'string', required: true },
|
|
26
|
+
slug: { type: 'uid', targetField: 'title' },
|
|
27
|
+
content: { type: 'richtext' },
|
|
28
|
+
readTime: { type: 'integer' },
|
|
29
|
+
featured: { type: 'boolean' },
|
|
30
|
+
publishedAt: { type: 'datetime' },
|
|
31
|
+
level: { type: 'enumeration', enum: ['a', 'b'] },
|
|
32
|
+
cover: { type: 'media' },
|
|
33
|
+
hero: { type: 'component', component: 'blocks.hero', repeatable: false },
|
|
34
|
+
sections: { type: 'dynamiczone', components: [] },
|
|
35
|
+
category: { type: 'relation', relation: 'manyToOne', target: 'api::post-category.post-category' },
|
|
36
|
+
tags: { type: 'relation', relation: 'manyToMany', target: 'api::post-tag.post-tag' },
|
|
37
|
+
author: { type: 'relation', relation: 'manyToOne', target: 'plugin::users-permissions.user' },
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
writeSchema('post-category', 'post-category', {
|
|
41
|
+
kind: 'collectionType',
|
|
42
|
+
info: { singularName: 'post-category', pluralName: 'post-categories', displayName: 'Post Category' },
|
|
43
|
+
attributes: { name: { type: 'string' }, slug: { type: 'uid' } },
|
|
44
|
+
});
|
|
45
|
+
writeSchema('post-tag', 'post-tag', {
|
|
46
|
+
kind: 'collectionType',
|
|
47
|
+
info: { singularName: 'post-tag', pluralName: 'post-tags', displayName: 'Post Tag' },
|
|
48
|
+
attributes: { name: { type: 'string' } },
|
|
49
|
+
});
|
|
50
|
+
writeSchema('global', 'global', {
|
|
51
|
+
kind: 'singleType',
|
|
52
|
+
info: { singularName: 'global', pluralName: 'globals', displayName: 'Global' },
|
|
53
|
+
pluginOptions: { i18n: { localized: true } },
|
|
54
|
+
attributes: { siteName: { type: 'string' } },
|
|
55
|
+
});
|
|
56
|
+
const schemas = readStrapiSchemas(dir);
|
|
57
|
+
expect(schemas.map((s) => s.name)).toEqual(['BlogPost', 'Global', 'PostCategory', 'PostTag']);
|
|
58
|
+
const blogPost = schemas.find((s) => s.name === 'BlogPost');
|
|
59
|
+
expect(blogPost.isSingleton).toBe(false);
|
|
60
|
+
expect(blogPost.localized).toBe(false);
|
|
61
|
+
expect(blogPost.sourceUid).toBe('api::blog-post.blog-post');
|
|
62
|
+
expect(blogPost.fields).toMatchObject({
|
|
63
|
+
title: { type: 'string' },
|
|
64
|
+
content: { type: 'richtext' },
|
|
65
|
+
readTime: { type: 'number' },
|
|
66
|
+
featured: { type: 'boolean' },
|
|
67
|
+
publishedAt: { type: 'date' },
|
|
68
|
+
level: { type: 'string' },
|
|
69
|
+
cover: { type: 'media' },
|
|
70
|
+
hero: { type: 'json' },
|
|
71
|
+
sections: { type: 'json' },
|
|
72
|
+
category: { type: 'reference', target: 'PostCategory' },
|
|
73
|
+
tags: { type: 'reference', target: 'PostTag', many: true },
|
|
74
|
+
author: { type: 'json' },
|
|
75
|
+
});
|
|
76
|
+
// slug is reserved entry metadata — never a schema field.
|
|
77
|
+
expect('slug' in blogPost.fields).toBe(false);
|
|
78
|
+
// Structural fields get the correct empty shape so pages render:
|
|
79
|
+
// dynamic zone → array, single component → object.
|
|
80
|
+
expect(blogPost.examples?.sections).toEqual([]);
|
|
81
|
+
expect(blogPost.examples?.hero).toEqual({});
|
|
82
|
+
const global = schemas.find((s) => s.name === 'Global');
|
|
83
|
+
expect(global.isSingleton).toBe(true);
|
|
84
|
+
expect(global.localized).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
it('returns an empty list when the app has no content types', () => {
|
|
87
|
+
dir = mkdtempSync(join(tmpdir(), 'strapi-schema-empty-'));
|
|
88
|
+
mkdirSync(join(dir, 'src'), { recursive: true });
|
|
89
|
+
expect(readStrapiSchemas(dir)).toEqual([]);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type StrapiCallReason = 'strapi-sdk-import' | 'env-ref' | 'uid-ref';
|
|
2
|
+
export interface StrapiCallSite {
|
|
3
|
+
/** Path relative to the frontend directory. */
|
|
4
|
+
file: string;
|
|
5
|
+
line: number;
|
|
6
|
+
reason: StrapiCallReason;
|
|
7
|
+
snippet: string;
|
|
8
|
+
}
|
|
9
|
+
export interface StrapiTouchpoints {
|
|
10
|
+
/** Strapi SDK / client / generated-type packages still declared in package.json. */
|
|
11
|
+
dependencies: string[];
|
|
12
|
+
/** Source lines that still reach Strapi at runtime. */
|
|
13
|
+
callSites: StrapiCallSite[];
|
|
14
|
+
/** `STRAPI_*` keys still present in dotenv files (cleanup; low severity). */
|
|
15
|
+
envVars: string[];
|
|
16
|
+
/** No runtime Strapi coupling remains (no SDK deps, no call sites): the cutover is complete. */
|
|
17
|
+
clean: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Enumerate every remaining Strapi coupling in a wrapped frontend. This is the
|
|
21
|
+
* shared primitive behind both the unwire worklist (the agent's to-do) and the
|
|
22
|
+
* cutover gate (`clean === true`).
|
|
23
|
+
*
|
|
24
|
+
* `clean` is true when no runtime coupling remains — no Strapi SDK/client
|
|
25
|
+
* dependency and no source line that reaches Strapi — i.e. the site can no
|
|
26
|
+
* longer call Strapi. Detection keys on actual Strapi *references* (base-URL
|
|
27
|
+
* env, `api::` UID, SDK import), NOT filenames: a `lib/strapi.ts` the agent has
|
|
28
|
+
* rewritten to call Fimo has no Strapi references and therefore reads as clean.
|
|
29
|
+
* The `fimo/strapi` compat adapter is never counted.
|
|
30
|
+
*/
|
|
31
|
+
export declare function detectStrapiTouchpoints(frontendDir: string): StrapiTouchpoints;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
const SOURCE_EXT_RE = /\.(tsx?|jsx?|mjs|cjs|astro|vue|svelte)$/;
|
|
4
|
+
const SKIP_DIRS = new Set([
|
|
5
|
+
'node_modules',
|
|
6
|
+
'.next',
|
|
7
|
+
'.nuxt',
|
|
8
|
+
'.svelte-kit',
|
|
9
|
+
'.turbo',
|
|
10
|
+
'dist',
|
|
11
|
+
'build',
|
|
12
|
+
'out',
|
|
13
|
+
'coverage',
|
|
14
|
+
'.vercel',
|
|
15
|
+
'.git',
|
|
16
|
+
]);
|
|
17
|
+
const MAX_FILES = 2000;
|
|
18
|
+
/** A runtime reference to Strapi's base-URL / token env config. */
|
|
19
|
+
const ENV_REF_RE = /\bSTRAPI[A-Z0-9_]*_URL\b|\bNEXT_PUBLIC_STRAPI[A-Z0-9_]*\b|\bSTRAPI[A-Z0-9_]*_TOKEN\b/;
|
|
20
|
+
/** A Strapi content-type UID string (`api::article.article`). */
|
|
21
|
+
const UID_REF_RE = /api::[a-z0-9-]+\.[a-z0-9-]+/;
|
|
22
|
+
/** An import/require specifier — captured so a Strapi SDK can be told apart from `fimo/strapi`. */
|
|
23
|
+
const IMPORT_RE = /(?:from\s+|require\(\s*|import\(\s*)['"`]([^'"`]+)['"`]/;
|
|
24
|
+
/** A STRAPI env assignment in a dotenv file. */
|
|
25
|
+
const ENV_KEY_RE = /^\s*([A-Z0-9_]*STRAPI[A-Z0-9_]*)\s*=/;
|
|
26
|
+
function readDeps(dir) {
|
|
27
|
+
try {
|
|
28
|
+
const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'));
|
|
29
|
+
return { ...pkg.dependencies, ...pkg.devDependencies };
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function classifyLine(line) {
|
|
36
|
+
const importMatch = IMPORT_RE.exec(line);
|
|
37
|
+
// A Strapi SDK/client import — but NOT the `fimo/strapi` compat adapter.
|
|
38
|
+
if (importMatch && /strapi/i.test(importMatch[1]) && !/fimo/i.test(importMatch[1])) {
|
|
39
|
+
return 'strapi-sdk-import';
|
|
40
|
+
}
|
|
41
|
+
if (ENV_REF_RE.test(line)) {
|
|
42
|
+
return 'env-ref';
|
|
43
|
+
}
|
|
44
|
+
if (UID_REF_RE.test(line)) {
|
|
45
|
+
return 'uid-ref';
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
function scanCallSites(frontendDir) {
|
|
50
|
+
const sites = [];
|
|
51
|
+
let scanned = 0;
|
|
52
|
+
const walk = (dir) => {
|
|
53
|
+
if (scanned >= MAX_FILES) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
let entries;
|
|
57
|
+
try {
|
|
58
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
if (scanned >= MAX_FILES) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (entry.isDirectory()) {
|
|
68
|
+
if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith('.')) {
|
|
69
|
+
walk(join(dir, entry.name));
|
|
70
|
+
}
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (!SOURCE_EXT_RE.test(entry.name)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
scanned += 1;
|
|
77
|
+
const full = join(dir, entry.name);
|
|
78
|
+
let text;
|
|
79
|
+
try {
|
|
80
|
+
text = readFileSync(full, 'utf8');
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const lines = text.split('\n');
|
|
86
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
87
|
+
const reason = classifyLine(lines[i]);
|
|
88
|
+
if (reason) {
|
|
89
|
+
sites.push({
|
|
90
|
+
file: relative(frontendDir, full),
|
|
91
|
+
line: i + 1,
|
|
92
|
+
reason,
|
|
93
|
+
snippet: lines[i].trim().slice(0, 200),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
walk(frontendDir);
|
|
100
|
+
return sites;
|
|
101
|
+
}
|
|
102
|
+
function scanEnvKeys(frontendDir) {
|
|
103
|
+
const keys = new Set();
|
|
104
|
+
for (const name of ['.env', '.env.local', '.env.development', '.env.production']) {
|
|
105
|
+
const file = join(frontendDir, name);
|
|
106
|
+
if (!existsSync(file)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
for (const line of readFileSync(file, 'utf8').split('\n')) {
|
|
111
|
+
const match = ENV_KEY_RE.exec(line);
|
|
112
|
+
if (match) {
|
|
113
|
+
keys.add(match[1]);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// unreadable — ignore
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return [...keys];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Enumerate every remaining Strapi coupling in a wrapped frontend. This is the
|
|
125
|
+
* shared primitive behind both the unwire worklist (the agent's to-do) and the
|
|
126
|
+
* cutover gate (`clean === true`).
|
|
127
|
+
*
|
|
128
|
+
* `clean` is true when no runtime coupling remains — no Strapi SDK/client
|
|
129
|
+
* dependency and no source line that reaches Strapi — i.e. the site can no
|
|
130
|
+
* longer call Strapi. Detection keys on actual Strapi *references* (base-URL
|
|
131
|
+
* env, `api::` UID, SDK import), NOT filenames: a `lib/strapi.ts` the agent has
|
|
132
|
+
* rewritten to call Fimo has no Strapi references and therefore reads as clean.
|
|
133
|
+
* The `fimo/strapi` compat adapter is never counted.
|
|
134
|
+
*/
|
|
135
|
+
export function detectStrapiTouchpoints(frontendDir) {
|
|
136
|
+
const dependencies = Object.keys(readDeps(frontendDir)).filter((name) => /strapi/i.test(name));
|
|
137
|
+
const callSites = scanCallSites(frontendDir);
|
|
138
|
+
const envVars = scanEnvKeys(frontendDir);
|
|
139
|
+
return {
|
|
140
|
+
dependencies,
|
|
141
|
+
callSites,
|
|
142
|
+
envVars,
|
|
143
|
+
clean: dependencies.length === 0 && callSites.length === 0,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
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 { detectStrapiTouchpoints } from './touchpoints.js';
|
|
6
|
+
let dir;
|
|
7
|
+
function frontend(files) {
|
|
8
|
+
dir = mkdtempSync(join(tmpdir(), 'strapi-touchpoints-'));
|
|
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('detectStrapiTouchpoints', () => {
|
|
22
|
+
it('enumerates deps, call sites, and env for a still-wired frontend', () => {
|
|
23
|
+
const cwd = frontend({
|
|
24
|
+
'package.json': JSON.stringify({
|
|
25
|
+
dependencies: { '@strapi/client': '^1.0.0', next: '16.0.0' },
|
|
26
|
+
devDependencies: { '@repo/strapi-types': 'workspace:*' },
|
|
27
|
+
}),
|
|
28
|
+
'src/lib/strapi.ts': "import { strapi } from '@strapi/client';\nexport const client = strapi({ baseURL: process.env.STRAPI_URL });\n",
|
|
29
|
+
'src/app/page.tsx': "const data = await client.collection('api::article.article').find();\n",
|
|
30
|
+
'.env': 'STRAPI_URL=https://cms.example.com\nNEXT_PUBLIC_STRAPI_URL=https://cms.example.com\nOTHER=1\n',
|
|
31
|
+
});
|
|
32
|
+
const result = detectStrapiTouchpoints(cwd);
|
|
33
|
+
expect(result.dependencies.sort()).toEqual(['@repo/strapi-types', '@strapi/client']);
|
|
34
|
+
expect(result.envVars.sort()).toEqual(['NEXT_PUBLIC_STRAPI_URL', 'STRAPI_URL']);
|
|
35
|
+
expect(result.callSites.map((site) => site.reason).sort()).toEqual(['env-ref', 'strapi-sdk-import', 'uid-ref']);
|
|
36
|
+
expect(result.callSites.find((site) => site.reason === 'strapi-sdk-import')?.file).toBe('src/lib/strapi.ts');
|
|
37
|
+
expect(result.clean).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
it('reads a rewired frontend as clean — a lib/strapi.ts calling Fimo is not a touchpoint', () => {
|
|
40
|
+
const cwd = frontend({
|
|
41
|
+
'package.json': JSON.stringify({ dependencies: { fimo: '0.7.0', next: '16.0.0' } }),
|
|
42
|
+
// Same filename, but now Fimo-backed: no Strapi references.
|
|
43
|
+
'src/lib/strapi.ts': "import { fimoStrapiFetch } from 'fimo/strapi';\nexport const client = fimoStrapiFetch;\n",
|
|
44
|
+
'src/app/page.tsx': "const data = await client('/articles');\n",
|
|
45
|
+
});
|
|
46
|
+
const result = detectStrapiTouchpoints(cwd);
|
|
47
|
+
expect(result.dependencies).toEqual([]);
|
|
48
|
+
expect(result.callSites).toEqual([]);
|
|
49
|
+
expect(result.clean).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
it('flags a lingering STRAPI env reference in config even when deps are gone', () => {
|
|
52
|
+
const cwd = frontend({
|
|
53
|
+
'package.json': JSON.stringify({ dependencies: { next: '16.0.0' } }),
|
|
54
|
+
'next.config.mjs': 'export default { images: { domains: [process.env.STRAPI_MEDIA_URL] } };\n',
|
|
55
|
+
});
|
|
56
|
+
const result = detectStrapiTouchpoints(cwd);
|
|
57
|
+
expect(result.dependencies).toEqual([]);
|
|
58
|
+
expect(result.callSites).toHaveLength(1);
|
|
59
|
+
expect(result.callSites[0]).toMatchObject({ file: 'next.config.mjs', reason: 'env-ref' });
|
|
60
|
+
expect(result.clean).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CmsCollectionSchema } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Machine-wire a Strapi frontend to read from Fimo: emit the `fimo-strapi`
|
|
4
|
+
* module in the CMS client dir, then swap every bare `fetch(...)` in that dir
|
|
5
|
+
* to `fimoStrapiFetch(...)`. Because every Strapi client funnels through one
|
|
6
|
+
* `fetch`, this makes the WHOLE site read scaffolded content from Fimo — no
|
|
7
|
+
* per-method or per-call-site knowledge, no agent step. Unscaffolded types fall
|
|
8
|
+
* through to the live CMS inside the shim.
|
|
9
|
+
*/
|
|
10
|
+
export interface StrapiWireResult {
|
|
11
|
+
changes: string[];
|
|
12
|
+
warnings: string[];
|
|
13
|
+
wired: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function wireStrapiClient(cwd: string, clientDir: string, schemas: CmsCollectionSchema[]): StrapiWireResult;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parseSync } from 'oxc-parser';
|
|
4
|
+
import { renderFimoStrapiModule } from './wiring.js';
|
|
5
|
+
function walk(node, visit) {
|
|
6
|
+
if (!node || typeof node !== 'object') {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (Array.isArray(node)) {
|
|
10
|
+
for (const child of node) {
|
|
11
|
+
walk(child, visit);
|
|
12
|
+
}
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const record = node;
|
|
16
|
+
if (typeof record.type === 'string') {
|
|
17
|
+
visit(record);
|
|
18
|
+
}
|
|
19
|
+
for (const [key, value] of Object.entries(record)) {
|
|
20
|
+
if (key !== 'type' && value && typeof value === 'object') {
|
|
21
|
+
walk(value, visit);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Callee spans of bare `fetch(...)` calls (not `x.fetch()` / `this.fetch()`). */
|
|
26
|
+
function bareFetchCallees(filePath, code) {
|
|
27
|
+
let program;
|
|
28
|
+
try {
|
|
29
|
+
program = parseSync(filePath, code).program;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
const spans = [];
|
|
35
|
+
walk(program, (n) => {
|
|
36
|
+
if (n.type === 'CallExpression' && n.callee?.type === 'Identifier' && n.callee.name === 'fetch') {
|
|
37
|
+
spans.push({ start: n.callee.start, end: n.callee.end });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return spans;
|
|
41
|
+
}
|
|
42
|
+
function applyReplacements(code, edits) {
|
|
43
|
+
let next = code;
|
|
44
|
+
for (const edit of [...edits].sort((a, b) => b.start - a.start)) {
|
|
45
|
+
next = next.slice(0, edit.start) + edit.text + next.slice(edit.end);
|
|
46
|
+
}
|
|
47
|
+
return next;
|
|
48
|
+
}
|
|
49
|
+
function relativeImport(fromFile, toFile) {
|
|
50
|
+
const rel = path
|
|
51
|
+
.relative(path.dirname(fromFile), toFile)
|
|
52
|
+
.replace(/\.tsx?$/, '')
|
|
53
|
+
.split(path.sep)
|
|
54
|
+
.join('/');
|
|
55
|
+
return rel.startsWith('.') ? rel : `./${rel}`;
|
|
56
|
+
}
|
|
57
|
+
function sourceFiles(dir) {
|
|
58
|
+
const out = [];
|
|
59
|
+
const walkDir = (current) => {
|
|
60
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
61
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const full = path.join(current, entry.name);
|
|
65
|
+
if (entry.isDirectory()) {
|
|
66
|
+
walkDir(full);
|
|
67
|
+
}
|
|
68
|
+
else if (/\.(tsx?|jsx?|mjs)$/.test(entry.name)) {
|
|
69
|
+
out.push(full);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
if (fs.existsSync(dir)) {
|
|
74
|
+
walkDir(dir);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
export function wireStrapiClient(cwd, clientDir, schemas) {
|
|
79
|
+
const changes = [];
|
|
80
|
+
const warnings = [];
|
|
81
|
+
if (!fs.existsSync(clientDir)) {
|
|
82
|
+
return { changes, warnings: [`CMS client directory not found: ${path.relative(cwd, clientDir)}`], wired: false };
|
|
83
|
+
}
|
|
84
|
+
const modulePath = path.join(clientDir, 'fimo-strapi.ts');
|
|
85
|
+
fs.writeFileSync(modulePath, renderFimoStrapiModule(schemas));
|
|
86
|
+
changes.push(`generated ${path.relative(cwd, modulePath)}`);
|
|
87
|
+
let swapped = 0;
|
|
88
|
+
for (const file of sourceFiles(clientDir)) {
|
|
89
|
+
if (path.resolve(file) === path.resolve(modulePath)) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const code = fs.readFileSync(file, 'utf8');
|
|
93
|
+
const spans = bareFetchCallees(file, code);
|
|
94
|
+
if (spans.length === 0) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
let next = applyReplacements(code, spans.map((span) => ({ ...span, text: 'fimoStrapiFetch' })));
|
|
98
|
+
next = `import { fimoStrapiFetch } from '${relativeImport(file, modulePath)}';\n${next}`;
|
|
99
|
+
fs.writeFileSync(file, next);
|
|
100
|
+
swapped += spans.length;
|
|
101
|
+
changes.push(`rewired ${path.relative(cwd, file)}: ${spans.length} fetch call(s) → fimoStrapiFetch`);
|
|
102
|
+
}
|
|
103
|
+
if (swapped === 0) {
|
|
104
|
+
warnings.push(`No bare fetch() call found under ${path.relative(cwd, clientDir)} — wire fimoStrapiFetch into the client manually.`);
|
|
105
|
+
}
|
|
106
|
+
return { changes, warnings, wired: swapped > 0 };
|
|
107
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
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 { wireStrapiClient } from './wire-client.js';
|
|
6
|
+
let dir;
|
|
7
|
+
const schemas = [
|
|
8
|
+
{
|
|
9
|
+
name: 'BlogPost',
|
|
10
|
+
label: 'Blog Post',
|
|
11
|
+
isSingleton: false,
|
|
12
|
+
localized: false,
|
|
13
|
+
fields: {},
|
|
14
|
+
sourceUid: 'api::blog-post.blog-post',
|
|
15
|
+
sourcePath: '/blog-posts',
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
if (dir) {
|
|
20
|
+
rmSync(dir, { recursive: true, force: true });
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
describe('wireStrapiClient', () => {
|
|
24
|
+
it('generates the fimo-strapi module and swaps bare fetch calls', () => {
|
|
25
|
+
dir = mkdtempSync(join(tmpdir(), 'wire-strapi-'));
|
|
26
|
+
const clientDir = join(dir, 'src', 'lib', 'strapi-api');
|
|
27
|
+
mkdirSync(clientDir, { recursive: true });
|
|
28
|
+
writeFileSync(join(clientDir, 'base.ts'), 'export async function req(url: string, init: RequestInit) {\n const response = await fetch(url, init);\n return response;\n}\n');
|
|
29
|
+
writeFileSync(join(clientDir, 'other.ts'), 'export const x = 1;\n');
|
|
30
|
+
const result = wireStrapiClient(dir, clientDir, schemas);
|
|
31
|
+
expect(result.wired).toBe(true);
|
|
32
|
+
const generated = readFileSync(join(clientDir, 'fimo-strapi.ts'), 'utf8');
|
|
33
|
+
expect(generated).toContain('createFimoStrapiFetch');
|
|
34
|
+
expect(generated).toContain("'blog-posts': 'BlogPost',");
|
|
35
|
+
const base = readFileSync(join(clientDir, 'base.ts'), 'utf8');
|
|
36
|
+
expect(base).toContain('await fimoStrapiFetch(url, init)');
|
|
37
|
+
expect(base).not.toContain('await fetch(');
|
|
38
|
+
expect(base).toContain("import { fimoStrapiFetch } from './fimo-strapi';");
|
|
39
|
+
// A file with no fetch stays untouched.
|
|
40
|
+
expect(readFileSync(join(clientDir, 'other.ts'), 'utf8')).not.toContain('fimoStrapiFetch');
|
|
41
|
+
});
|
|
42
|
+
it('warns when no bare fetch is found', () => {
|
|
43
|
+
dir = mkdtempSync(join(tmpdir(), 'wire-strapi-none-'));
|
|
44
|
+
const clientDir = join(dir, 'client');
|
|
45
|
+
mkdirSync(clientDir, { recursive: true });
|
|
46
|
+
writeFileSync(join(clientDir, 'base.ts'), 'export const x = 1;\n');
|
|
47
|
+
const result = wireStrapiClient(dir, clientDir, schemas);
|
|
48
|
+
expect(result.wired).toBe(false);
|
|
49
|
+
expect(result.warnings[0]).toContain('No bare fetch()');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CmsCollectionSchema } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Machine-wiring for the native `fimo/strapi` read adapter. From the scaffolded
|
|
4
|
+
* schemas it derives the Strapi-path → Fimo-type map (+ the singletons) and
|
|
5
|
+
* emits a tiny repo module that instantiates the Fimo-backed fetch. The
|
|
6
|
+
* migration then swaps the CMS client's `fetch` for `fimoStrapiFetch` — after
|
|
7
|
+
* which the whole site reads scaffolded content from Fimo, deterministically.
|
|
8
|
+
*/
|
|
9
|
+
export interface StrapiWiring {
|
|
10
|
+
/** Strapi REST path segment (pluralName) → Fimo type name. */
|
|
11
|
+
types: Record<string, string>;
|
|
12
|
+
/** Fimo type names that are single types. */
|
|
13
|
+
singletons: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function strapiWiring(schemas: CmsCollectionSchema[]): StrapiWiring;
|
|
16
|
+
/** Emit the repo module (`fimo-strapi.ts`) that wires the fetch to Fimo. */
|
|
17
|
+
export declare function renderFimoStrapiModule(schemas: CmsCollectionSchema[]): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function strapiWiring(schemas) {
|
|
2
|
+
const types = {};
|
|
3
|
+
const singletons = [];
|
|
4
|
+
for (const schema of schemas) {
|
|
5
|
+
const pluralName = schema.sourcePath.replace(/^\/+/, '');
|
|
6
|
+
if (pluralName) {
|
|
7
|
+
types[pluralName] = schema.name;
|
|
8
|
+
}
|
|
9
|
+
if (schema.isSingleton) {
|
|
10
|
+
singletons.push(schema.name);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return { types, singletons };
|
|
14
|
+
}
|
|
15
|
+
/** Emit the repo module (`fimo-strapi.ts`) that wires the fetch to Fimo. */
|
|
16
|
+
export function renderFimoStrapiModule(schemas) {
|
|
17
|
+
const { types, singletons } = strapiWiring(schemas);
|
|
18
|
+
const typeEntries = Object.entries(types)
|
|
19
|
+
.map(([pluralName, typeName]) => ` '${pluralName}': '${typeName}',`)
|
|
20
|
+
.join('\n');
|
|
21
|
+
return `// AUTO-GENERATED by \`fimo migrate --scaffold-cms\`. Serves this site's Strapi
|
|
22
|
+
// content reads from Fimo (scaffolded example content) through fimo/strapi.
|
|
23
|
+
// The migration points the CMS client's fetch at \`fimoStrapiFetch\`; reads for
|
|
24
|
+
// scaffolded types resolve from Fimo, everything else falls through to Strapi.
|
|
25
|
+
import { createFimoStrapiFetch } from 'fimo/strapi';
|
|
26
|
+
|
|
27
|
+
// Strapi REST path (pluralName) -> Fimo type name.
|
|
28
|
+
const TYPES: Record<string, string> = {
|
|
29
|
+
${typeEntries}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const SINGLETONS = [${singletons.map((name) => `'${name}'`).join(', ')}];
|
|
33
|
+
|
|
34
|
+
export const fimoStrapiFetch = createFimoStrapiFetch({ types: TYPES, singletons: SINGLETONS });
|
|
35
|
+
`;
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { renderFimoStrapiModule, strapiWiring } from './wiring.js';
|
|
3
|
+
const schema = (name, sourcePath, isSingleton = false) => ({
|
|
4
|
+
name,
|
|
5
|
+
label: name,
|
|
6
|
+
isSingleton,
|
|
7
|
+
localized: false,
|
|
8
|
+
fields: {},
|
|
9
|
+
sourceUid: `api::${name.toLowerCase()}.${name.toLowerCase()}`,
|
|
10
|
+
sourcePath,
|
|
11
|
+
});
|
|
12
|
+
describe('strapiWiring', () => {
|
|
13
|
+
it('maps pluralName paths to Fimo types and lists singletons', () => {
|
|
14
|
+
const wiring = strapiWiring([schema('BlogPost', '/blog-posts'), schema('Global', '/globals', true)]);
|
|
15
|
+
expect(wiring.types).toEqual({ 'blog-posts': 'BlogPost', globals: 'Global' });
|
|
16
|
+
expect(wiring.singletons).toEqual(['Global']);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
describe('renderFimoStrapiModule', () => {
|
|
20
|
+
it('emits a module importing fimo/strapi with the type map + singletons', () => {
|
|
21
|
+
const src = renderFimoStrapiModule([schema('BlogPost', '/blog-posts'), schema('Global', '/globals', true)]);
|
|
22
|
+
expect(src).toContain("import { createFimoStrapiFetch } from 'fimo/strapi';");
|
|
23
|
+
expect(src).toContain("'blog-posts': 'BlogPost',");
|
|
24
|
+
expect(src).toContain("const SINGLETONS = ['Global'];");
|
|
25
|
+
expect(src).toContain('export const fimoStrapiFetch = createFimoStrapiFetch({ types: TYPES, singletons: SINGLETONS });');
|
|
26
|
+
});
|
|
27
|
+
});
|